Post

Fixing Broken Emulator Audio on iPod Classic (Rockbox Rockboy)

Fixing Broken Emulator Audio on iPod Classic (Rockbox Rockboy)

The Problem

I run Rockbox 4.0 on my iPod Classic 7G and use the built-in retro handheld emulator (rockboy) to play compatible ROM files. The games run fine visually, but the audio sounds terrible: harsh, crackly, metallic. Nothing like the warm chiptune audio the original hardware produces.

This bothered me enough to dig into the source code. I found five bugs, one of which is a single misread preprocessor condition that degrades audio quality to 25% of what it should be.

The Biggest Bug: Wrong Sample Rate

The iPod Classic defines HW_HAVE_11 because its hardware supports 11025 Hz as one of its available rates. Rockboy misreads this as “use 11025 Hz” and forces the emulator output to the lowest supported sample rate instead of the best one.

1
2
3
4
5
6
// Original code — WRONG
#if defined(HW_HAVE_11) && !defined(TOSHIBA_GIGABEAT_F)
    pcm.hz = SAMPR_11;  // 11025 Hz forced on iPod Classic
#else
    pcm.hz = SAMPR_44;  // 44100 Hz for everything else
#endif

At 11025 Hz, the emulator sets snd.quality = 44100 / 11025 = 4, meaning it generates only every 4th audio sample with zero interpolation between them. The four emulated sound channels get destroyed by aliasing artifacts. Classic chiptune soundtracks sound like they’re being played through a broken AM radio.

The fix is one line: always output at 44100 Hz. The iPod Classic’s audio hardware handles it natively. The Rockbox mixer resamples if needed.

1
2
// Fixed
pcm.hz = SAMPR_44;

The Other Four Bugs

Beyond the sample rate, the audio pipeline had:

Broken double-buffering: The code allocates two buffer halves (N_BUFS=2) but pcm.buf always points to the first half. The audio ISR callback reads from buf[pcm.len * doneplay] — when doneplay=1, this accesses the second half which is always zeroed. Hardware alternates between real audio and silence on every callback, producing constant crackling.

Non-volatile ISR flag: The doneplay synchronization flag between the audio ISR and the emulation thread was a plain bool. With GCC -O2, the compiler caches it in a register and the ISR write becomes invisible to the main thread — a potential permanent hang.

No timeout on the yield loop: while (!doneplay) { rb->yield(); } has no escape condition. If audio hardware stops (headphones unplugged, mixer error), the emulator hangs until you force-reset the iPod.

Redundant copy buffer: A separate hwbuf allocation memcpy’d the entire audio buffer on every ISR callback. Wasteful 4KB of plugin heap and unnecessary latency — the Rockbox mixer API accepts a direct pointer.

The Fix

The rewritten audio backend uses a proper ping-pong double buffer:

  • pcmbuf[0] and pcmbuf[1] are the two slots
  • fill_buf tracks which slot the emulator is writing to
  • submit_buf (volatile) tells the ISR which slot to deliver
  • ready (volatile bool) signals when a slot is full
  • Timeout prevents deadlock if audio hardware disappears
1
2
3
4
5
6
7
static void get_more(const void **start, size_t *size)
{
    if (ready)
        ready = false;  // emulator can now refill the other slot
    *start = pcmbuf[submit_buf];
    *size  = sizeof(pcmbuf[0]);
}

On underrun the ISR replays the last good buffer rather than playing silence, avoiding pops.

Building the Toolchain

Getting this to compile on macOS took some work. The Rockbox build system requires its own arm-elf-eabi-gcc 9.5.0 cross-compiler, which rockboxdev.sh builds from source.

Two macOS-specific issues I hit and documented:

  1. Spaces in path: autoconf’s --prefix cannot contain spaces. Google Drive paths (~/Library/CloudStorage/Google Drive/...) break the build silently. Solution: install the toolchain to ~/rockbox-toolchain instead.

  2. -fbracket-depth=512 is Clang-only: rockboxdev.sh injects this flag on Darwin assuming you’re using Clang as the host compiler. With Homebrew GCC 15 it fails immediately. Fix: detect whether $CXX contains g++ and skip the flag.

Both issues are documented in the repo’s BUILD.md so nobody else hits them.

Result

Five bugs fixed. Compiled ARM binary built and tested against the Rockbox 4.0 source for iPod Classic 6G/7G. The plugin is 51KB.

The pre-built binary is in the releases folder — copy it to .rockbox/rocks/games/rockboy.rock on your iPod to try it.

The patch is also formatted for upstream submission to the Rockbox project via their Gerrit review system, which is the next step.

GitHub repo: Tyal13/rockbox-rockboy-audio-fix


If this fix helped you, consider sponsoring the project on GitHub. Every contribution helps fund continued Rockbox development work.

This post is licensed under CC BY 4.0 by the author.