Chiptune music player for the GameCube, on libogc2. Two formats, two engines, one internal interface:
| format | engine | state on a real GameCube |
|---|---|---|
.mid |
TinyMidiLoader + TinySoundFont + GM soundfont | 48 kHz with 64 voices |
| modules | libxmp (MOD/XM/IT/S3M and ~90 others) | free — 18 per mille at worst |
There was a third format, farbrausch's .v2m. It has been dropped: the V2
synth does not hold real time on the Gekko, one file in six stutters, and no
workaround covers the files a user might bring. The numbers and the options that
were examined are in docs/STATUS.md §13.
Tracks are read from an SD card (SD Gecko slot A/B, or SD2SP2), walked recursively. FAT16, FAT32 or exFAT — the last of these is new in 1.1.0, and it matters more than it sounds: every card above 32 GB is sold formatted that way. Until 1.0.1 such a card mounted as nothing and the player reported an empty card. Not a line of the player changed to read it; the filesystem library under it did.
State: complete. Two-pane GX interface, playback from the SD card, index and resume, measurements taken on a real GameCube. The release lands in
build-host/release/— see Install.
| you want to… | read |
|---|---|
| set the environment up and build | docs/INSTALLATION.md |
| know what the player does not do | docs/LIMITS.md |
| measure on a real GameCube | docs/TEST-HARDWARE.md — the protocol |
| re-run a measurement in Dolphin | docs/TEST-DOLPHIN.md |
| know why 12 files of the corpus do not play | docs/corrupt-modules.txt |
docs/STATUS.mdis not published. It is the study journal — the chronological account of every fault found, every measurement taken and every decision reversed, written for the person maintaining this. The source comments and the documents above cite its sections by number (§13,§12.20,§20…); those citations point into a document that lives outside this repository. Nothing needed to build, run or measure the player depends on it: the conclusions it reaches are restated inLIMITS.mdand in the comments at the point they apply.
Download the release archive. Nothing to convert, nothing to prepare — the soundfont in it is ready to use.
| what | where |
|---|---|
gc-chiptune.dol |
anywhere; loaded by Swiss, SD Media Launcher, GC Loader, a modchip |
soundfont.sf2 |
next to the .dol, as it is. Without it .mid files are skipped; modules play regardless |
| your music | sd:/chiptunes, sd:/GC-Chiptune, sd:/music, or the root — sub-folders allowed |
Modules and MIDI files are copied as they are.
The archive also holds
gpl-source/TimGM6mb.sf2. That one is not for the card — it is the unconverted original, present only because GPL-2.0 requires the source to travel with the binary. The player refuses it and says so.
The player writes two files next to the music: gc-chiptune.idx (the index and
the durations — without it the first start-up walks the card again, 4.5 s for
5000 tracks) and gc-chiptune.state (where you were). Deleting them breaks
nothing.
Releases are built by a machine, from a tag. Pushing vX.Y.Z builds the
.dol in the libogc2 maintainer's own container and publishes the two assets;
every other push builds, so a tree that stops compiling is found out on the push
that broke it. See .github/workflows/release.yml.
The two asset names never change — gc-chiptune.dol and
gc-chiptune-card.zip — because that is the only way anything can watch for a
new version:
https://github.com/RyoSaeba89/gc-chiptune/releases/latest/download/gc-chiptune-card.zip
That URL resolves only while the asset is spelled as it was last time. Until
1.0.1 the archive was gc-chiptune-1.0.1.zip, which no updater could follow.
The version lives in the tag, in the release title, and in VERSION.txt inside
the archive.
The same archive locally, from the same recipe:
bash -lc "make dist" # build-gc/dist/One button = one role, always the same. There is no mode. The first version
moved a focus with START; in use, nobody knew which state they were in.
THE LIST THE PLAYER
↑ ↓ cursor (hold to scroll) X pause
← → leave / enter a folder Y repeat mode
L R jump ten rows Z shuffle
A enter / play C ← → volume
B up one level C ↑ ↓ previous / next
START play the WHOLE tree
L+R rebuild the index
A on a file plays the current folder alone, START plays the targeted
folder and everything under it. The current track stays marked in the list
even while you browse elsewhere — and playback does not stop while you navigate.
git clone https://github.com/RyoSaeba89/gc-chiptune.git && cd gc-chiptune
dkp-pacman -S libogc2-git libogc2-libdvm-git libogc2-libansnd-git \
libogc2-grrlib ppc-libxmp
<devkitPro>/msys2/usr/bin/bash.exe -lc "cd \"$PWD\" && make"No absolute path anywhere: the repository and devkitPro can live wherever you like.
libogc2-libdvm-git, and not libogc2-libfat-git. They are two providers of
one package name, they install the same libfat.a and the same fat.h, and the
difference is exFAT. A build against the wrong one links and runs: nothing in
this tree can tell you which you have, and only a card above 32 GB shows it.
pacman -Qo $DEVKITPRO/libogc2/gamecube/lib/libfat.a answers, and the release
workflow refuses to build when the answer is not libdvm.
Until 2026-08-26 this repository compiled libogc2, libfat, libansnd and libxmp
itself, from clones in extern/, at exactly the revisions those packages carry.
docs/INSTALLATION.md section 3 is the whole account.
1. A soundfont has to be prepared offline — and the one you are given already
is. The Gekko is big-endian, the .sf2 format is little-endian, and
TinySoundFont reads the RIFF container by raw pointer copy: a stock soundfont
cannot work here. So the release ships it already converted. Copy it as it
is; you have nothing to run.
This only becomes your problem if you insist on a different bank — and there
is no reason to. If you do, tools/sf2_prep converts one:
.\build-host\sf2_prep.exe your-bank.sf2 soundfont.sf2 # one other bank
.\tools\make-sdcard.ps1 -Source Chiptunes # or a whole card at onceThe player detects an unprepared soundfont and says so ("soundfont not prepared") instead of failing with no explanation. Modules and MIDI files, on the other hand, are always copied as they are.
2. extern/ stays strictly pristine. No patch is applied there, so an
upstream update requires nothing to be reapplied.
In per mille of real time — 1000 = hard limit. Corpus worst cases, tracks played in full, and it is the worst second that is reported, not the mean.
| track | avg | worst second | dry | verdict |
|---|---|---|---|---|
| MIDI worst case, 64 voices (the setting) | 593 | 675 | 0 | factor 1.5 of margin |
| MIDI worst case, no voice limit | 1346 | 2574 | 20820 | 254 stacked voices — not the setting |
| module worst case | 15 | 23 | 0 | free |
Three results that were not guessable:
- the console/Dolphin gap is only ×1.15, and modules are even faster on real silicon than under the emulator;
- twelve seconds of an opening do not describe a track. That is what made the V2M budget look tenable for two days: its load rose well after the window the bench was looking at (§13). The bench now plays tracks all the way through;
- the DMA transfer to ARAM costs ~3 per mille, now counted in the load. It was excluded, and "libansnd costs no more CPU" rested on that exclusion.
Full detail, method and caveats: docs/STATUS.md §10 and §12.
src/gc/ the GameCube binary: audio, SD card, playlist, UI
src/player/ common interface to the 2 backends + soundfont conversion
tools/ PC measurement tools, soundfont preparation, card preparation
packaging/ the READMEs that go inside the release archive
docs/ INSTALLATION.md, LIMITS.md, TEST-HARDWARE.md, TEST-DOLPHIN.md
data/ tracks embedded in the bench, and the shipped soundfont
test/ a test set ready to copy onto an SD card, with the .dol
extern/ third-party code — PRISTINE. tsf and the soundfont are here;
libxmp is cloned by tools/bootstrap.sh for the PC tools ONLY,
the GameCube build takes every library from pacman
Two binaries from the same tree:
make # the player, tracks read from the SD card
make APP=bench # the CPU measurement bench, tracks and soundfont embeddedThe bench exists because Dolphin emulates neither the SD Gecko nor the SD2SP2 (checked on Dolphin 2606): embedding the tracks is the only way to measure anything without a console.
test/ holds the .dol and four tracks — one light and one heavy per engine,
chosen by measurement after ranking the whole corpus — plus the soundfont.
Copy test/chiptunes to the root of an SD card — FAT32 or exFAT, either
works. See test/README.txt.
The source and the .dol are MIT. The soundfont shipped alongside
is GPL-2.0.
Two separate works on the same medium: the player reads a data file at start-up, it does not derive from it. That was not true for a while — the soundfont was embedded in the binary, which imposed GPL-2.0 on the whole thing. It was taken back out for memory reasons (5.7 MB permanently out of 24, §12.20), and the licence followed.
Redistributing the soundfont requires providing its source: hence
extern/soundfont/TimGM6mb.sf2, the only file of extern/ that is versioned
here, and gpl-source/TimGM6mb.sf2 in the release archive — kept in a
sub-folder so it cannot be mistaken for the one to copy.
| component | licence |
|---|---|
| the code in this repository | MIT |
| libogc2, libfat | see their repositories |
| libxmp | LGPL-2.1+ |
| TinySoundFont, TinyMidiLoader | MIT |
| GRRLIB | MIT |
| FreeType | FTL or GPL-2+ |
| libpng, zlib, brotli, bzip2 | permissive |
| DejaVu Sans Condensed | Bitstream Vera + Arev |
| TimGM6mb.sf2 | GPL-2.0 — separate file |
Everything else being permissive, swapping TimGM6mb for MuseScore's MS Basic
(MIT) would make the release entirely permissive, binary and data. That is a
change to make here, in a future release, so that users keep getting a
soundfont that just works — not something to hand to them as homework. See
§12.20 of docs/STATUS.md.