Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

USB protocol

nord-usb speaks the protocol Nord Sound Manager uses, worked out from USB captures. It moves bytes to and from the instrument. nord-format owns the bytes.

What works

Verified on an instrument from macOS: inventory, object info, dependencies, geometry, focus, walking the occupied slots, reading and writing programs and set lists, moving, deleting, renaming, duplicating and selecting slots, reading and writing live slots and settings in place, and reading, deleting and writing sample and piano libraries. From Linux, reads and a multi-chunk sample write are verified, and every recorded request is byte-identical to its macOS counterpart. The WebUSB backend is verified for reads and writes from Chrome. Windows passes the replay tests but has not been run against an instrument.

Not implemented: backup bundles, firmware updates, and relink.

The wire

Every message is a length-prefixed frame of big-endian 32-bit words with a CRC-16 trailer:

┌────────┬─────────┬───────────┬─────────┬───────────────┬───────┐
│ length │ service │ subsystem │ command │ args…         │ crc16 │
└────────┴─────────┴───────────┴─────────┴───────────────┴───────┘

The CRC is CRC-16/CCITT-FALSE. A response carries the request’s command plus one, with a status word inserted before the echoed arguments. Two things bite. Request codes are not reliably even, so direction is recorded at decode time rather than inferred. And operations are generic primitives parameterised by object class (1 piano, 3 sample, 4 program, 5 set list, 6 live, 7 settings), so one rename or move command serves every class.

The instrument reads a message until a short packet ends it. A frame that is an exact multiple of the packet size needs a zero-length packet after it, or the device never answers.

The closing exchanges of a transaction clear the instrument’s progress display. Abandoning a transaction after a progress label has been sent leaves the device stuck until it is power-cycled, which is why every session closes on the error path.

Layering

ModuleRole
wireFraming and codec. No I/O.
transportThe byte pipe, and the only code that touches a device.
sessionThe transaction every operation runs inside.
opTyped operations.
deviceAn instrument as a value: session brackets and its geometry.

Features

FeatureDefaultGives
nusbyesDesktop backend for macOS, Linux and Windows, in pure Rust.
webBrowser backend over WebUSB. Chrome and Edge only.
replayDrive the protocol from captures, with no hardware.
blockingBlock on the async API from synchronous code.
corpusTests against the private capture corpus. Implies replay.
fault-injectionDeliberate protocol faults for research. A wrong value can stall the instrument’s endpoints.

WebUSB handles are not Send, so neither is the Transport trait, which keeps the crate free of any particular async runtime. Building web needs --cfg=web_sys_unstable_apis, which crates/.cargo/config.toml supplies when Cargo runs from crates/.

The API documentation is on docs.rs, and Testing covers the replay suite.