Contributing to osc-bridge
May 14, 2026 Β· View on GitHub
Thanks for wanting to help. There are five concrete ways to contribute:
- Add a new device JSON (doc-derived, untested).
- Update an existing device JSON β extend its SysEx layer after sniffing, correct a wrong CC number, rename routes, extend coverage. Especially important after a bulk import (e.g. pencilresearch) where the device starts as CC/NRPN only and grows SysEx over time.
- Promote a π device to β (you own the hardware and tested it).
- Fix a bug in the engine, runtime, or an existing device JSON.
- Extend the schema for a device class the declarative surface can't yet describe.
All five follow the same broad workflow: fork β branch β commit β PR. Each has its own PR template under .github/PULL_REQUEST_TEMPLATE/ β GitHub will surface the right one when you use ?template=<name>.md in the PR URL, or you can paste the template block manually.
General rules
- One concern per PR. Don't bundle a new device with a schema change. If the device needs the schema change, open two PRs: schema first, device second.
- Run
cargo test --releaseandcargo run --release -- lint <your-device.json>before you submit. PRs with failing tests won't be merged. - Be honest about coverage. A device you haven't tested on real hardware is π, not β . This matters β users trust the table.
- No
transform/scriptunless absolutely needed. The Lua escape hatch is documented indocs/scripting.md;osc-bridge lintwarns on every use. Reach for it only when the declarative schema genuinely can't express the protocol. - Commit style: imperative present, include a why. Co-author trailers welcome.
One source, one file β no silent fusion
A MIDI implementation changes between firmware versions, and community sources (Electra presets, pencilresearch CSVs) are often tied to a specific firmware that the source itself doesn't always record. Fusing two sources into one JSON hides contradictions β we tried it on Tempera and ended up with duplicate CCs, decalΓ© emitter ranges, and 193 unsectioned entries.
The rule going forward:
-
One
_sources[]entry per file. If you have two sources for the same device, ship two files. Do not merge their CC tables, do not union their commands. -
Pin firmware when you can.
_sources[].firmwareis required on everyvendor-docdriver and strongly recommended everywhere else. Use"unknown"explicitly rather than omitting the field. -
File naming convention:
<device>.<source-tier>.fw-<version>.json. Examples:tempera.vendor.fw-2.2.jsonβ vendor doc, firmware 2.2tempera.electra-community.fw-unknown.jsonβ Electra community preset, firmware unknownsubsequent-37.pencilresearch.fw-1.1.jsonβ pencilresearch CSV, firmware 1.1 Source-tier tokens:vendor,electra-busa/electra-community/electra-<author>,pencilresearch,hardware-verified.
Companion docs (
.md) follow the same slug. A device JSON may ship with a prose companion (protocol deep-dive, SysEx notes, capture logs) next to it:push-3.jsonβpush-3.md. When you rename the JSON, rename the.mdwith the exact same stem β e.g.push-3.vendor.fw-1.1.jsonpairs withpush-3.vendor.fw-1.1.md. If several variants share the same protocol notes, keep a single shared.mdnamed after the most authoritative variant and cross-link the others from their_limitations. -
Catalogue groups variants automatically.
scripts/regen_supported_devices.pygroups files that sharedevice.name+device.vendorinto one catalogue entry listing every variant with its firmware and tier. -
Prefer the user's firmware at runtime. The bridge takes a
--devicepath, so users pick the variant that matches their hardware. If you have a vendor-doc variant that clearly supersedes a community one (newer firmware, authoritative), note it in the community variant's_limitationsso users know which to prefer.
Adding a new device JSON
New to this? Start with docs/TUTORIAL_FIRST_DEVICE.md
β a 30-minute hands-on walkthrough that builds a working CC driver end to end.
The steps below are the reference checklist.
Step 0 β is there a source?
We only merge device JSONs that have a verifiable source. In decreasing order of preference:
- A vendor's published programmer's reference / MIDI implementation guide (Novation, Arturia, Electra One, Elektron, Sequential, Moog, Roland, Yamaha, Korgβ¦).
- A canonical community CSV, e.g. pencilresearch/midi.
- Your own hardware reverse-engineering (USBPcap + tshark on the vendor editor). Attach the pcap or at least the SysEx captures in the PR description so a reviewer can independently verify.
Pull requests without a cited source get a "source?" comment and stay open until one is provided.
Step 1 β file & folder
- Path:
devices/<vendor>/<device-slug>.json(e.g.devices/moog/subsequent-37.json). vendoris a kebab-case folder (moog,arturia,expressive-e,tasty-chipsβ¦).- Use the existing JSON schema. Reference:
docs/DEVICE_JSON_SCHEMA.md.
Step 2 β required fields
{
"_source": "<one line: vendor doc URL / version / state>. NOT verified on hardware.",
"device": {
"name": "Your Device",
"vendor": "Vendor",
"revision": "firmware X.Y",
"osc_prefix": "/yourdevice",
"manufacturer_id": [...],
"device_id": [...]
},
"sysex": { "header": [...], "footer": [247] },
"commands": [ /* β¦ */ ],
"params": { /* or */ } ,
"cc_params": { /* or */ },
"midi_in": { /* standard note/cc/pitchbend mapping */ },
"replies": [ /* β¦ */ ]
}
The _source field is not used by the runtime but IS checked by reviewers. It's the single most important line in your JSON β don't skip it.
Step 3 β OSC naming rules
- Use
/snake_case/sub_section/paramstyle. - Don't duplicate the section in the param name (
/oscillator_1/octave, not/oscillator_1/oscillator_1_octave). - Keep names close to the vendor's terminology. If the vendor calls it Fatness LFO, emit
fatness_lfo, notfat_modulation.
Step 4 β tests
Every new device MUST ship with at least one test file, tests/<device_slug>.rs. Required tests:
- Load succeeds:
Device::load(...).unwrap(). - Sample a couple of commands and assert the exact bytes of the generated SysEx frame.
- Loop over
device.commandswith synthetic bindings and verify every frame starts with0xF0and ends with0xF7.
Copy tests/electra_one.rs or tests/launch_control_xl_3.rs as a template.
Step 5 β lint
cargo run --release -- lint devices/<vendor>/<slug>.json
Zero warnings expected unless you deliberately used transform / script.
Step 6 β update the docs
- Add a one-line row in
README.md's compact table (vendor, device, status marker, entry count). - Add a full section in
docs/SUPPORTED_DEVICES.md(source URL, file path, coverage breakdown, notes). - If your device needed a schema extension, add it to
docs/DEVICE_JSON_SCHEMA.mdin the same PR.
Step 7 β PR
Title: Add <Vendor> <Device> (doc-derived) β or (hardware-verified) if you tested it.
In the description:
- Link to the source (doc URL or pcap artefact).
- Entry count (commands / params / replies).
cargo test --releaseoutput.- Any schema extensions, even tiny ones.
- Anything you deliberately didn't model and why (e.g. unsupported SysEx opcodes).
Updating an existing device
Most updates fall into one of these buckets:
- Adding SysEx on top of an imported CC/NRPN skeleton. Bulk-imported devices (via
scripts/import_pencilresearch.py) start as pure CC/NRPN. Once you sniff the vendor editor or read the official SysEx spec, you can extend the same JSON with asysex.header/footerand acommandsblock. Keep the existingcc_paramsentries intact unless the vendor's SysEx actively conflicts with them. - Fixing a wrong CC / NRPN / range / orientation. Happens especially when a doc says one thing and the device behaves differently. Always cite the source of the correction (hardware test log or alternative doc).
- Renaming OSC routes for consistency. Breaking change for any client already bound to the old path. Bump the device's
revisionfield and flag the change loudly in the PR. - Extending coverage. Parameters omitted by the original source but legitimate. Document the new source.
Use the update_device.md PR template. Required bits:
- Kind of change (SysEx layer / CC correction / rename / extension / schema / docs).
- Source of the change β same seriousness as
_sourceon a new device. We don't merge corrections without a citable reason. - Coverage delta (entry counts before/after).
- Explicit flag if any OSC route changed name or arg layout.
Promoting π β β (hardware verification)
- Plug the device in and run the bridge.
- Exercise every declared command, param, and reply.
- Update any bytes / addresses / OSC names that were wrong in the doc-derived version.
- Record the test session (a text log is enough β list the OSC messages you sent and the MIDI you observed).
- Open a PR titled
Promote <Device> to hardware-verified, attach the log in the description, flip the marker in bothREADME.mdanddocs/SUPPORTED_DEVICES.md.
These PRs are first-class β they're how the catalogue gains credibility.
Schema extensions
Schema extensions (new frame-token kinds, new arg types, new runtime behaviours) go through an open issue first. Post:
- The device or class of devices that motivates the change.
- A concrete example of what the JSON would look like.
- Why the Lua escape hatch isn't enough.
Once aligned, the PR should add:
- The new Rust fields in
src/device.rs(Option-wrapped,#[serde(default)], retro-compat). - Handling in
src/frame.rsand / orsrc/runtime.rs. - At least one device exercising the new capability (can be a minimal example under
devices/examples/). - Updated docs in
docs/DEVICE_JSON_SCHEMA.mdandCHANGELOG.mdunder the next minor version.
Local-only files β the .local/ zone
.local/ at the repo root is git-ignored. Use it for machine-local files
that aren't part of the project: scratch space, local config overrides,
experiment caches.
One supported hook: scripts/sync_sources.py reads an optional
.local/sources.toml and merges its [[source]] blocks with the committed
sources.toml. This lets you point the bulk-import tooling at a
local-only source declaration without editing the tracked config. Absent
file = normal case, nothing happens.
A note on _sources[] provenance: author names are kept as public credit;
internal account identifiers are not stored β the importer drops them.
Bug reports
If you observe wrong bytes going out or a JSON that refuses to load:
- Run
cargo run --release -- inspect <file.json>and paste the output. - Attach a minimal OSC message that triggers the bug and the expected vs observed MIDI bytes.
- Open an issue. Don't skip the "expected vs observed" part β half the bug reports we can action have it, the other half we can't.
License
Contributions are accepted under GPL-3.0-or-later, same as the rest of the project. You retain copyright on your contributions.