Files

RPi Audio Mixer — Disk Image Builder (SD / USB / NVMe)

This directory contains the automated build system for creating a ready-to-flash Raspberry Pi 4B disk image with the full audio mixer stack. The same image works for SD cards, USB SSDs, and NVMe HATs — just choose your target.

Quick Start

# Install build dependencies
sudo apt install wget xz-utils dosfstools e2fsprogs qemu-user-static

# Optional: for PREEMPT_RT kernel cross-compilation
sudo apt install gcc-aarch64-linux-gnu

# Build the image
./build/build.sh

# The image lands in:
ls -lh build/out/rpi-audio-mixer-*.img.xz

Build Modes

Command Description Time (approx)
./build/build.sh Full build with PREEMPT_RT kernel 30-45 min
./build/build.sh --skip-kernel Quick build, stock kernel 10-15 min
./build/build.sh --kernel-only Only build RT kernel 10-15 min
./build/build.sh --clean Remove all build artifacts < 1 min
./build/build.sh --target usb Build for USB SSD boot same as default
./build/build.sh --target nvme Build for NVMe HAT (NVMe driver enabled) +1 min

Flashing to SD Card

From Linux

# 1. Decompress the image
xz -d build/out/rpi-audio-mixer-*.img.xz

# 2. Find your SD card device
lsblk
# Look for /dev/sdX or /dev/mmcblkX (e.g., /dev/sdb or /dev/mmcblk0)

# 3. Flash the image (WARNING: this overwrites the target device!)
sudo dd if=build/out/rpi-audio-mixer-*.img of=/dev/sdX bs=4M status=progress conv=fsync

# 4. Sync and eject
sync
sudo eject /dev/sdX

Flashing to USB SSD / NVMe

The same image works for USB SSD and NVMe HAT — it's just flashed to a different device.

USB SSD

# 1. Find your USB SSD device
lsblk  # look for /dev/sda, /dev/sdb, etc.

# 2. Flash the image
sudo dd if=build/out/rpi-audio-mixer-*.img of=/dev/sda bs=4M status=progress conv=fsync

# 3. On first boot, the wizard can configure EEPROM for USB boot
#    Or do it manually:
#    sudo rpi-eeprom-config --edit  →  BOOT_ORDER=0xf41

NVMe HAT

Requires an NVMe HAT (official RPi, Pimoroni, or Geekworm) connected via PCIe.

# 1. Flash to NVMe SSD (connect via USB adapter or directly on Pi)
sudo dd if=build/out/rpi-audio-mixer-*.img of=/dev/nvme0n1 bs=4M status=progress conv=fsync

# 2. Configure EEPROM for NVMe boot:
#    sudo rpi-eeprom-config --edit  →  BOOT_ORDER=0xf614

# 3. Boot order: NVMe → USB → SD → Restart

⚠️ USB performance note: During audio operation, minimize writes to USB SSDs as they share the USB bus with audio interfaces. NVMe HAT is recommended for production use — it uses dedicated PCIe lanes with no USB bus contention.

From macOS

# 1. Decompress
xz -d build/out/rpi-audio-mixer-*.img.xz

# 2. Find the SD card
diskutil list
# Look for /dev/diskX (external, physical)

# 3. Unmount (do NOT eject)
diskutil unmountDisk /dev/diskX

# 4. Flash
sudo dd if=build/out/rpi-audio-mixer-*.img of=/dev/rdiskX bs=4m status=progress

# 5. Eject
diskutil eject /dev/diskX

From Windows

Use Raspberry Pi Imager or balenaEtcher:

  1. Decompress the .img.xz with 7-Zip or WinRAR
  2. Open Raspberry Pi Imager
  3. Choose "Use custom" → select the .img file
  4. Select your SD card and flash

First Boot

  1. Insert the SD card into a Raspberry Pi 4B (4GB+ RAM recommended)
  2. Connect your USB audio interface
  3. (Optional) Connect HDMI touchscreen + Ethernet
  4. Power on

The first-boot setup wizard will appear automatically on the HDMI display (or serial console). It will guide you through:

  • Audio interface detection and selection
  • WiFi configuration
  • Hostname setting
  • API key generation
  • JACK buffer/latency settings

After the wizard completes, the system reboots into normal operation.

Default Access

