Salta ai contenuti

Ports: the backend contract

Questa pagina è in inglese: in italiano ci sono per ora i tutorial e il glossario; menu, ricerca e navigazione sono in italiano.

A port is an interface that a core declares and a backend implements. This page lists every port, grouped by the capability it abstracts. Together they are the complete contract between shuffle_engine and whatever runs underneath it: implement them all, pass their contract tests, and the framework runs on a new backend.

Status. A port exists only when a core needs it: ports are designed when the first domain that uses them is specified (see modules.md). Until then an entry is planned: its capability and threading contract are fixed, its exact signature is not. Port headers are the source of truth once they exist.

The JUCE (shuffle_juce_*) and Tracktion (shuffle_te_*) implementations named below live in the shuffle_juce satellite; the AI task ports (core/ai_tasks) and their audio.cpp adapter live in shuffle_ai. They are listed here because they complete the contract.


  • Where a port lives: in the core that consumes it, under core/<domain>/include/shuffle/<domain>/ports/. Ports shared by many domains live in core/base.
  • Names: a port is named after its capability and ends in Port only when the plain name would collide with a core type (MidiOutputPort, but Clock, FileStore).
  • Ownership: a core holds ports by reference (it does not own its environment). A port that hands out resources returns std::shared_ptr when a resource can outlive the call, for example a device that may be unplugged while a stream still uses it.
  • Threading column: control = only on the control thread; any = thread-safe; RT = callable on the audio thread (noexcept, no allocation, no blocking).
  • Availability: a port never assumes a capability exists on every platform. It reports what is there (isAvailable(), empty lists) and the core degrades gracefully.

Declared in core/base. Every backend must provide all of them. shuffle_platform (adapters/platform) provides them without any backend, on the C++ standard library and a small OS layer; shuffle_juce_platform provides them on JUCE.

