// ZTRANSLATOR — REAL-TIME MIDI ROUTING ENGINE

Embeddable engine · MIDI / keystroke / mouse / AppleScript routing · signed-32-bit rules VM · BOME .bmtp import · JSON project model · music / MIDI 2.0 layer · pure Rust

13,062 source lines · 10,556 code · 40 Rust files · 161 tests (128 inline + 33 integration)

Report Port GitHub
// Color scheme

>_EVERY KNOB, WIRED TO ANYTHING

A self-contained, real-time MIDI translation engine in pure Rust. It listens on MIDI input ports, matches each incoming message against translator entries, runs a rules script on a small signed-32-bit integer VM, and fires an outgoing action — MIDI out, keystrokes, mouse, AppleScript, timers, or host-defined commands. It imports and exports BOME MIDI Translator Pro .bmtp projects and stores native projects as JSON. The crate carries no UI of its own: it is the engine meant to be hosted by a GUI/CLI application.

Pipeline

An incoming MIDI message flows through four stages on a dedicated worker thread:

MIDI in ──▶ matcher ──▶ rules VM ──▶ outgoing action
 (midir)    (per-entry)  (integer)   (MIDI / keys / mouse / AppleScript / timer / custom)

The engine owns the open input ports (midir) and emits EngineEvents back to the host through a sink callback. Actions the host wants to handle itself route through Outgoing::Custom { command } plus a host-installed handler — so the engine maps MIDI to host commands without being modified.

Embedding

The crate is a library (ztranslator). A host wires the engine's events to its own UI and IPC:

use ztranslator::{Engine, EngineConfig, EngineEvent};

let mut engine = Engine::new(EngineConfig::default())?;
engine.set_event_sink(|ev: EngineEvent| { /* forward to UI */ });
engine.set_custom_handler(|cmd: String| { /* host-defined actions */ });

let project = ztranslator::bmtp::import("Ableton.bmtp")?;
engine.load_project(project);
engine.start()?;
engine.open_input("nanoKONTROL2 SLIDER/KNOB")?;

Data model

Project → Preset → Translator, where each Translator carries:

Incoming

The trigger — one of 26 types: MIDI / 14-bit, OSC, Art-Net + sACN DMX, serial, gamepad, HID, MIDI clock, MTC, MMC, Ableton Link, audio, CV/gate, HTTP / WebSocket / MQTT / TCP, RTP-MIDI, keystroke, timer, or cron schedule. The matcher captures the value into the rules VM's inputs.

Rules

An ordered list of rules run on the integer VM before the action fires — conditionals, arithmetic, variable assignment, control flow.

Outgoing

The action — one of 39: MIDI / OSC / DMX / sACN out, MIDI clock + MTC generators, MMC, RTP-MIDI, Ableton Link, HTTP / WebSocket / MQTT / TCP, CV/gate, gamepad rumble, keystroke, mouse, AppleScript, speak / play-sound / clipboard, notify, file write, serial, timer, preset control, or a host-defined custom command.

The model is serde-serializable; native projects are stored as JSON via Project::load_json / Project::save_json. Presets group translators and can be switched at runtime.

Rules language

A faithful implementation of BOME's rules language, compiled and run on a small signed-32-bit integer VM:

Expressions

Arithmetic + - * / % and bitwise & | ^ >> << over signed 32-bit values.

Conditionals

IF a <cmp> b THEN <action> with the six comparators == != >= <= > <.

Control flow

Goto / Label, Skip Next [n], exit-and-execute / ignore, and Log.

Variables

10 locals (oo…xx, one event) and two-character globals (ga…z9, project-lifetime, init 0).

Values wrap on overflow; division / modulo by zero yields 0 — matching BOME's documented semantics exactly. These behaviors are pinned by tests/rules_vm.rs.

.bmtp import + export

bmtp::import(path) parses BOME MIDI Translator Pro project files: the INI container, MID3 / KAM3 XML payloads, timer / EnDi payloads, and the Options rules blob. Export re-serializes a Project back to an unsigned .bmtp that round-trips through import. The RSA signature section is not produced, so a signed export for BomeBox / MT Player is out of scope — the native store is JSON.

Import is lossless: encodings whose binary layout is not yet decoded (older MID1 MIDI, KAM1 keystrokes, mouse, serial, launch) are preserved verbatim, so a project round-trips even when individual entries are not yet natively understood.

Music & MIDI 2.0 layer

Twenty pure, side-effect-free modules sit on top of the raw MIDI byte. They hold no state and open no ports, so each is unit-tested in isolation and callable from a rule, from the host, or from the editor. Nothing in this layer has a BOME counterpart — BOME treats a note as a bare 0–127 integer.

theory.rs · spelling.rs · voicing.rs

