← Portfolio

Technical Documentation

EduToneBox

Real-time sound exploration instrument — C++17 DSP, Qt6/QML, CoreAudio, and Raspberry Pi

C++17 Qt6/QML CoreAudio ALSA FFTW3 OSC/UDP CMake In development


EduToneBox grid view EduToneBox waveform view

Origin

I've been teaching synthesis for years. For most of that time, I've wanted a tool that lets students actually see what's happening — not just hear it, but watch a waveform change shape in real time, watch an envelope rise and fall, watch a filter sweep across a spectrum. I finally stopped waiting for someone to build it. EduToneBox is for my students, but honestly it's also for me. I'm obsessed with looking closely at things — getting microscopic, getting immersed. Synthesis is beautiful when you can see it. That's what this is trying to show. And while it starts in the classroom, I expect it'll travel — the kind of tool that finds its way into studios, onto workbenches, and into the hands of people who weren't looking for a synthesizer but can't put it down once they find one.


Overview

EduToneBox is a physical, real-time educational synthesizer prototype built around a C++17 DSP engine, a Qt6/QML control surface, and OSC-over-UDP communication, deployable on both macOS and Raspberry Pi. The goal is to make sound visible and playable without prescribing a curriculum. Its oscillator, 32-partial additive engine, and self-sampler are peer sources feeding the same envelope, multi-algorithm filter bank, effects, and calibrated post-output visualization path.

The project is structured as a portfolio-grade systems project rather than a single demo script. It includes platform-specific audio backends, reusable DSP modules, a desktop and Raspberry Pi UI, automated tests, and deployment scripts for both macOS and Raspberry Pi. A shared parameter registry, atomic preset snapshots, bounded callback-to-worker telemetry, and defensive recursive-DSP tests make the instrument a systems project rather than a single synthesis demo.

This project is actively in development. Current local verification: 85 of 85 CTest tests pass. The macOS packaging path produces a dependency-closed, ad-hoc-signed application bundle, and CI covers macOS plus a Pi-representative ARM64 Linux build. Native listening/calibration and physical Raspberry Pi audio/display acceptance remain outstanding and are not claimed here.


Architecture

The audio engine and UI are deliberately separate processes, connected by OSC over UDP on localhost. This keeps UI crashes and graphics performance isolated from audio generation, makes the DSP modules independently testable, and leaves the door open for future hardware control sources.

+---------------------------------------------+
| Qt6/QML UI Process                          |
| Controls, visualizations, theme, pages      |
+----------------------+----------------------+
                       | OSC over UDP
                       | localhost:4559
+----------------------v----------------------+
| C++ Audio Engine Process                    |
| OSC server, DSP graph, audio callback       |
+----------------------+----------------------+
                       |
+----------------------v----------------------+
| Platform Audio Backend                      |
| CoreAudio on macOS · ALSA on Linux/Pi       |
+---------------------------------------------+

Signal flow

Oscillator | AdditiveEngine | Sampler
  → ADSR envelope
  → FilterBank (optional)
  → EffectsProcessor (optional)
  → output safety handling
  → CoreAudio or ALSA device

Control and visualization flow

QML control change
  → OSCClient
  → UDP OSC message
  → OSCServer handler
  → AudioEngine parameter update

Post-output audio block
  → bounded SPSC telemetry queue
  → non-real-time visualization worker
  → atomic waveform, spectrum, and measurement frames
  → UI oscilloscope and spectrum analyzer

Audio Engine Capabilities

Real-time mono synthesis at 44.1 kHz with a default 512-sample buffer.

Oscillator

Peer sound sources

Modulation

Filter bank

Effects processor

Spectrum analysis

State and real-time architecture

Platform delivery


User Interface

The Qt6/QML application presents a multi-page synthesizer interface intentionally designed as a fixed-aspect hardware-style instrument panel rather than a generic desktop form. Large controls, fixed 4:3 scaling, and pages organized by synthesis concept make it suitable for teaching rather than just operation.


Tech Stack

Language C++17
Build CMake 3.20+
UI Qt6, QML, Qt Quick Controls
Tests Catch2, CTest
macOS audio CoreAudio, AudioToolbox, Accelerate/vDSP
Linux/Pi audio ALSA
Linux/Pi FFT FFTW3
IPC OSC-style UDP messages
DSP support Local sndkit-derived modules for filters, effects, and helpers

Repository Layout

AUDIO_BOX/
├── CMakeLists.txt
├── README.md
├── benchmarks/       Representative offline DSP throughput benchmark
├── commands/
│   ├── mac/          Clickable macOS command wrappers
│   └── pi/           Raspberry Pi desktop launchers
├── deploy/
│   ├── mac/          macOS build, run, sync, and app packaging
│   └── pi/           Pi build/run, audio test, and systemd kiosk services
├── docs/             Planning and deployment docs
├── src/
│   ├── audio_engine/
│   │   ├── AudioEngine.*     Main synthesis engine and audio backend
│   │   ├── Oscillator.*      Waveform generation and PolyBLEP
│   │   ├── AdditiveEngine.*  32-partial additive source
│   │   ├── Sampler.*         Preallocated volatile self-sampler
│   │   ├── LFO.*             Modulation source
│   │   ├── ADSR.*            Envelope generator
│   │   ├── SVFilter.*        Stable topology-preserving filter
│   │   ├── FilterBank.*      Multiple algorithms behind one interface
│   │   ├── EffectsProcessor.* Reverb, delay, modulation, distortion
│   │   ├── AudioTelemetry.h  Bounded callback-to-worker queue
│   │   ├── ScopeProcessor.*  Triggered oscilloscope framing
│   │   ├── PresetStore.*     Versioned crash-safe snapshots
│   │   ├── OSCServer.*       UDP OSC control server
│   │   └── sndkit/           Embedded DSP helper modules
│   ├── common/
│   │   └── ParameterRegistry.h Shared engine/UI control contract
│   └── ui_app/
│       ├── OSCClient.*       Qt UDP OSC client
│       ├── main.cpp
│       └── qml/              Instrument UI pages and controls
└── tests/
    └── test_*.cpp             DSP, OSC, presets, telemetry, scope, and FFT tests

