|
Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
|
Template-based C++ firmware for ARM Cortex-M processors
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.
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.
Both stacks share Infrastructure (primitives, logging, profiling, generated config).
The DSP processing chain — from individual signal primitives to complete audio modules.
Location: src/sbl/dsp/
Single-concern DSP primitives. Each atom manipulates one property of sound without conflating it with others (see design-philosophy.md).
dsp/phase.hpp - Phase accumulator (32-bit natural wrapping)dsp/wavetable.hpp - WavetableReader (linear/cubic interpolation)dsp/one_pole.hpp - One-pole IIR filter (LP/HP smoother)dsp/slew.hpp - Slew limiter (independent rise/fall)dsp/delay_line.hpp - Delay line (float, fractional interpolated reads)dsp/waveshaper.hpp - Table-driven transfer functiondsp/warp.hpp - Curve warp engines (ExpCurveWarp, RationalWarp, lut_curve_warp)dsp/segment.hpp - SegmentGenerator for multi-stage envelopesdsp/lut.hpp - Lookup table functions (linear, cubic, crossfade)dsp/pitch.hpp - Pitch utilities (semitones_to_ratio, note_to_frequency)dsp/fast_math.hpp - Fast approximations (fast_exp2f, fast_tan_pif, fast_sinf)dsp/convert.hpp - Float↔int32 conversion at DMA boundaries (interleave/deinterleave, NaN guard)dsp/stereo.hpp - Constant-power panning and stereo mix (float)dsp/envelope_follower.hpp - Envelope follower (rectifier + asymmetric smoothing)dsp/fixed.hpp - Sample type alias (Sample = int32_t) for control-domain codedsp/tables/ - LUTE-generated lookup tables (float32 waveforms, pitch)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.
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)Hardware interface chain — from MCU registers to application-level controls.
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 utilitieshal/timing/ - Timing utilitieshal/memory/ - Memory barriers, alignmenthal/interrupts/ - Interrupt handlingLocation: 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)Location: src/sbl/midi/, src/sbl/usb/
Communication protocol handlers:
midi/ - MIDI parser (running status, SysEx, channel filtering), MIDI input pollingusb/ - USB CDC (virtual serial port), USB MIDI (composite descriptor, bidirectional)Shared by both stacks:
src/sbl/primitives/): Data structures (ring buffers, fixed arrays, integer math, lightweight printf)src/sbl/patterns/): Synchronization patterns (timing, critical sections)src/sbl/common/): Shared constants, sensible defaultssrc/sbl/log/): Lightweight, zero-overhead logging with compile-time level filteringsrc/sbl/profiling/): DWT cycle counter, AudioBudget tracker (EWMA avg/peak/min/overruns), compile-time gated behind SBL_PROFILING_ENABLEDsrc/sbl/assert.hpp, src/sbl/fault.hpp): SBL_ASSERT()/SBL_PANIC() with HardFault register dump over polling UARTsrc/sbl/validation/): Compile-time SFINAE validation and error messagingsbl::hw::): Build-time generated hardware configuration from slothWhen 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.
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.
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.
**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 stereodeinterleave_to_float(rx, L, R, frames) — deinterleave stereo int32 to floatThe 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.
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:
For amplitude parameters (volume, mix, feedback), use linear modulation (multiply by signal directly).
Static allocation only.
MCU drivers are maintained in sbl-hardware and implement the HAL interfaces. Exclusively supports 32-bit ARM Cortex-M processors.
Hardware is described using JSON manifests that define connections and generate type-safe handles at build time. The manifest system supports:
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.
Sound Byte Libs enforces platform implementations through compile-time SFINAE validation. Every platform must satisfy interface contracts with clear error messages guiding implementation.
Typical audio application setup:
Environment variables point to library locations:
SBL_PATH - Path to sound-byte-libsSBL_HW_PATH - Path to sbl-hardware