Service URL / Command Default
Web UI http://pi-mixer.local:8080 API key from wizard
SSH ssh pi@pi-mixer.local Password: raspberry
Touch UI HDMI display (auto-starts)

Important: Change the default password on first login:

passwd

Image Contents

Partition layout:
├── /boot (FAT32, 256MB)
│   ├── kernel8-rt.img        PREEMPT_RT kernel (if built)
│   ├── config.txt             GPU/boot configuration
│   ├── cmdline.txt            Kernel command line
│   └── *.dtb, overlays/       Device Tree Blobs
├── / (ext4, ~4GB used of 6GB)
│   ├── /opt/rpi-mixer/        Mixer source + venv
│   ├── /etc/systemd/system/   Service files
│   └── /home/pi/              pi user home
└── /data (ext4, remaining space)
    ├── sessions/              Saved mixer sessions
    ├── recordings/            Multi-track recordings
    ├── presets/               Plugin presets
    └── logs/                  System/audio logs

Systemd Services

Service Description Auto-start
cpu-performance.service Lock CPU to max frequency Boot
jackd.service JACK2 real-time audio server After first-boot
mixer-api.service REST API + WebSocket + OSC server After first-boot
mixer-ui.service Kivy touchscreen UI After first-boot
mixer-firstboot.service First-boot setup wizard First boot only
# Check service status
systemctl status jackd mixer-api mixer-ui

# View logs
journalctl -u mixer-api -f
journalctl -u jackd -f

# Manual control
sudo systemctl stop mixer-ui mixer-api jackd
sudo systemctl start jackd && sleep 2 && sudo systemctl start mixer-api

CI / Automated Builds

Build without a Raspberry Pi (CI-friendly):

# On any x86_64 Linux host with qemu-user-static
export MIXER_VERSION="$(git describe --tags --always)"
export MIXER_HOSTNAME="ci-mixer"
export MIXER_API_KEY="ci-test-key-12345"

./build/build.sh --skip-kernel

For GitHub Actions / Gitea CI, add a workflow step:

- name: Build SD card image
  run: |
    sudo apt install -y wget xz-utils dosfstools e2fsprogs qemu-user-static
    ./build/build.sh --skip-kernel
- name: Upload image artifact
  uses: actions/upload-artifact@v4
  with:
    name: sd-card-image
    path: build/out/rpi-audio-mixer-*.img.xz

Customization

Pre-configure WiFi and hostname

MIXER_WIFI_SSID="MyNetwork" MIXER_WIFI_PASS="secret123" \
MIXER_HOSTNAME="stage-mixer" ./build/build.sh --skip-kernel

Custom image size

./build/build.sh --image-size 16384   # 16 GB image

Different Raspberry Pi OS base

RPIOS_URL="https://example.com/my-custom-base.img.xz" ./build/build.sh

Architecture Decision

This project uses Raspberry Pi OS Lite (64-bit, Bookworm) + post-install configuration rather than Buildroot or Yocto. Rationale:

  • Full Debian package ecosystem for rapid audio stack iteration
  • apt install for JACK, Carla, debugging tools — no image rebuilds
  • Standard systemd-based system — familiar and well-documented
  • PREEMPT_RT kernel built via cross-compilation on x86 host
  • Buildroot/Yocto can be revisited for production appliance images

See docs/research/base-os-decision.md for the full analysis.

Troubleshooting

Build fails on "qemu-aarch64-static not found"

sudo apt install qemu-user-static
sudo update-binfmts --enable qemu-aarch64

Build fails on apt in chroot

This usually means DNS isn't resolving inside the chroot. The configure script uses the host's /etc/resolv.conf — make sure your host has working DNS.

Image won't boot on RPi

  • Check you're using a Raspberry Pi 4B (not Pi 3 or Pi 5)
  • Verify the SD card is at least 16 GB (Class A2 recommended)
  • Try a different power supply (RPi4 needs 5V/3A)
  • Check the HDMI display for kernel panic messages

No audio on first boot

  • Run sudo systemctl status jackd to check JACK
  • Run aplay -l to list audio devices
  • Re-run the setup wizard: sudo touch /force-firstboot && sudo reboot

Web UI not accessible

  • Check the service: sudo systemctl status mixer-api
  • Find the IP: ip addr show | grep inet
  • Check the API key in the service file: grep API_KEY /etc/systemd/system/mixer-api.service