Firmware

RemoteFM1

Peaking98/100

RemoteFM1 is custom firmware for the M-VAVE FM-1, by @Quixotic7. Open source under GPL-3.0, latest release v0.3.3 (November 2026), 25 stars on GitHub.

forM-VAVE FM-1·by@Quixotic7

Unclaimed. Is this yours? Sign in with GitHub as @Quixotic7 to claim it.

Downloads
0
Stars
25
Forks
3
Commits
235
Last commit
Download v0.3.3

remote-0.3.3.fwsc· 597 KB· +5 files

GPL-3.0· Quixotic7/RemoteFM1·downloads come from the creator’s GitHub releases

199
Commits
10
Releases
199 commits and 10 releases in the last year
Activity · last 12 months

RemoteFM1

The firmware for your firmware so you can run firmwares without installing firmwares

Play in the browserremotefm1.com: the FM-1 Player
Install RemoteFM1 on an FM-1remotefm1.com/installer (Chrome or Edge, USB)
iPhone and iPad (app and AUv3)TestFlight beta (RemoteFM1IOS)
Androidthe APK, sideloaded (RemoteFM1Android)
Mac plugin and desktop app (VST3 / AU)FM1VST
The firmwares (.rfm1)RemoteFM1FW: every app and the site read it
Firmware authorsRemoteFM1Metadata.json: one file in your repo sets your firmware's card, screenshot, skin, panel labels and the release we build

RemoteFM1 is a firmware for the FM-1 that does almost nothing by itself. It sends every key, button, encoder and the MASTER pot to the computer, and shows whatever the computer sends back on its LEDs and its 240×240 screen. On the computer, phone or browser, an FM-1 firmware runs at full speed and uses the real FM-1 as its panel. That lets you try firmware changes without reflashing the unit, switch firmwares in a click, and run things the FM-1's own CPU and memory couldn't hold. Without a host, it is an instrument of its own: a DX7, a MIDI controller and a looper (see "Using it").

What the hosts run. The FM-1 Player at remotefm1.com lists about 160 FM-1 firmwares. Over 70 run as .rfm1 files, a firmware's own code built to WebAssembly (from RemoteFM1FW, the same files the iOS and Android apps use), and about 20 as native cores built by the site. The rest run in the FM-1 emulator (.fwsc packages from the FM-1 Firmware Hub). Each plays on screen with the keyboard, mouse, touch or a MIDI keyboard, or on your own FM-1 running RemoteFM1. The stock M-VAVE firmware can't run on a host.

M-Vave SMK-37: RemoteFM1 also builds for the SMK-37 (SMK-37 Pro, Elite, MKE-P37), which every host sees as an FM-1. It is in testing: a hidden tester page at remotefm1.com/installer/smk37 and a browser emulator of it at installer/smk37/emu. See docs/SMK37.md.

Status: 0.3.1 released on the site (2026-10-31): the FM-1 on its own. 0.3.1 adds 16 user cartridges (list, replace, delete), loops saved on the unit, a VELOCITY setting, and velocity and pressure for hosts. Without a host, 0.3 is an instrument: a DX7 (saadet's FM6, Yamaha's 256 ROM voices and your own cartridges), a MIDI controller over USB, Bluetooth and the TRS jack, a 16-loop looper, an arp, scales and a REMOTE MIDI mode; a host still takes it over as its panel (STANDALONE-PLAN.md). 0.2 moves the firmware onto fm1-base and felucca-bsp and adds Bluetooth MIDI; 0.2.1 adds USB audio; 0.2.2 makes Bluetooth about 6x faster and shows each screen change whole; 0.2.3 fixes recording from the FM-1 over USB. Tested on an FM-1 on 2026-10-28. 0.1.1 (on TransitFM1) stays there as the older release. The plan and every measurement are in PLAN.md.

For firmware authors

We build most FM-1 firmwares as .rfm1 files for you, from your releases: you don't need to build the format yourself. To choose how your firmware looks and what we build, add one file, RemoteFM1Metadata.json, to the root of your repository. It sets your firmware's card (description, subtitle, category, links, screenshot), a skin for the on-screen FM-1 (background images and colours), names for the panel's buttons and knobs, and the release we build. Every part is optional.