Port Capability Threading JUCE implementation Status
TaskQueue post a task to the control thread; ask whether the caller is on it post: any; query: any JuceTaskQueue (shuffle_juce_platform) on juce::MessageManager::callAsync, isThisTheMessageThread done (TaskQueue.h); JUCE adapter and ControlLoop (shuffle_platform, a loop of its own on the standard library) pass the contract
TimerService repeating and one-shot callbacks on the control thread; cancellable handles control ControlTimer (shuffle_juce_platform) on juce::Timer: the host’s control timer, a concrete class until a core needs the port; the same ControlTimer in shuffle_platform on ControlLoop timers (cancellable TimerId handles) planned
WorkerPool run a job on a background thread, get completion on the control thread (engine loading, take writing, preparation; settings saves; environment refreshes) run: any except RT JuceWorkerPool (shuffle_juce_platform) on juce::ThreadPool done (WorkerPool.h), FakeWorkerPool in core/base; ThreadWorkerPool (shuffle_platform) on std::thread
Clock monotonic time; absolute TAI time with a lock state (network-locked or local only); replaceable by a manual clock in tests any, RT-safe reads std::chrono + the netaudio PTP follower when locked done (Clock.h); SteadyClock (local only) in shuffle_net_sockets and shuffle_platform; JuceClock (high-resolution ticks, local only) in shuffle_juce_platform
FileStore whole files by path: read, write, atomic replace, copy, remove, exists (durable saves build on these) worker JuceFileStore (shuffle_juce_platform) on juce::File, juce::TemporaryFile; atomic writes and replace by rename done (FileStore.h), consumed by showcontrol and synth; JUCE adapter and OsFileStore (shuffle_platform: Win32 and POSIX, POSIX-semantics rename on NTFS) pass the contract
SettingsStore key/value text persistence for what belongs to the machine (preferences, the routing machine patch); reads from memory, writes persisted later and atomically control JuceSettingsStore (shuffle_juce_platform) on juce::PropertiesFile, saved on a worker, temporary file and rename done (SettingsStore.h); JUCE adapter and JsonSettingsStore (shuffle_platform: one JSON file through any FileStore, saved on a worker) pass the contract with reopen
Logger leveled diagnostics; never on the audio thread any except RT juce::Logger planned (no core consumes it yet; errors are values)
FeatureGate whether a feature may run now (licence, entitlement, user setting) control application-defined planned
DisplayInfo per display: stable identifier, name, full bounds and work area, scale, physical density, refresh rate, safe-area insets; notify on change control toolkit or OS display APIs declared (DisplayInfo.h), fake and contract suite; consumed by ui_model (DisplayAssignments, WindowEnvironment); OsDisplayInfo (shuffle_platform) on Windows and macOS (CoreGraphics), declared unavailable on Linux, iOS and Android, where the window system or toolkit is needed
SystemAppearance dark or light, increased contrast, reduced motion, forced colours; notify on change control OS appearance settings declared (SystemAppearance.h), fake and contract suite; consumed by ui_model (WindowEnvironment) and ui_theme (ThemeController); OsSystemAppearance (shuffle_platform) on Windows, macOS and Linux (gsettings), neutral on iOS and Android
FrameClock frame ticks per display, started and stopped on demand so an idle UI costs nothing control display link / vsync callbacks declared (FrameClock.h), fake and contract suite; consumed by ui_model (WindowEnvironment) and ui_theme (Animator); adapter planned: it belongs to the UI backend that owns a window (display link, swap chain), not to shuffle_platform
CivilTime TAI to and from local civil time in IANA zones, daylight-saving gaps and overlaps, whether the clock is synchronised control OS time-zone database (the C++20 one is not available on every platform) done (CivilTime.h), consumed by cues; moves to core/base when a second core needs it
Port Capability Consumers Threading JUCE / Tracktion implementation Status
OutputStage processing on the final device output, after the mix and before clipping, on every callback timecode, voiceover, click process: RT Tracktion DeviceManager::setGlobalOutputAudioProcessor (stock API), wrapped by TeOutputStageHost (shuffle_te_devices), a host that chains several stages; on the framework’s engine the Engine is the host, run by EngineAudioCallback (shuffle_juce_audio, or shuffle_miniaudio without JUCE) done (OutputStage.h, OutputStageHost.h); Tracktion host adapted (README); engine host passes the contract through the JUCE device callback and through the miniaudio one
AudioOutputTopology the named outputs a user routes to, and per endpoint channel the physical channel it lands on now, with the route (own binding, index guess, declared fallback) timecode, tuning, mixer control provided by the routing core on top of AudioDeviceChannels done (AudioOutputTopology.h)
AudioInputTopology the same for inputs timecode, tuning control provided by the routing core done (AudioInputTopology.h)
AudioDeviceChannels the audio devices present: stable key, kind, channel counts, driver channel names, sample rate, clock domain, readiness routing control TeAudioDevices (shuffle_te_devices): the juce::AudioIODevices of Tracktion’s DeviceManager; JuceAudioDevices (shuffle_juce_audio) on JUCE’s audio driver types, without Tracktion; MiniaudioDevices (shuffle_miniaudio) on miniaudio’s backends, without JUCE done (AudioDeviceChannels.h); Tracktion adapted; JUCE adapted; miniaudio adapted
AudioDeviceSwitch ask for a device to become the active one; the result arrives asynchronously routing (backup device) control TeAudioDevices (shuffle_te_devices) on Tracktion’s DeviceManager; JuceAudioDevices (shuffle_juce_audio): the open posted through TaskQueue, the previous device reopened on failure; MiniaudioDevices (shuffle_miniaudio) the same way on miniaudio done (AudioDeviceSwitch.h); Tracktion adapted; JUCE adapted; miniaudio adapted
InsertProcessor a per-block processing slot on a source, bus or output, with named fadeable parameters routing, cues process: RT; parameters: control plugin hosting (rack); PluginInsert (shuffle_juce_plugins) on a hosted JUCE plugin; PluginInsert (shuffle_plugin_host) on a CLAP, VST3, LV2 or AU plugin, without JUCE done (InsertProcessor.h); JUCE plugin adapter passes the contract; the plugin host adapter passes it with CLAP, VST3 and LV2
MidiOutputPort send MIDI bytes now, from any thread; whole UMP packets to a port that takes them (sendUmp) timecode, showcontrol, midi, routing, sync (MIDI clock: sendNow from a timing thread, sendAt from the audio thread) send: RT te::MidiOutputDevice, or JuceMidiOutput (shuffle_juce_midi) on juce::MidiOutput without Tracktion: real-time send through a preallocated queue, no UMP; or the outputs of LibremidiDevices (shuffle_libremidi, no JUCE): real-time send through a preallocated queue, UMP packets on Windows MIDI Services, CoreMIDI UMP and ALSA UMP done (MidiOutputPort.h)
MidiOutputDirectory list outputs with driver type, persistent unique id, protocol and UMP groups (outputPorts, MidiPortInfo); find one by name; notify on rescan routing, showcontrol, midi control Tracktion DeviceManager MIDI outputs, or JuceMidiDevices (shuffle_juce_midi) without Tracktion, or LibremidiDevices (shuffle_libremidi) without JUCE done (MidiOutputDirectory.h)
MidiInputDirectory list the enabled inputs with driver type, unique id, protocol, groups, aggregate and own-published flags (inputPorts); open one by exact name, never a default or closest one; rescans arrive through DeviceEvents midi, showcontrol, routing control Tracktion DeviceManager MIDI inputs, or JuceMidiDevices (shuffle_juce_midi) without Tracktion, or LibremidiDevices (shuffle_libremidi) without JUCE done (MidiInputDirectory.h)
MidiInputSource one open input of its physical origin: raw MIDI 1.0 bytes or whole UMP packets with a monotonic timestamp, delivered to a core receiver (the midi core parses running status, real-time bytes and SysEx) midi, showcontrol callback: RT or control (declared per subscription) te::MidiInputDevice, or JuceMidiInput (shuffle_juce_midi) on juce::MidiInput, or the inputs of LibremidiDevices (shuffle_libremidi) on libremidi’s raw callbacks (bytes or whole UMP packets) done (MidiInputSource.h)
MidiEndpointTopology list named MIDI inputs and outputs; resolve a named MIDI output to its shared tracked output (or its declared fallback’s), or to nothing when the port is missing (never a default port) timecode, showcontrol, cues, midi control provided by the routing core done (MidiEndpointTopology.h)
VirtualMidiPorts publish and unpublish MIDI inputs and outputs visible to other applications; platform capability and health routing control; data: MIDI thread OS virtual MIDI (CoreMIDI, ALSA sequencer, Windows MIDI Services) done (VirtualMidiPorts.h)
VirtualAudioDevices publish audio inputs and outputs visible to other applications (inter-app audio, loopback); capability, channel limits, added latency routing control; data: RT once per block platform-specific adapters done (VirtualAudioDevices.h)
InstrumentChains inject MIDI at internal instrument inputs and reset their processors (panic empties tails) midi RT Tracktion plugin chains done (InstrumentChains.h)
DeviceEvents “the device set changed”: audio device switched, MIDI rescanned every I/O consumer control juce::ChangeListener on the device manager; MIDI rescans: JuceMidiDevices (shuffle_juce_midi) on juce::MidiDeviceListConnection, or LibremidiDevices (shuffle_libremidi) on libremidi’s hotplug observer; audio devices: TeAudioDevices (shuffle_te_devices) on Tracktion’s DeviceManager, or JuceAudioDevices (shuffle_juce_audio) on the drivers’ change notifications, or MiniaudioDevices (shuffle_miniaudio): the device list polled on the control thread (miniaudio has no list notification) and the open device’s own notifications done (DeviceEvents.h); MIDI side adapted; audio side adapted on Tracktion, on JUCE and on miniaudio

