Salta ai contenuti

Replacing JUCE or Tracktion

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

shuffle_engine is designed so that JUCE and Tracktion Engine can be swapped out. This page is the procedure, and it describes exactly what a replacement costs. The JUCE and Tracktion adapters live in the shuffle_juce satellite (satellites.md); shuffle_engine itself builds no JUCE code.

Part of the code When JUCE is replaced When Tracktion is replaced
core/ (all domain knowledge, view models, ports, contract tests) unchanged unchanged
adapters/juce/ (shuffle_juce) rewritten as adapters/<new>/ unchanged
adapters/tracktion/ (shuffle_juce) rewritten too: it is built on JUCE types rewritten as adapters/<new engine>/
ui/juce/ rewritten as ui/<new toolkit>/ (drawing and input forwarding only) unchanged
application composition root constructs the new adapters constructs the new adapters
show files and settings unchanged: formats belong to the framework unchanged

The rule that makes this table true: no JUCE or Tracktion type ever appears in a core, so nothing in core/ can notice that the backend changed. CI proves it on every commit by building and testing every core with no backend at all.

flowchart TD
    a["1. List the ports<br/>(docs/ports.md)"] --> b["2. Create adapters/&lt;new&gt;/<br/>one adapter per port"]
    b --> c["3. Instantiate every contract suite<br/>in the new adapters' test runner"]
    c --> d{"all contract<br/>tests pass?"}
    d -- no --> b
    d -- yes --> e["4. New composition root<br/>constructs the new adapters"]
    e --> f["5. Run the golden-flow tests<br/>(end-to-end show journeys)"]
    f --> g["6. Optional: ui/&lt;new toolkit&gt;/<br/>draw the same view models"]
  1. List the ports the application uses. ports.md has them all, with the JUCE and Tracktion API each one is built on today, which is the starting point for the equivalent in the new backend.
  2. Write one adapter per port in adapters/<new>/. Adapters are expected to be short: they translate types and calls. If one grows logic, that logic probably belongs in a core.
  3. Run the contract suites. Every port has a header-only suite in core/<domain>/contracts/. Instantiate each one with a factory for the new adapter. They check the behaviour the cores rely on: threading, ordering, lifetime, failure reporting.
  4. Compose. A new composition root (or a backend switch in the existing one) constructs the new adapters and passes them to the same core objects.
  5. Golden flows. The end-to-end journeys (“prepare a show”, “run a show”) run against the new backend unchanged.
  6. UI. If the GUI toolkit changes too, write ui/<toolkit>/ widgets that draw the existing view models. All geometry and interaction logic is already in core/ui_model and does not need rewriting.

What JUCE provides today, and the port each item hides behind.

