{"id":51611951,"url":"https://github.com/kschzt/harp","last_synced_at":"2026-07-12T09:02:06.779Z","repository":{"id":364029300,"uuid":"1265294037","full_name":"kschzt/harp","owner":"kschzt","description":"Make hardware instruments behave like plugins — Git-style total recall, sample-accurate automation, and deterministic audio in your DAW. Reference implementation + open spec.","archived":false,"fork":false,"pushed_at":"2026-07-02T17:29:50.000Z","size":3784,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-02T19:24:30.700Z","etag":null,"topics":["audio","audio-unit","clap-plugin","daw","embedded-audio","midi","midi2","raspberrypi","synthesizer","usb-gadget","vst-plugin","vst3"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kschzt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-10T16:35:05.000Z","updated_at":"2026-07-02T17:30:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kschzt/harp","commit_stats":null,"previous_names":["kschzt/harp"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/kschzt/harp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kschzt%2Fharp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kschzt%2Fharp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kschzt%2Fharp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kschzt%2Fharp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kschzt","download_url":"https://codeload.github.com/kschzt/harp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kschzt%2Fharp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35387177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-12T02:00:06.386Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio","audio-unit","clap-plugin","daw","embedded-audio","midi","midi2","raspberrypi","synthesizer","usb-gadget","vst-plugin","vst3"],"created_at":"2026-07-12T09:02:06.024Z","updated_at":"2026-07-12T09:02:06.767Z","avatar_url":"https://github.com/kschzt.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HARP — Hardware Audio Runtime Protocol\n\n[![ci](https://github.com/kschzt/harp/actions/workflows/ci.yml/badge.svg)](https://github.com/kschzt/harp/actions/workflows/ci.yml)\n[![hw — real device](https://github.com/kschzt/harp/actions/workflows/hw.yml/badge.svg)](https://github.com/kschzt/harp/actions/workflows/hw.yml)\n[![eth — IP transport](https://github.com/kschzt/harp/actions/workflows/eth.yml/badge.svg)](https://github.com/kschzt/harp/actions/workflows/eth.yml)\n[![reaper — real DAW](https://github.com/kschzt/harp/actions/workflows/reaper-e2e.yml/badge.svg)](https://github.com/kschzt/harp/actions/workflows/reaper-e2e.yml)\n[![perf — RT feed](https://github.com/kschzt/harp/actions/workflows/perf-gate.yml/badge.svg)](https://github.com/kschzt/harp/actions/workflows/perf-gate.yml)\n[![license: Apache-2.0 | CC-BY-4.0](https://img.shields.io/badge/license-Apache--2.0%20%7C%20CC--BY--4.0-blue)](LICENSING.md)\n\n**Make hardware instruments behave like plugins.** HARP is an open protocol — a\n**Version 1.1.2** specification with a machine-readable CDDL schema — and a complete\nreference implementation that proves it: a Raspberry Pi 4B plays as an instrument\ntrack in Ableton Live today, with Git-style total recall through the project's own\nsave/reopen, knobs as sample-accurate automation lanes, and offline bounce *through\nthe physical box*. Use the reference device, or **build your own device or host on\nthe protocol**.\n\n\u003c!-- ▶ DEMO VIDEO GOES HERE — ~60s: Pi as a Live track → automate a knob →\n     save \u0026 reopen the set → recall restores the hardware → offline bounce. --\u003e\n\nHardware synths, drum machines, and effects have connected to computers for\nforty years, and the connection is still fragile in one specific way:\n*recall*. Audio mostly works, MIDI mostly works — but reopen last month's\nproject and the hardware is not in the state you saved, and nothing will\ntell you. The deep integrations that fix this (multichannel USB audio,\nplugin-shell control, total recall) have existed only inside closed\nsingle-vendor ecosystems, because every vendor has to rebuild drivers, state\nsync, and DAW compatibility from scratch.\n\nHARP is that integration as an open standard plus a working reference. The spec\n(§1–§19) is normatively complete and implementable — a vendor can build a conforming\ndevice or host **from the spec alone**, against the same wire formats the CDDL pins.\nThe reference device is the proof: a Raspberry Pi running a 16-part multitimbral,\n8-voice-polyphonic synth (12 automatable params, indexed per part). Anything that\nspeaks the protocol gets the same treatment from any conforming host:\n\n- **Total recall, Git-style** — device state is content-addressed and\n  hash-verified; a saved project reopens with the hardware *provably* in\n  the saved state, every overwrite preceded by a free snapshot, every\n  mismatch resolved through explicit safe actions — never silently. The\n  archive doubles as patch time-travel: every state the box has ever been\n  pushed over is one click away.\n- **Audio as a plugin** — a dedicated stream into the plugin shell that\n  bypasses the OS audio stack (no aggregate-device hacks), including a\n  host-paced mode where the hardware renders *deterministically*, byte-\n  identical, faster than real time: offline bounce through a physical box.\n  Reported latency is **~21 ms at DAW buffers ≤ 256** (host-paced, the PDC the\n  DAW compensates), and free-running live play goes **as low as ~9 ms** with a\n  device-declared real-time profile — in the neighborhood of a good audio\n  interface, and *measured*, never guessed (§14.3 loopback).\n- **Audio in, not just out (§8.8, new in 1.1)** — a device may also *process*\n  host audio as an insert or send effect, not only synthesize: the host streams\n  the track in and mixes the device's returned **wet** against the dry it keeps\n  locally (the dry never crosses the wire), with round-trip latency reported for\n  PDC. The reference ships a dedicated **FX shell** (`harp-fx-shell`) that drives\n  an `audio.fx` device this way; a tight-feedback effect closes its loop *in the\n  hardware*, at single-sample latency — the reason to put such an engine in a box\n  at all.\n- **Multitimbral, addressed like plugins** — one physical device is one\n  instance, and multitimbrality lives in the host: the DAW routes multi-channel\n  MIDI into that single multi-out instance — channel N plays part N — and the\n  instance exposes the device's per-part output buses (bus 0 the summed main mix,\n  buses 1..N the 16 per-part stereo pairs) so each part returns to its own track.\n  Per-part timbre and recall state persist in the project and move intact between\n  the VST3 and AU formats (the CLAP shell writes the same recall bundle).\n- **Polyphonic, with per-voice modulation** — each part is an 8-voice pool with\n  deterministic voice allocation, so overlapping notes ring out on their own\n  voices instead of stealing one. Modulation is *non-destructive* and *per\n  voice*: a VST3 Note Expression or a CLAP per-note parameter modulation bends a\n  single sounding note's filter cutoff without touching the stored patch — and\n  the two formats render byte-identically (the device applies the §9.5 mod the\n  same regardless of which shell sent it).\n- **Sample-accurate everything** — DAW automation becomes\n  device-interpolated ramps applied within ±1 sample; notes travel as UMP;\n  an event *fence* makes \"applied late\" structurally impossible rather\n  than statistically rare. Turn a knob on the hardware and the DAW records\n  the automation (echo).\n- **Musical time** — devices follow the DAW's transport: tempo,\n  position, loops. The reference device's arpeggiator locks to Live's grid\n  sample-exactly, survives loop wraps, and renders a *byte-identical\n  groove* — determinism extended to musical time.\n- **Identity, timing, diagnostics** — engine versioning and parameter-map\n  hashing protect old songs from new firmware (a breaking engine-major change\n  reopens the project read-only; an incompatible protocol surfaces an update\n  prompt, not a dead session), latency is measured (never guessed), and honest\n  error counters at every layer end \"it glitched\" support threads with evidence.\n- **Roadie-proof sessions** — unplug the cable mid-song and plug it back:\n  the shell reconnects, re-asserts the project's state, and audio resumes.\n  Hostile or corrupt wire input ends in a clean session reset, never a\n  crash (every parser is fuzzed; a live abuse test is part of CI).\n\n**What \"works\" means here:** the four protocol planes (control, state, events,\naudio) are implemented end-to-end and verified on real hardware — recall\nthrough Ableton's own save/reopen, a tempo-locked arpeggiator, sample-accurate\nautomation, offline bounce through the box, and a 16-part multitimbral,\n8-voice-polyphonic instrument with per-voice modulation, driven as one\nmulti-out instance with the DAW routing a MIDI channel per part. The plugin shell builds and is\nCI-validated on **macOS, Windows, and Linux** — a VST3 on all three (pluginval\nstrictness 10), an Audio Unit on macOS (`auval`) at full parity, and a CLAP on\nall three — all three formats rendering byte-identically (the conformance kit\nasserts it), and a project's per-part recall state moving between VST3 and AU\n(CLAP writes the same recall bundle). The device has been driven from **Ableton\nLive** (macOS + Windows) and **Renoise** (Windows) by hand, plus a headless\n**REAPER** render-and-recall e2e on Linux that runs on every push.\n\n**Where the spec stands.** The spec is **Version 1.1.2** (30 June 2026) — the 1.0 wire\nfrozen, with `audio.fx` (§8.8) added as a backward-compatible capability in 1.1 and\n1.1.1–1.1.2 editorial clarifications on top; a spec→code audit cleared the implementation\nwith **zero blockers**. What 1.x *is*: a complete, normative protocol with a CDDL schema\nand a working reference device + host that prove it implementable end-to-end and\nbyte-identically. What it *isn't, yet*: an ecosystem of instruments (building richer\nsynths and shipping real devices on the protocol is the roadmap, not a claim about the\npresent), and not yet a turn-key *certification* kit — the unified T1–T17 conformance\nharness now ships (`scripts/cert-harness.sh`, gated per-PR in `ci.yml`) and runs the\ncloud-capable battery with its numeric thresholds asserted, but the rig-only tests at\nfull certification scale and the §13 firmware-management bar remain. The\n[Status](#status) section is the full breakdown, and [Building on HARP](#building-on-harp)\nis the on-ramp for device and host implementers.\n\n## Repository map\n\n```\nspec/             the specification (v1.1.2) + machine-readable CDDL (the\n                  normative wire schema, Appendix A)\ncore/             portable C11 protocol library, no dependencies:\n                  framing, deterministic CBOR, SHA-256, content-addressed\n                  objects, crash-atomic ref store, audio frame codec\ndevice/           harp-deviced — the reference device daemon: a 16-part\n                  multitimbral synth engine (12 automatable params, indexed\n                  per part) behind the protocol. Transports: TCP (simulation,\n                  any OS) and FunctionFS USB gadget (Linux)\nhost/             harp-probe — host-side CLI: recall flows, audio capture,\n                  offline render, determinism + conformance checks (libusb)\nshell/            the plugin shells over one embedded runtime: VST3 (\"HARP\n                  RefDev\"), Audio Unit (shell/au) and CLAP (shell/clap) — same\n                  params, same Recall Bundle, same noteId-\u003evoice map\n                  (note_voice_map.h) — plus the §8.8 audio.fx variant\n                  (fx_plugin.cpp / harp-fx-shell). One PRIVATE runtime per\n                  instance (no shared image, no cross-instance registry): a single\n                  multi-out instance exposes the device's per-part output buses and\n                  the DAW routes a MIDI channel per part; the AU also joins the\n                  host's CoreAudio workgroup. All three render BYTE-IDENTICAL audio\n                  and a project's recall state moves between them (asserted by the\n                  conformance kit)\ntools/vst3-host/  CLI VST3 host for automated testing of any plugin —\n                  params, block processing, WAV+hash, state round-trips,\n                  multi-out per-part channel routing, and the §8.8 FX\n                  multi-insert repro (--fx-instances)\ntools/au-host/    its Audio Unit twin (drives the AU shell; same hashes;\n                  save/load-state for the cross-format recall e2e)\ntools/clap-host/  its CLAP twin (dlopens the .clap; drives notes/params and\n                  CLAP per-note PARAM_MOD; same golden hashes as the others)\ntests/            unit tests for the core (RFC 8949 vectors included) + the\n                  device codec and the safety-contract invariants\ndocs/             architecture one-pager; VST3, multitimbral, and Ethernet-\n                  transport (§4.4) design notes\nscripts/          the conformance suites (eth-suite.sh is the single source of\n                  truth across OSes) + Raspberry Pi provisioning runbook\nexternal/         VST3 SDK + CLAP SDK clones (gitignored; shell/tools only)\n```\n\n## Getting started\n\n### Path 1 — laptop only, five minutes (no hardware)\n\n```sh\ncmake -B build \u0026\u0026 cmake --build build\n./build/harp-tests                                # unit tests\n./build/harp-deviced --state-dir /tmp/refdev \u0026    # simulated \"hardware\"\n./build/harp-probe demo                           # narrated total-recall walkthrough\n```\n\nThe `demo` walks the canonical project-open flow (spec §12.2): save,\nfront-panel edits dirtying state, mismatch detection on reopen,\narchive-before-push, hash-verified restore.\n\n**See it yourself:** that walkthrough reproduces the total-recall claim on\nyour laptop, no hardware. The bolder claims — byte-identical renders and\n±1-sample timing — need a device: `harp-probe -d usb t15 4` renders twice and\nbyte-compares (Path 2 below), and those same checks run on a real Pi in CI\n(`hw.yml`, on every push whenever the self-hosted rig is online).\n\nFor the visual version, start the web panel (the device's \"front panel\"\nplus a live protocol inspector — refs, generations, the dirty flag in\nreal time):\n\n```sh\npython3 web/harp-panel.py /tmp/harp-panel.sock 8080 \u0026\nopen http://localhost:8080      # drag sliders, watch the state model react\n```\n\n### Path 2 — a real device (Raspberry Pi 4B)\n\n`scripts/pi-bringup.md` is the operations runbook: provision a Pi as a USB\ngadget (vendor interface + dedicated audio endpoints), connect it to your\ncomputer over USB-C, and the same `harp-probe` commands run against real\nhardware — plus audio:\n\n```sh\n./build/harp-probe -d usb demo                 # recall, over the wire\n./build/harp-probe -d usb record 4 take.wav    # free-running capture, MSC-verified\n./build/harp-probe -d usb render 8 bounce.wav  # host-paced offline bounce (deterministic, faster than real time)\n./build/harp-probe -d usb t15 4                # determinism: render twice, byte-compare\n```\n\n### Path 3 — the DAW (VST3, AU, or CLAP)\n\nBuilds on macOS, Windows, and Linux. Requires CMake ≥ 3.16 (3.20 for the\nplugin tools), libusb (macOS: `brew install libusb`; Linux:\n`apt install libusb-1.0-0-dev` plus\n`sudo cp scripts/99-harp.rules /etc/udev/rules.d/` for rootless device\naccess; Windows: prebuilt MSVC binaries under `external/libusb-win`, built\nwith the Visual Studio 2022 generator), and the VST3 + CLAP SDKs cloned under\n`external/`:\n\n```sh\ngit clone --recursive https://github.com/steinbergmedia/vst3sdk.git external/vst3sdk\ngit clone --branch 1.2.8 https://github.com/free-audio/clap.git external/clap\n```\n\nOn **Windows** also fetch the prebuilt libusb into `external/libusb-win` — there is no\npkg-config, so the shell looks for it there; without it the configure aborts with\nthe cause (previously the shell targets silently weren't generated and a later\n`--target install-live` failed with \"project file does not exist\"):\n\n```powershell\n$ver = '1.0.27'\nInvoke-WebRequest -UseBasicParsing \"https://github.com/libusb/libusb/releases/download/v$ver/libusb-$ver.7z\" -OutFile libusb.7z\nNew-Item -ItemType Directory -Force external/libusb-win | Out-Null\n7z x libusb.7z -oexternal/libusb-win -y   # needs 7-Zip; the bundled tar lacks the LZMA codec\n```\n\n```sh\ncmake -B build-vst -S tools/vst3-host   # Windows: add -G \"Visual Studio 17 2022\" -A x64 if the default generator fails\ncmake --build build-vst --target install-live   # macOS/Windows: install to the OS VST3 folder (+sign on macOS)\ncmake --build build-vst --target install-linux  # Linux: install to ~/.vst3\ncmake --build build-vst --target install-au     # macOS: the Audio Unit shell\ncmake --build build-vst --target install-clap   # CLAP: ~/Library/Audio/Plug-Ins/CLAP (mac, bundle+signed) | %CommonProgramFiles%\\CLAP (win) | ~/.clap (linux)\n```\n\n(The AU appears as **HARP Project: HARP RefDev**; `auval -v aumu rfdv\nHARP` is Apple's own validation and runs in this repo's CI. All three\nshells render byte-identically from the same drive — the conformance kit\nasserts it.)\n\nRescan plug-ins in your DAW and drop **HARP RefDev** on a track. The\nplugin claims the device over USB, streams host-paced audio into the\nDAW's engine, exposes the knobs as automatable parameters, and stores a\nRecall Bundle in the project file — reopening the project restores the\nhardware (archiving whatever was on it first).\n\nThe shell can also be driven without any DAW, which is how it is tested:\n\n```sh\n./build-vst/harp-vst3-host ~/Library/Audio/Plug-Ins/VST3/harp-shell.vst3 \\\n    --set 3=0.8 --seconds 2 --out take.wav --hash\n```\n\n### Path 4 — a DAW with no hardware (the simulated device over §8.7 Ethernet)\n\nPath 3 claims a real device over USB. To drive a DAW against the **simulated**\ndevice instead — no hardware, the daemon and the shell talking over the §8.7\nEthernet/IP binding on loopback — run the device on a TCP port and point the shell\nat it with an env var:\n\n```sh\n./build/harp-deviced --port 47987 --serial SIM-0001 --state-dir /tmp/refdev\n```\n\nOn the network path a device declares its safe real-time setpoints in its identity\n(`rt-profile`, capability `audio.rt-floor`) and the shell adopts them in place of the\nconservative default jitter buffer / RTP packet size: **`--rt-floor N`** (buffer\nfloor) and **`--rt-nsamples N`** (RTP packet size). Each device supplies its own measured\nvalues through its on-device service config (the systemd unit) — a board over a direct\n1 Gbps cable runs `--rt-floor 320 --rt-nsamples 64`, a Pi over a switch `--rt-floor 448\n--rt-nsamples 128`; with no flag a device declares nothing and the host keeps a safe\n1024-frame / 256-sample default. The host clamps whatever is declared (floor to\n`[2·max-DAW-block, 12288]`, packet to `[32, kBlock]`). A declared profile brings\nfree-running live latency down from ~32 ms (undeclared default, block 256) toward\n**~9 ms** (320/64 at block 64). See [`scripts/eth-rtfloor-test.sh`](scripts/eth-rtfloor-test.sh).\n\nSynths on a network advertise over mDNS (`_harp._tcp`); **`harp-probe discover`** browses\nthe segment and resolves each to its `host:port`. The shell discovers them too: with no\nUSB device and no `HARP_ETH_DEVICE`, `selectDevice` browses `_harp._tcp` and dials the\nfirst synth it finds (§6.1). Set **`HARP_ETH_DEVICE=mdns`** to force that browse explicitly,\n`HARP_ETH_DEVICE=HOST:PORT` to pin a literal address, or `HARP_NO_MDNS=1` to disable the\nauto-browse. A GUI DAW does **not** inherit your terminal's environment, so set it where\nthe OS launcher sees it, *before* launching the DAW:\n\n```sh\nlaunchctl setenv HARP_ETH_DEVICE 127.0.0.1:47987    # macOS; Linux/Windows: set it in the env the DAW inherits\n```\n\nThen launch the DAW, rescan plug-ins if needed, and drop **harp-shell** on a MIDI\ntrack — you'll see `connected: …` print in the device's terminal. Quit and relaunch\nthe DAW if it was already open (the env is read at the first dial); `launchctl\nunsetenv HARP_ETH_DEVICE` returns to real USB hardware. The device may come up before\nor after the DAW — the shell's supervisor reconnects either way.\n\n## Building on HARP\n\nHARP is a protocol first. The spec is the product; the reference implementation is the\nproof; the kit is honest about what's turn-key and what isn't. If you want to build a\ndevice, a host, or just verify a claim, start here.\n\n**Build a device.** The spec (§4–§16) is normatively complete and implementable from\nthe document alone — transport (USB §4.3 or Ethernet/IP §4.4, both normative and proven),\nthe control envelope (§5), identity + capabilities (§6), time/clocking (§7–§8), events\n(§9), the state model (§10–§11), session lifecycle (§12), and security (§16). The wire\nformats are pinned in [`spec/harp.cddl`](spec/harp.cddl). The reference device\n(`device/`, ~3k lines of C11) is the worked example — `session.c` (the protocol state\nmachine), `state.c` (content-addressed, crash-atomic storage), `engine.c` (the synth\nDSP), `device.h` (the module contracts). *Not yet provided:* a step-by-step device-\nimplementer's guide or marked extension points — read the spec and the reference; and\n`scripts/pi-bringup.md` is the reference Pi's **operations** runbook (provision/deploy/\ndebug), not an implementer tutorial. §13 firmware management is specified but not yet\nimplemented on the reference device.\n\n**Build a host.** §15 defines the host runtime + shell requirements; the reference\nruntime (`shell/runtime.cpp` / `.h`) — embedded in the VST3/AU/CLAP/FX shells — is working\nproof of DAW integration: discovery, sessions, per-part MIDI-channel routing, per-part\naudio-bus demux, and recall. *By design (§15.1):* the runtime is one private instance per\ndevice, embedded in-process — not packaged as a standalone library, with no shared\ncross-instance registry; the speculative per-machine daemon was dropped in 1.1.2, so the\nin-process runtime is the reference architecture, not a placeholder for one.\n\n**Verify conformance.** The conformance classes (§2.3, §17 — harp-core, harp-recall,\nharp-stream, harp-class-audio, harp-perf, harp-fw) tell you exactly what a given claim\nrequires, and the test suite is strong functional verification: `scripts/eth-suite.sh`\nis the single source of truth across three OSes, the four \"no silent state loss\" safety\ncontracts (CAS conflict §11.3, archive-before-push §11.4, param-map-hash §9.3, the event\nfence §8.3.1) each have a regression-catching test, and the CDDL is machine-readable\n*and CI-enforced* (`scripts/cddl-validate.py` checks encoded wire samples against the\nschema on every push). A\n**unified §17 T1–T17 harness ships** (`scripts/cert-harness.sh`, the `cert-harness` gate\nin `ci.yml`): it indexes the battery by number (`scripts/cert-tests.tsv`), runs the\ncloud-capable covering tests, asserts the cloud-reachable numeric thresholds (±1 ms\nloopback, ±1 sample, cross-format byte-identity, converter SINAD floor), and emits one\npass/skip/uncovered report — with the rig-only tests (real USB unplug/replug, sleep/wake,\npower-loss, chained hubs, 24 h soak, multi-device-at-scale) and the full-scale numeric\nthresholds (24 h soak, ≥4 device / ≥64 channel, ±200 ppm) skipped with a logged reason and\nrun on the rig in `hw.yml` / `soak.yml`. Closing those at certification scale, together\nwith §13 firmware management, is the remaining bar from *reference implementation* to a\nturn-key *certification kit*.\n\n## Documentation\n\n- **The specification**: [`spec/harp-spec.md`](spec/harp-spec.md).\n  First read: §1 (motivation and design principles), §2 (conformance + terminology),\n  §10–§11 (the state model — \"Git, not SysEx\"), §8 (the audio plane and clock domains),\n  §17 (conformance classes). The changelog at the top records what implementation taught us.\n- **Machine-readable schema**: [`spec/harp.cddl`](spec/harp.cddl) — the normative\n  wire format (Appendix A), the contract a conforming implementation is checked against.\n- **Architecture one-pager**: [`docs/architecture.md`](docs/architecture.md)\n  — components, planes, and the shell's threading model.\n- **VST3 shell design plan**: [`docs/vst3-shell-plan.md`](docs/vst3-shell-plan.md)\n  — decisions, DAW-compatibility lore, and what's deliberately deferred.\n- **Multitimbral plan + test matrix**: [`docs/multitimbral-plan.md`](docs/multitimbral-plan.md)\n  — the per-part build history (device parts → per-part params → per-part audio\n  buses), what each phase verified, and the deferred items. **Superseded** by the\n  1.1.2 one-instance / host-side-multitimbral model (§15.1); see the banner atop\n  that doc.\n- **Ethernet/IP transport design**: [`docs/ethernet-transport-plan.md`](docs/ethernet-transport-plan.md)\n  — the §4.4 network binding (RTP/UDP for audio, the framed link over TCP for\n  control, trusted-segment security, an AES67 bridge reserved). Now folded into\n  the spec (§4.4, §7.3, §8.7) and CI-gated on three OSes (`eth.yml`) — and, as of\n  2026-07-06, over a **real network hop** against the rig Pi (`hw.yml`): the framed\n  control link, the RTP/UDP audio plane, the host-locked rate-trim loop + ASRC,\n  and host-paced offline bounce. PTP-grade sync stays a hardware prototype\n  (~22 µs idle), not yet wired into the runtime.\n- **Pi runbook**: [`scripts/pi-bringup.md`](scripts/pi-bringup.md) —\n  provisioning, the sudo-free deploy loop, USB debugging tricks.\n\n## Status\n\n**Working — verified end-to-end on real hardware:**\n\n- **Protocol core** — `harp-core` (framing, deterministic CBOR, SHA-256,\n  content-addressed object store), `harp-recall` (save / snapshot / restore),\n  `harp-stream` (free-running and host-paced; `audio.deterministic` +\n  `audio.offline-rate`), and two transport bindings: USB (FunctionFS gadget) and\n  the §8.7 Ethernet/IP binding (framed control over TCP + an RTP/UDP audio plane).\n- **The plugin shell** — VST3 on macOS/Windows/Linux, an Audio Unit on macOS,\n  and a CLAP on macOS/Windows/Linux (all three byte-identical), driving the device in\n  Ableton Live (macOS + Windows), Renoise (Windows), and an automated headless\n  REAPER render (Linux).\n- **Total recall, safety contracts tested** — content-addressed, hash-verified\n  state with compare-and-swap conflict detection (§11.3), archive-before-push so\n  every overwrite is preceded by a recoverable snapshot (§11.4), param-map-hash\n  change-detection that protects stored automation (§9.3), and the four-safe-actions\n  reconcile UI (Push / Pull / Read-only / Duplicate) in the Electra front-panel\n  sidecar. Each of these contracts has a regression-catching test in CI.\n- **Polyphony + per-voice modulation (§9.5)** — each part is an 8-voice pool\n  with deterministic allocation (overlapping notes ring out, never steal one\n  voice); a VST3 Note Expression or a CLAP per-note PARAM_MOD bends one voice's\n  filter cutoff non-destructively, byte-identically across the two formats. The\n  device's golden render is unchanged when no modulation is sent.\n- **MPE input (§9.5/§9.10), all three shells** — an MPE controller's three\n  per-note axes drive per-voice modulation: pitch-bend (semitones), timbre\n  (CC74/brightness → filter cutoff), and pressure (→ loudness). They arrive as\n  CLAP note expressions, VST3 Note Expression (Cubase + the per-note UI), or —\n  for the DAWs that send MPE as **raw 16-channel MIDI** (a member-channel-per-note\n  stream): Logic and Ableton Live into the AU, and Ableton Live into the VST3 (via\n  `IMidiMapping`, engaged by the plugin's \"MPE\" toggle, which persists with the\n  project). `shell/mpe_zone.h` collapses the zone onto one device part — a member\n  channel must never become the multitimbral part (§9.4). However it arrives, the\n  shell maps each axis to the SAME §9.5 per-voice mod, so a chord bends every note\n  independently and the *same gesture renders byte-identically across all three\n  shells* (a neutral MPE chord across member channels == the plain chord; raw bend\n  −12 == VST3 Note-Expression == CLAP note-expression bend −12).\n- **Output metering (§9.9)** — per-part and main-mix peak/RMS, exposed as\n  readonly params streamed via echo and read back through `harp-probe meters`;\n  folded read-after-write in the render, so the golden render is unperturbed.\n- **The event plane (§9)** — parameter sets and ramps at exact sample\n  timestamps (±1 sample on hardware), DAW automation synthesized into\n  device-interpolated ramps, UMP note input (the refdev is a playable\n  envelope+portamento synth), and front-panel echo (turn a knob on the device,\n  the DAW records the automation). Note-offs are unloseable; the DAW's panic\n  (CC 120/123) reaches the hardware. An event *fence* (§8.3.1) makes \"applied\n  late\" structural rather than statistical, and has a test that fails if the\n  fence regresses.\n- **Musical time (§9.7)** — the device follows `evt.transport`: a note-latch\n  arpeggiator whose step clock derives from the (timestamp, PPQ, tempo) anchor\n  lands on division boundaries sample-exactly, survives loop wraps, and renders\n  a byte-identical *groove hash*. (The automatable param map is 12 ids, shared\n  across parts; old projects map onto matching ids with a warning, §9.3.)\n- **Multitimbral (§9.4, §15.1–§15.2)** — one device is one multi-out instance;\n  multitimbrality lives in the host. The DAW routes a MIDI channel per part into\n  the single instance (channel N → part N), and the instance exposes the device's\n  per-part output buses (16 per-part stereo pairs plus a summed main mix)\n  demultiplexed to the tracks. Per-part timbre and recall state persist in the\n  project and move between the VST3 and AU formats (CLAP writes the same recall\n  bundle). Verified on hardware: channel→part routing is exclusive (no bleed),\n  per-part timbres restore losslessly across save/reopen, per-part output buses\n  demux to their tracks (including a bus that activates mid-session and\n  re-negotiates its own audio), and a per-part param reaches only its own part.\n- **Multi-device** — two *separate* boards on one bus (distinct from\n  multitimbral above): each instance binds its own by USB identity (saved\n  serial, else first unclaimed same-model — never a different synth), reconnect\n  pins that exact unit, two instances claim two devices by contention.\n- **Reported latency** — **~21 ms** at DAW buffers ≤ 256 (host-paced, the PDC the\n  DAW compensates; §6.4), built from the ring buffer + event headroom + the device\n  render/turnaround block measured by the §14.3 loopback (never guessed). Free-running\n  live play reaches **~9 ms** with a device-declared rt-profile (320/64 at block 64).\n- **The web panel** — front panel plus a live protocol inspector: refs with\n  ticking generations, the dirty flag, counters, and Snapshot / Panic /\n  one-click Load of any archived state (patch time-travel — the current state\n  is archived first, so nothing is ever lost).\n\n**Gated in CI on every push:**\n\n- **Ethernet/IP conformance suite** against the simulated device on three OSes\n  (`scripts/eth-suite.sh`, the `eth` badge): the framed control link + RTP/UDP\n  audio plane with no hardware — bit-exact live play with a host-locked rate-trim\n  loop, multichannel ASRC, deterministic host-paced offline bounce, mid-session\n  reconnect, RTP packet-loss tolerance, hostile-frame fault injection, the\n  spec-conformance closures (credit flow-control §4.2.1, event transactions §9.6,\n  admission control §8.4, engine-major read-only §12.2, the §14.4 diag bundle and\n  §14.3 loopback, mDNS discovery §4.4.3 on macOS), and the four safety-contract\n  tests (CAS conflict, archive-before-push, param-map-hash, event fence). As of\n  2026-07-06 the same §8.7 suite also runs over a **real network hop** against the\n  rig Pi (PI4B-0002 in TCP transport) as a green standing gate in `hw.yml` —\n  closing the loopback-only gap. The host-paced kOffline bounce is gated\n  **byte-exact over the real link** (pinned to the arm64 device's on-device DSP —\n  the strongest possible gate); the free-running/live ASRC path is gated over the\n  real hop by a non-silence RMS floor **and a free-running SINAD gate**\n  (`freerun-sinad-eth-test.sh`: the device streams a fixed tone, the host recovers\n  it through the rate-trim loop + ASRC and asserts the tone's frequency ±5 Hz with\n  **zero packet loss, underflow, or malformed frames**, plus a converter-SINAD\n  floor as a gross-failure catch — the SINAD absolute value is jitter-variable on a\n  consumer NIC, so frequency + zero-loss carry the assertion). Sub-10 ms latency\n  remains proven in synthetic units. Only the tests that structurally need a\n  harness-local co-process (SIGKILL fault injection, daemons launched with specific\n  flags) self-skip there with a logged reason.\n- **Hardware suite** on real Pis (`scripts/hw-tests-linux.sh`, the `hw`\n  badge) driving the two-board, no-AU CI rig (PI4B-0002 + PI4B-0003): VST3↔AU\n  cross-format recall self-skips (AU is macOS-only) and the two sustained\n  wide-stream per-part tests run on the eth loopback rig instead; everything else\n  runs over real USB. It gates the **golden oracle across VST3 + AU + CLAP**\n  (byte-identical), **8-voice polyphony with deterministic voice-stealing**,\n  **per-voice modulation** (VST3 Note Expression, CLAP per-note PARAM_MOD, and\n  **MPE** pitch/timbre/pressure), **§9.9 output metering**, ±1-sample note timing\n  with zero late events, a realtime soak flooding automation + notes + panel\n  traffic at the DAW block (zero silence gaps, zero drops, bounded padding; a\n  separate `soak-matrix.sh` sweeps buffers 64–1024 as a release-grade check), a\n  dedicated **multi-minute continuous-audio dropout gate** (default 180 s,\n  `soak.sh` with `SOAK_KNOB=0`: sustained real-time audio asserting zero silence\n  gaps, padded samples within a 0.5% budget, and clean device counters), an\n  automated mid-stream replug, an IDM-flood determinism gate, the REAPER real-DAW\n  determinism + recall round-trip, a **USB never-wedge stress guard** (DAW-crash +\n  daemon-SIGKILL mid-stream), a **SCHED_FIFO fence-under-load gate** (§8.3.1 zero\n  fence_timeouts under CPU contention), **multi-device selection across the two\n  boards** (exact-serial / same-model fallback / never-cross-model), and the\n  **multitimbral matrix** — channel→part routing, per-part recall, several parts\n  playing into the one multi-out instance, per-part audio demux (incl. a\n  mid-session re-negotiation), per-part param isolation, and cross-format VST3↔AU\n  recall (macOS) — with the daemon under test built on the board from the commit\n  being tested. The runtime threads are also ThreadSanitizer-clean (incl. the\n  per-part event + audio demux) on the rig.\n- **Sandboxed suite**: builds + unit tests on three OSes with **warnings\n  promoted to errors** on every core/device build (`-Werror` / `/WX` via\n  `HARP_WERROR`, so a warning can no longer land silently), pluginval\n  strictness 10 (macOS / Windows / Linux), `auval` (macOS), fuzzed parsers\n  (libFuzzer + ASan), and a protocol-abuse test that slams a live daemon with\n  hostile traffic (sessions reset, nothing crashes). The **raw device-DSP render**\n  is pinned as a regression oracle (`engine-golden-test.sh`, debt #19: it drives\n  `render_output()` / `engine_voices_cold()` directly — no shell, no transport —\n  and asserts determinism + non-silence plus a **per-OS byte hash on all three of\n  Linux, macOS, and Windows/MinGW**, since libm `sin`/`exp` differ across\n  platforms), so a silent change to the synth math cannot slip past cloud CI.\n- **Static analysis + coverage**: a `static-analysis` job runs **cppcheck** and\n  **clang-tidy** (path-sensitive `clang-analyzer-*`, findings-as-errors — config\n  and per-check rationale in `.clang-tidy`) over the wire-facing and real-time C\n  sources, plus a **CDDL wire-validation gate** (`scripts/cddl-validate.py`,\n  pycddl) that parses the normative `spec/harp.cddl` and checks representative\n  encoded wire artifacts — envelope, param/mod/transaction events, blob/list/tree/\n  snapshot objects, refs, identity, and the recall bundle — against it, so the\n  schema is an enforced contract, not just documentation (it already caught a real\n  schema bug). A `coverage` job publishes a quantified `gcovr`/`lcov` line-coverage\n  metric with regression floors on the protocol core and the device daemon.\n\n**Certification status.** The §17 conformance battery (T1–T17) is normatively\nspecified, and a **unified harness now runs it by number and emits a report**\n(`scripts/cert-harness.sh`, the `cert-harness` gate in `ci.yml`): it runs the\ncloud-capable covering tests — including **T2** (unplug/replug, `replug-test`),\n**T5** (silent hash-verified reopen, `recall-test`), **T9** (malformed input,\ncorrupt-CBOR + abuse), **T11** (loopback latency ±1 ms), **T15** (byte-identical\non-device renders, `golden-test`), **T16** (event timing, `timing-test`), **T17**\n(musical-time tempo lock, `tempo-lock-test`) — asserts the cloud-reachable §17 numeric\nthresholds, and marks the rig-only and uncovered tests honestly. What remains for a\ncertification *claim*: the rig-only battery at full certification scale (**T3**\nsleep/wake, **T7** chained hubs, **T10** power-loss, T13's ≥4-device / ≥64-channel 24 h\nsoak, T14's ±200 ppm drift torture) and §13 firmware management.\n\n**Not yet:** firmware management (§13 — the structural bar for certification);\nclass-audio coexistence (§8.5 — specified, hardware-pending); the free-running ASRC\nanalog-device proof (the loop is implemented; the analog converter HAT is pending); and\nPTP-grade Ethernet clock sync wired into the runtime (the §8.7 binding ships with a\nrate-trim loop + ASRC; PTP stays a hardware prototype). Per §15.1 (1.1.2) the runtime is\nembedded in-process **by design** — one private runtime per instance — and the\nspeculative per-machine daemon is dropped, not deferred. The §5.5 `core.changed` /\n`core.bye` / `core.identify` / `core.ping` handlers ship; only the shell's\nauto-`bye`-on-disconnect is deferred (it regressed the USB teardown — pending a USB-safe\nfix).\n\nBreaking protocol changes are still negotiated at `core.hello` (§5.4) and flow through\nHARP Enhancement Proposals (§18); two interoperating implementations are required before\na HEP merges, once the process formalizes. [`CONTRIBUTING.md`](CONTRIBUTING.md) documents\nthe HEP mechanics and the \"prove it with a test\" bar, [`GOVERNANCE.md`](GOVERNANCE.md) how\nthe spec and the reference evolve and who decides, [`SECURITY.md`](SECURITY.md) the §16\nthreat model and the disclosure process, and [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) the\ncommunity norms; bug/feature issue forms and a PR checklist live under\n[`.github/`](.github/).\n\n## Licensing\n\n- Specification text: **CC BY 4.0**\n- Schemas, reference implementation, test suite: **Apache-2.0**\n- Patent posture: royalty-free; contributors sign a non-assertion\n  covenant (spec §19). Use of the protocol is unrestricted; the\n  certification *mark* (future) requires passing the test suite.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkschzt%2Fharp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkschzt%2Fharp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkschzt%2Fharp/lists"}