OSC Control API

The OSC control API is fully documented. The audio engine listens on localhost:4559 . All control messages follow the OSC address pattern below.

Oscillator

Address Value Meaning
/osc/frequency float Frequency in Hz
/osc/amplitude float Output amplitude, 0.0 to 1.0
/osc/waveform int as float 0 Sine · 1 Triangle · 2 Saw · 3 Square · 4 Pulse · 5 Custom
/osc/pulsewidth float Pulse duty cycle, clamped to 0.01–0.99

LFO

Address Value Meaning
/osc/lfo/frequency float LFO rate
/osc/lfo/amplitude float LFO depth
/osc/lfo/waveform int as float 0 Sine · 1 Triangle · 2 Saw · 3 Square
/osc/lfo/range int as float 0 Bipolar · 1 Unipolar
/osc/lfo/target int as float 0 None · 1 Frequency · 2 Amplitude · 3 Filter

ADSR

Address Value Meaning
/osc/adsr/attack float Attack time in ms
/osc/adsr/decay float Decay time in ms
/osc/adsr/sustain float Sustain level, 0.0 to 1.0
/osc/adsr/release float Release time in ms
/osc/adsr/curvetype int as float 0 Linear · 1 Exponential
/osc/adsr/noteon — Trigger envelope
/osc/adsr/noteoff — Release envelope

Filter

Address Value Meaning
/osc/filter/cutoff float Cutoff frequency in Hz
/osc/filter/resonance float Resonance/Q
/osc/filter/type int as float 0 LowPass · 1 HighPass · 2 BandPass · 3 Notch
/osc/filter/enabled float 0 off · 1 on
/osc/filter/algorithm int as float 0 StateVariable · 1 Butterworth · 2 VirtualAnalog · 3 ModalResonator

Effects

Address Value Meaning
/osc/effect/type int as float 0 None · 1 Reverb · 2 Delay · 3 Chorus · 4 Flanger · 5 Distortion · 6 BitCrusher
/osc/effect/mix float Dry/wet mix
/osc/effect/param1 float Effect-specific normalized parameter
/osc/effect/param2 float Effect-specific normalized parameter

Visualization feedback

Address Values Meaning
/visualization/scope/sync 0 or 1 Select free-run or rising-edge automatic synchronization
/visualization/scope/window 1–100 ms Set the oscilloscope time window
/waveform/frame sample rate, samples, window, trigger state Atomic post-output oscilloscope frame
/spectrum/frame sample rate and calibrated band triples Atomic logarithmic spectrum frame
/measurement/signal peak Hz, peak/RMS dBFS, amplitude Live post-output signal measurements

Peer sources

Address Value Meaning
/osc/source 0–2 Select oscillator, additive engine, or sampler
/osc/additive/reconstruction 0–7 Custom, all, odd, even, sine, saw, square, or triangle
/osc/additive/harmonic/01..32 0.0–1.0 Individual harmonic levels
/sampler/record 0 or 1 Start or finish recording the final limited output
/sampler/clear · /sampler/restart trigger Clear the volatile take or restart playback

State and presets

Address Value Meaning
/state/request trigger Request publication of every registered parameter
/state/parameter parameter index, value Authoritative engine-to-client state
/preset/save · /preset/load slot 1–16 Atomically save or restore a complete versioned control snapshot

Test Coverage

Unit tests are written in Catch2 and run via CTest. Current local status: 85 of 85 tests pass.

CI builds and tests on macOS and an ARM64 Linux runner, then runs a representative offline DSP benchmark. Automated pixel-level UI testing and physical audio-device acceptance are still separate tasks.


Roadmap

Near-term engineering

Product and hardware


Known Limitations


Design Notes

The split-process architecture keeps UI crashes and graphics performance separate from audio generation. OSC is simple enough to inspect manually, portable across languages, and a natural fit for future hardware control sources.

DSP modules are kept as plain C++ classes with explicit state and parameter setters — straightforward to unit test, reuse, and eventually move into a plugin or embedded target. Recursive filters and effects are tested for bounded output and actual decay rather than only for finite numbers, because a final limiter can hide a dangerous internal limit cycle.

The UI is intentionally instrument-like: large controls, fixed 4:3 scaling, shallow interactions, and visual feedback designed for close listening rather than decoration. EduToneBox supplies raw capabilities; it does not embed lessons or dictate curriculum, and optional MIDI or external input should not become requirements for using the instrument.

The interesting aspect of this project is not only that it produces sound — it is that the codebase is organized to grow from a desktop prototype into a physical educational instrument.