What a live show needs from an audio engine. Implemented by the framework’s own engine (core/engine) and by the Tracktion adapters (adapters/tracktion in shuffle_juce). These are the ports with the largest surface; their exact shape is fixed by the setlist and showcontrol specs.

Port Capability Consumers Threading Tracktion implementation Status
Transport play and stop now, at a sample, at an absolute instant or on the musical count; locate, loop range, roll-in; set playback rate; end-of-song and jump plans; switch-and-start handover; segments with flags; position is the audible position; reports end reached, looped, jump executed, late start setlist, showcontrol, timecode, video, ui control (state readable: any; segments: RT) TeTransport (shuffle_te_engine): the core TransportCursor in a graph node per song steering Tracktion’s play head (rate 1 only); the engine (engine.md EN-R136) done (Transport.h); Tracktion adapted with declared limits
TempoMap beats <-> seconds, tempo and time-signature changes of a song tempo, showcontrol, ui any (immutable snapshots) te::TempoSequence planned
SongSessionFactory load a song document into a playable session in the background, report progress, cancel, release, detach a listener, health setlist control; loading on workers TeSongSessionFactory (shuffle_te_engine): te::Edit files read by te::EditLoader done (SongSessionFactory.h); Tracktion adapted
SongSession one loaded song: its transport, tempo map and arrangement, arm and disarm without a gap, tempo-follow capability, health setlist control TeSongSession (shuffle_te_engine): one te::Edit per song; tempo map from its tempo sequence, arrangement from its markers done (SongSession.h); Tracktion adapted
TempoSync shared musical tempo and phase with other apps and devices: join/leave/rejoin a session, tempo, beat and phase at a time, quantum, number of peers, start/stop sync, peer events setlist, cues, tempo, ui control; status RT-safe the framework’s own sync protocol (UDP multicast/unicast, BLE, USB/serial, WebSocket relay, PTP when available); MIDI clock bridge; optional Ableton Link bridge done as the set list consumes it (TempoSync.h); moves to core/base when a second core needs it; protocol specified (sync.md)
LiveSession the live-input session that stays up for the whole show, across songs setlist control TeLiveSession (shuffle_te_engine): a dedicated Tracktion edit for live inputs done (LiveSession.h); Tracktion adapted
ShowStore write the songs and the set list marked as changed, on workers, atomically setlist control; writing on workers the show document (package) through FileStore done (ShowStore.h)
ConfirmationHandler show and withdraw the requests a person answers on stage (pending switch, peer stop) setlist control the user interface done (ConfirmationHandler.h)
TimelineEvents events placed on a song timeline, delivered on the audio thread in the block that crosses them, with the block’s segments (starts, jumps, loop wraps) showcontrol, events, voiceover events and receiver: control; delivery: RT Tracktion playback graph node per song feeding the core SegmentTimeline (TeSongSession::timelineEvents(), shuffle_te_engine); the core SegmentTimeline on set-list transport segments done (TimelineEvents.h); Tracktion adapted
TimecodeActions start (for a length or until stopped), stop and query a named timecode definition; transport stopped showcontrol control the timecode service done (TimecodeActions.h)
CommandRunner run an internal action’s command through the command layer as a show-control trigger showcontrol control the core SessionCommandRunner on a CommandSession done (CommandRunner.h)
MixControl gain, pan, mute, solo, sends of the tracks a show exposes; the song output gain for a stop with fade setlist, mixer view models, assistant control song gain: TeMixControl (shuffle_te_engine) applied by each song’s graph node; tracks: te::VolumeAndPanPlugin, te::AuxSendPlugin song output gain done (MixControl.h), Tracktion adapted; track gains planned
CueTarget one per output domain: describe and validate, prepare on workers, start at the instant its first sample is audible (from an offset, on normal or audition outputs), cancel a start not yet performed, stop (fade, at once, hard, leave loop), pause and resume, declared panic behaviour, reset; reports ended, failed, late, cancelled and validity changes cues control; start instants in TAI each output domain; audio by the engine (engine.md EN-R142) or by TeAudioCueTarget (shuffle_te_engine, rate 1, one output) done (CueTarget.h); audio adapted on Tracktion
CueParameters resolve a parameter by name (range, unit, interpolation space, fader law), read its current value, receive per-block fade values and duck offsets on the audio thread cues resolve and read: control; values: RT routing, the engine (EN-R155), effect and device adapters; the level of Tracktion audio cues (TeAudioCueTarget) done (CueParameters.h); cue levels adapted on Tracktion
MusicPosition tempo map and song position of the music a cue list follows, the musical count, loop wraps and jumps (generations), playing state cues control (snapshot with a shared tempo map) setlist done (MusicPosition.h)
PluginHost scan, list, instantiate, save and restore plugin state asynchronously; a PluginInstance is prepared, processes blocks with MIDI, parameter changes and transport, reports latency and tail, and names its own bypass parameter engine, rack, plugin editors control; scanning and instantiation on workers; process: RT JucePluginHost / JucePluginInstance (shuffle_juce_plugins) on JUCE 9’s headless juce::AudioPluginFormatManager (VST3, AU, LV2), without a device manager; NativePluginHost (shuffle_plugin_host) on the formats themselves (CLAP, VST3, LV2 through lilv, AU), without JUCE; or te::PluginManager, te::ExternalPlugin done (PluginHost.h), fake and contract suite in core/engine; JUCE adapter passes the contract; the plugin host adapter passes it with CLAP, VST3 and LV2 test plugins
FourierTransform optional faster platform FFT behind the core’s own transform (sizes, normalisation, real and complex) dsp, mir, spectral plans: control; execution: any thread, RT-safe, a plan shared by many threads platform DSP libraries; the core CoreFourierTransform is the default done (FourierTransform.h), fake and contract suite in core/dsp
ProcessingThreads a fixed set of real-time worker threads with wake and wait that are safe on the audio thread; 0 means single-threaded engine RT OS real-time threads (audio workgroups where the platform has them): JuceProcessingThreads (shuffle_juce_audio) on juce::Thread::startRealtimeThread, joined to the device’s juce::AudioWorkgroup; OsProcessingThreads (shuffle_miniaudio) on std::thread with MMCSS, QoS or SCHED_FIFO, no workgroup done (ProcessingThreads.h), fake and contract suite in core/engine; JUCE adapted; OS threads adapted
TimeStretcher time-stretch and pitch-shift voices from a preallocated pool (TimeStretchers): ratio, pitch, process, reset, latency engine RT permissive stretch library done (TimeStretcher.h), fake (resampling) and contract suite in core/engine
AudioFileProbe duration, sample rate, channels, peak overview of an audio file ui waveform, package worker te::AudioFile, juce::AudioThumbnail data planned
Recorder record an input to a file with a known start position voiceover control; writing on workers te::InputDevice recording the engine’s own Recorder component (Recording.h); a port when a second consumer needs it
MediaReader, MediaSource read decoded frames by position without waiting on the audio thread (missing frames are silence, counted), or with an unlimited wait offline; open files engine read: RT (wait none) or worker (unlimited); open: worker buffered readers of formats done as the engine’s reader seam (MediaReader.h), fake and contract suite in core/engine; implemented on the formats buffered reader when it lands
AudioWriter, AudioWriters write takes (recording mode) and renders (atomic mode), report clipping and non-finite counts, tell whether a path is taken engine worker writers of formats; TeAudioWriters (shuffle_te_engine, WAV through te::AudioFileWriter) done as the engine’s writer seam (AudioWriter.h), fake and contract suite in core/engine
SongReader read a song document into tracks, items, tempo map and arrangement engine worker the session document (package) done (SongReader.h), fake in core/engine
Port Capability Consumers Threading Implementation today Status
VideoDecoder open a clip, seek, decode frames at a time, report format video worker; frames handed to control platform decoders in shuffle_media (Media Foundation, AVFoundation, MediaCodec, WebCodecs; no FFmpeg) specified (shuffle_media specs/video.md)
DocumentRenderer render pages of a document (PDF) to images at a size documents worker platform PDF renderer planned
ImageSink hand a decoded frame or page to a display surface video, documents control toolkit image planned
SpeechSource provide audio for a spoken cue (synthesised from text, at the requested rate; voice and language chosen by name) voiceover worker (stop token, progress) audio.cpp TTS models (shuffle_ai_audiocpp); recorded cues via FileStore to come done (SpeechSource.h, declared in core/ai_tasks), fake and contract suite; adapted
Port Capability Consumers Threading Implementation today Status
DatagramSocket UDP send/receive, unicast/multicast/broadcast, interface selection, TTL, DSCP, kernel receive timestamps when available (transmit times user-space) netaudio, redundancy, osc, showcontrol, remote, lighting send: any (RT-safe); receive: network thread OS sockets: OsDatagramNetwork (shuffle_net_sockets) done (DatagramSocket.h), adapted
StreamSocket TCP listen, connect with timeout, read and write netaudio (RTSP), osc (TCP framing), showcontrol (raw TCP actions) worker OS sockets: OsStreamNetwork (shuffle_net_sockets) done (StreamSocket.h), adapted
HostResolver a host name to an IP address; dotted addresses resolve to themselves showcontrol network sender thread (may block) OS resolver (getaddrinfo) done (HostResolver.h)
NetworkInterfaces list interfaces with addresses; timestamping capability and change polling in the adapter (OsNetworkInterfaces::timestamping, InterfaceWatcher) netaudio, remote control OS APIs: OsNetworkInterfaces (GetAdaptersAddresses, getifaddrs) done (NetworkInterfaces.h), adapted
OscEndpoint send and receive OSC messages and bundles, scheduled against the Clock showcontrol, remote receive: control core component of the osc domain on DatagramSocket and StreamSocket (no backend needed) done (OscEndpoint.h)
HttpClient requests with timeouts and cancellation lyrics lookup, updater, assistant worker juce::URL (needs curl on Linux) planned
MessageServer bidirectional messaging with remote devices (WebSocket-like) remote callbacks: control to be chosen per backend planned
SerialPort byte streams to serial devices lighting (DMX interfaces), hwcontrol worker platform adapter planned
Port Capability Consumers Threading Implementation Status
TextMeasurer the advance of a run of text in a ui_theme font role at a size (natural, one line), the font’s ascent and descent, optionally the platform’s line-break opportunities ui_layout (the text-fitting rule, layouts, widgets fitting one label) the owner’s thread (normally control) DWriteTextMeasurer (shuffle_text_dwrite, Windows); Core Text, FreeType with HarfBuzz, Android and the browser’s canvas to come done (TextMeasurer.h), testing::FakeTextMeasurer and contract suite; DirectWrite adapter passes it
Port Capability Consumers Threading Implementation Status
BridgeChannel the sending side of one UI connection: send text or binary, buffered bytes, close, transport kind ui_bridge control WebUiPage (in-process, over a WebUiHost); WebSocketServer (shuffle_net_websocket); WebAssembly to come done (BridgeChannel.h), fake and contract suite
TopicSource a declared topic’s current value for bound parameters, change notification ui_bridge control each domain (ThemeSource in ui_bridge; the example’s set list) done (TopicSource.h), fake and contract suite
StreamSource fill one frame of a declared real-time stream from values the audio side already published, without blocking ui_bridge control or worker, non-blocking each domain done (StreamSource.h), fake and contract suite
WebUiHost an embedded webview: load packaged content under a policy (served paths, CSP, navigation, permissions, developer tools), post and receive messages with frame and origin, report page starts and crashes ui_bridge (WebUiPage), applications control, non-blocking JuceWebUiHost (shuffle_juce_webui) on juce::WebBrowserComponent: WebView2, WKWebView, WebKitGTK, Android WebView done (WebUiHost.h); the JUCE adapter passes the contract suite on WebView2, with declared limits (no permission, developer-tools or crash events in JUCE’s API)

