Music and Books
Two apps that are not emulators: an MP3 player (media-go) and an e-book
reader held upright (ebook-go). Both are SD apps: their images live in
/retro-go/apps/ on the card and take turns with OutRun in the shared
sdapp partition (640 KB). The flash layout does not change. Starting one when
another SD app is in the partition copies it first. This takes 4-6 s, on a black
screen with the hourglass and a percentage.
| Music | Books | |
|---|---|---|
| Launcher tab | Music (/roms/music) | Books (/roms/books) |
| Files | .mp3 | .epub (no DRM), .txt |
| App, size | media-go, 444 KB | ebook-go, 568 KB |
| Started by | A on the file (no game menu) | A on the file (no game menu) |
| Source | retro-go/media-go/main/main.c | retro-go/ebook-go/main/main.c |
Test media stay out of git: test-media/{audio,book,video} in the main
repository is ignored.
Music (media-go)
| Key | Action |
|---|---|
| A | play / pause |
| X / Y, LEFT / RIGHT | previous / next track (LEFT restarts the track after 3 s) |
| L / R | back / forward 10 s |
| UP / DOWN | volume ±5 % |
| B | back to the launcher |
| MENU | retro-go menu |
- Playlist: the other
.mp3files of the same folder, sorted by name. At the end of the playlist the player stops on the first track. - Decoding: minimp3 (CC0, unmodified)
on a task on core 1.
rg_audio_submit()blocks on the I2S queue and so paces the decoder.- minimp3 keeps a ~16 KB scratch on its stack, so the task has 32 KB.
- Output: 32 kHz like every app on this board. Other rates are resampled linearly, and mono is doubled to stereo.
- Screen: title, format, progress, volume, and a 48-band spectrum. The spectrum is a 512-point FFT of the latest decoded samples: Hann window, bands log-spaced from 40 Hz to 16 kHz, scale -66..0 dB, with a slow fall. The UI is drawn at 20 Hz. An earlier peak meter sat at full scale on any modern master.
- Duration and seeking: both come from the first frame's bitrate.
- Duration: file size divided by the bitrate, with the ID3v2 tag skipped.
- Seeking: L/R jump in the file by bytes, and minimp3 resyncs on the next frame.
Board check (2026-10-16):
- Test tones at 440 Hz (44.1 kHz stereo) and 1 kHz (22.05 kHz mono) came out
at 440 and 1000.0 Hz in the submitted samples (
board_ctl.py acap). - Next/previous, pause (no samples), seek and B all work.
- A 128 kbps song plays without dropouts.
Books (ebook-go)
The console is held a quarter turn, D-pad below the screen. "Rotation" in the menu flips it to D-pad above. The keys follow the picture.
| Key | Action |
|---|---|
| A, R, D-pad toward screen right or bottom | next page (into the next chapter) |
| L, D-pad toward screen left or top | previous page |
| X / Y | smaller / larger text (3 sizes) |
| B or START | reader menu; B closes it |
| MENU | back to the launcher (the position is saved) |
The menu holds: continue, chapters (list of the book's chapters), text size,
light/dark theme, rotation, back to the launcher. The reading position is
saved per book in /retro-go/saves/ebook-go/<file>.pos every 5 pages and on
exit.
How a book is read
- EPUB: the app has its own ZIP reader. It reads the central directory and
inflates with the ROM's
tinfl. The built-inrg_storage_unzip_fileonly reads the first entry, which is not enough for an EPUB.META-INF/container.xmlgives the OPF file, and the OPF spine gives the chapters.- The XHTML of each chapter becomes words, paragraphs, headings, italic and bold, and entities are decoded. CSS is ignored.
- Inline tags (
<i>,<a>,<span>) glue the pieces of a word, so no stray spaces appear before punctuation. - A chapter's title is its first heading.
- The cover comes from the OPF (
cover-image, or<meta name="cover">). When no page shows it, it becomes the first page.
- Pictures: JPEG goes through the ROM's TJpgDec (baseline only, descaled by 1/2-1/8 before the final fit), PNG through lodepng. An SVG page uses its PNG or JPG twin when the book ships one.
- TXT: the file is read as UTF-8 when it is valid UTF-8, else as
Windows-1252.
- Hard-wrapped lines are joined when the text separates its paragraphs with blank lines.
- The text is cut into parts of about 24 KB, so that a part is laid out quickly.
- Layout: justified text, with a first-line indent and long words split.
- A line that would stretch by more than six spaces per gap is left ragged.
- Font: Noto Serif (SIL Open Font License 1.1), regular, italic and bold at 15, 18 and 22 px, 4-bit anti-aliased.
- Character set: Latin-1 plus the Italian typographic marks (’ “ ” « » – — …) and €.
- The fonts are generated by
retro-go/tools/ebook_fonts.pyintoebook-go/main/fonts.h.
- Portrait: the page is drawn into a 320×480 buffer and rotated into the panel's 480×320 frame on the way out. The display driver is unchanged.
- Fade-in: the app fade-in in
rg_display.cadvances only when a frame is sent. A page is sent once, so the first frame is re-sent for 400 ms, until the fade has finished. Without this the screen sometimes stayed dark until the first key.
Not supported:
- PDF: fixed A4 pages are unreadable at 320 px, and a PDF engine is too heavy.
- MOBI/AZW: Amazon formats, nearly always with DRM. Calibre converts DRM-free books to EPUB.
- Progressive JPEG: shown as
[picture].
Where to find books:
- Standard Ebooks (English, public domain).
- Liber Liber (Italian).
- Project Gutenberg cannot be reached from Italian ISPs (blocked at DNS).
Board check (2026-10-16): framebuffer shots (console shot) and the
webcam.
- Standard Ebooks' The Adventures of Pinocchio: cover JPEG 1400×2100, title page and logo PNG, 41 chapters.
- An Italian test TXT: accents, typographic quotes, dashes, €, joined lines and a split long word.
- Page turns, X/Y sizes, the chapter list, the dark theme and the rotation all work.
- The position resumes after MENU and reopening.
- Three entries out of three were fully lit 3 s after the start.
Testing on the PC
retro-go/ebook-go/test/run.sh <book> <keys> <sheet.png> builds the reader's
C file on Linux and runs it.
- Build: against stub headers (
ebook-go/test/inc: rg_* calls replaced, zlib in place of the ROMtinfl, no JPEG), with AddressSanitizer. - Run: a key script drives it, one character per press (
anext,bmenu,x/ysize,u/d/l/rD-pad). - Output: a contact sheet of the pages it sent.
This bench found the chapter-menu overflow before it reached the board.