Skip to content

02 Architecture & Development Guide

apiel edited this page Aug 3, 2026 · 6 revisions

zicBox is a modular C++ framework for building music applications, grooveboxes, synths, and custom audio hardware across desktop Linux and embedded targets (e.g., Raspberry Pi, STM32H7, ESP32).

1. Core System Architecture

Applications in zicBox follow a 3-tier decoupled architecture designed to ensure real-time audio stability, portable UI rendering, and hardware abstraction.

graph TD
    Main["Main Entry (zic.cpp)"] --> SFML["Desktop Runtime (runtimeDesktopSFML.h)"]
    Main --> HW["Hardware Runtime (runtimeHardware.h)"]
    
    SFML --> UI["Generic UI Layer (ui.h, draw.h)"]
    HW --> UI
    
    UI <-->|"Shared State & Mutexes"| Audio["Audio Thread (audioWorker.h)\n[SCHED_FIFO Priority 30]"]
    
    Audio --> Pool["TrackRenderPool (Multi-core Render)"]
    Audio --> Engines["Audio Engines (audio/engines/)"]
    Audio --> Seq["Audio Sequencer (audio/sequencer/)"]
    Audio --> ALSA["ALSA Audio Output Stream"]
Loading

The 3 Architecture Tiers

  1. Target Hardware Abstraction Layer (HAL) (runtime*.h): Manages platform-specific inputs (GPIO keys, rotary encoders, keyboard/mouse) and output displays (SFML desktop window, ST7789 SPI, framebuffer).
  2. Generic Application & UI Layer (ui.h, draw/): Handles application state, navigation, visual layout, and component interaction without depending on platform-specific UI frameworks.
  3. High-Priority Audio Engine & Sequencer (audioWorker.h, TrackRenderPool.h, audio/): Performs real-time audio synthesis, pattern sequencing, and DSP processing on dedicated high-priority threads.

2. Firmware & Application Architecture

Target Hardware Abstraction (runtime*.h)

Applications isolate hardware-specific logic behind runtime headers, selected at compile time via preprocessor defines:

  • Desktop Runtime (runtimeDesktopSFML.h):
    • Map PC keys to hardware button definitions (KEY_F1..KEY_F5, KEY_1..KEY_8).
    • Includes a Headless Screenshot Pipeline (ZIC_SCREENSHOT=<path_prefix>): when set, the application renders every view sequentially, exports PNG screenshots, and terminates cleanly for automated documentation.
  • Embedded Hardware Runtime (runtimeHardware.h):
    • GPIO Key Matrix: Reads physical push buttons using GpioKey.
    • Rotary Encoders: Reads quadrature encoders via GpioEncoder with speed scaling (encGetScaledDirection()).
    • SPI Display: Renders pixel buffers directly to physical displays (ST7789 via DrawToST7789).
    • Dynamic Mapping: Reads GPIO pin configurations from config.json (or ZIC_CONFIG_PATH), falling back to built-in hardware defaults.
    • Lock-Free Event Queueing: Interrupt-driven GPIO events are queued safely and flushed on the main loop iteration to eliminate race conditions.

Real-Time Thread Isolation

UI and Audio processing must never share a single thread loop.

Thread Priority Role
Main Thread (zicBox_UI) SCHED_OTHER (Normal) Hardware/desktop event loop, UI component state updates, display rendering.
Audio Thread (zicBox_Audio) SCHED_FIFO (Priority 30) Realtime clock timing, step clock processing, multi-track DSP synth rendering, master FX, ALSA buffer writes.

Multi-Core Audio Rendering (TrackRenderPool)

To fully leverage multi-core CPUs without starving UI rendering or the OS scheduler:

  • Core Reservation: Allocates worker threads reserving cores for UI and OS tasks (workers = hardware_concurrency() - 2).
  • 2-Phase Pipeline:
    1. Phase 1 (Serial Tick): Locks audioMutex, advances step clocks, and collects per-track frame events (noteOn, noteOff, loadClip).
    2. Phase 2 (Parallel Render): Renders N tracks concurrently across worker threads into thread-local mix buffers, sums partial mixes, applies master FX (Scatter, Filter, Compressor, Tape saturation, Soft clip), and outputs 16-bit PCM to ALSA.

3. Audio Engine Framework

All synth, drum, and sample engines reside in audio/engines/.

Static Polymorphism via CRTP

Engines inherit from EngineBase<Derived> using the Curiously Recurring Template Pattern (CRTP):

#pragma once
#include "audio/engines/EngineBase.h"

class MyEngine : public EngineBase<MyEngine> {
public:
    // CRITICAL: Size N must match the exact number of addParam calls!
    Param params[4];