Specified by ui_widgets. Renderer2D is declared by core/ui_render, PlatformWindow and RequestSink by core/ui_widgets, TextMeasurer by ui_layout.

Port Capability Consumers Threading Implementation Status
Renderer2D draw a plain-data draw list (rectangles, rounded rectangles, paths, arcs, gradients, shadows, text runs through the platform text engine, images, clips, opacity layers) into a window surface or an offscreen target, only inside the damage, with buffer age; register fonts, images and static paths; present with frame pacing; read back pixels; report device loss ui_widgets, applications drawing their own canvases control (the window’s thread) D2dRenderer (shuffle_render_d2d: Direct2D, DirectWrite with the fonts of shuffle_text_dwrite, DXGI flip model; WARP fallback); Core Graphics or Metal, Android, Vulkan or GLES, Canvas or WebGPU later done (Renderer2D.h), testing::FakeRenderer2D on the CPU reference rasteriser and the contract suite with the reference scenes in core/ui_render; the Direct2D adapter passes it on the GPU and on WARP (spec UW-R143 to UW-R158, UW-R179 to UW-R183)
PlatformWindow one window or mobile surface (main, output, child of a plugin host, offscreen): pointer, wheel, magnify, key, text-input and composition events, focus, size, scale and visibility; cursor, pointer capture, input-method area and soft keyboard, title, chrome hit regions, accessibility tree and announcements, clipboard ui_widgets (WindowHost) control, non-blocking Win32Window (shuffle_window_win32: per-monitor DPI v2, WM_POINTER pen and touch, IMM32, custom title bars, a UI Automation provider; its Win32FrameClock on the vertical blank); AppKit and UIKit, Android, Wayland and X11, the browser later done (PlatformWindow.h), testing::FakePlatformWindow and the contract suite in core/ui_widgets; the Win32 adapter passes it driven by window messages (spec UW-R159 to UW-R169, UW-R184)
RequestSink hand a screen’s command request to the command session or the bridge and return its identifier at once; answer a confirmation ui_widgets control, non-blocking the application’s (over commands::Session in process, over the bridge remotely); testing::RecordingRequestSink done (RequestSink.h, UW-R6, UW-R121)
Port Capability Consumers Threading Implementation today Status
InferenceRuntime load a model graph, report capabilities and execution providers, run tensors, cancel inference worker or inference thread ONNX Runtime, TF Lite, llama.cpp adapters specified
ModelFetcher fetch model files from https, hf:// or folders with progress and cancel inference worker HTTP adapter specified
SpeechToText audio to text with segment and word timestamps at the input’s rate, language, optional voice-activity detection voiceover, assistant, lyrics worker (stop token, progress) audio.cpp ASR models with a VAD model (shuffle_ai_audiocpp) done (SpeechToText.h), fake and contract suite; adapted
StemSeparator a mix to named stems aligned with it (same rate, channels, frames) neural, edit, applications worker (stop token, progress) audio.cpp separation models (shuffle_ai_audiocpp) done (StemSeparator.h), fake and contract suite; adapted
AudioToMidi audio to MIDI notes (midi::Sequence in seconds of clip time), optionally narrowed to instruments neural, edit, applications worker (stop token, progress) audio.cpp transcription models (shuffle_ai_audiocpp) done (AudioToMidi.h), fake and contract suite; adapted
AudioDecoder, AudioEncoder, PacketDecoder, PacketEncoder, CodecProvider decode and encode audio files and packets with exact delay/padding; codec capabilities with licence and patent notes formats, neural, mir, render, library worker OpenCodecs (shuffle_codecs: libFLAC, libvorbis, libopus, dr_mp3); platform codecs and the Apple ALAC and OpenCORE AAC libraries for AAC and ALAC; the core’s ContainerAudioDecoder / ContainerAudioEncoder implement the first two done (AudioDecoder.h, AudioEncoder.h, CodecProvider.h); fake and contract suites in core/formats; adapted for FLAC, Vorbis, Opus and MP3 reading (contract suites pass)
ByteSource, ByteSink a file to read (random access, size or stream, optional mapped view) and to write (sequential, header patches, atomic or recording) formats worker OS files (streaming, memory mapping); MemoryBytes.h and FileStoreBytes.h in the core done (ByteSource.h, ByteSink.h), contract suites in core/formats
SecretStore store API keys in the operating system keychain, never in files or logs llm control OS keychains specified
ProcessLauncher start a process without a shell, with pipes (MCP stdio servers) llm worker OS process APIs specified
HttpListener serve HTTP on loopback by default (MCP server) llm, generated surfaces network thread HTTP adapter specified
SecureRandom cryptographically secure random bytes (tokens) llm, remote any OS random sources specified
LanguageModel complete a prompt, optionally constrained by a grammar or a JSON Schema, or with tool calling; report context size and capabilities; cancellable AI adapters on top of the commands core worker; never the audio thread llama.cpp or ONNX locally; hosted APIs planned

