# Audio guide This is the pupil-facing walkthrough for adding music and sound effects to your NES game. We use a free music tool called **FamiStudio** to compose, and the editor's **Audio page** to manage the songs and sound effects that play in your game. If you just want to hear *something* now, jump to [Quick start with the starter pack](#quick-start-with-the-starter-pack). ## Why FamiStudio? The NES has a tiny built-in sound chip with five "voices": - **Square 1 / Square 2** — the classic NES blip sound (the lead and harmony in most games). - **Triangle** — bass / sub. - **Noise** — drums, explosions, ocean. - **DPCM** — short recorded samples (we leave this off — saves ROM space). You could *technically* poke this chip directly from C, but writing music that way is hard. FamiStudio is a friendly piano-roll editor that knows exactly how to talk to the chip. When you finish a song you click **Export** and FamiStudio gives you a `.s` file the editor can load. The bit that actually plays the music inside your game is called the **FamiStudio sound engine**. We bundle it with the editor — you don't need to install or configure anything separately for the runtime. You only need FamiStudio itself to *write* music. ## Quick start with the starter pack The fastest way to hear audio in your game: 1. Open the **🎵 Audio** page in the editor. 2. Click **📦 Load starter pack**. 3. Click **▶ Play in NES**. 4. Listen. The starter pack contains two original looping background tracks (*Cheerful loop* and *Tense loop*) plus a six-slot sound-effect pack (*jump*, *hit*, *pickup*, *land*, *blip*, *error*). These are generic, royalty-free starter content — feel free to keep them, or replace any of them with your own work whenever you like. ## Writing your own music ### 1. Install FamiStudio Download FamiStudio from . It runs on Windows, macOS, and Linux and is free. ### 2. Compose a song Open FamiStudio and: - Click an **Instrument** in the left-hand panel (the default *Square 1* is fine to start). - Click in the piano roll on the right to drop notes. - Press **▶** in the toolbar to listen. Two-minute crash course: the keyboard along the bottom is **time** (left to right) and the rows are **pitch** (higher rows = higher notes). Click a cell to add a note, click again to remove it. The default **Square 1** instrument plays the classic NES blip. If you want a longer guide, FamiStudio has its own tutorial: . ### 3. Export to the editor From FamiStudio's menu: > **File → Export → FamiStudio Sound Engine assembly** In the dialog: - **Format** → `ca65` - **Output file** → save as e.g. `my_song.s` somewhere you'll find it. Click **Export**. ### 4. Upload it to the editor Back in the **🎵 Audio** page: - Click **⬆ Upload song (.s)** under the *Songs* section. - Pick the `my_song.s` file you just exported. It now appears as a card in the songs grid. Click **⭐ Make default** to choose which song plays when the game starts; the card with a yellow **default** badge is the one that boots. ## Sound effects Sound effects work the same way but go through a *different* FamiStudio export: > **File → Export → FamiStudio Sound Engine sound effects** In FamiStudio you build an SFX project where **each "Song" is one sound effect**. Name them clearly (`jump`, `hit`, `pickup` …) — when you upload the resulting `.s` to the editor you'll see those names listed as numbered slots (slot 0 = first song, slot 1 = second, etc.). You can have **one** sfx pack per project. Replace it any time by clicking **🗑 Remove** on the sfx card and uploading a new one. ## Hearing it in your game ### Builder pupils If you're using the **🧱 Builder**, audio is part of your project the moment you've uploaded *either* a song or an sfx pack — the editor fills in a silent default for whichever side you skipped. Most pupils want music, so just uploading a song is the typical path. The default song plays when the game starts. *Coming in a follow-up: an Audio module in the Builder lets you map sound effects to specific events (jump, hit, pickup, land) and switch background music when the scene or HP changes — see [plan §T2.6](../plans/current/2026-04-26-fixes-and-features.md).* For now, you can preview the audio with the **▶ Play in NES** button on the Audio page itself. ### Code-page pupils If you're using the **📝 Code** page (writing your own C), the FamiStudio engine API is available to call directly from your `main.c`. The pupil-uploaded songs and sfx are linked into the ROM as `audio_default_music` and `audio_sfx_data`; the engine functions are declared in your `main.c` template. The functions you'll use most: ```c famistudio_init(FAMISTUDIO_PLATFORM_NTSC, audio_default_music); famistudio_sfx_init(audio_sfx_data); famistudio_music_play(0); // 0 = first song you uploaded famistudio_update(); // call once per frame in vblank ``` And to fire a sound effect: ```c famistudio_sfx_play(2, FAMISTUDIO_SFX_CH0); // slot 2, on channel 0 ``` The starter `main.c` does the four init calls and the per-frame update for you — you only need to add `famistudio_sfx_play(...)` calls where you want sound effects, and `famistudio_music_play(N)` when you want to switch to a different song. #### Connecting sound effects to events The four sounds pupils most often want are **jump**, **land**, **hit** (taking damage), and **pickup** (collecting an item). Each one is a single `famistudio_sfx_play(...)` call placed at the right spot in your `main.c`. In the starter template these spots all live inside the main game loop — search for the existing keywords below and drop the matching line in. | Event | Where to add the call (search for…) | Snippet | | ------- | ----------------------------------- | ------- | | Jump start | `jumping = 1;` (the line that starts a new jump, around the `JOY_A` button check) | `famistudio_sfx_play(0, FAMISTUDIO_SFX_CH0);` | | Landing | `jumping = 0;` immediately after the player hits the ground (look for the comment `feet on a surface`) | `famistudio_sfx_play(3, FAMISTUDIO_SFX_CH1);` | | Hit / damage taken | wherever your code decreases the player's HP | `famistudio_sfx_play(1, FAMISTUDIO_SFX_CH0);` | | Pickup collected | wherever your code marks a pickup as collected | `famistudio_sfx_play(2, FAMISTUDIO_SFX_CH1);` | The slot numbers (0, 1, 2, 3) match the order you uploaded sfx on the Audio page — slot 0 is the first sound, slot 1 the second, and so on. The channel argument (`FAMISTUDIO_SFX_CH0` or `FAMISTUDIO_SFX_CH1`) picks which of the two engine sfx voices plays the sound; using different channels for different events lets two sounds play at the same time without cutting each other off. *Tip:* pair "jump" with "land" on the **same** channel — when you land mid-jump the land sound naturally replaces the jump tail. > **Coming in a follow-up.** The 🧱 Builder will gain an > Audio module that lets you point each event at a sfx slot > from a dropdown, with no code editing — see > [`docs/plans/current/2026-04-26-fixes-and-features.md`](../plans/current/2026-04-26-fixes-and-features.md) > §T2.6. Until that lands, the snippets above are the way. A more comprehensive Code-page guide (including this audio section, plus everything else the Code page lets you do) is on the roadmap; for now the **🧱 Builder**'s audio module is the smoothest path. ## ROM-size budget NES cartridges are small. The bar at the bottom of the Audio page shows you how much of the 32 KB program area your audio is using. A rough breakdown: - **Engine** — about 3.5 KB (always there when audio is on). - **Each song** — anywhere from 200 bytes (a short loop) to 2 KB+ (a fully orchestrated track with vibrato, slides, and arpeggios). - **SFX pack** — usually under 1 KB. The audit panel turns yellow above 12 KB so you've got room left over for game code, sprites, and backgrounds. If you go over, the build will fail with a "memory area overflow" error — remove a song or simplify one in FamiStudio and try again. ## Troubleshooting **"Could not find an .export line"** when uploading. The file you picked isn't a FamiStudio sound-engine `.s` export. Make sure you chose **Export → FamiStudio Sound Engine assembly** (or **Sound effects** for the sfx upload), not one of the other export options like NSF or WAV. **"The starter pack is missing on the server"** when you click the button. The starter `.s` files haven't been built yet — your teacher or whoever installed the editor needs to run `tools/audio/starter/build.sh` once. Authoring still works without the starter pack; just upload your own files. **The build fails after I upload audio** with a message about memory area overflow. You're over budget — remove a song or compress one in FamiStudio (fewer instruments, no slides/vibrato). **The build fails with `audio_songs.s(3): Error: Constant expression expected`.** This was the most common pupil-reported audio build failure (2026-04-27). Newer FamiStudio versions wrap their `.export` lines in an `.if FAMISTUDIO_CFG_C_BINDINGS` block, and ca65 errors when that symbol isn't defined. The playground server now auto-prepends a `FAMISTUDIO_CFG_C_BINDINGS = 0` definition to every staged audio file, which makes the assembler skip the wrapped exports cleanly — our editor's own alias trailer maps `audio_default_music` and `audio_sfx_data` to the right symbols directly, so the wrapped exports aren't needed anyway. If you're still seeing this error on a project from before the fix, hit **▶ Play in NES** to rebuild — no re-upload needed. **My sound effect plays silently in-game.** Check that you uploaded a sfx pack on the Audio page — the editor displays it as a card with the slot names you exported from FamiStudio. *(You no longer need to upload BOTH a song and a sfx pack for audio to engage — upload either one and the editor auto-stubs the other side. But you do need the sfx pack present if you want to actually hear sound effects.)* **My uploaded music doesn't play, but the starter pack does.** This is the most common pupil-reported audio bug. Causes, in the order to check. 1. **You uploaded music but no sound effects (pre-2026-04-27 only).** In an earlier version, audio only engaged when a project had *both* a song and a sound-effects pack uploaded — a pupil uploading just music got silence. This is now auto-fixed: the editor stubs in a silent sfx pack for you when one is missing, so a music-only project plays its music. If you're hearing silence on a project from before the fix, just hit **▶ Play in NES** to rebuild — no re-upload needed. 2. **Multiple songs in your FamiStudio project, with the leftover empty default at song 0.** When you create a new FamiStudio project it ships with a default song called *NewSong* (or similar). If you compose your music in a *second* song instead of overwriting that one, the export contains both, with the empty NewSong as song 0. Our editor currently always plays song 0, so you hear silence. **Fix.** Open your project in FamiStudio. Look at the **Songs** panel on the left. If there's more than one song, either: - Drag the song you want to play to the **top** of the list, **or** - Right-click any extra empty songs and choose **Delete song**. Then re-export. Best practice: keep one song per FamiStudio project so this can't happen. 3. **Wrong "machine" target on export.** FamiStudio's project properties has a *Machine* setting — NTSC, PAL, or Dual. Our sound engine is NTSC-only. If the project (or the export dialog) is set to PAL, the song's tempo header tells the engine "this is a PAL song" and our NTSC engine throttles itself to match — making playback very slow and often inaudibly quiet because envelopes don't tick fast enough to hear. **Fix.** In FamiStudio: **File → Project Properties → Machine → NTSC** (or, in the export dialog, pick the NTSC machine target). Re-export. 4. **Wrong export type.** FamiStudio has two engine exports — *FamiStudio Sound Engine* (the one we want) and *FamiTone2* (the older format). Both produce `.s` files that look superficially similar. If you exported FamiTone2 by mistake, the file uploads fine but the runtime engine reads the bytes the wrong way and the song plays as silence or noise. **Fix.** Re-export with **File → Export → FamiStudio Sound Engine assembly**, format = ca65. If you'd rather not guess: there's a diagnostic script under `tools/audio/diagnose_song.py` that scans a `.s` file for all of the above and tells you exactly what's wrong. From a terminal in the project directory: ```bash python3 tools/audio/diagnose_song.py path/to/pupil_song.s ``` It prints `OK` if nothing obvious is wrong, or specific warnings with fix steps if it finds one of the failure modes above. Teachers can pipe a folder of pupil exports through it to triage quickly. **The music tempo speeds up or slows down when lots of stuff is moving on-screen.** This happens in both the in-browser preview and the local FCEUX emulator, and it's the same root cause in each: when the player's frame has a lot of work to do (collision checks, moving sprites, scrolling), the main loop drops below 60 frames per second, and the FamiStudio engine — which gets called once per game frame — ticks more slowly along with it. We tried moving the engine onto the NES's hardware vblank interrupt to fix this, but the engine takes longer than the NES's tiny vblank window allows, and pushing it there caused background tiles to glitch on busy screens (you may have seen this in the very brief stretch between v1 and v2 of the audio update). The current arrangement keeps graphics rock-steady at the cost of mild tempo drift on heavy frames. Two ways to mitigate it if it bothers you: keep the scene lighter (fewer simultaneous moving sprites), or compose your song at a slightly slower BPM so the drift is less musically noticeable. **The music feels too fast.** FamiStudio bakes the song's tempo into the exported `.s` data — there's no runtime knob in the editor to override it. To change tempo, open the song in FamiStudio, click the song name in the left-hand panel, change the **BPM** field (default is often 150 BPM, which sounds quite brisk; 100-120 BPM is gentler for a slow-paced game), and re- export via *File → Export → FamiStudio Sound Engine assembly*. Upload the new `.s` over the old one on the Audio page. The *Cheerful loop* and *Tense loop* in the starter pack are 150 BPM — recompose them at whatever tempo fits your game. **The 🔊 / 🔇 button doesn't seem to do anything the first time.** Some browsers require the page to play a sound *once* before they fully unlock audio. Click ▶ Play, then the mute button — second click should toggle correctly. ## What's next Want to swap background music when you reach a new scene, or trigger a fanfare when the player wins? That's the Audio Builder module's job — it's queued for the next phase. Right now you can do the same things by writing a few lines of C on the Code page calling `famistudio_music_play(N)`. For more advanced FamiStudio features (DPCM samples, expansion-chip support, multi-channel music) see the FamiStudio docs at .