    Param& pitch   = addParam({ .key = "pitch", .label = "Pitch", .value = 60.0f, .max = 127.0f });
    Param& cutoff  = addParam({ .key = "cutoff", .label = "Cutoff", .value = 0.5f, .max = 1.0f });
    Param& reso    = addParam({ .key = "reso",   .label = "Reso",   .value = 0.1f, .max = 0.95f });
    Param& volume  = addParam({ .key = "vol",    .label = "Volume", .value = 0.8f, .max = 1.0f });

    MyEngine(const float sampleRate = 44100.0f)
        : EngineBase(Synth, "MyEngine", params)
        , sampleRate(sampleRate) {}

    // Required audio render callback
    float sampleImpl() { return 0.0f; }

    // Optional callbacks
    void noteOnImpl(uint8_t note, float velocity) { pitch.value = note; }
    void noteOffImpl(uint8_t note) {}

private:
    float sampleRate;
};

Why CRTP? On embedded microcontrollers (such as STM32H7), virtual function calls inside high-frequency sample loops incur vtable lookup overhead and prevent compiler inlining. CRTP resolves implementation calls at compile time, eliminating vtable overhead.

Engine Parameter Rules

  • Declare a fixed-size Param params[N] array where N matches the exact number of addParam registrations.
  • Safety Rule: Calling addParam() more times than the size of params causes out-of-bounds memory access and results in a runtime segmentation fault.

4. Audio Sequencer & Data Layer

All reusable sequence and persistence components reside in audio/sequencer/:

audio/sequencer/
├── Step.h           # Step data model & SEQ_STEPS override guard
├── Clip.h           # Clip container & ParamValue data structures
├── Generator.h      # Algorithmic pattern generators (Kick, Bass, Drum, Perc, Snare, Hat, Clap)
├── SequenceUtils.h  # Sequence manipulation (stretch, compress, clear)
├── ClipChain.h      # Clip chain helper utilities (add, remove, clear, toggle)
└── ProjectIO.h      # JSON project & clip persistence (saveClip, loadClip, saveProject, loadProject)

Core Sequencer Features

  • Configurable Step Resolution: Step.h defines step structure guarded by #ifndef SEQ_STEPS (default: 64 steps), allowing compile-time overrides (-DSEQ_STEPS=32).
  • Clip Container: Clip.h stores track step sequences, active parameter key-value pairs (ParamValue), engine IDs, and note repeat settings.
  • Algorithmic Generators: namespace Generator in Generator.h provides pattern generation algorithms (Kick, Bass, Drum, Perc, Snare, Hat, Clap) with 4-parameter UI knob overloads and 1-parameter default function wrappers.
  • SFINAE JSON Persistence: ProjectIO.h uses C++17 type traits (nlohmann/json.hpp) to serialize and deserialize project and clip files across different Track or Studio data structures without tight coupling.

5. Building Projects with AI Agents (.agents/skills/)

zicBox includes Agent Skills located in .agents/skills/. These skills provide specialized domain context and architectural guidelines for AI coding agents (such as Gemini, Antigravity, Claude, ChatGPT, etc.).

When building, refactoring, or extending a zicBox application using AI agents, the agent reads these skill definitions to strictly adhere to established project standards.

Available Agent Skills

Skill Path Description
audio-engine .agents/skills/audio-engine Rules for creating audio engines (EngineBase CRTP inheritance, parameter array bounds safety, file location, callback casting).
audio-sequencer .agents/skills/audio-sequencer Standards for sequencer steps, clips, pattern generators (namespace Generator), clip chains, and SFINAE JSON persistence.
firmware-architecture .agents/skills/firmware-architecture Application architecture rules (HAL separation, POSIX realtime threads, TrackRenderPool multi-core rendering, screenshot pipeline).

Using AI Agents to Develop on zicBox

AI agents equipped with these skills can autonomously perform complex development tasks:

  1. Creating New Audio Engines: Prompt the agent to generate a new synth or drum engine (e.g., "Create an FM Percussion engine"). The agent uses the audio-engine skill to automatically write a CRTP engine with exact Param params[N] sizing and register it in audio/engines/.
  2. Adding Sequencer Generators & FX: Prompt the agent to add pattern generators or sequence manipulators. The agent uses the audio-sequencer skill to follow namespace Generator design patterns.
  3. Target Porting & Hardware Architecture: Prompt the agent to structure a new firmware target or application view. The agent uses firmware-architecture to enforce HAL decoupling, real-time priority allocation (SCHED_FIFO), and multi-core TrackRenderPool rendering.

6. Canonical Application Directory Layout

When creating a new zicBox application, structure project files according to this standard format:

myApp/
├── zic.cpp                 # Main entry point (ALSA initialization, UI loop, realtime audio thread startup)
├── audioWorker.h           # Audio thread loop, ALSA management, sequencer clock, multi-track rendering
├── runtimeDesktopSFML.h    # Desktop SFML window driver, keyboard/mouse mapping, headless screenshot mode
├── runtimeHardware.h       # Target hardware GPIO keys, rotary encoders, ST7789 display driver, config loader
├── studio.h (or state.h)   # Application state container, track arrays, shared audio mutexes
├── ui.h                    # Main UI coordinator, view state switcher, unified event dispatcher
├── ui*.h                   # Individual modular UI view components (e.g. uiTrack.h, uiSeq.h, uiMenu.h)
└── makefile                # Build configuration defining DRAW_SMFL for desktop or embedded targets

60 Contribute to zicBox

🚧 Help Wanted: Whether you're a coder, designer, maker, or musician, your contributions are more than welcome!

In addition to making your own zicBox build, you can take part in developing and improving the existing ecosystem. Contributions can range from hardware optimizations, firmware enhancements, and UI/UX improvements to documentation and testing. The project thrives on collaboration and shared experimentation.

The best way to get started is to reach out on the Discord thread, let's have a quick chat and figure out how you can jump in. Don't be shy, there are plenty of ways to get involved, no matter your background or experience.

🎨 3D Enclosure Design

The zicBox community has already produced multiple great enclosure designs — mostly 3D-printable and easy to assemble. But innovation doesn’t stop there! We’d love to see experiments with CNC carving, laser-cut acrylics, or even metal housings. If you’re a designer or maker interested in refining the look and feel of Zic Pixel and other builds, your ideas are more than welcome.

🔌 PCB Design Improvements

The current PCB was designed in a self-taught, DIY style. It works well, but the project is ready to evolve into something more modular and flexible.

The next phase focuses on creating a small, reusable core board that includes everything needed to power a custom zicBox build:

  • Integrated audio codec
  • MIDI interface (with optocoupler isolation)
  • Optional battery controller
  • I/O expander for additional inputs and outputs

The goal is to make this board compatible with a range of platforms — from Raspberry Pi models to microcontrollers like the ESP32 — making it easy to prototype and build new instruments or devices.

Although there’s currently no active CM4-based project, exploring a custom CM4 carrier board remains a future goal once the modular base design is solidified.

If you’re experienced in hardware design or want to help refine and expand this next-generation board, your input is highly appreciated.

Skills welcome:

  • PCB design (currently working on EasyEDA)
  • SMD component selection and layout
  • Designing for manufacturing or open-source hardware projects

👨‍💻 Software Development

🧠 C++ Core

zicBox is primarily written in C++. The code is structured around reusable libraries, with plenty of room to expand:

  • Create new audio modules
  • Develop UI components
  • Improve performance or interconnectivity between modules

Beginner to expert, every contribution is welcome, as long you know your way around C++.

Tip

To simplify development, install a cross-compiler toolchain for ARM on your dev machine. In order to do this, let's use zicOs a custom OS create for zicBox, based on buildroot: https://github.com/apiel/zicOs

Install alongside to your zicBox project:

- dev folder
-- zicBox
-- zicOs

🎨 UX & Visual Design

Good design makes a huge difference, not only in how zicBox looks, but in how easy and enjoyable it is to use.

We’re looking for help with:

  • UX Design: Simplifying and improving the user interface while keeping CPU usage minimal. Clean, low-overhead UI/UX patterns are key, thoughtful layout and flow.
  • Visual Design: Logos, icons, UI assets, and visual elements for both the app and the GitHub/docs.
  • Promotional Material: Graphics or layouts for sharing zicBox on social media, in videos, or presentations.

Skills welcome:

  • UX thinking for embedded or constrained environments
  • Design tools like Figma, Sketch, or even pen & paper
  • Visual identity and branding for open source projects

📚 Documentation

Documentation often falls behind as development moves fast. We need help keeping things up-to-date and beginner-friendly.

Great contributions include:

  • Updating documentation with recent changes
  • Writing clear guides, diagrams, and module descriptions
  • Improving the README and setup instructions
  • Helping with onboarding and dev setup notes

🔍 Testing & Presets

Using zicBox and exploring its capabilities is a valuable form of contribution! You can help by testing features, reporting bugs, and creating preset examples or full projects.

Things to do:

  • Create presets or musical setups
  • Try new builds and report bugs or suggestions
  • Share use cases and feedback

🎥 Videos & Demos

zicBox is best understood when seen in action. If you're into video content, your help is gold.

Ideas for video contributions:

  • Walkthroughs and feature overviews
  • Performance demos or live jams
  • Setup tutorials and build guides
  • Showcasing what zicBox can do!

Clone this wiki locally