* new launcher and save converts and pipeline * fixing bugs
Jukebox Example
Adds one song authored note-by-note in Lua, swaps it in as the Pallet Town theme, replaces Mew's cry, and ships a jukebox screen that lists every song in the merged registry.
Persona: the Musician. Nothing here is a .ogg. The song is a
Game Boy channel program assembled at load time by ChipAsm.
Try it
python3 tools/modkit.py validate mods/examples/example_jukebox --base imported
luajit mods/examples/example_jukebox/tests/example_jukebox_test.lua
# render the song to a wav to hear it without launching the game
python3 tools/modkit.py bounce Music_ExamplePalletRain --seconds 20 --out bounce
Enable it (example_jukebox = true under mods in options.lua, or the
F10 manager), then open OPTIONS → JUKEBOX.
What it demonstrates
| Seam | Where |
|---|---|
content.music:register (ChipAsm DSL) |
song.lua + main.lua |
content.cries:override (chip program) |
main.lua |
hooks:wrap("music.select") |
main.lua — one choke point covers map, battle and jingle music |
content.screens:register |
main.lua — a screen factory the engine instantiates by id |
hooks:wrap("ui.options.rows") |
main.lua — how the player reaches the screen |
content.music:each |
main.lua — the jukebox list is the merged view, not a hard-coded array |
mod:read |
main.lua — loading a sibling file through the loader's filesystem |
Authoring a song
song.lua returns what ChipAsm.song{...} builds: a self-contained
program blob plus its channel layout. Events are Lua tables, one per
command:
{ duty = 2 },
{ notetype = { speed = 12, volume = 11, fade = 2 } },
{ octave = 4 },
{ label = "lead" },
{ note = "E", len = 6 }, { note = "D", len = 2 },
{ loop = { count = 0, to = "lead" } },
The assembler is the validator. An unknown note name or an out-of-range
length raises there, naming the channel and event index, and main.lua
turns that into one mod-attributed load error. The music system never
latches on a bad program, because a bad program never reaches it.
Two shapes share the music registry and are dispatched per definition,
not by a global flag: { chip = ... } for an authored program and
{ file = "..." } for an audio file. This example uses the first; a file
track is one line:
mod.content.music:register("Music_MyTheme", { file = mod.path .. "/theme.ogg" })
Note the cry uses ChipAsm.sfx{...}.chip — the assembler returns
{ chip = program }, and a cry record wants the program under its own
chip key.
Deferring is the parity guarantee
mod.hooks:wrap("music.select", function(next, chosen, ctx)
if ctx and ctx.reason == "map" and ctx.mapId == "PALLET_TOWN" then
return next(SONG_ID, ctx)
end
return next(chosen, ctx)
end)
Every path that is not Pallet Town calls next(chosen, ctx) with the
argument it was given, so playback everywhere else is exactly what it was
before the mod loaded.
Empty state
The jukebox is a ListMenu, which draws Nothing here. when its item list
is empty rather than an empty frame. That cannot happen with the engine's
45 songs present, but it is the behavior a mod screen owes the player.
Permissions
This mod declares engine_internals, because playing a song from a screen
currently needs require("src.core.Music") — the mod surface has no audio
playback facade yet. Declaring it is the honest path: modkit validate
accepts a declared require and flags an undeclared one.
Credits
- Pallet Rain arrangement: this project.
- pret/pokered: the channel command set
ChipAsmassembles to.