Changelog
All notable changes to open-stomp. The format follows
Keep a Changelog and the project uses
Semantic Versioning.
Unreleased
Added
- Release channels: the
stablebranch follows the newest release andunstablethe newest release or pre-release (vx.y.z-beta.ntags, published as GitHub pre-releases). Follow one from Nix or cargo instead of a version tag. - License files: dual-licensed under MIT or Apache-2.0, at your option.
- A contributing guide (
CONTRIBUTING.md) and issue and pull request templates.
Changed
- Rewrote the README to be shorter and easier to follow.
- docs: open questions (docs/09) list what’s still unknown first, with solved ones under “Resolved”; the
protocol cheat sheet moved into docs/01; corrected the
blefeature (on by default) and the Linux D-Bus headers.
0.6.1 - 2026-10-24
Fixed
- CI only: the docs-site and release jobs now install the D-Bus headers the
bledefault feature needs on Linux. No library changes from 0.6.0.
0.6.0 - 2026-10-23
Added
- Bluetooth (the pedal’s Wireless setting), feature
ble: the engine also scans for the pedal over BLE-MIDI (btleplug: CoreBluetooth, BlueZ, WinRT) and lists it as a port"JME STOMPBOX (Bluetooth)", with no pairing. It works like USB: confirmed on the pedal for the full startup sync, effect on/off, parameters, volume, page, preset switching, preset and settings writes, drum and looper (examples/ble_check.rs), and NAM and IR imports (about 10 s each; the official app doesn’t offer imports over Bluetooth). Auto-connect prefers USB and moves to USB when the pedal is plugged in.Config::bluetooth(default on) turns it off; PythonDevice(bluetooth=...), which is built withble.is_ble_port,BLE_PORT_SUFFIX. blemidi: BLE-MIDI packet framing (encode with packing and SysEx splitting, decode with running status).
Changed
- Messages to a Bluetooth pedal go out at least 15 ms apart, and a CC replacing the one queued just before it overwrites it: an unpaced burst of 61 CCs froze the pedal until a power cycle.
- On Linux,
bleneeds the D-Bus headers (libdbus-1-dev); CI and the Linux wheel install them.
Fixed
- The edit-buffer read (
0x0C) is padded with 14 zero bytes like the official app’s. A bare request made the pedal take its parameters from whatever the previous message left in its buffer, giving empty or truncated replies.
0.5.1 - 2026-10-22
Added
- Rename user slots without touching their sound:
Device::rename_user_amp(slot, name)andDevice::rename_user_ir(slot, name)(Python too) send only the import’s name record (the AMP slot-info write, the IR0x09name write) and re-read the slot. The official app can’t rename; confirmed on the pedal (a slot’s sweep response before and after differs by at most 0.09 dB, rig noise; docs/05, “Renaming a slot”). Names are cut to 31 characters.
Changed
- The activity event for a user AMP slot-info write is “User AMP slot” (it was “User AMP import”), since a rename sends the same record; like “User IR slot”.
- README and docs site: the NAM and IR graphs re-measured on hardware in one session with the official app’s, 0.4’s
and 0.5’s conversions (
tools/pedalsim/readme_plots.py <session>, now with a dry-take reference instead of the Mac rig’s calibration); the real-playing comparison is on the README and the site’s front page.
0.5.0 - 2026-10-22
Added
- Documentation site (mdBook,
site/, published to GitHub Pages by.github/workflows/pages.yml): install guides, both repositories’ docs, the NAM / IR analysis with an interactive re-amp comparison, and the API reference.
Changed
nam::optimise: the loss now weights frequencies so each octave counts about the same (Options::weighting,Weighting::Octave, the new default). The old flat weighting gave the bottom octave ~0.5% of the fit, and imports came out 5–10 dB short at 50–160 Hz. In simulation the low-end error (40–160 Hz) of the Ampeg SVT-VR Classic (Normal) model drops from 4.9 to 2.1 dB rms and the frequency response error from 2.5 to 1.2 dB; the frequency response improves on all six models tried, the low end on five (docs/05, “Low end”).Weighting::Flatkeeps the old loss; examplenam_optimise --flat. On hardware, a bass DI re-amped through the pedal against the model and IR in software: low end 5.2 → 3.8 dB rms, overall tone 3.5 → 2.8 dB (2.1 dB with the IR change below).ir::convert_optimised: the tap fit now covers 30 Hz – 16 kHz instead of 25 Hz – 20 kHz. Cab IRs have next to nothing above 16 kHz, and matching it cost taps the rest of the range needs: on fifteen Ampeg IRs the 1/3-octave error of what the pedal plays drops from 2.42 to 2.18 dB rms (better on 13; the two DI recordings get slightly worse), and the 40–160 Hz error from 3.55 to 2.90 dB.
0.4.0 - 2026-10-22
Added
- NAM import level normalisation, like NAM plugins:
nam::convert::normalise_levelscales G2 (after the pedal’s saturation, so a pure volume change) so that every model plays at the same level,NORMALISED_LEVEL_DB, measured withpedal_level_dbon pink noise and calibrated on the pedal to its built-in bass amps (two SVT-VR models 6 dB apart raw land within 0.2 dB of each other; un-normalised they sat 13–19 dB below the built-in amps). Models span about 17 dB raw; the conversions inherit the official app’s level, which plays models quietly (24–34 dB below their own output). On by default forDevice::import_nam_file;Device::import_nam_file_as(.., normalise), Pythonnam.convert(..., normalise=True)andDevice.import_nam_file(..., normalise=...).nam::Model::loudness(the file’s metadata). Examplenam_level. irmodule: read and write WAV files (Wav: PCM 8–32-bit and float, any channel count), andconvert, the official app’s IR conversion recovered from its native code: files that aren’t 48 kHz mono 24-bit are converted first (first channel, JUCE’s resampler,resample_like_app, 24-bit), then the first 2048 samples are reversed and scaled so the 398 Hz – 3 kHz response is centred on 0 dB (app_gain). Identical to eleven captured imports (tests/ir_import.rs).Device::import_ir_file(slot, path)converts and uploads, named like the app (first 16 characters). Also a windowed-sincresample. Python:open_stomp.ir,Device.import_ir_file. Examplesir_check,ir_test_files(designed test IRs,tests/data/ir) andir_capture_check.- The pedal plays only the first 384 taps of an uploaded IR (re-amped probe IRs;
ir::PEDAL_IR_TAPS, docs/05). ir::convert_optimised/Method::Optimised(featurenam): a 384-tap minimum-phase filter fitted to the whole IR’s 1/6-octave response, at the level the app means to give it. On fifteen bass IRs in software, what the pedal plays is 2.4 dB rms from the full IR against 7.9 dB for the official conversion, which leaves IRs with a delayed start 35–44 dB too quiet. Confirmed on hardware (re-amped: 2.1 vs 4.8 dB rms, and 5.5 dB vs 16 dB rms plus 46 dB too quiet for a delayed IR). The default forDevice::import_ir_file(Python too);convert_with,Device::import_ir_file_with(.., Method::App)for the official conversion, Pythonmethod=, exampleir_compare.
Fixed
- User IR uploads put the slot in data-header byte 0, as the official app does (it was in byte 1, NAM’s slot byte,
so only slot 0 was right).
upload::Chunk::ir_slot;Reassemblerreports either slot.
0.3.0 - 2026-10-21
Added
namfeature and module (on by default): load and run Neural Amp Modeler.nammodels (a pure-Rust port of NeuralAmpModelerCore’s WaveNet, plain andSlimmableContainer; matches it to ≤ 3e-5 on its example models, which are now test vectors), andnam::convert, the official app’s NAM → pedal converter: a fundamental/3rd-harmonic tone test and a small-signal linear response give two minimum-phase FIRs. With the app’s own excitation the result matches its upload to ≤ 0.03 dB; with ours (the default) within ~1–1.5 dB rms, the app’s own measurement noise.Device::import_nam_file(slot, path)converts and uploads. Examplesnam_convert,nam_render,nam_check. Python:open_stomp.nam,Device.import_nam_file. Confirmed on hardware: an official and an open conversion of the same bass model sound identical.nam::convert::Measure::Clean(convert_measured; Pythonnam.convert(..., measure="clean")), experimental: the same converter with a steady-state small-signal measurement instead of the app’s transient-affected one. Re-amp tests on hardware against the real model (Ampeg SVT bass model): closer mid scoop, top-end roll-off and upper-note distortion than the official algorithm, but still short on the 63–90 Hz resonance and about 5 dB quieter. Not the default.nam::convert::PEDAL_TAPS,upload_data,pedal_irs: the pedal runs only the first 512 taps of each uploaded filter (found by re-amping; docs/05).Measure::Cleannow uploads exactly those taps, with the ignored halves zeroed;Measure::Appkeeps the app’s layout.nam::optimiseandMeasure::Optimised, the default forDevice::import_nam_file(about a minute per model): fit G1/G2 so a simulation of the pedal (y = G2 ⊛ f(G1 ⊛ x), its saturation curve fitted from re-amp recordings) matches the model on sweeps, tones and pink noise, starting from the clean conversion. Hand-written gradients, Adam, no ML framework. On hardware (Ampeg SVT bass model) it scores 6.72–6.85 dB rms error against the real model, against 9.32 for the official conversion in the same session.Device::import_nam_file_with(.., Measure::App)keeps the official algorithm (a few seconds). Progress and cancel:convert::convert_optimised,optimise::optimise_with_progress. Alsooptimise::simulate, examplenam_optimise. Python:Device.import_nam_file(..., measure="optimised")(default) andnam.convert(..., measure="optimised").nam::convert::official_excitation/find_official_excitation: read the official app’s test signal from the user’s own Stomp Manager install (located by fingerprint; no vendor data in the source). With it the conversion equals the official one (error energy −51 to −93 dB on ten models). Strictly opt-in:Device::import_nam_fileand everything else never read the app. Python:nam.official_excitation().uploadmodule: user AMP (NAM, SysEx0x58) and user IR (0x10+0x09) upload framing: chunking, the 128-byte slot-info record (UserAmpInfo: name, factor), delete, and aReassemblerthat turns captured chunks back into floats. Every captured import rebuilds byte for byte.Device::import_user_amp/delete_user_amp/import_user_irsend the official app’s sequences (2048 finite floats, slots 0–9, elseUploadError). Also in the Python bindings (open_stomp.upload,Device.import_user_amp…).DeviceStatefollows user AMP imports and deletes (slot names, log events with progress).
Changed
- docs/05: corrected chunk header (plain byte offset + total length, not 8-byte units), the slot-info record layout, and the app selecting the slot’s model before uploading.
- docs/05, docs/09: the NAM converter algorithm, recovered from the vendor code and verified stage
by stage: a small-signal best linear approximation and a sine tone test (fundamental and 3rd
harmonic) through the network give two minimum-phase FIRs (Wiener–Hammerstein G1/G2) whose first
1024 taps, reversed, are the upload;
factoris always 1.0.
0.2.0 - 2026-10-21
Added
- Python bindings (
bindings/python,pip install ./bindings/python,import open_stomp): the whole library through PyO3, mirroring the crate’s modules: protocol, catalogue, records,DeviceState,RequestQueue, capture reader and theDeviceengine. Oneabi3wheel per platform for Python ≥ 3.9, typed stubs checked withstubtest, GIL released while waiting, Ctrl-C interrupts blocking calls.DeviceState.feed(update)applies engine updates. Tests driven by the capture logs,monitor.pyandshell.py(interactive) examples and a Python guide (docs/11). - The repository is now a Cargo workspace; the crate and the Python package share its version.
0.1.0 - 2026-10-21
First release as a standalone library. The protocol code comes from the stomp-proto crate of
Open Stomp Manager 0.3.0, and the connection engine from that
app’s device module.
Added
- Protocol layer (no I/O, no dependencies): 8-in-7 packing, message parsing and SysEx framing,
stream reassembly, request (
req::) and command (host::) builders, the module / model / parameter catalogue, preset and settings records (decode and encode, unknown bytes preserved), theDeviceStatetracker with plain-English events, and the capture log reader. queue::RequestQueue: the pedal’s one-request-at-a-time pacing as a pure state machine (reply matching, timeouts, sync progress, settings-write coalescing),queue::handshake()(the official app’s startup reads) andqueue::follow_up()(re-reads after pedal notifications).devicefeature (default):Deviceengine thread with port scanning and hotplug, auto-connect, startup sync, live sends, queued writes, listen-only mode and capture replay. Typed helpers for presets, models, parameters, volume, pedal page, looper, preset save and settings writes. Clean shutdown when the handle is dropped. GUI-agnostic: a wake callback replaces the app’s egui dependency, and timestamps areSystemTime.is_stomp_port,USB_ID.- Examples:
monitor(live or replayed, decoded),decode(capture to text),ports. - Tests: the startup sync against a fake pedal answering from the capture, and the engine thread end to end through replay, on top of the existing capture-driven tests.
- Documentation: the full protocol docs (01–09) and a library guide (10).