Files
shawn c2071a9724 feat(audio): Focusrite 2i2 stereo JACK config for 4CM
Adds:
- AudioConfig.mode field: 'mono' (default) or 'stereo_4cm'
- capture_channels / playback_channels properties for dynamic JACK -i/-o counts
- FOCUSRITE_PROFILES dict with focusrite_2i2_3gen entry
- AudioSystem.channel_mapping_help() static method
- stereo_4cm port auto-connect wiring (4 cable method)
- Systemd service content uses dynamic channel counts
- Default config YAML includes 'mode: mono'
- 11 new tests (31 total pass)
- Focusrite 4CM wiring docs in docs/config-audio.md
2026-06-08 10:40:38 -04:00

9.1 KiB
Raw Permalink Blame History

Audio Configuration — PCM1808+PCM5102 I2S HAT

HAT: Custom PCM1808 ADC + PCM5102 DAC I2S audio HAT
RPi: 4B, Raspberry Pi OS (Bookworm, 64-bit)
Kernel: rpi-6.6.y (upstream Raspberry Pi kernel)
Last updated: 2026-06-07


Device Tree Overlay

The PCM1808 ADC has no upstream overlay in the rpi-6.6.y kernel tree. A custom overlay is provided at:

hardware/pcm1808-pcm5102-overlay.dts

Build & Install

# Install device-tree-compiler if not present
sudo apt-get install device-tree-compiler

# Compile overlay
dtc -@ -I dts -O dtb -o pcm1808-pcm5102-overlay.dtbo \
    hardware/pcm1808-pcm5102-overlay.dts

# Copy to boot overlays directory
sudo cp pcm1808-pcm5102-overlay.dtbo /boot/firmware/overlays/
# On older Pi OS: /boot/overlays/

This compiles to /boot/firmware/overlays/pcm1808-pcm5102-overlay.dtbo.

Enable Overlay

Edit /boot/firmware/config.txt (or /boot/config.txt on older installs):

# ── Custom I2S HAT: PCM1808 ADC + PCM5102 DAC ─────────
dtoverlay=pcm1808-pcm5102-overlay

# Disable onboard audio to free I2S pins
dtparam=audio=off

# Optional: force I2S pins if overlay doesn't set them
# dtparam=i2s=on

Verify Overlay Loads

# Check overlay is loaded
dmesg | grep -i "pcm1808\|pcm5102\|i2s"

# Check ALSA sees both devices
aplay -l
arecord -l

# Expected output for playback:
#   card 0: pcm1808pcm5102hat [pcm1808-pcm5102-hat], device 0: ... []
#   Subdevices: 1/1

# Expected output for capture:
#   card 0: pcm1808pcm5102hat [pcm1808-pcm5102-hat], device 0: ... []
#   Subdevices: 1/1

# Check /proc device tree
cat /proc/device-tree/sound/name  # should show "pcm1808-pcm5102-hat"

ALSA Configuration

Default Card Setup

Create /etc/asound.conf (system-wide) or ~/.asoundrc (per-user):

# ── PCM1808+PCM5102 I2S HAT ─────────────────────────

# Default playback device
pcm.!default {
    type asym
    playback.pcm {
        type hw
        card 0
        device 0
    }
    capture.pcm {
        type hw
        card 0
        device 0
    }
}

# Hardware device aliases
pcm.i2s_playback {
    type hw
    card 0
    device 0
}

pcm.i2s_capture {
    type hw
    card 0
    device 0
}

# Software volume control (PCM1808/PCM5102 have no HW volume)
pcm.softvol_playback {
    type softvol
    slave.pcm "i2s_playback"
    control {
        name "Master Playback Volume"
        card 0
    }
    min_dB -50.0
    max_dB 0.0
}

# 48kHz upsampler for 44.1kHz sources (like YouTube/Spotify)
pcm.rate_convert {
    type plug
    slave {
        pcm "softvol_playback"
        rate 48000
        format S24_3LE
        channels 2
    }
}

# Default: use rate conversion + softvol
pcm.!default {
    type plug
    slave.pcm "rate_convert"
}

# Control device
ctl.!default {
    type hw
    card 0
}

Test Audio

# --- Playback test ---
# Sine wave at 440Hz (A4)
speaker-test -t sine -f 440 -l 1 -c 2

# Play a WAV file
aplay /usr/share/sounds/alsa/Front_Center.wav

# --- Capture test ---
# Record 3 seconds from ADC
arecord -d 3 -f cd -t wav test_record.wav

# Play back the recording
aplay test_record.wav

# --- Full loopback test ---
# Play tone while recording (connect DAC output to ADC input)
# Check latency: <10ms at 48kHz/128 frames
arecord -d 5 -f S24_3LE -r 48000 -c 2 loopback_test.wav &
speaker-test -t sine -f 440 -l 2
wait
aplay loopback_test.wav

JACK Audio Configuration

The pedal DSP runs under JACK. The existing JACK config in main.py uses hw:1,0 (USB). Swap to hw:0,0 for I2S:

JACK Start

# Kill PulseAudio (conflicts with JACK on RPi)
pulseaudio --kill

# Start JACK at 48kHz/128 frames (recommended target: ~5.3ms buffer)
jackd -R -d alsa \
  -d hw:0,0 \
  -r 48000 \
  -p 128 \
  -n 3

# For low-latency mode (risky, check xruns):
jackd -R -d alsa -d hw:0,0 -r 48000 -p 64 -n 2

Update main.py Config

In /home/oplabs/projects/pi-multifx-pedal/main.py, change:

# FROM (USB Audio):
# config = {
#     "input_device": "hw:1,0",
#     "output_device": "hw:1,0",
# }

