Playground for exploring an optimal audio architecture
  • C++ 85.3%
  • HTML 11.7%
  • CMake 3%
Find a file
Henry 4d50bc2f0d MIDI: auto-connect every source, hotplug via periodic rescan
MidiInput now wires up all MIDI sources itself -- no `aconnect` needed. At startup
it subscribes to every readable source port; while running it rescans once per
second and grabs anything newly plugged in (snd_seq_connect_from is idempotent, so
already-wired sources just return -EBUSY).

Hotplug deliberately does NOT use System:Announce: under PipeWire that stream
delivers no PORT_START events (verified with aseqdump), so an announce-based
approach silently never fires. The timed rescan is cheap, runs on the existing
poll loop's wakeups, and works everywhere.

A manual `aconnect` still works but is no longer necessary. Docs updated
(README, ARCHITECTURE.md §6c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:49:41 +02:00
cmake Clean-code polish: follow the story from main() 2026-07-09 13:52:33 +02:00
doc MIDI: auto-connect every source, hotplug via periodic rescan 2026-07-09 18:49:41 +02:00
include/ui-art-rt Add real MIDI input (notes, aftertouch, velocity, pitch bend) 2026-07-09 18:19:48 +02:00
src MIDI: auto-connect every source, hotplug via periodic rescan 2026-07-09 18:49:41 +02:00
tests Add real MIDI input (notes, aftertouch, velocity, pitch bend) 2026-07-09 18:19:48 +02:00
.clang-format Initial commit: ui-art-rt playground 2026-07-09 10:40:02 +02:00
.gitignore Initial commit: ui-art-rt playground 2026-07-09 10:40:02 +02:00
AGENTS.md Initial commit: ui-art-rt playground 2026-07-09 10:40:02 +02:00
CMakeLists.txt Add real MIDI input (notes, aftertouch, velocity, pitch bend) 2026-07-09 18:19:48 +02:00
LICENSE Initial commit 2026-07-08 15:59:34 +02:00
README.md MIDI: auto-connect every source, hotplug via periodic rescan 2026-07-09 18:49:41 +02:00

ui-art-rt

Eine Spielwiese, um eine Audio-Engine-Architektur auszuprobieren: den Schnitt in drei entkoppelte, über lock-freie Kanäle kommunizierende Schichten.

UI-Thread  ──Control/Play (SpscQueue)──▶  ART-Thread  ──RT::BlockParameters (PointerExchange)──▶  RT-Thread
(MainLoop)                                (~1ms Tick)                                 (Audio-Callback)
  • UI nimmt User-Input entgegen, editiert UI::State (via UI::Processor) und schickt Messages nach ART.
  • ART (Almost-Realtime) merged Messages + Play-Events, macht Voice-Allocation und Macro-Modulation und legt pro Zeitscheibe einen kompletten RT::BlockParameters für RT ab.
  • RT (Realtime) läuft im ALSA-Playback-Thread, holt pro Block die neuesten RT::BlockParameters, smoothed alle Parameter und rendert nahezu branchfrei alle Voices direkt in den ALSA-Puffer.

Der ART/RT-Split ist neu — genau das ist hier der Untersuchungsgegenstand. Die tragende Idee: jede Voice hält einen eigenen SoundContext-Snapshot, wodurch „alte Voices spielen alten Stand aus", „Drum-Modus", „Layer/Split" und Live-Preset-Edits ohne Sonderfälle im Allocator funktionieren.

➡️ Design, Diagramme (UML) und offene Fragen: doc/ARCHITECTURE.md

Layout

include/ui-art-rt/   öffentliche Header, nach Schicht sortiert (lockfree/ model/ art/ rt/ ...)
src/                 Implementierungen + main.cpp (verdrahtet die drei Threads)
tests/               Catch2-Unit-Tests (deterministisch, ohne Threads)
doc/                 Architektur-Dokumentation mit Diagrammen

Code lesen — wo anfangen?

Der Code ist bewusst so geschrieben, dass man ihn von oben nach unten in dieser Reihenfolge lesen kann; jede Schicht baut auf der vorigen auf:

  1. model/ — die Daten: Parameters.h, MacroControls.h, SoundContext.h. Kleine, kommentierte Wert-Typen, kein Threading. Hier anfangen.
  2. messages/ + Channels.h — was zwischen den Threads fließt und über welche Kanäle.
  3. ui/Processor.h — die einfachste der drei Schichten: editieren + Message schicken.
  4. art/VoiceAllocator (welche Voice spielt was) und Processor (Tick-Schleife).
  5. rt/ — die anspruchsvollste Schicht. BlockParameters.h (das Datenlayout) zuerst, dann src/rt/Processor.cpp (oben steht ein „How to read"-Block zur SIMD-Notation).
  6. lockfree/SpscQueue.h und PointerExchange.h: klein, aber subtil. Zuletzt.

Jede Klasse hat oben einen Kommentar, der ihre Aufgabe und ihren Thread erklärt.

Bauen

Voraussetzung: alsa-lib inkl. Devel (pkg-config alsa) und ein spielbares Audio-Device. Keine weiteren Abhängigkeiten — die UI ist eine eingebettete Web-App (nur POSIX-Sockets).

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build

Testen

ctest --test-dir build --output-on-failure

Spielen

./build/ui-art-rt-app        # startet Engine (live über ALSA) + Web-Server auf Port 8080

Dann im Browser http://localhost:8080 öffnen. Die Seite (eine einzige, selbst-enthaltene HTML-Datei) bietet Preset-Buttons, Modus-/Unison-Wähler, Slider für Macro A/B und Volume, ein Pitch-Wheel (federt zurück) und ein Oktav-Keyboard (Maus oder Computertastatur a w s e d f t g y h u j k; Drücken → Note-On, Loslassen → Note-Off). Der Browser schickt knappe Text-Kommandos über WebSocket; die App reicht sie an UI::Processor / den Play-Ring weiter. UIARTRT_LOG=1 loggt die empfangenen Kommandos.

MIDI-Keyboard

Beim Start öffnet die App einen ALSA-Sequencer-Eingang ui-art-rt:input und hängt jede MIDI-Quelle automatisch an — sowohl beim Start vorhandene als auch später eingesteckte (die App scannt einmal pro Sekunde nach). Du musst also nichts verbinden: Keyboard einstecken und spielen. (Ein manuelles aconnect 'Dein Keyboard' ui-art-rt funktioniert weiterhin, ist aber nicht nötig.)

Unterstützt werden Note-On/Off (mit Velocity), Aftertouch (Channel- und Poly-Key- Pressure → öffnet den Filter), Pitch-Bend (±2 Halbtöne) und das Sustain-Pedal (CC 64). MIDI ist optional — ohne Sequencer läuft die App normal mit nur der Web-UI weiter.

Für echte RT-Priorität ohne root: sudo setcap 'cap_sys_nice=eip' ./build/ui-art-rt-app.

Konventionen

Code-Style und Agent-Regeln folgen den NL/C15-Konventionen (.clang-format, AGENTS.md). Ausnahme in diesem Projekt: ausführliche Kommentare sind ausdrücklich erwünscht, weil es eine Erkundungs-Spielwiese ist.