The commands core never calls a model: it produces the catalogue, the schemas, the grammars and the snapshots, and validates whatever comes back. Which model is used, local or hosted, small or large, is a choice of the composition root and of the source’s profile.


Every port ships a contract test suite: header-only Catch2 test templates in core/<domain>/contracts/, parameterised by a factory that builds an implementation. Each adapter’s test runner instantiates the suites for the ports it implements; the core’s own tests instantiate them for the fakes.

Each core with contract suites exposes them as a CMake target, shuffle::<core>_contracts (header-only, links the core and Catch2). They are built with the engine’s tests (SHUFFLE_ENGINE_BUILD_TESTS), or alone with SHUFFLE_ENGINE_BUILD_CONTRACTS=ON: that is how an adapter tested in another repository, such as a satellite, runs the suites without building the engine’s own tests. shuffle_engine_fetch_catch2() (in cmake/ShuffleEngineDeps.cmake) brings Catch2 and puts its CMake scripts on the caller’s module path for catch_discover_tests.

flowchart LR
    suite["contracts/MidiOutputPortContract.h<br/>(behaviour every implementation must show)"]
    fake["core tests:<br/>FakeMidiOutput"]
    juce["shuffle_juce tests:<br/>JuceMidiOutput"]
    other["adapters/&lt;new backend&gt; tests:<br/>NewMidiOutput"]
    suite --> fake
    suite --> juce
    suite --> other

A contract test checks the behaviour the cores rely on, for example:

  • TaskQueue: tasks run on the control thread, in posting order, never synchronously inside post;
  • FileStore: an interrupted write leaves the previous content intact;
  • OutputStage host: a stage removed from the host is never called again, and is released off the audio thread;
  • Transport: stop followed by play restarts from the stop position unless a locate happened in between.

This is what turns “replace the backend” from an act of faith into a checklist: when the new adapters pass every contract suite, the cores behave the same on top of them.