Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
Sound Byte Libs Architecture

Template-based C++ firmware for ARM Cortex-M processors

Application structure

We build with a painter's workshop as our guiding abstraction. In this concept, the hardware is the canvas, Sound Byte Libs is the toolkit (paint, brushes, palette knives, etc), and the application is the artwork.

Two-Stack Architecture

Sound Byte Libs organizes code into two orthogonal stacks — Audio and I/O — plus shared infrastructure. The stacks are independent: audio processing doesn't depend on hardware I/O, and hardware components don't depend on DSP. Applications compose from both.

┌─────────────────────────────────┐
│ Application │
└──────┬──────────────┬────────────┘
│ │
┌──────────▼──────┐ ┌────▼────────────┐
│ Audio Stack │ │ I/O Stack │
│ │ │ │
│ Widgets │ │ Components │
│ ↑ │ │ ↑ │
│ Signal │ │ HAL + Protocols │
│ ↑ │ │ ↑ │
│ Atoms │ │ Drivers │
└─────────────────┘ └──────────────────┘

Both stacks share Infrastructure (primitives, logging, profiling, generated config).

Audio Stack

The DSP processing chain — from individual signal primitives to complete audio modules.

Atoms (<tt>sbl::dsp::</tt>)

Location: src/sbl/dsp/

Single-concern DSP primitives. Each atom manipulates one property of sound without conflating it with others (see design-philosophy.md).

Signal (<tt>sbl::signal::</tt>)

Location: src/sbl/signal/

Composed building blocks for signal chains. Signal-level components eliminate boilerplate that every instrument would otherwise duplicate, without dictating signal flow.

  • signal/frame.hpp - Frame<N>: Stereo buffer management (begin/end/scale/apply/mix/tap/clear)
  • signal/exp_mod.hpp - exp_mod(): Exponential modulation converter (the "exponential converter IC" of the digital world)

Design principle: Signals follow [-1.0, 1.0]. Configuration uses engineering units. The exponential converter bridges the gap at the modulation application point — exactly as in analog hardware.

Widgets (<tt>sbl::widgets::</tt>)

Location: src/sbl/widgets/

User-facing audio components — a "module on a panel" abstraction with meaningful parameter names and musical behavior.

  • widgets/source/ - WavetableOsc, ScanningOsc (multi-table crossfade morph), PolyBlepOsc (band-limited saw/square/triangle/pulse), SuperSawOsc (3-osc stereo detune), Noise (Galois LFSR white + pink)
  • widgets/proc/ - SVF (ZDF topology, unconditionally stable, LP/HP/BP/Notch + process_modulated()), VCA (amplitude modulation), Delay (stereo ping-pong, OnePole tone), PlateReverb (Griesinger-inspired, OnePole damping)
  • widgets/mod/ - Envelope (ADSR/AR/AHR/AD factories, curve engines), LFO (wavetable, normalized ±1.0 output)

I/O Stack

Hardware interface chain — from MCU registers to application-level controls.

HAL (<tt>sbl::hal::</tt>, <tt>sbl::driver::</tt>)

Location: src/sbl/hal/, drivers in sbl-hardware

Hardware abstraction templates and convenience functions. HAL templates resolve to platform-specific driver implementations at build time.

  • hal/gpio/ - GPIO types (PinMode)
  • hal/adc/ - ADC convenience (read, start_scan, stop_scan)
  • hal/audio/ - Audio output convenience (start, SFINAE validation)
  • hal/cv/ - CV input (read, read_dma) and V/Oct tracking (voct.hpp)
  • hal/button/ - Button HAL convenience (sbl::button::)
  • hal/encoder/ - Encoder HAL convenience (sbl::encoder::)
  • hal/led/ - RGB LED HAL convenience (sbl::led::)
  • hal/pot/ - Potentiometer HAL convenience (sbl::pot::)
  • hal/uart/ - UART utilities
  • hal/timing/ - Timing utilities
  • hal/memory/ - Memory barriers, alignment
  • hal/interrupts/ - Interrupt handling

Components (<tt>sbl::components::</tt>)

Location: src/sbl/components/

Hardware I/O components with signal conditioning:

  • components/cv/ - CvInput (EWMA smoothing, scaling), GateInput (Schmitt trigger), VoctInput (linear calibration)
  • components/control/ - Button (debounce, edge detection), Encoder (quadrature decode), Pot (EWMA + deadband + pickup mode)
  • components/display/ - RgbLed (Color enum, uint8_t duty)

Protocols (<tt>sbl::midi::</tt>, <tt>sbl::usb::</tt>)

Location: src/sbl/midi/, src/sbl/usb/

Communication protocol handlers:

  • midi/ - MIDI parser (running status, SysEx, channel filtering), MIDI input polling
  • usb/ - USB CDC (virtual serial port), USB MIDI (composite descriptor, bidirectional)

Infrastructure (Cross-cutting)

