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.
What changes and what does not
Section titled “What changes and what does not”| 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.
The procedure
Section titled “The procedure”flowchart TD
a["1. List the ports<br/>(docs/ports.md)"] --> b["2. Create adapters/<new>/<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/<new toolkit>/<br/>draw the same view models"]
- 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.
- 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. - 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. - 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.
- Golden flows. The end-to-end journeys (“prepare a show”, “run a show”) run against the new backend unchanged.
- UI. If the GUI toolkit changes too, write
ui/<toolkit>/widgets that draw the existing view models. All geometry and interaction logic is already incore/ui_modeland does not need rewriting.
Replacing JUCE: checklist
Section titled “Replacing JUCE: checklist”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 capability checklist
Section titled “Tracktion capability checklist”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.
Platform, threads, events, networking
Section titled “Platform, threads, events, networking”| 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 |
Audio and MIDI devices
Section titled “Audio and MIDI devices”| 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 |
Audio engine pieces (replacing Tracktion)
Section titled “Audio engine pieces (replacing Tracktion)”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 |
User interface (replacing JUCE GUI)
Section titled “User interface (replacing JUCE GUI)”| 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 |
Media, documents, AI, scripting
Section titled “Media, documents, AI, scripting”| 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 |
A fully permissive stack
Section titled “A fully permissive stack”The combination the framework can converge on when it stands alone:
platform C++20 std + small OS layer (or libuv), Asio, mjansson/mdnsaudio/MIDI miniaudio (+ Oboe on Android), libremidiengine own engine on dr_libs, r8brain, Signalsmith Stretch, CLAP/VST3/LV2/AU hostingUI Visage or ThorVG/Skia on SDL3; Flutter or the browser for mobile/remotemedia platform video decoders, dav1d, PDFiumAI llama.cpp, whisper.cppEvery piece covers Windows, macOS, Linux, iOS and Android (and the browser for most), and none restricts how applications are licensed.
Going fully independent
Section titled “Going fully independent”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:
- platform ports on the C++ standard library plus thin OS layers (done:
shuffle_platform;FrameClockcomes with the UI backend of step 4); - audio and MIDI ports on miniaudio and libremidi (done:
shuffle_miniaudioandshuffle_libremidi; virtual MIDI ports still to write); - 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); - 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.
© 2026 Umberto Sorbo. Il software è distribuito con la Apache License, Version 2.0, e fornito “così com’è”, senza garanzie o condizioni di alcun tipo; leggi l’esclusione di responsabilità.
SHUFFLE Framework e il logo di SHUFFLE Framework sono marchi di Umberto Sorbo; la licenza non concede alcun diritto su di essi (marchi). Gli altri nomi appartengono ai rispettivi titolari. Caratteri: IBM Plex Sans e JetBrains Mono, SIL Open Font License 1.1.
Contatti: shuffleframework@gmail.com · Instagram @shuffleframework. Problemi di sicurezza: scrivi allo stesso indirizzo con “Security” nell’oggetto, non in una issue pubblica.