Note name ↔ MIDI number (scientific pitch), chord construction, in-scale enumeration; key-signature-aware enharmonic spelling (Eb, not D#, in flat keys); inversion, drop voicings, open/close spacing, voice leading.

tuning.rs · mpe.rs

12-TET frequency math, cents ↔ pitch-bend, Scala .scl scale files, MIDI Tuning Standard 3-byte frequencies + real-time single-note tuning SysEx; MPE zone layout, the MPE Configuration Message, per-note bend-range setup, least-loaded voice allocation.

gm.rs · drummap.rs · ccmap.rs

The 128 GM Level 1 programs and 16 families plus Bank-Select-then-Program-Change builders; the GM percussion map and drum-note remapping; CC assignment names, MSB/LSB 14-bit pairing, and the seven Channel Mode messages (CC 120–127).

sysex.rs · smf.rs · stream.rs

SysEx identity request, GM / Roland GS / Yamaha XG resets, Universal Real Time master volume, Roland checksums, 8↔7-bit packing; Standard MIDI File variable-length quantities, MThd header, MTrk writer; running-status stream parse + re-serialize with interleaved real-time bytes.

midi2.rs · chanmatrix.rs

MIDI 2.0 Universal MIDI Packet codecs (type 0x2 and 0x4), spec-accurate min-center-max 7↔16-bit scaling, (N)RPN build / parse; 16-bit channel allow / block masks and a 16×16 routing matrix that drops, passes, or fans a channel-voice message out.

transform.rs · smooth.rs

Response curves, quantize, analog deadzone, slew limiting, musical-scale snap, transpose, 14-bit packing; exponential moving average, median filter, hysteresis, and an interpolation ramp for CC fades.

timing.rs · clock.rs

BPM ↔ 24-PPQN clock interval, Song Position Pointer pack / unpack, debounce and auto-repeat; tap tempo, note-value (1/8., 1/8t, …) durations, tick ↔ millisecond at arbitrary PPQ, bar/beat/tick location.

generators.rs · groove.rs · arp.rs

LFO with six shapes including sample-and-hold, and an ADSR envelope, both returning a MIDI value for a given phase / elapsed time; swing quantize, named groove templates, chord strum, note-repeat rolls with a velocity ramp, velocity compression; arpeggiator orders (up / down / updown / downup / converge, octaves) and a step-sequencer step + gate model.

Outgoing actions & platforms

The Outgoing enum has 41 variants:

MIDI out

Sent through a midir output port. Cross-platform.

Keystroke / mouse

OS synthesis via macOS CGEvent (incl. mouse wheel), through a keycode table (keycodes.rs).

AppleScript / launch

AppleScript (with global injection) and launch-file with args. macOS only — Error::Unsupported elsewhere.

MIDI route / port

MidiRouterAction and MidiPort manage routes and port open/close at runtime.

Project / preset

ProjectAction (load / save / switch) and PresetControl (enable / disable).

Timer / serial

Internal scheduler (engine/timers.rs) drives timer translators; Serial writes to a serial port.

Custom / Stryke

Custom routes to the host via Engine::set_custom_handler; Stryke runs a stryke script through a host-installed runner hook.

Raw / None

Raw preserves an undecoded action for lossless .bmtp round-trip; None is the no-op.

MIDI I/O and the rules / import core build on all platforms. OS-control actions are macOS-only and require the host application to hold Accessibility permission (System Settings → Privacy & Security → Accessibility). actions::accessibility_trusted() reports the grant; executors return Error::NeedsAccessibility when it is missing.

Architecture

The crate is 40 Rust files — 37 under src/ plus 3 integration modules under tests/ — totalling 13,062 source lines (10,556 code). See the engineering report for the full subsystem and per-file breakdown.

music / MIDI 2.0 layer

Twenty pure modules with no BOME counterpart: theory, spelling, voicing, tuning, mpe, gm, drummap, ccmap, chanmatrix, sysex, smf, stream, midi2, transform, smooth, timing, clock, generators, groove, arp (4,154 lines).

engine/

Worker-thread engine: input ports, dispatch loop, all non-MIDI trigger sources, MIDI clock + MTC generators, event sink, timer scheduler, outgoing delay (3,626 lines).

bmtp/

BOME .bmtp import + export: INI, XML payloads, options, unsigned re-serialize, lossless passthrough (1,396 lines).

model.rs

Project / Preset / Translator model, 41 Outgoing + 28 Incoming variants, JSON load/save (1,184 lines).

rules.rs

Rules compiler + signed-32-bit integer VM (644 lines).

actions/ + keycodes.rs

macOS CGEvent keystroke / mouse / AppleScript + say / afplay / pbcopy (705 lines).

sources.rs + serial.rs

OSC / Art-Net / sACN / RTP-MIDI codecs + serial framing (386 lines).

Proprietary · part of the MenkeTechnologies paid stack · engineering report · GitHub