Shared by both stacks:

  • Primitives (src/sbl/primitives/): Data structures (ring buffers, fixed arrays, integer math, lightweight printf)
  • Patterns (src/sbl/patterns/): Synchronization patterns (timing, critical sections)
  • Common (src/sbl/common/): Shared constants, sensible defaults
  • Logging (src/sbl/log/): Lightweight, zero-overhead logging with compile-time level filtering
  • Profiling (src/sbl/profiling/): DWT cycle counter, AudioBudget tracker (EWMA avg/peak/min/overruns), compile-time gated behind SBL_PROFILING_ENABLED
  • Safety (src/sbl/assert.hpp, src/sbl/fault.hpp): SBL_ASSERT()/SBL_PANIC() with HardFault register dump over polling UART
  • Validation (src/sbl/validation/): Compile-time SFINAE validation and error messaging
  • Generated config (sbl::hw::): Build-time generated hardware configuration from sloth

Template-based HAL

When you build, templates resolve to concrete platform types. A GPIO template becomes STM32 HAL calls, RP2040 SDK calls, or SAMD peripheral access - whatever your specific ARM Cortex-M platform provides.

Minimal runtime dispatch, optimized for ARM Cortex-M with direct hardware access wrapped in clean APIs.

Platform Neutrality

The core library contains zero platform-specific code. No #ifdef blocks, no hardcoded platform names, no conditional compilation.

MCU driver implementations are maintained separately in the sbl-hardware repository. The build system discovers them via sloth resolution from sbl.json.

This approach provides architectural integrity. Every MCU is equal - there are no defaults or preferred platforms baked into the core library.

Configuration System

Applications define hardware through sbl.json, which points to a mainboard or module manifest. The sloth tool resolves the manifest chain and generates type-safe headers in sbl/hw/config/ (GPIO handles, ADC handles, UART handles, Board abstraction, MCU metadata) with compile-time constants.

See namespaces.md for the namespace structure.

Audio Data Format

**float [-1.0, 1.0] is the canonical audio type.** All widgets, atoms, lookup tables, and LUTE-generated data use single-precision float. This is the natural format for Cortex-M7 (hardware FPU) and eliminates redundant type conversions between processing stages.

The only integer audio exists at DMA boundaries, where convert.hpp provides:

  • interleave_from_float(L, R, tx, frames) — NaN guard, clamp ±1.0, scale to 24-bit int32, interleave stereo
  • deinterleave_to_float(rx, L, R, frames) — deinterleave stereo int32 to float

The DMA boundary is the audio safety net. interleave_from_float() implements defense-in-depth: NaN values (from uninitialized buffers, division by zero, or corrupted filter state) are replaced with silence before the clamp, preventing undefined behavior from static_cast<int32_t>(NaN * SCALE). A profiling-gated counter tracks NaN occurrences for diagnostics.

All internal processing between these boundaries is float.

Modulation Convention

Signals follow [-1.0, 1.0]. Configuration uses engineering units. Audio buffers and modulation signals are normalized. Widget setters take Hz, ms, MIDI note numbers — human-readable units. The exponential converter (sbl::signal::exp_mod()) bridges the gap at the modulation application point, exactly as in analog hardware.

For frequency parameters (filter cutoff, oscillator pitch, delay time), use exponential modulation:

// LFO outputs normalized signal, depth is in octaves
lfo.process(lfo_buf, n);
filter.process_modulated(buf, n, lfo_buf, cutoff_hz, depth_octaves);

For amplitude parameters (volume, mix, feedback), use linear modulation (multiply by signal directly).

Memory Management

Static allocation only.

Platform Support

MCU drivers are maintained in sbl-hardware and implement the HAL interfaces. Exclusively supports 32-bit ARM Cortex-M processors.

Hardware Manifest System

Hardware is described using JSON manifests that define connections and generate type-safe handles at build time. The manifest system supports:

  • Mainboards - Primary boards with MCU running SBL (e.g., Raspberry Pi Pico, STM32 dev boards)
  • Modules - Expansion boards or ICs that attach to mainboards (e.g., LED panels, DAC chips)

Mainboards expose pins and buses; modules claim them. The resolver walks the attaches_to chain to map logical pin names to physical MCU registers with conflict detection.

See the Hardware Schema Documentation for schema definitions and diagrams.

Platform Contract Validation

Sound Byte Libs enforces platform implementations through compile-time SFINAE validation. Every platform must satisfy interface contracts with clear error messages guiding implementation.

Project Structure

Typical audio application setup:

your-app-name/
├── hardware/ # Schematics, PCB
└── firmware/
├── main.cpp # Application entry point
├── sbl.json # Hardware target configuration
└── CMakeLists.txt # Build configuration

Environment variables point to library locations:

  • SBL_PATH - Path to sound-byte-libs
  • SBL_HW_PATH - Path to sbl-hardware

Related Documentation