JUCE capability Port(s) Notes for a replacement
message thread, callAsync, timers TaskQueue, TimerService done without JUCE: ControlLoop and ControlTimer (adapters/platform); a GUI toolkit’s own loop works the same way
thread pool WorkerPool done without JUCE: ThreadWorkerPool on std::thread
files, temporary files, app data dirs FileStore must write atomically (temp file + rename); done without JUCE: OsFileStore (Win32 and POSIX); the application data folder is the composition root’s choice
properties files SettingsStore any key/value store; done without JUCE: JsonSettingsStore (one JSON file through any FileStore)
audio device I/O AudioDeviceChannels, AudioDeviceSwitch, DeviceEvents, ProcessingThreads, a callback that calls Engine::process with flush-to-zero (shuffle_juce_audio, or shuffle_miniaudio without JUCE); routing builds AudioOutputTopology and AudioInputTopology on them e.g. RtAudio, PortAudio, miniaudio, Oboe (Android), AVAudioEngine (Apple); the callback is about 60 lines
MIDI I/O MidiOutputPort, MidiOutputDirectory, MidiInputSource, MidiInputDirectory, DeviceEvents done without JUCE: LibremidiDevices (adapters/libremidi), MIDI 1.0 and UMP on every platform; VirtualMidiPorts still to write
OSC, UDP OscEndpoint, DatagramSocket e.g. oscpack, liblo, asio
HTTP HttpClient platform clients or libcurl
GUI components, graphics ui/juce/* widgets rewrite drawing only; see architecture.md
plugin hosting (VST3/AU/LV2) PluginHost, InsertProcessor (shuffle_juce_plugins) done without JUCE: NativePluginHost and PluginInsert (adapters/plugin_host) on the formats themselves: CLAP and VST3 headers, lilv for LV2, AudioToolbox for AU; plugin editors still to host

Tracktion is the larger dependency. A replacement engine must offer the capabilities below, through the engine ports. This list is the honest size of the job; it is kept current as domains are implemented.

Capability Engine port Used by JUCE + own engine (core/engine on shuffle_juce_audio) Tracktion adapter (adapters/tracktion in shuffle_juce) Permissive stack (own engine without JUCE)
audio device I/O driving the graph AudioDeviceChannels, AudioDeviceSwitch, DeviceEvents, the process entry routing, every audio domain ✅ JuceAudioDevices on JUCE’s driver types (contracts pass), one Engine::process per device callback with flush-to-zero (DS-R16), inputs to the engine and routing, xruns to the overload monitor; no allocation in the callback (tested) ✅ Tracktion’s DeviceManager (contracts pass) ✅ shuffle_miniaudio: MiniaudioDevices on miniaudio’s backends (contracts pass on its null backend), one Engine::process per device block with flush-to-zero, inputs to the engine and routing, no allocation in the callback (tested); no xruns (miniaudio reports none)
parallel processing on real-time threads ProcessingThreads engine ✅ JuceProcessingThreads: real-time JUCE threads, flush-to-zero, audio workgroups where JUCE has them (contract passes; parallel output equals single-threaded) — (Tracktion’s own thread pool) ✅ OsProcessingThreads (shuffle_miniaudio): std::thread with MMCSS, QoS or SCHED_FIFO, flush-to-zero (contract passes; parallel output equals single-threaded)
sample-accurate multitrack playback of audio files SongSession, Transport setlist ✅ audio items (CU-R168 mapping, bit-exact unity, loops, fades, rate); Transport and SongSession contract suites pass ✅ one Edit per song; Transport contract passes (core cursor, audio checked sample by sample); rate 1 only, in-block discontinuities rendered from the next block ✅ as the engine column: core/engine needs no JUCE
several songs loaded at once, the next one armed while the current plays SongSessionFactory, SongSession setlist ✅ load on workers with cancel and missing media, arm and release as sub-graphs, segue without a gap; MIDI tracks of a song not built yet ✅ SongSessionFactory contract passes: reading on workers, building on the control thread, missing media, cancel; a segue to another Edit may start one block late (reported) ✅ as the engine column: core/engine needs no JUCE
tempo map with tempo and time-signature changes TempoMap tempo, showcontrol, ui 🔄 immutable time maps per session; a new map is not adopted while loaded 🔄 read once from the Edit’s tempo sequence (ramps as steps); no TempoMap port 🔄 as the engine column: core/engine needs no JUCE
timeline events delivered on the audio thread as the playhead crosses them TimelineEvents showcontrol, events, voiceover 🔄 audible segments per block; events through showcontrol::SegmentTimeline on the transport ✅ per song: SegmentTimeline fed by the song’s graph node with audible segments 🔄 as the engine column: core/engine needs no JUCE
per-track gain, pan, mute, solo, sends, buses MixControl mixer, setlist 🔄 song output gain and stop with fade (contract passes); track gains and sends are edge gains and nodes, without a MixControl surface yet 🔄 song output gain (contract passes); track gains not exposed 🔄 as the engine column: core/engine needs no JUCE
processing on the final device output OutputStage host timecode, voiceover, click ✅ contract passes ✅ global output hook; contract passes ✅ the engine as host through the miniaudio callback (contract passes)
plugin scan, instantiate, state save/restore PluginHost rack, plugin editors ✅ boundary, placeholder, latency, bypass, isolation; VST3, AU and LV2 through shuffle_juce_plugins (JUCE, no Tracktion) ⬜ shuffle_te_plugins ✅ shuffle_plugin_host: CLAP, VST3, LV2 (lilv) and AU (AudioToolbox) without JUCE; scan with a crash-safe skip list, sample-accurate parameters and MIDI, transport, latency, own bypass, state, placeholder (contracts pass with our CLAP, VST3 and LV2 test plugins); editors to host
recording an input with a known start position Recorder voiceover, looper ✅ aligned takes, punch, loop passes, gaps, write errors (component, no port yet) 🔄 AudioWriters (WAV through te::AudioFileWriter, contract passes); input recording not adapted ✅ as the engine column: core/engine needs no JUCE
audio file metadata and peak overviews AudioFileProbe ui waveform, package ⬜ waits for formats ⬜ no port yet ⬜ no port yet
time-stretching for tempo changes through SongSession setlist (optional feature) ✅ through the TimeStretcher port; a stretch library adapter to write ⬜ rate 1 only (sessions report they cannot follow a tempo) ✅ as the engine column: core/engine needs no JUCE
tempo and phase shared with other apps (own sync protocol, optional Ableton Link bridge) TempoSync setlist, cues, looper ✅ consumed through the transport’s playback rate ⬜ needs a playback rate ✅ as the engine column: core/engine needs no JUCE
named audio and MIDI endpoints, virtual ports shared with other apps AudioDeviceChannels, VirtualMidiPorts, VirtualAudioDevices routing ✅ the routing program runs as an engine node (routing contract passes) 🔄 AudioDeviceChannels, AudioDeviceSwitch, DeviceEvents (contracts pass); the routing program is not run inside Tracktion (RT-R6) ✅ as the engine column: core/engine needs no JUCE; MIDI ports on shuffle_libremidi (contracts pass; UMP on the OS MIDI 2.0 APIs); VirtualMidiPorts to write
latency compensation across the audio graph core/engine routing, cues ✅ every pin, render lead, monitoring, crossfaded changes ✅ Tracktion’s own; the transport’s render lead includes the graph latency ✅ as the engine column: core/engine needs no JUCE

The Tracktion column is what the Tracktion adapters cover today, with their declared limits (adapters/tracktion/README.md in shuffle_juce). The permissive-stack column is what runs with no JUCE and no Tracktion at all: core/engine on shuffle_miniaudio (and the permissive codecs of shuffle_codecs); everything the engine does in its own code runs there unchanged.

Still needed on top of core/engine before Tracktion can go: the reader and writer seams on formats, a stretch library behind TimeStretcher, plugin editors (the hosting itself is done: shuffle_plugin_host), AudioFileProbe, and a MixControl surface for track gains. Progress per requirement is in core/engine/README.md.

Candidate backends, permissive licences first

Section titled “Candidate backends, permissive licences first”

The adapters can be built on any of the libraries below. Permissive licences (public domain, MIT, BSD, ISC, zlib, BSL, Apache-2.0) are preferred: they impose nothing on applications, keep shuffle_engine’s Apache-2.0 stack free of copyleft terms, and allow static linking on iOS and Android. Licences are as published by each project at the time of writing; check them again when adopting a library.

Capability (ports) Permissive candidates Licence Platforms Notes
event loop, timers, workers (TaskQueue, TimerService, WorkerPool) C++20 standard library + a small OS layer ours all done: shuffle_platform (adapters/platform), with Clock, FileStore, SettingsStore, SystemAppearance and DisplayInfo
libuv MIT all loop, timers, threads, files, sockets in one
UDP/TCP, multicast, timers (DatagramSocket, StreamSocket) standalone Asio BSL-1.0 all header-only
mDNS / DNS-SD mjansson/mdns Unlicense all single header
HTTP, WebSocket (generated surfaces) cpp-httplib · IXWebSocket · uWebSockets MIT · BSD-3 · Apache-2.0 desktop, mobile
OSC tinyosc · oscpack ISC · MIT-style all
JSON (recorder, surfaces) glaze · nlohmann/json · simdjson MIT · MIT · Apache-2.0/MIT all
compression (SAP payloads, packages) miniz · zlib MIT · zlib all
Capability (ports) Permissive candidates Licence Platforms Notes
audio I/O, output stages (OutputStage host, topologies) miniaudio Unlicense / MIT-0 Windows, macOS, Linux, iOS, Android, Web one file, every platform: first choice; adapted as shuffle_miniaudio
RtAudio · PortAudio MIT-style · MIT desktop mature, ASIO/JACK support
Oboe Apache-2.0 Android lowest latency on Android
SDL3 audio zlib all if SDL3 is also the windowing layer
MIDI 1.0 / 2.0 (MidiOutputPort, MidiInputSource) libremidi BSD-2 all incl. Web MIDI MIDI 2.0/UMP support; adapted as shuffle_libremidi
RtMidi MIT-style desktop
Windows MIDI Services MIT Windows OS MIDI 2.0 stack

No permissively licensed engine matches Tracktion’s scope, so the plan is an engine of our own (see below) assembled from permissive building blocks:

Capability (engine ports) Permissive candidates Licence Notes
audio file reading/writing (AudioFileProbe, sessions) containers in core/formats; codecs in shuffle_codecs: libFLAC, libvorbis, libopus, dr_mp3 BSD-3 · BSD-3 · BSD-3 · Unlicense/MIT-0 done for FLAC, Vorbis, Opus and MP3 reading (adapters/codecs); AAC and ALAC through platform codecs
sample-rate conversion (ASRC, file rates) r8brain-free-src · libsamplerate · speexdsp MIT · BSD-2 · BSD-3
time-stretch and pitch shift Signalsmith Stretch MIT replaces LGPL/GPL options
FFT and DSP helpers pffft · KissFFT · choc BSD-style · BSD-3 · ISC
plugin hosting (PluginHost) CLAP · VST3 SDK (3.8+) · LV2 · Audio Unit (OS) MIT · MIT · ISC · OS all four formats are now permissive
audio graph reference LabSound BSD-2 ideas for a node graph, if needed
tempo sync with other apps Ableton Link GPL-2.0+ / commercial not permissive: an optional adapter behind TempoSync, off by default, for applications released under a compatible GPL licence; closed-source products need Ableton’s Link licence
Capability Permissive candidates Licence Notes
GPU-accelerated widgets for audio apps Visage MIT built for audio plugin and app UIs
2D vector rendering Skia · ThorVG · NanoVG BSD-3 · MIT · zlib ThorVG is small enough for mobile
windows, input, graphics context SDL3 · GLFW zlib · zlib
mobile and desktop UI over the cores (FFI) Flutter BSD-3 strong on iOS/Android; talks to cores through a C API
browser UI WebAssembly cores + any web framework — decision 0013
developer and diagnostic tools Dear ImGui MIT not for stage UI
Capability (ports) Permissive candidates Licence Notes
video decoding (VideoDecoder) platform decoders (Media Foundation, AVFoundation, MediaCodec) · dav1d (AV1) · libvpx (VP8/VP9) OS · BSD-2 · BSD-3 avoids FFmpeg’s LGPL/GPL and patent pools on mobile
PDF pages (DocumentRenderer) PDFium BSD-3 / Apache-2.0 MuPDF is AGPL: avoid
local language models (LanguageModel) llama.cpp / ggml · ONNX Runtime MIT · MIT
AI audio tasks: speech synthesis, speech recognition with voice-activity detection, stem separation, audio to MIDI (SpeechSource, SpeechToText, StemSeparator, AudioToMidi) audio.cpp (ggml; in use: shuffle_ai_audiocpp in shuffle_ai) · whisper.cpp Apache-2.0 · MIT desktop only, off by default; model weights carry their own licences, filtered by the licence policy (specs/ai_tasks.md)
scripting Lua · QuickJS MIT · MIT
Python bindings nanobind BSD-3 in shuffle_python

Avoid, or keep optional and out of open-source builds

Section titled “Avoid, or keep optional and out of open-source builds”
Library Licence Why
FFmpeg LGPL/GPL static linking on iOS conflicts with LGPL; codec patents
libsndfile, SoundTouch, liblo LGPL same static-linking issue on mobile
Rubber Band GPL / commercial use Signalsmith Stretch
Qt LGPL/GPL / commercial licensing weight for apps
MuPDF, Cmajor, KFR AGPL or GPL / commercial would impose copyleft on every application
NDI SDK proprietary optional adapter only
Ableton Link GPL-2.0+ / commercial optional adapter only

The combination the framework can converge on when it stands alone:

platform C++20 std + small OS layer (or libuv), Asio, mjansson/mdns
audio/MIDI miniaudio (+ Oboe on Android), libremidi
engine own engine on dr_libs, r8brain, Signalsmith Stretch, CLAP/VST3/LV2/AU hosting
UI Visage or ThorVG/Skia on SDL3; Flutter or the browser for mobile/remote
media platform video decoders, dav1d, PDFium
AI llama.cpp, whisper.cpp

Every piece covers Windows, macOS, Linux, iOS and Android (and the browser for most), and none restricts how applications are licensed.

The long-term goal is a framework with no third-party engine or toolkit. The path is the same procedure with “new backend” being shuffle_engine’s own adapters:

  1. platform ports on the C++ standard library plus thin OS layers (done: shuffle_platform; FrameClock comes with the UI backend of step 4);
  2. audio and MIDI ports on miniaudio and libremidi (done: shuffle_miniaudio and shuffle_libremidi; virtual MIDI ports still to write);
  3. an engine of our own behind the engine ports, grown capability by capability from the checklist above, on the permissive building blocks (plugin hosting done: shuffle_plugin_host, CLAP, VST3, LV2 and AU; editors still to host);
  4. widgets on Visage or ThorVG/Skia (or Flutter/browser front ends) behind the same view models.

Each step can ship independently, because the contract tests define “done” for every port.