Adding Music with FamiStudio
No NES game is complete without music. Luckily, there's FamiStudio! This music tracker is custom-built for NES developers, and it does the job nicely. But I found the documentation a bit incomplete, especially when it comes to integrating music into your game, so I wrote this tutorial.
Exporting from FamiStudio
So you've got some cool chiptunes written in FamiStudio and you're ready to add them to your game as background music. Nice! The first step is to export the tracks in the right format. Luckily, FamiStudio makes this easy.
To open the Export Songs window, hit the button in the top left that looks like a document with a big right-facing arrow on it.
On the left side of the Export Songs window is a list of every format FamiStudio can export to. Choose FamiStudio Music Code. Exporting in this format will allow us to play tracks in our game using FamiStudio's NES music engine.
You'll now be presented with various options for your export. Select CA65 from the "Format" dropdown menu. (Obviously you would choose NESASM or ASM6 if you're using one of those compilers.) Leave "Separate Files" and "Generate song list include" unchecked.
Finally, you're given a list of all the songs in your project. Check all the ones you want to include in your game and uncheck the rest. If you have songs and sound effects in the same project, make sure you don't export any of the SFX tracks.
Hit the big checkmark icon to run the export. Give your file a nice, clear name like "mygame_music" and make sure to include an assembly language file extension (.s or .asm). This file will get included in your compiled ROM, so save it somewhere with the file structure of your game in development.
The Other File You Need
Now you have an assembly file containing all your music data. But how do we play it in our game? For that, we need the FamiStudio sound engine. Head over to the FamiStudio website, scroll down to the Downloads section, and download the NES Sound Engine (the one with a cool 6502 icon).
This gets you a .zip file that expands into a folder with lots of interesting stuff in it. For example, there's a demo ROM that lets you try out the FamiStudio engine's functionality by playing various music tracks and sound effects. But really, we only need one file: "famistudio_ca65.s". (Or, again, the file matching your assembler.) Grab this and move it to your development files, next to the music data file you exported from FamiStudio.
Setting Your Settings
To use the sound engine, there are a few settings we need to check before we integrate the files into our game.
First, open the music data file you exported ("mygame_music.s" or whatever). At the very top of this file, you'll see something like this (or at least you will as of version 4.20).
.if FAMISTUDIO_CFG_C_BINDINGS
.export _music_data_trapfinder=music_data_trapfinder
.endif
Go ahead and comment out these lines, or just delete them. They're meant to support code written in C, but in my
experience, the FAMISTUDIO_CFG_C_BINDINGS
variable can't be found, so the build breaks. Since we're not using C,
we can just remove this bit.
Now, open "famistudio_ca65.s". This is a nicely written bit of code with tons of comments to guide you, but we need to set a few settings for it to run properly.
First, scroll down to around line 76, the section titled "SEGMENT CONFIGURATION". This is where we tell the sound engine
where to find our zero page, RAM, and code segments. You'll see three .define
statements, one for each of those
three segments. For each, make sure the value following the variable name is the name of that segment in your code.
For example, my setup looks like this.
.define FAMISTUDIO_CA65_ZP_SEGMENT ZEROPAGE
.define FAMISTUDIO_CA65_RAM_SEGMENT BSS
.define FAMISTUDIO_CA65_CODE_SEGMENT CODE
These should match what's defined in the SEGMENT
portion of your .cfg file, as well as the .segment
statements throughout your code. For example, when I'm using the RAM segment, I call .segment "BSS"
.
Your zero page might be called ZEROPAGE
or ZP
, your RAM segment is likely
BSS
or RAM
, and so on. Don't use quotation marks in this config.
Immediately below this section in the sound engine is "AUDIO EXPANSION CONFIGURATION". We can ignore this for now; it's only relevant if your game uses a mapper with an audio expansion, like MMC5. Note that the sound engine only supports one extension—you can't turn on both MMC5 and VRC6, for example.
Next comes "GLOBAL ENGINE CONFIGURATION", with another set of options. Make sure the line for your TV type, PAL or NTSC, is live. The other type must be commented out. The next two options must be on if you plan to use sound effects as well as music. I'm less familiar with the rest, but you can safely leave them commented out.
Finally we reach "SUPPORTED FEATURES CONFIGURATION". I'm also not familiar with the meanings of all these options, but they have to do with various composition options within FamiStudio. Basically, if you used a certain option when creating any of your tracks, it must be enabled here, otherwise you'll get playback bugs. If you don't need an option, leave it commented out; this will save RAM in your compiled game.
If you're having playback issues or catching other bugs after adding music, this is a good place to look, especially if an
emulator debugger like MESEN's shows you randomly hitting BRK opcodes or the IRQ interrupt. You can easily debug by turning
these settings on and off. For example, to get music to play in my first game, I needed to activate FAMISTUDIO_USE_VOLUME_TRACK
.
Integrating the Sound Engine
At last, we're ready to integrate the engine into our code! First, let's talk about structure. I currently run all the audio functionality through a simple file called "audio.asm", which contains subroutines to initialize the sound engine, play different music tracks and sound effects, and so on. This file is added via my makefile, as is the sound engine file itself—but not the music data we exported.
...
ca65 src/audio/famistudio_ca65.s
ca65 src/audio/audio.asm
...
Over in "audio.asm", we bring in everything we'll need from the sound engine, as well as the music data file. All the subroutines being imported here are exported by "famistudio_ca65.s"—they are, essentially, the public API that the sound engine exposes. (If you're also using sound effects, there will be more to import. See here.)
...
.import famistudio_init
.import famistudio_music_play
.import famistudio_music_stop
.import famistudio_music_pause
.include "trapfinder_music.s"
...
Now all the pieces are in place, so let's make some music!
Initializing the Sound Engine
The first call we have to make to the sound engine is to famistudio_init. This subroutine sets up the music engine by telling it where to find the music data and whether to run in NTSC or PAL. We pass this information using the X, Y, and A registers.
.export audio_init
.proc audio_init
; load music data address from "mygame_music.s"
LDX #.lobyte(music_data_mygame)
LDY #.hibyte(music_data_mygame)
; non-zero for NTSC
LDA #1
; initialize music engine
JSR famistudio_init
RTS
.endproc
This is what my audio intialization subroutine looks like if we leave out the sound effects portion. It's fairly straightforward.
First, we load the low and high bytes of a data address into X and Y. This address points to the
music data in the "mygame_music.s" file, and if we look in that file, we'll see an export near the top called
music_data_mygame
. Yours will be different—"mygame" here is based on the project name you set in
FamiStudio—so get the correct export label from your data file. Our subroutine has access to this label because
we .include
d the music data file in the previous step.
All that's left is to set the TV type in the A register. Use 0 for PAL. For NTSC, any non-zero value will do.
We call the famistudio_init
subroutine, and that's it!
So when do we run this initialization routine? I call it from my top-level main routine, the one that runs the core game loop. At this point the reset interrupt has already run, but nothing else. Obviously we only need to call it once, so place it before the game loop begins.
Loading a Song
With the engine initialized, we can ask it to play us a tune. In the subroutine that runs when my title screen loads
(creatively named load_title_screen
) I make a call to another routine in "audio.asm", audio_title_screen
.
This tells the sound engine to play a specific track, which I've chosen as the title screen music. Let's see how it works.
.export audio_title_screen
.proc audio_title_screen
JSR famistudio_music_stop
LDA #0
JSR famistudio_music_play
RTS
.endproc
Again, pretty straightforward. First, it tells the engine to stop any music that might currently be playing. (I discovered through
experimentation that doing this before starting a new track is a necessity for a reliable song-switching experience.) Then we load
the track number—zero in this case—into register A and call famistudio_music_play
. And... that's it!
How do you know what the track numbers are? Look back at your music data file. The FamiStudio export kindly added comments above each
chunk in the music_data_mygame
export, giving the name and number of every track. Thanks, export!
Playing Sound
There's only one more step to get music running in our game, and it's a quick one. How does the sound engine know to step forward through a track, playing each note at the right time? For this, we'll rely on the NES's built-in clock: the NMI interrupt. (And all the hard work of FamiStudio's developers, of course.)
Over in your NMI interrupt code, import the famistudio_update
subroutine. Then, at the end of the interrupt handler itself,
call it.
JSR famistudio_update
That's it! This will trigger the sound engine during every NMI, moving music (and sound effect) tracks forward by one sixtieth-of-a-second frame. (Or fiftieth, if you're running on NTSC.)