{
  "$schema": "https://raw.githubusercontent.com/Quixotic7/RemoteFM1FW/main/docs/RemoteFM1Metadata.schema.json",
  "schema": 1,
  "card": { "tag": "Granular sampler", "what": "One paragraph about your firmware.", "image": "docs/card.png" },
  "labels": { "buttons": { "FX": "DRUMS", "SEQ": "STEPS" }, "knobs": { "K1": "CUTOF" } }
}

The full spec, with every field and its limits: the RemoteFM1Metadata.json guide.

How it works

 FM-1 (RemoteFM1)                                  Mac
 keys, buttons, encoders, MASTER  ──plain MIDI──►  libremotefm1 ──► the firmware (an .fm1core)
 LEDs, screen                     ◄─SysEx / bulk── screen encoder ◄── its screen and LEDs
  • Input goes out as plain MIDI, so any MIDI app sees the FM-1 as a controller:

    • keys: notes on channel 16;
    • buttons: notes on channel 15;
    • encoders: relative CCs 16–22;
    • MASTER: 14-bit CC 7/39.
  • The screen goes as 16×16 tiles. Only tiles that changed are sent, oldest first, each in the smallest of five encodings. A typical UI frame is about 2 KB; a full redraw of a busy screen is 2–10 KB.

  • Three transports:

    • USB-MIDI SysEx, which works everywhere, including Web MIDI and the iPad;
    • a vendor bulk interface for macOS (libusb) and Chrome (WebUSB), about 830 KB/s;
    • Bluetooth MIDI SysEx (0.2): the same messages over the air, at a few KB/s, so a still screen and the panel more than a moving one (the host paces frames for it: about 5.5 KB/s, at most 20 a second, tiles reduced to 4 colours until the screen is still, then sent exactly; docs/PROTOCOL.md section 1). From 0.2.2 each screen change shows whole: the FM-1 keeps the old screen until the new one has all arrived, then switches at once (TILES' HOLD flag; a turning knob shows whole screens at the rate the link allows).

    The host uses bulk when it can. A session lives on the transport it started on; the panel's events go to that port (USB-MIDI for a USB session, Bluetooth for a Bluetooth one).

  • The protocol is in docs/PROTOCOL.md, with its constants in proto/rfm1.h.

On the host side, any firmware that exists as an fm1core can run, through FM1VST's engine: natively in FM1VST and fm1remote, and as an .rfm1 (the same core built to WebAssembly) on the site and in the iOS and Android apps. Firmwares built only as .fwsc packages run in the site's FM-1 emulator. The stock M-VAVE firmware can't run on a host.

Using it

  1. Install RemoteFM1 on the FM-1 with its web installer (USB + Bluetooth MIDI, or USB only) or any Felucca-family installer, for example python3 tools/fm1_install.py web/installer/firmware/remote-0.3.1.fwsc (USB only: remote-usb-0.3.1.fwsc). 0.2 answers installers as itself, FM-1_52XXYYZZ (release X.Y.Z; fm1-base's scheme), on the USB device "FM-1"; 0.1 answered as the official V15 (FM-1_015).

  2. Without a host, the screen reads REMOTE FM1 / WAITING FOR HOST, with USB's state, the Bluetooth name it advertises ("FM-1 1F2D") and whether a computer is connected, and the battery; the key lights glow dim.

  3. Run a firmware. The easiest host is the site: open remotefm1.com, pick a firmware, then Play on my FM-1 (or Settings > Use my FM-1), in Chrome or Edge over USB or Bluetooth MIDI. The iOS and Android apps and FM1VST connect the same way. From the command line on a Mac:

    cmake -S host -B host/build -DCMAKE_BUILD_TYPE=Release && cmake --build host/build -j
    host/build/fm1remote --device --port FM-1 --stats                  # ChoralRoot
    host/build/fm1remote --device --port FM-1 --stats --core felucca   # or melodee
    host/build/fm1remote --device --port FM-1 --stats --pattern        # test screens only, no firmware
    
    • The sound plays on the Mac.
    • Each firmware's flash image is kept in ~/Library/Application Support/fm1remote/.
    • Ctrl-C ends the session, and the FM-1 goes back to its waiting screen.
    • fm1remote expects the FM1VST checkout next to this one (cores in FM1VST/build/cores).
  4. To leave RemoteFM1, install the next firmware with its own installer, as you would over V15.

Standalone (the standalone branch; STANDALONE-PLAN.md): with no host, the FM-1 plays DX7 voices, works as a MIDI controller (CC banks on FX..EDIT and KN1..KN4: the CC-Bank screen a bank button opens stays until HOME or another screen), loops and arps. SAVE opens the theme menu: KN1 picks the theme, KN2 or ALGORITHM its background (black, five dark grounds, or the theme's classic colour, such as the C64's blue, where its text keeps 4.5:1). The user decides which screen is open: a menu or the CC-Bank screen stays until HOME or its own button, and a passing screen (tempo, voice, channel, octave, latch) goes back to it, never to HOME by itself. In a menu KN1..KN4 set its values; PRESETS is always the voice (past a cartridge's end, the next cartridge's), SELECT the tempo (the settings: the row; the recording options: the page). REC arms a take (it starts with the first note played); the recording options (REC held) have a count-in of 1, 2 or 4 bars and a metronome on the audio out; a loop holds the keys as played and the arp steps them on playback. SEQ opens the 16 loops: the white keys pick one, the black keys (F# G# A#) or KN1 when it comes in (INSTANT, AT BAR, AT END). GLO + a white key sets the MIDI channel, GLO + a black key the octave; GLO held shows both on the keys' LEDs. SAVE held saves everything: the settings, and (0.3.1) the 16 loops into the unit's flash, back at the next power-on. A DX7 32-voice bank sent over USB or Bluetooth MIDI (any librarian, or the site) becomes a user bank after the ROM cartridges: up to 16 (0.3.1; 0.3.0 kept 3, taken over at 0.3.1's first start), a 17th replaces the oldest; SA_INFO (docs/PROTOCOL.md section 8) tells a host how many are kept and their names. MIDI REMOTE: GLO + FX turns the whole panel into a plain MIDI controller (GLO + FX again leaves; not kept across a power cycle); the DX7 is silent and every control sends MIDI on the channels of the settings page (HOME held):

ControlMessage (CC CH: the settings' channel for buttons, encoders and MASTER; KEY CH: the keys')
Keys (27)notes on KEY CH, F3..G5 shifted by the octave as it was when the mode came on, with velocity; note off velocity 64
FX SEL ENV LFO EDITCC 20, 21, 22, 23, 24 on CC CH: 127 pressed, 0 let go
GLO HOME SAVECC 25, 26, 27 (127 / 0); GLO none in MIDI KEYS (it is the shift there)
ARP SEQ PLAY RECCC 28, 29, 30, 31 (127 / 0)
OCT- OCT+CC 85, 86 (127 / 0); in MIDI KEYS with OCT KEYS on: the keys' octave -3..+3 instead, nothing sent
SELECT ALGORITHM PRESETSCC 102, 103, 104: absolute 0..127, each from 64 when the mode comes on (x2 / x4 when turned fast); or relative (below)
KN1 KN2 KN3 KN4CC 105, 106, 107, 108: the same
MASTERCC 109, 0..127, sent when it moves (always absolute)
GLO + a white key (MIDI KEYS)KEY CH: the white keys left to right are channels 1..16; no note
GLO + a black key (MIDI KEYS)the octave: the 11 black keys left to right -3 -3 -3 -2 -1 0 +1 +2 +3 +3 +3 (the sixth, F#4, is 0); no note
GLO + PRESETS / ALGORITHM (MIDI KEYS, SCALES on)the scale's root / the scale (the instrument's, shared and kept; the Scale screen a moment); no CC. With SCALES off they send their CCs 104 / 103 (GLO itself still sends none)
The keys in MIDI KEYSsnapped to the scale (SNAP: a note out of it moves to the nearest in it, a tie down); the keys' LEDs: the root at 75 %, the scale's other notes at 40 % (3/8), held keys lit
The keys' LEDs with the screen offan encoder turned in ABSOLUTE, or MASTER moved: its value on the 16 white keys for 1.5 s (0 the first, 127 all 16); in MIDI KEYS, GLO held: the keys' channel (its white key) and octave (its black key) lit

The settings page ("REMOTE MODE SETTINGS": HOME held or GLO + HOME in the instrument, not in the mode; one row big at a time, its value bigger, the rows before and after peeking; SELECT the row, KN1 its value, KN2..KN4 nothing; HOME, HOME held or GLO + HOME closes; GLO + FX from it goes into MIDI REMOTE and comes back to it; kept with the settings):

RowValues
SCREENVOID (black), SOLID (the theme's background), ALL (the REMOTE MIDI screen), KNOBS ONLY (an encoder's or MASTER's value for 1.5 s, else black)
KNOBSABSOLUTE; REL 1/127 (two's complement: +n sends n, -n sends 128-n, n up to 63); REL 65/63 (offset: 64+n); each detent group sent as it turns (x2 / x4 when fast)
CC CH1..16: buttons, encoders, MASTER
KEY CH1..16: the keys (GLO + a white key sets it in MIDI KEYS)
KEYSFIXED (the keys send their notes, every button its CC, as before); MIDI KEYS (a keyboard: GLO is a shift, the shortcuts above)
OCT KEYSOCTAVE (in MIDI KEYS, OCT- / OCT+ shift the keys' octave) or CC 85/86 (they send their CCs)
SCALE SHORTCUTSON (in MIDI KEYS, GLO + PRESETS / ALGORITHM set the scale) or OFF
KNOB LEDSON (an absolute knob's or MASTER's value on the 16 white keys' LEDs for 1.5 s) or OFF

docs/PROTOCOL.md section 8 has the details.

macOS keeps the MIDI port name of the first firmware it saw on the unit (for example "ChoralRoot FM-1"). The host finds RemoteFM1 by asking (HELLO), never by name. If the port shows up offline after a firmware change, use --midi-restart or Audio MIDI Setup > Rescan MIDI.

Over Bluetooth (0.2, the default package remote-0.3.1.fwsc; remote-usb-0.3.1.fwsc has no radio): Bluetooth is on at every power-on. Connect the FM-1 in Audio MIDI Setup > MIDI Studio > Bluetooth (on a Mac) or the app's Bluetooth MIDI page; its port is named after what it advertises, "FM-1 1F2D". The site and FM1VST send HELLO to any port whose name holds "FM-1", so they find it there too. The update path stays USB-only.

USB audio (0.2.1): the Mac sees the FM-1 as a sound output ("FM-1" in System Settings > Sound and Audio MIDI Setup) and plays through its speaker and headphones, with MASTER as its volume. Pick it as the output of fm1remote's sound (or the Mac's) to hear the firmware on the unit. On the site, Settings > Playing > Sound output > FM-1 does it in Chrome and Edge (Chrome asks once for the microphone, the only way it names the outputs; nothing is recorded) and in Firefox (its output chooser); in Safari, pick FM-1 as the Mac's output. The FM-1 is an audio input too, carrying what it plays. The USB interfaces are 0 audio control, 1 MIDI, 2 audio in, 3 audio out and 4 the vendor bulk pair (2 in 0.1 and 0.2.0); hosts find the vendor interface by its class (0xFF), not its number.

On an iPhone or iPad, the RemoteFM1 app (TestFlight beta; also an AUv3 plugin) runs the .rfm1 firmwares and takes the FM-1 as its panel over USB or Bluetooth. On Android, the RemoteFM1 APK does the same. In a browser on iOS, Safari and Chrome play the site's firmwares, but they have no Web MIDI, so the FM-1 can't be their panel there. MIDIWeb Browser, an iOS browser that adds Web MIDI, runs the whole site: the FM-1 as the panel over Bluetooth or USB, and its USB audio. Tested on an iPhone 13 Pro Max on 2026-10-28. In any iOS browser, an FM-1 plugged in by USB becomes the phone's sound output by itself (a Lightning iPhone needs a Lightning to USB camera adapter).

Your own .rfm1 firmwares. The site's player also runs .rfm1 files: a firmware's own code built to WebAssembly (FM1VST's core of it), the format RemoteFM1 for iOS runs. Download one from RemoteFM1FW's releases (or a firmware's own release), then open it with the firmware browser's "Open a file..." or drop it on the page. It runs natively, as the site's own native cores do, and stays in the browser under "Your firmware" (IndexedDB) with its saves, apart from the firmware's native and emulated ones. The About page shows its licence, its authors and its source at the commit it was built from. The site checks the file before it runs: its manifest, imports, exports and memory (web/engine/API.md section 6).

FIZZ on the site. FIZZ, the FM TONE step sequencer, is in the firmware browser as an .rfm1 the site hosts itself (web/site/fw/, ?fw=fizz): built from its release by tools/rfm1/build_fizz.sh with FM1VST's fm1_add_rfm1, nothing to download or import. PLAY starts its demo pattern; REC turns the keys into a keyboard. Its licence is MPL-2.0 AND GPL-3.0-only AND MIT AND FTL AND OFL-1.1 (FIZZ, FM1VST's glue, plutovg and Emscripten's runtime, FreeType's rasterizer, the Inter Tight font); tools/rfm1/README.md says how to rebuild it for a new release.

The .rfm1 catalog on the site. Every deploy fetches the latest release of RemoteFM1FW (the .rfm1 files the iOS app reads) with web/site/tools/fetch_fw_catalog.mjs, checks each file against its SHA256SUMS and hosts them at fw/rfm1/ (never committed; web/site/fw/rfm1/ is ignored). build_catalog.mjs --fw-catalog web/site/fw/rfm1 lists each by its own manifest: a Hub firmware or an FM1VST core gets the .rfm1 as its default engine when it is the Hub's and the native core's version or newer (else as an alternative); a firmware already hosted in fw/ (S1Vave) keeps the committed file unless the release's is newer; anything else is a new entry, with card text from web/site/data/fw-catalog.json, else from RemoteFM1FW's own catalog/catalog.json (fetched beside the files). That catalog also gives each firmware its editor page and its USB MIDI port name. The browser lists the firmwares with an .rfm1 or a native core first; the ones that only run emulated (.fwsc) are folded below. For local work, node web/site/tools/fetch_fw_catalog.mjs (or --from DIR offline) then node web/site/tools/build_catalog.mjs --fw-catalog web/site/fw/rfm1; the deploy guard checks every fetched file's SHA-256 and that the catalog lists it.

The firmwares' own editors. The player's Editor button opens the running firmware's own web editor or preset manager beside the FM-1, talking to the firmware playing in the page (native or .rfm1) through a stand-in Web MIDI port named as the firmware's USB port. No FM-1 is needed. The panel loads the author's live editor page (web/site/js/live-editor.js: fetched from the author's site, run in a sandbox without the site's origin, with the Web MIDI shim web/managers/fm1-shim.js); where that can't work, it shows a copy the site builds at deploy from the firmware's source at a pinned commit (web/managers/managers.json, web/managers/build.mjs). "Pop out" opens it in a tab of its own. node web/managers/e2e.mjs checks each editor end to end in headless Chromium (--live for the live pages). For RemoteFM1's own .rfm1, the Editor is the DX7 cart sender (docs/CART-UPLOAD.md).

Fantasy CFW. Some .rfm1 firmwares are made to run only on a host, free of the FM-1's CPU and memory limits (the first is Ripple). They read and write files in a RemoteFM1 folder on your computer, shared by every such firmware, for example a folder of samples (web/site/js/folder.js; FM1VST docs/VIRTUAL-CARTS.md). The site asks for the folder when one starts (Chrome and Edge use a folder you pick; Safari and Firefox keep it in the site's own storage, with Add files and Export data).

An OMX-27 as the controller. With the OMX-27 firmware that has REMOTE mode (the q7-2026-3 branch), the site's player takes it over: Settings > Use my OMX-27 > Connect (Chrome or Edge, Web MIDI with SysEx; the port whose name holds "OMX"). The page puts it in REMOTE mode; its keys, encoder and five pots then play the firmware, and the page lights its 27 keys and draws its screen. K1 picks the view: Base (shortcuts on the left, the FM-1's keys on the right, two ranges to reach all 27), Seq (the FM-1's keys 1:1, each OMX key lit as its FM-1 key is), Keyboard (notes into the firmware's MIDI in, K3 the channel, K4 the octave) or Quick Keys (every key a knob or button shortcut). K2 picks one of six colour themes, K5 is MASTER (with pickup), the encoder turns the selected knob. Disconnect clears its keys and screen; to leave REMOTE mode on the OMX-27, hold AUX and the encoder. The owner's design is design/RemoteFM1---OMX-27-Remote.json.

Safety

From 0.2, RemoteFM1 is an fm1-base firmware, with fm1-base's and felucca-bsp's safeguards: the boot guard (two failed starts: safe mode, USB on), the crash screen and record, the watchdog, the USB soft key into the chip's update mode, REC held at power-on for that same mode (also in safe mode; 0.1's OCT- went to its own loader), felucca-bsp's update loader (ota-FM-1_900, writing the app area only), and the installers' way back to M-VAVE's firmware. A package whose image reaches past the app area (M-VAVE's V16, Baud Girl's) is refused by RemoteFM1's installers before anything is sent.

What it writes in flash: the Bluetooth build keeps the radio's address (and any pairing) in two 4 KiB sectors at 0xF2000..0xF3FFF, written at its first Bluetooth start (free in ChoralRoot's map and the stock firmware's); OCT- + OCT+ held at power-on (the panel calibration) writes 0xFC000..0xFDFFF. From 0.3.1 the standalone keeps its user DX7 banks and its loops in 0x97000..0xD8FFF, part of the data area where ChoralRoot and FIZZ keep theirs (the owner's choice, 2026-10-30: both can back up and restore their data): a bank sent or a SAVE held there overwrites what another firmware kept, and installing one of them loses RemoteFM1's banks and loops (0.3.0 used XDATA0, 0x93000..0x96FFF, for three banks; 0.3.1 only reads it). Nothing else: the data an earlier firmware left elsewhere stays where it is. Every update stages its loader at 0xE0000..0xE4FFF, as 0.1's did. docs/SAFETY.md is 0.1's (TransitFM1's) design, kept for its history.

Installing firmware is at your own risk. Keep the FM-1 Transporter or MvaveFM1Unbricker at hand.

Building the firmware

The firmware is fm1-base's: base/ is fm1-base, a git submodule pinned at a commit (felucca-bsp inside it, base/bsp). ./build.sh copies it to build/fm1-base, applies RemoteFM1's patches (firmware/patches/: the vendor bulk interface, beside USB audio, and the SysEx hooks in felucca-bsp's usb.c, SysEx over Bluetooth MIDI in fm1-base; each one an upstream pull request, firmware/patches/UPSTREAM.md) and runs fm1-base's tools/build.py on the app in firmware/. USB audio in and out are fm1-base's (firmware/fm1.toml).

You need Docker on macOS or Windows (or Linux x86-64), Python 3 with Pillow (with libraqm: on a Mac Homebrew's, which build.sh finds in /opt/homebrew/lib), the JieLi toolchain (base/tools/get_toolchain.sh, in ~/.jieli/toolchain) and the JieLi AC79 SDK (its Bluetooth libraries; never in this tree):

git submodule update --init --recursive
git clone --depth 1 --branch AC79NN_SDK_V1.2.1_2023-12-13 https://gitee.com/Jieli-Tech/fw-AC79_AIoT_SDK.git ~/fw-AC79_AIoT_SDK
./build.sh --release 0.3.1           # -> firmware/build/remotefm1-0.3.1.fwsc (USB + Bluetooth MIDI), FM-1_52000301
./build.sh --release 0.3.1 --no-ble  # -> firmware/build/remotefm1-usb-0.3.1.fwsc (USB only: no Bluetooth library)
./build.sh                           # a development build, FM-1_5200 (the standalone's DX7 voices: Yamaha's ROM
                                     # cartridges from DX7_ROM_DIR, default Presets/DX7-ROM beside the checkout;
                                     # without them the INIT VOICE only)
./build.sh --bench [--no-ble]        # the standalone DX7's bench at boot (firmware/build/remotefm1[-usb]-bench.fwsc);
                                     # firmware/tools/sa_bench.py reads it over USB-MIDI (--hash: the render hash)
FM1B_CPU2=0 ./build.sh [...]         # one core (the packages start the second core: fm1.toml cpu2, FIZZ's settings)
tests/run_tests.sh                   # the app in fm1-base's host simulator (firmware/tests/), the protocol code,
                                     # the patched usb.c and Bluetooth SysEx, tools/fm1_install.py, the DX7's checks
tests/fm6_parity.sh SAADET_DIR       # the DX7 (saadet's FM6) against saadet's own build: tables and samples
host/run_tests.sh                    # the host library's tests, with the cross test against the firmware's own code
tools/hub_build.sh 0.3.2             # a release's two packages, as GitHub builds and signs them (docs/RELEASING.md)

Releases (0.3.2 on) are built and signed on GitHub by The Firmware Hub's shared workflow from a vX.Y.Z tag: docs/RELEASING.md has the steps and how to check a package with gh attestation verify.

What is where

firmware/app.h, app.c, fm1.toml    the app's contract with fm1-base, its build settings (fm1-base tools/build.py)
firmware/src/rfm_main.c     the app: transports (USB SysEx, USB bulk, Bluetooth SysEx), panel, LEDs, hand-over lines
firmware/src/rfm_dev.c      what only the FM-1 has (felucca-bsp usb.c's rings, the panel's DMA); rfm_host.c: the
                            host simulator's stand-ins, commands and checks (firmware/tests/*.txt)
firmware/src/rfm_app.c      the protocol layer (no hardware; also compiled into the host tests)
firmware/src/rfm_draw.c     the TILES decoder;  rfm_push.c: the screen push, which never blocks USB
firmware/sa/                the standalone instrument (STANDALONE-PLAN.md): sa_synth.c, the DX7 on saadet's FM6;
                            sa_main.c, the mode switch, the keys, MIDI, the controls; sa_ui.c / sa_gfx.c, the screens
firmware/third_party/jersey10/     the screens' pixel font (Jersey 10, OFL)
firmware/third_party/saadet-fm6/   saadet's FM6 (Kerem Kilic; MSFA), pinned: PROVENANCE.md
firmware/tools/             gen_fm6_tables.py (FM6's tables from MSFA's formulas), gen_pixfont.py (the font),
                            gen_dx7_rom.py (Yamaha's ROM cartridges from DX7_ROM_DIR, never committed), sa_bench.py
firmware/patches/           RemoteFM1's changes to fm1-base and felucca-bsp (MPL-2.0), applied by build.sh
base/                       fm1-base (git submodule), felucca-bsp in base/bsp
proto/, docs/PROTOCOL.md    the protocol
host/                       libremotefm1, the macOS back ends, fm1remote, tests
web/                        the site (remotefm1.com): the player (web/site), the installer (web/installer, with
                            installer/smk37/), Use my FM-1 (web/remote), the editors (web/managers), the engines
                            (web/engines: native, rfm1, emu)
tools/m0_bench.py, m0_bulk.c, web/m0_bench.html   transport benchmarks (SysEx, bulk, Web MIDI / WebUSB)
PLAN.md                     the plan, the milestones and every measurement

License

The firmware (firmware/) is MPL-2.0 from 0.2, by its author's choice: it links JieLi's Bluetooth library (Apache-2.0, binary only), which a GPL firmware could not. It is built on fm1-base (Charles Vestal) and felucca-bsp (Leo Kuroshita, Hügelton Instruments), both MPL-2.0. Everything else (the site, host/, the tools) stays GPL-3.0-only, and so do the firmware's versions before 0.2 (on TransitFM1, from Dinghy and Felucca). See LICENSING.md for who made what. "M-VAVE" and "FM-1" belong to their owners; RemoteFM1 is not made or endorsed by them.