# TO (I2S HAT):
config = {
    "input_device": "hw:0,0",       # PCM1808 ADC
    "output_device": "hw:0,0",      # PCM5102 DAC
    "sample_rate": 48000,
    "period_size": 128,              # 2.6ms buffer
    "num_periods": 3,                # triple-buffered
}

Integration with NAM DSP

The I2S HAT uses the same block size as the NAM DSP engine (256 samples):

Parameter Value Note
Sample rate 48,000 Hz Both JACK and NAM
Block size 256 frames NAM internal block
JACK period 128 frames 2 × periods per block
Round-trip (target) <8ms 128fr × 2 + codec delay + DSP

At 48kHz/128 frames × 3 periods = ~8ms round-trip, well within the 10ms target for NAM DSP. The 256-sample NAM block fits exactly into 2 JACK periods.


Troubleshooting

No card 0 detected

# Check if I2S is enabled in pin controller
dmesg | grep -i i2s

# Check loaded overlays
vcgencmd get_config dtov
# Should show: pcm1808-pcm5102-overlay

# Rebuild overlay if kernel was updated
sudo dtc -@ -I dts -O dtb -o /boot/firmware/overlays/pcm1808-pcm5102-overlay.dtbo \
    /home/oplabs/projects/pi-multifx-pedal/hardware/pcm1808-pcm5102-overlay.dts

No capture device

PCM1808 has no upstream overlay — the custom overlay provides the pcm1808 codec binding. If capture isn't showing:

# Verify the overlay node compiled
dtc -I dtb -O dts /boot/firmware/overlays/pcm1808-pcm5102-overlay.dtbo | grep -i pcm1808

# Check I2S pin muxing is correct
raspi-gpio get 18 19 20 21
# Expect: GPIO 18: level=0 func=ALT5 (I2S BCLK)
#          GPIO 19: level=0 func=ALT5 (I2S LRCLK)
#          GPIO 20: level=0 func=ALT5 (I2S DIN)
#          GPIO 21: level=0 func=ALT5 (I2S DOUT)

PCM5102 hiss

  • Add 330Ω + 10µF RC filter on DAC output (R5, C10 in BOM)
  • Ensure XSMT (pin 17) is pulled to 3.3V, not floating
  • Check 3.3V rail noise with oscilloscope (<50mV ripple at audio frequencies)

JACK xruns

  • Increase period size: -p 256 (5.3ms, safer)
  • Increase num periods: -n 3 (triple buffer)
  • Isolate RPi 4B from USB power noise
  • Check cat /proc/asound/card0/pcm0p/sub0/status for xrun count

Pinout Reference

Signal BCM GPIO Physical Pin ALT Function Direction
BCLK GPIO18 Pin 12 ALT5 (I2S) RPi → HAT
LRCLK GPIO19 Pin 35 ALT5 (I2S) RPi → HAT
DIN GPIO20 Pin 38 ALT5 (I2S) HAT → RPi
DOUT GPIO21 Pin 40 ALT5 (I2S) RPi → HAT
3.3V Pins 1, 17 Power RPi → HAT
5V Pins 2, 4 Power RPi → HAT
GND Pins 6, 9, 14, 25, 30, 34, 39 Ground

The I2S HAT uses a stacking header (2×20 female socket) to pass through all 40 GPIO pins so footswitches, LEDs, and display are accessible from the top of the HAT.


Focusrite Scarlett 2i2 — 4CM (Four Cable Method)

Interface: Focusrite Scarlett 2i2 (3rd gen) USB audio interface Mode: stereo_4cm in audio.mode Device: hw:1,0 (USB card index, may vary)

Wiring

Guitar ─────→ Input 1 (ADC channel 0) ─→ hw:1,0 capture_0 ─→ Pipeline input_0
Amp FX Send ─→ Input 2 (ADC channel 1) ─→ hw:1,0 capture_1 ─→ Pipeline input_1

Pipeline output_0 (pre-amp) ─→ Output 1 (DAC channel 0) ─→ hw:1,0 playback_0 ─→ Amp Input (Send)
Pipeline output_1 (post-amp) ─→ Output 2 (DAC channel 1) ─→ hw:1,0 playback_1 ─→ Amp FX Return

JACK Config

# Kill PulseAudio first — it grabs USB audio devices
pulseaudio --kill

# Start JACK at 48kHz/128 frames, 2 capture + 2 playback channels
jackd -R -d alsa -d hw:1,0 -r 48000 -p 128 -n 3 -C 2 -P 2

Application Config

Set these fields in ~/.pedal/config.yaml:

audio:
  hat_type: "focusrite_2i2_3gen"
  profile: "standard"
  mode: "stereo_4cm"
  input_device: "hw:1,0"
  output_device: "hw:1,0"
  jack_enabled: true
  auto_connect: true

Port Auto-Connect

When auto_connect: true and mode is stereo_4cm, the system creates these JACK connections:

Source Destination Signal
system:capture_1 fx_in:input_0 Guitar → pre-amp DSP
system:capture_2 fx_in:input_1 FX Send → post-amp DSP
fx_out:output_0 system:playback_1 Pre-amp DSP → amp input
fx_out:output_1 system:playback_2 Post-amp DSP → FX return

Channel Mapping Helper

At runtime, call AudioSystem.channel_mapping_help("stereo_4cm") to get these mappings as structured data.

Notes

  • The Focusrite 2i2 registers as ALSA card index 1 when the I2S HAT is card 0 (Linux arranges by driver probe order). If HDMI audio or other USB devices are present, the index may shift — use aplay -l / arecord -l to confirm.
  • 2-in/2-out at 24-bit/48kHz. JACK runs at 24-bit by default over ALSA.
  • At 48kHz/128 frames × 3 periods, round-trip latency is ~6-8ms — well within the 10ms target for real-time guitar DSP.