Features
How Rock N Roller solves its harder problems: local CDLC, catalog search, practice speed and section loops, Demucs stems, left-handed / inverted highway layout, and a 3D chart that stays locked to the audio — from one C + raylib codebase on macOS, Windows, and WebAssembly.
Share a section:
/features/#stems,
/features/#loop,
/features/#sqlite, …
- Stack & platforms
- Library that stays on your disk
- SQLite on disk (macOS / Windows)
- Catalog search & query language
- Browser library access
- Multithreaded wasm
- Practice speed without pitch shift
- Section loop
- Stems & Demucs MLX
- Left-handed & string order
- Long songs without blowing RAM
- 3D highway
- Keeping the UI smooth
- What ships
Stack & platforms
The app is C17 (plus one C++17 wrapper for Signalsmith Stretch) on raylib 6.0. One CMake project builds macOS (Apple Silicon and Intel), Windows x64, and Emscripten wasm from the same commit — platform differences stay in small shims for paths, dialogs, and threading.
Heavy lifting is a short list of libraries: SQLite for state and the song catalog, an in-repo PSARC reader, vgmstream for Wwise audio (no FFmpeg), ogg/vorbis, and Signalsmith for time stretch. The UI is drawn in-app; there is no separate toolkit.
Library that stays on your disk
Opening every .psarc on every launch would be slow. Instead,
a local SQLite DB caches each song's metadata (title, artist, tunings,
arrangements, length) keyed by file size and mtime. Startup loads the
catalog from the DB; a background rescan only re-parses files that
changed. Profiles, playlists, play history, and settings live in the
same database. Charts are Rocksmith's binary SNG — parsed once when you
pick a song, not from XML at runtime.
SQLite on disk (macOS / Windows)
On desktop, the database is a normal file on your machine — not in the
cloud, not inside the app bundle. The state directory is created on
first launch; the DB file is rocknroller.db (plus SQLite
WAL sidecars while the app is open).
| Platform | Path |
|---|---|
| macOS / Linux |
~/.nicapotato-app-saved-state/rocknroller/rocknroller.db
|
| Windows |
%LOCALAPPDATA%\nicapotato\rocknroller\rocknroller.db
|
| Web | Same logical path inside an IDBFS mount, persisted to the browser’s IndexedDB for this site (see browser library access) |
That file holds the song scan cache, profiles, playlists, play history, library folder list, stems folder path, autoplay mode, chart layout, and per-profile cell settings (including left-handed / invert). Your PSARC and stem audio stay wherever you put them — only metadata and prefs are in the DB. Reset in Settings wipes this state, not your media files.
Catalog search & query language
A large CDLC library is useless if you can only scroll it. The catalog search bar accepts plain text or a small query language that filters on the metadata already in SQLite — no reopening PSARCs.
Bare words are a global substring match (case-insensitive) across
title, artist, album, year, and tuning. Column filters use
field="value" and can be mixed with free text:
| Clause | Filters on |
|---|---|
title="…" |
Song title |
artist="…" |
Artist |
album="…" |
Album |
year="…" |
Year |
tuning="…" |
Tuning (display label or raw offsets), e.g.
tuning="Drop D"
|
instruments="…" |
Arrangements present: v vocals, l lead,
r rhythm, b bass. Optional per-arrangement
tuning: l-E Standard, b-Drop D
|
official="true|false" |
Official Rocksmith DLC vs CDLC |
stems="true|false" |
Songs with all six external stem MP3s indexed |
Inside the quotes, comma means AND and
| means OR. Examples:
-
stems="true" tuning="E Standard | Drop D"— songs with stems whose tuning matches either string -
artist="Rush" instruments="l, b"— Rush charts that have both lead and bass -
instruments="l-E Standard|b-Drop D"— lead in E Standard or bass in Drop D -
official="false" stairway— CDLC only, plus free-text “stairway”
Clicking an album or artist card fills the bar with matching
artist="…" / album="…" clauses. The same
query powers the play queue when autoplay advances to the next song.
Browser library access
The browser has no filesystem, but the app needs a folder of your CDLC. The solution:
- Chromium's File System Access API grants read-only access to a local folder; the handle is remembered in IndexedDB so you don't re-pick every visit.
- The folder is mirrored into wasm MEMFS as zero-byte skeleton files with real names, sizes, and mtimes. Desktop scan/cache logic runs unchanged — and because the cache keys on size + mtime, cataloging never needs the file bytes.
- Opening a song stages the real PSARC on demand: a worker parks while the main thread copies bytes into a bounded LRU cache (512 MiB), then wakes. Large libraries stream through a fixed footprint.
Bytes go disk → page memory only. Nothing is uploaded. This (and folder picking) is why the web build needs Chrome or Edge.
Multithreaded wasm
The web build is the same C, compiled with Emscripten as multithreaded
wasm: pthreads → Web Workers sharing memory via
SharedArrayBuffer. That requires a
cross-origin-isolated page (COOP/COEP headers). GitHub
Pages can't send those, so a same-origin
coi-serviceworker injects them (one reload on first
visit). The ~4 MB engine then streams from CDN; the site itself
stays a thin shell.
Practice speed without pitch shift
Slowing audio naively drops pitch. Practice mode needs
0.3×–3.0× with pitch held steady. Inside the audio
callback, Signalsmith Stretch time-stretches each period: pull
rate × source frames, emit device-rate frames at original
pitch. At 1.0× the stretcher is bypassed. The chart clock compensates
for stretch latency so notes stay locked to what you hear; speed and
seek reset the engine atomically.
Section loop
Rocksmith charts tag named sections (intro, verse, solo, …). Press P or the footer Loop button to loop the section under the playhead: the app seeks to that section’s start and repeats until you turn loop off. While loop is on, Enter or O jumps back to the loop start and unpauses — useful after you scrub away mid-practice.
Loop edges show on the seek bar and can be dragged to tighten the span (as long as the chart has sections). Combined with pitch-preserving speed and stem mutes, this is the usual “drill this four bars” workflow. See keybinds.
Stems & Demucs MLX
Stem mode lets you mix six isolated tracks — drums, bass, other,
vocals, guitar, piano — each with its own fader (0–200%). Mute the
guitar and play over the rest of the band, or solo a part to learn by
ear. Separation does not run inside Rock N Roller; it
is done offline with the companion macOS app
Demucs MLX Audio Stemmer
(Apple Silicon, Demucs htdemucs_6s by default).
Stemming workflow
-
In Demucs MLX, add an MP3/WAV or a Rocksmith
*_p.psarc(it extracts the chart’s audio first). - Pick an output folder — this becomes your Rock N Roller stems root.
-
Run separation. For
Eagles_Hotel-California_v1_1_p.psarcit writes:{stems_root}/Eagles_Hotel-California_v1_1_p/ drums.mp3 bass.mp3 other.mp3 vocals.mp3 guitar.mp3 piano.mp3 -
In Rock N Roller Settings → Choose stems folder,
point at that same
{stems_root}, then resync.
How they link to PSARCs: Rock N Roller takes the PSARC
filename without .psarc, looks for
{stems_root}/{that_name}/, and requires all six MP3s.
Only then is the song marked has_stems in SQLite (searchable
with stems="true"). Paths are indexed per song; playback
opens six streaming MP3 decoders under one mixer that shares the same
time-stretch path as normal audio. On the web,
?demo=stems loads curated stem trees into the same layout.
Left-handed & string order
Two independent highway layout flags, saved per profile and per cell size class (also bulk-set from catalog Settings):
| Setting | Default | What it does |
|---|---|---|
| String order (invert) | Red / low-E at top | Rocksmith-style default. Inverted puts red at the bottom of the lane — useful if you prefer that visual mapping. |
| Left-handed | Right-handed | Mirrors fret positions about the neck midpoint (Rocksmith-style lefty), so open/low frets sit on the opposite side of the highway. |
Toggle them in Cell Settings while playing, or use Settings → “Set all to Left-handed / Right-handed” and “Set all to Default / Inverted” for every size class at once. Details: settings.
Long songs without blowing RAM
A long chart can be huge if kept fully renderer-ready. Songs over 10 minutes keep the parsed chart in a compact cache and only expose a 3-minute window to the highway. As you near the edge (with preload that scales with practice speed), the window slides and refills. Shorter songs drop the cache after the first fill.
Audio streams similarly: OGG from disk, stems as MP3 with seek points, and long WEM-only tracks decode in the background while the chart starts. Memory stays flat for a three-minute song or a forty-minute one.
3D highway
Each instrument pane gets its own scissored 3D viewport (raylib
rlgl) with an asymmetric projection so the camera can sit
high without keystoning the frets. Camera modes track the active fret
region on a 24-fret neck; zoom, runway grade, and lookahead are
adjustable. Left-handed and invert flags remap frets and strings before
draw (see above).
Notes are Rocksmith-style gems (halo / body / edge), with sustains as bendable ribbons and SDF fret labels on top. Each frame batches only the notes in the visible time window and draws them in a fixed depth order. Target is 60 FPS with four panes up.
Keeping the UI smooth
The main thread renders; workers handle library sync, song load, WEM decode, stem mixer open, and album art. They finish via atomic flags the frame loop polls — no blocking joins on the hot path. Speed, seek, and volume cross into the audio callback with atomics only, so the UI can't stall playback. The same model runs on web as the pthread pool above.
What ships
Desktop is roughly an 8 MB binary; the web package is about 1.8 MB zipped (~4 MB wasm + JS + a ~420 KB preload of fonts/shaders/logo — songs never ride along). That size is mostly “link only what Rocksmith content needs” and keep assets out of the download, not a separate size project.
Numbers move with releases. See versions or press play. Section links: #sqlite, #loop, #stems, #lefty, …