Skip to content

snd: music and sound effects

Sound is a synth for music and short PCM samples for effects. Both are authored entirely in the App: music is a text file you write in the editor, effects are .wav files you drop in (the App converts them).

The one rule that shapes everything: sound is an effect of _update(), never of _draw(). _draw() may be dropped under load without breaking replay, so snd.* called from it is an error. Put your sound calls in _update().

The functions

snd.music(name [, loop]) / snd.music()

Plays the song name — the name of a .son file in the game, without the extension (game.son → snd.music("game")). loop true repeats it. With no argument, stops the music. An unknown name is an error listing the songs that exist (like gfx.background).

snd.play(name [, vol])

Plays a sound effect once — the name of a .sfx file, without the extension. vol is 0 to 15 (default full). Two effects play at a time; a third steals the oldest voice. An unknown effect is silent, not an error (like a missing sprite): a typo never crashes a game.

snd.stop()

Cuts everything, music and effects, at once.

snd.volume(music [, sfx])

Master volumes, 0 to 15. snd.volume(8) sets the music level; a second argument sets the effects level.

function _update()
  local p = pad.get()
  if start and p & pad.START ~= 0 then snd.music("theme", true) end
  if p & pad.A ~= 0 and not (prev & pad.A) then snd.play("shoot") end
  prev = p
end

Music: the .son file

A song is a small text file in the tracker tradition — readable, diffable, written by hand in the editor. Create a music in the workshop (library, Sounds, +) and it opens with a skeleton that already plays.

; theme.son — the title music
tempo 8                          ; frames per line (60/8 ≈ 7.5 lines a second)

inst lead  onde=pulse25 env=1,3,10,6   ; A,D in frames; S level 0..15; R frames
inst bass  onde=tri     env=0,0,12,2

pattern intro
C-4 lead v12 | C-2 bass | --- | ---
E-4          | ---      | --- | ---
G-4 . a047   | G-2      | --- | ---
--- | --- | --- | ---
fin

morceau
intro intro
boucle 0                         ; loop back to entry 0 (omit for no loop)
fin
  • tempo N — frames per line. Lower is faster.
  • inst name onde=… env=A,D,S,R — an instrument. onde is one of pulse12, pulse25, pulse50 (square), tri, scie (sawtooth), bruit (noise). The envelope is counted in frames: attack, decay, sustain level (0–15), release.
  • pattern name … fin — a block of lines. Each line is the 4 channels separated by |. In a cell: a note like C-4 (C-0 to B-7), --- for nothing, off for note-off, . to keep the channel's current instrument. Optional per-cell: vNN volume, aXYZ arpeggio (root, +Y, +Z semitones). A pattern is as long as the lines you write — it loops there, not at 64.
  • morceau … fin — the sequence of patterns to play, then optionally boucle i to repeat from entry i.

Four synth channels play at once. A noise channel still needs a note (it sets the LFSR rate). A game can hold several songs; snd.music("name") picks one.

Effects: .sfx from a .wav

Drop a .wav on the library's Sounds and the App converts it to a .sfx (8-bit mono PCM at 24 kHz). PCM WAV, 8 or 16 bits, mono or stereo, any rate — it is downmixed and resampled. A game holds up to 64 effects and about 80 seconds of them in total (1,920 KiB at 24 kHz); the App refuses a bundle that goes over, with the numbers. You can also drop a ready-made .sfx.

Sounds in the workshop

In the workshop's library, Sounds holds the raw sounds and Audio bundles groups them. Drop a .wav (converted to an effect), a .sfx or a .son, or create a music; double-click a sound to open it in a tab, where an effect shows its waveform and a music is edited as text, and ▶ Listen plays it with the console's synthesizer. Group sounds into an audio bundle, give each a flat name, and attach the bundle to the project — its pieces land in the game as <name>.sfx / <name>.son, exactly what snd.play("name") / snd.music("name") read. Unlike a level there is nothing to compile: a sound stands on its own. ▶ Play runs the game with its sound.

You can still upload a .son/.sfx/.wav straight into a project's files for a one-off; the library is the reusable part on top. See working in the App.

What the names don't say

  • Timing is in frames, not seconds. A song's tempo, an envelope, all count logical frames — the same clock the game runs on, so replay is exact.
  • No math.sin in the sandbox. The synth uses frozen integer tables, like sys.sin; you never touch that, but it is why the sound is bit-identical on the simulator and the board.
  • On the board, effects live in flash and are read in place; music is tiny and lives in RAM. You don't manage any of that. The first time a game runs, its effects are written to the flash — a few seconds for a full reserve — and left alone until its sounds change; another game's effects take their place when that game runs.