devices/preenfm/preenfm2.vendor.fw-3.10-alpha.jsonEdit on GitHubJSONMIDNAMOSCQueryPreenFM2 (3.10 alpha) MIDI CC chart, NRPN and OSC map
The PreenFM2 MIDI implementation: 84 CC, 8 NRPN, each named, for Claude (MCP), SuperCollider or any OSC app.
- sourcemaker's documentation
- firmware3.10-alpha
- real unitnot yet checked
Not checked on a real PreenFM2 yet. Own one? Load a sound or send a control, and tell us whether it worked.
- Maker
- PreenFM
- Version
- 3.10 alpha (alternative firmware by sscheidl)
- Source
- maker's documentation
- OSC prefix
/preenfm2_310alpha
From the README of sscheidl/preenfm2-vosim-editor-firmware at 1107a62, licence GPL (as upstream, per the README).
PreenFM2 3.10 alpha firmware — VOSIM + editor protocol + PolyAT/MPE
⚠ Experimental developer firmware — hardware validation of the new expression work is still pending
This repository contains the PreenFM2 VOSIM firmware plus the editor remote-store protocol and true MIDI Polyphonic Key Pressure and Lower-Zone MPE. The source version is 3.10 alpha (
Makefile, shown on the boot screen aspreenfm2 v3.10 alpha).3.10 alpha binaries are available as the pre-release
v3.10alpha, built with GCC 4.7.4 from commitf6fe06c. They contain PolyAT and Lower-Zone MPE but have not yet been validated on hardware. The earlierv3.00alpha-full-mpe-rc1pre-release carries the same code labelled 3.00 alpha. The only binaries in the repository itself (release/editor-protocol-3.00alpha/) are the older editor-protocol-only 3.00 alpha build without PolyAT/MPE, which has run successfully on a physical PreenFM2.Do not flash the GCC-13 cloud binaries. The build gate (clean local build of 3.10 alpha with
gcc-arm-none-eabi 4.7-2014q2/ GCC 4.7.4) is passed; the open gate is manual hardware validation.
Current development status — 2026-10-02
Default branch: feature/editor-remote-store, which contains feature/full-mpe (fast-forward, source commit 374be62f05ead717b1fa674abbd97a2deac643d8, pre-release v3.00alpha-full-mpe-rc1) plus the version change to 3.10 alpha. Apart from the version string the source is identical to that release candidate. The 3.10 alpha binaries (pre-release v3.10alpha) were built from commit f6fe06c2677afe82b92c480f1671d79120b13c21.
Current state:
- VOSIM / editor remote protocol (3.00 alpha base): retained.
- Version 3.10 alpha: the minor bump marks the PolyAT and Lower-Zone MPE work on top of the 3.00 alpha editor-protocol base.
- True MIDI Polyphonic Key Pressure: implemented and independently source-reviewed. Existing
AftTmatrix routes become note-local when MIDI Polyphonic Key Pressure is received; ordinary Channel Pressure keeps the timbre-wide behaviour. - Lower-Zone MPE: implemented for the intended ROLI workflow:
- Strike → Note-On velocity
- Press → per-voice
AftT - Glide → per-voice pitch bend
- Slide → per-voice CC74 /
MPESLIDE - Member pitch-bend range: supported up to ±48 semitones. Larger member RPN-0 values are clamped because the current
exp2_harmfrequency representation cannot reproduce ±96 without saturation. - Host verification:
mpe_state_test.py325 checks,polyat_state_test.py79 checks,protocol_sim_test.py115 checks; these are simulations / structural tests, not firmware unit tests and not hardware tests. - Comparative cloud builds:
pfm,pfmo,pfmcv,pfmcvocompile/link with the GCC-13.2 substitute environment and no new warnings. These binaries are not release-valid and must not be flashed.
Known MPE limitations are documented rather than hidden: Lower Zone only, RPN 6 with n=0 is consumed but does not deactivate MPE, manager-channel pitch-bend sensitivity is recorded but not applied as a semitone range, no Lift/release-velocity expression, and MPE with Unison or the arpeggiator is not supported.
Detailed reports:
docs/POLYAT_IMPLEMENTATION_REPORT.mddocs/POLYAT_HARDWARE_TEST_CHECKLIST.mddocs/MPE_IMPLEMENTATION_REPORT.md
Hardware-test tools:
tools/polyat_test.py— deterministic MIDI Polyphonic Key Pressure / Channel Pressure sendertools/midi_monitor.py— MIDI input monitor to verify what the controller actually transmitstools/mpe_test.py— deterministic Lower-Zone MPE sender for Press / Glide / Slide / sustain / voice-allocation scenariostest/midi/— Standard MIDI Files with the Lower-Zone MPE scenarios A–J for DAW playback, generated fromtools/mpe_test.py(not yet verified in a DAW or on hardware)
Next gate
The next step is deliberately not more feature work. Steps 1–5 are done for 3.10 alpha; the builds and their SHA-256 values are in the v3.10alpha pre-release. Steps 6 and 7 remain:
- check out the final
feature/full-mpesource locally; - build cleanly with the historical
gcc-arm-none-eabi 4.7-2014q2/ GCC 4.7.4 toolchain; - build all four targets separately (
pfm,pfmo,pfmcv,pfmcvo) with a clean build directory between targets; - record the exact output filename and SHA-256 of every resulting binary;
- identify the correct hardware image for the physical PreenFM2;
- flash manually — never automatically;
- validate ordinary MIDI and Channel Aftertouch first, then PolyAT, then Lower-Zone MPE with LUMI / Seaboard RISE.
See docs/POLYAT_HARDWARE_TEST_CHECKLIST.md and §18 of docs/MPE_IMPLEMENTATION_REPORT.md.
What this repository is
This repository started as a focused extension of the PreenFM2 VOSIM firmware: a MIDI protocol that lets PreenFM+ store the current edit buffer, query the current bank/preset position and detect firmware capability support. That 3.00-alpha base is still preserved.
The current development branch additionally carries the PolyAT and Lower-Zone MPE work described above. It remains a standalone firmware repository; it is not a GitHub fork or a submodule of the editor project. Original history, GPL headers and author attribution are retained.
[!IMPORTANT] Experimental AI-assisted development. This tAUREON project explores the practical benefits and limits of AI-assisted work on audio and synthesizer software. Development has been human-directed with OpenAI Codex / ChatGPT and Anthropic Claude Code used in separate implementation and review roles. Treat the current expression branch as developer firmware until the historical-toolchain build and physical-hardware gates have passed.
| origin of the code | pvig/preenfm2 |
| VOSIM base branch | vosim |
| VOSIM base commit | 6ed604a43636c00bfbac9613c8f5a79a7582dfa7 |
| editor-protocol baseline branch | feature/editor-remote-store |
| reviewed PolyAT checkpoint | feature/true-poly-aftertouch @ f503e7fd34c1df887e421f782520275a7b807632 |
| PolyAT/MPE branch (merged into the default branch) | feature/full-mpe @ 374be62f05ead717b1fa674abbd97a2deac643d8 |
| firmware version | 3.10 alpha (pre-release; the editor-protocol build was 3.00 alpha, original base was 2.21b) |
Remotes are set up as:
upstream -> https://github.com/pvig/preenfm2.git
origin -> https://github.com/sscheidl/preenfm2-vosim-editor-firmware.git
Why the VOSIM branch
pvig/preenfm2 vosim carries VOSIM algorithms 29–32 and the additional LFO shapes 6–8. They are part of the base and are preserved by the 3.00-alpha work. The reference material for the historical VOSIM build is under release/fw_2.21b_lfo_vosim/.
Editor remote protocol
The editor extension uses NRPN page 4, which older firmware silently ignores:
| NRPN page / LSB | command |
|---|---|
| 4 / 0 | capability query |
| 4 / 1 | current-position query |
| 4 / 2 | store current edit buffer to a bank/preset target |
Responses use page 4, LSB 64 and above, with explicit status codes. The protocol specification and the earlier editor-protocol-only build are in release/editor-protocol-3.00alpha/. That directory is historical: it holds the hardware-tested 3.00 alpha binaries without PolyAT/MPE, and its SHA256SUMS belong to those files only. It is not updated for 3.10 alpha.
PreenFM+ 4.0.4 implements capability detection, position query, guarded Store and status reporting. On the device, Receives: must include NRPN for those requests.
Existing patch format and ordinary program/bank loading remain compatible with the underlying 2.21/VOSIM format.
Building
The historical toolchain is arm-gcc 4.7-2014q2. It is not bundled in this repository.
GCC_PATH in the Makefile still points to the original author's environment, so normally place the historical toolchain on PATH and override the tools on the command line:
export PATH="/path/to/gcc-arm-none-eabi-4_7-2014q2/bin:$PATH"
MK='make CC=arm-none-eabi-c++ AS=arm-none-eabi-as NM=arm-none-eabi-nm \
READELF=arm-none-eabi-readelf CP=arm-none-eabi-objcopy'
$MK clean && $MK pfm
$MK clean && $MK pfmo
$MK clean && $MK pfmcv
$MK clean && $MK pfmcvo
For the current hardware-validation build, also clear stale build outputs between targets and record the filename from .binfirmware; do not assume that the last .bin in build/ belongs to the target just built.
Useful host checks:
python test/host/protocol_sim_test.py
python test/host/polyat_state_test.py
python test/host/mpe_state_test.py
python tools/polyat_test.py --self-test
python tools/mpe_test.py --self-test
These checks complement — and never replace — the historical-toolchain build and physical-hardware test.
Authors and license
The PreenFM2 firmware is the work of Xavier Hosxe. The VOSIM branch used as the base is maintained by pvig. The tAUREON editor protocol (3.00 alpha), PolyAT and MPE (3.10 alpha) development in this repository were produced through human-directed development with AI assistance.
The boot screen for the 3.x alpha line credits By Hosxe & tAUREON.
GPL, as upstream. Original source-file copyright and GPL headers remain intact; the USB manufacturer string continues to identify the device as Xavier Hosxe's design.
Upstream README summary
The official PreenFM2 firmware repository is Ixox/preenfm2. The upstream project uses ARM GCC 4.7 and provides the original hardware, firmware and flashing documentation. For first-time flashing and bootloader details, follow the upstream PreenFM2 instructions rather than treating this development README as a replacement for them.
Use it
Find the MIDI ports, then start the bridge with this driver:
osc-bridge detect # prints the ready-to-run line for what is plugged in
osc-bridge run --device devices/preenfm/preenfm2.vendor.fw-3.10-alpha.json --out-port <N> --in-port <M>
Then send OSC to 127.0.0.1:7777, for example:
From Claude or any MCP client, the osc-bridge mcp server exposes this driver as preenfm2_310alpha: ask “what can the PreenFM2 do?” and it reads this map with list_routes. See the MCP guide.
MIDI controls
92 controls, each with its number, its range and the name to send
CC
| Control | CC | Range | Name to send |
|---|---|---|---|
| Bank | 0 | discrete | /preenfm2_310alpha/bank |
| Bank lsb | 32 | discrete | /preenfm2_310alpha/bank_lsb |
| Mod wheel | 1 | 0–127 | /preenfm2_310alpha/mod_wheel |
| Breath | 2 | 0–127 | /preenfm2_310alpha/breath |
| Volume | 7 | 0–127 | /preenfm2_310alpha/volume |
| Pan | 10 | 0–127 | /preenfm2_310alpha/pan |
| Scala enable | 12 | switch | /preenfm2_310alpha/scala/enable |
| Unison detune | 13 | 0–127 | /preenfm2_310alpha/unison/detune |
| Unison spread | 14 | 0–127 | /preenfm2_310alpha/unison/spread |
| Engine algo | 16 | discrete | /preenfm2_310alpha/engine/algo |
| Hold pedal | 64 | switch | /preenfm2_310alpha/hold_pedal |
| Filter type | 70 | discrete | /preenfm2_310alpha/filter/type |
| Filter param1 | 71 | 0–127 | /preenfm2_310alpha/filter/param1 |
| Filter param2 | 72 | 0–127 | /preenfm2_310alpha/filter/param2 |
| Filter gain | 73 | 0–127 | /preenfm2_310alpha/filter/gain |
| Mpe slide | 74 | 0–127 | /preenfm2_310alpha/mpe/slide |
| Lfo env2 silence | 59 | 0–127 | /preenfm2_310alpha/lfo_env2/silence |
| Env attack carriers | 80 | 0–127 | /preenfm2_310alpha/env/attack_carriers |
| Env attack modulators | 62 | 0–127 | /preenfm2_310alpha/env/attack_modulators |
| Env release carriers | 81 | 0–127 | /preenfm2_310alpha/env/release_carriers |
| Env release modulators | 63 | 0–127 | /preenfm2_310alpha/env/release_modulators |
| Arp clock | 100 | discrete | /preenfm2_310alpha/arp/clock |
| Arp direction | 101 | discrete | /preenfm2_310alpha/arp/direction |
| Arp octave | 102 | discrete | /preenfm2_310alpha/arp/octave |
| Arp pattern | 103 | discrete | /preenfm2_310alpha/arp/pattern |
| Arp division | 104 | discrete | /preenfm2_310alpha/arp/division |
| Arp duration | 105 | discrete | /preenfm2_310alpha/arp/duration |
| Current instrument | 119 | discrete | /preenfm2_310alpha/current_instrument |
| All sound off | 120 | trigger | /preenfm2_310alpha/all_sound_off |
| All notes off | 123 | trigger | /preenfm2_310alpha/all_notes_off |
| Reset | 127 | trigger | /preenfm2_310alpha/reset |
| Engine im1 | 17 | 0–127 | /preenfm2_310alpha/engine/im1 |
| Engine im2 | 18 | 0–127 | /preenfm2_310alpha/engine/im2 |
| Engine im3 | 19 | 0–127 | /preenfm2_310alpha/engine/im3 |
| Engine im4 | 20 | 0–127 | /preenfm2_310alpha/engine/im4 |
| Engine im5 | 21 | 0–127 | /preenfm2_310alpha/engine/im5 |
| Mix level1 | 22 | 0–127 | /preenfm2_310alpha/mix/level1 |
| Mix level2 | 24 | 0–127 | /preenfm2_310alpha/mix/level2 |
| Mix level3 | 26 | 0–127 | /preenfm2_310alpha/mix/level3 |
| Mix level4 | 28 | 0–127 | /preenfm2_310alpha/mix/level4 |
| Mix pan1 | 23 | 0–127 | /preenfm2_310alpha/mix/pan1 |
| Mix pan2 | 25 | 0–127 | /preenfm2_310alpha/mix/pan2 |
| Mix pan3 | 27 | 0–127 | /preenfm2_310alpha/mix/pan3 |
| Mix pan4 | 29 | 0–127 | /preenfm2_310alpha/mix/pan4 |
| Osc1 freq | 50 | 0–127 | /preenfm2_310alpha/osc1/freq |
| Osc2 freq | 51 | 0–127 | /preenfm2_310alpha/osc2/freq |
| Osc3 freq | 52 | 0–127 | /preenfm2_310alpha/osc3/freq |
| Osc4 freq | 53 | 0–127 | /preenfm2_310alpha/osc4/freq |
| Osc5 freq | 54 | 0–127 | /preenfm2_310alpha/osc5/freq |
| Osc6 freq | 55 | 0–127 | /preenfm2_310alpha/osc6/freq |
| Matrix1 mul | 46 | 0–127 | /preenfm2_310alpha/matrix1/mul |
| Matrix2 mul | 47 | 0–127 | /preenfm2_310alpha/matrix2/mul |
| Matrix3 mul | 48 | 0–127 | /preenfm2_310alpha/matrix3/mul |
| Matrix4 mul | 49 | 0–127 | /preenfm2_310alpha/matrix4/mul |
| Lfo1 freq | 56 | 0–127 | /preenfm2_310alpha/lfo1/freq |
| Lfo2 freq | 57 | 0–127 | /preenfm2_310alpha/lfo2/freq |
| Lfo3 freq | 58 | 0–127 | /preenfm2_310alpha/lfo3/freq |
| Stepseq1 gate | 60 | 0–127 | /preenfm2_310alpha/stepseq1/gate |
| Stepseq2 gate | 61 | 0–127 | /preenfm2_310alpha/stepseq2/gate |
| Performance cc1 | 115 | 0–127 | /preenfm2_310alpha/performance/cc1 |
| Performance cc2 | 116 | 0–127 | /preenfm2_310alpha/performance/cc2 |
| Performance cc3 | 117 | 0–127 | /preenfm2_310alpha/performance/cc3 |
| Performance cc4 | 118 | 0–127 | /preenfm2_310alpha/performance/cc4 |
| Env1 attack | 65 | 0–127 | /preenfm2_310alpha/env1/attack |
| Env2 attack | 75 | 0–127 | /preenfm2_310alpha/env2/attack |
| Env3 attack | 76 | 0–127 | /preenfm2_310alpha/env3/attack |
| Env4 attack | 77 | 0–127 | /preenfm2_310alpha/env4/attack |
| Env5 attack | 78 | 0–127 | /preenfm2_310alpha/env5/attack |
| Env6 attack | 79 | 0–127 | /preenfm2_310alpha/env6/attack |
| Env1 release | 82 | 0–127 | /preenfm2_310alpha/env1/release |
| Env2 release | 83 | 0–127 | /preenfm2_310alpha/env2/release |
| Env3 release | 84 | 0–127 | /preenfm2_310alpha/env3/release |
| Env4 release | 85 | 0–127 | /preenfm2_310alpha/env4/release |
| Env5 release | 86 | 0–127 | /preenfm2_310alpha/env5/release |
| Env6 release | 87 | 0–127 | /preenfm2_310alpha/env6/release |
| Lfo1 phase | 88 | 0–127 | /preenfm2_310alpha/lfo1/phase |
| Lfo2 phase | 89 | 0–127 | /preenfm2_310alpha/lfo2/phase |
| Lfo3 phase | 90 | 0–127 | /preenfm2_310alpha/lfo3/phase |
| Lfo1 bias | 91 | 0–127 | /preenfm2_310alpha/lfo1/bias |
| Lfo2 bias | 92 | 0–127 | /preenfm2_310alpha/lfo2/bias |
| Lfo3 bias | 93 | 0–127 | /preenfm2_310alpha/lfo3/bias |
| Lfo1 shape | 94 | 0–127 | /preenfm2_310alpha/lfo1/shape |
| Lfo2 shape | 95 | 0–127 | /preenfm2_310alpha/lfo2/shape |
| Lfo3 shape | 96 | 0–127 | /preenfm2_310alpha/lfo3/shape |
NRPN
| Control | CC | NRPN (MSB/LSB) | Range | Name to send |
|---|---|---|---|---|
| Param | 0 / 0–127 | 0–16383 | /preenfm2_310alpha/nrpn/param |
|
| Param page1 | 1 / 0–127 | 0–16383 | /preenfm2_310alpha/nrpn/param_page1 |
|
| Stepseq1 step | 2 / 0–15 | 0–16383 | /preenfm2_310alpha/nrpn/stepseq1_step |
|
| Stepseq2 step | 3 / 0–15 | 0–16383 | /preenfm2_310alpha/nrpn/stepseq2_step |
|
| Dump request | 127 / 127 | trigger | /preenfm2_310alpha/nrpn/dump_request |
Editor protocol
| Control | CC | NRPN (MSB/LSB) | Range | Name to send |
|---|---|---|---|---|
| Editor capability query | 4 / 0 | trigger | /preenfm2_310alpha/editor/capability_query |
|
| Editor position query | 4 / 1 | trigger | /preenfm2_310alpha/editor/position_query |
|
| Editor store | 4 / 2 | 0–16383 | /preenfm2_310alpha/editor/store |
Performance MIDI
Notes, pitch bend and any CC are also available: /preenfm2_310alpha/note/on <note> <velocity>, /preenfm2_310alpha/cc/<number> <value>, /preenfm2_310alpha/pitchbend <0-16383>, /preenfm2_310alpha/program_change <n>.
Limitations
PreenFM2 3.10 alpha (pre-release of 2026-10-02, built on Patrice Vigouroux's VOSIM branch). Against the official 2.21: true polyphonic key pressure (send it with /poly_aftertouch; the AftT matrix routes become per note), Lower-Zone MPE (master channel 1, member channels set in the menu; per-note bend up to +/-48, slide on CC 74), and the editor protocol on NRPN page 4 (capability, position and store). Each of the 4 timbres listens on its channel set in the menu; routes default to channel 1. CCs are received when the menu's Receive setting includes CC, NRPNs when it includes NRPN. The values and their scaling are those of the decoder (shown on each route). Replies of the editor protocol and the NRPN dump come back as CCs 99/98/6/38 and are not decoded. Not mapped: CC 12/13 Scala (CC 13 is also unison detune), omni on/off, the synth parameter list behind NRPN pages 0 and 1, SysEx patch transfers. Untested on hardware.
Firmwares
The PreenFM2 runs 2 firmwares SynthHub maps, each with its own MIDI or another's it shares. All the devices with several: firmwares.
| Firmware | From | Licence | MIDI | Sounds |
|---|---|---|---|---|
| 3.10 alpha | github.com/sscheidl | its own | ||
| pfm2sid | github.com/patrickdowling | its own |
Frequently asked questions
Which MIDI CC controls the filter cutoff on the PreenFM2?
CC 70 (0–127), named /preenfm2_310alpha/filter/type in SynthHub's map.
How many parameters of the PreenFM2 can be controlled over MIDI?
SynthHub's map of the PreenFM2 lists 84 MIDI CC and 8 NRPN parameters, each with its number, range and a readable name; the full chart is on this page.
Can an AI agent like Claude control the PreenFM2?
Yes: SynthHub's hosted MCP server knows the PreenFM2's MIDI map, and its browser relay lets the agent send it parameter changes or load a sound, each sound change confirmed by you.