Playing audio using Libdragon

Libdragon has an audio subsystem that uses the Reality Signal Processor (RSP) to play and mix audio. This page will cover how to get some basic music played on your Nintendo 64.

How it works

The audio subsystem is a bit unintuitive if you’re used to using more modern (or higher level) audio processors since you don’t really have to deal with buffers and mixers, so I’ll take a brief moment to go through how it works on the Nintendo 64 once you have your audio files compiled into the Dragon File System (DFS):

Here’s a bit of a summary of what is happening:

  1. Audio is loaded from the DFS to a data structure in memory.
  2. When ready to play, each track is played into the mixer.
  3. The mixer combines audio signals and places them in a free audio buffer.
  4. The audio output plays the buffers in the order in which they were written.

The audio buffer

The audio buffer is an array of memory that is used to store audio samples in preparation to be pushed to the audio output. You can select the amount of buffers with the audio_init() function, and it will automatically determine their size based on the frequency.

The synchronisation between the mixer, audio buffer and audio output is important. If you queue up too many buffers in advance, you’ll experience audio lag. If you have too few, you will run out of audio data and you’ll get some dead air. Generally speaking, 4 is a good number of buffers for general use. If you experience lag or skips, try changing the number.

This animation shows the relationship between the three components. The RSP-based mixer writes to the buffer at a variable rate depending on how complex the mixing is. The output then reads from the buffer at a constant rate in the order in which the buffers were written.

Starting and stopping the audio subsystem

The first step to working with audio on the Nintendo 64 is by starting the subsystem using these two functions. You’ll need to initialise both the audio system and the mixer for it to work.

// Start the audio subsystem
void audio_init (const int frequency, int numbuffers);
void mixer_init (int num_channels);

When choosing parameters for the audio_init() function, it’s best to leave it at 44100 and 4 unless otherwise needed. Similarly with the mixer, initialise the channels that you need (as a power of 2).

Playing the files

Getting the audio from the file in the ROM DFS to the audio interface output takes a few steps.

Opening the files

WAV, XM and YM files all have a similar format for loading them onto RAM.

First you need to create a structure to hold the data for the audio file. This will include any waveforms and notes that the file contains. Each file format is structured differently so they each need their own data structure.

Then you need to open the file. This is fairly straightforward since it works just like opening any file from the DFS. The only exception is ymplayer_open() which requires a ym64player_songinfo_t, which is a structure that holds the song’s metadata.

// Load XM file
xm64player_t xm;
xm64player_open(&xm, "rom:/turkish_march.xm64");

// Load YM file
ym64player_t ym;
ym64player_songinfo_t yminfo;
ym64player_open(&ym, "rom:/darkness.ym64", &yminfo);

// Load WAV file
wav64_t wav;
wav64_open(&wav, "rom:/sample.wav64");

Pushing to the mixer & audio buffer

The last step in getting your Nintendo 64 to sing is to get the audio data from the files to the mixer and audio buffer.

You start this process by playing the file. This can be done right after loading it in the previous step.

xm64player_play(&xm, 0);
ym64player_play(&ym, 0);
wav64_play(&wav, 0);

Note that I’m setting the channel to 0 for simplicity’s sake, but if you want to play more than one file at once, we’ll need to configure the mixer channels, which we will cover later.

Next, we need to push the played audio into the mixer, which in turn will push it to the audio output.

// Main loop
while(1) {
	// Check if there are any free audio buffers
	if (audio_can_write()) {
		// Select an audio buffer that we can write to
		short *buf = audio_write_begin();
		// Write to the audio buffer from the mixer
		mixer_poll(buf, audio_get_buffer_length());
		// Tell the audio system that the buffer has
		// been filled and is ready for playback
		audio_write_end();
	}
}

And now your audio should play!

Example

Here is a full example of the most basic audio playback system which combines each of the above bits of code. Note that I only included the XM file and that you’ll actually need to put the XM file in the DFS for it to work.

#include <libdragon.h>

int main(void) {
	audio_init(44100, 20);
	mixer_init(32);
	mixer_ch_set_limits(6, 0, 128000, 0);
	dfs_init(DFS_DEFAULT_LOCATION);

	xm64player_t xm;
	xm64player_open(&xm, "rom:/turkish_march.xm64");
	xm64player_play(&xm, 0);

	while(1) {
		if (audio_can_write()) {
			short *buf = audio_write_begin();
			mixer_poll(buf, audio_get_buffer_length());
			audio_write_end();
		}
	}
}

Stopping and closing things out

Once you’re done with playing all the audio you need, it’s time to close your files. This can be any time you don’t need the audio anymore for example the end of a level. It is important to close your audio files since they do take up quite a lot of RAM while they’re active.

These functions will stop actively playing audio.

// Stop a WAV file
void mixer_ch_stop(int ch);

// Stop the XM player
void xm64player_stop(xm64player_t *player);

// Stop the YM player
void ym64player_stop(ym64player_t *player);

The following functions will clear the audio from memory, allowing something else to take its place. Note that WAV files can only be closed in the Unstable branch, there is no equivalent in Trunk at the moment.

// This function to close a WAV file only exists in the unstable branch
void wav64_close(wav64_t *wav);

// Close an XM file
void xm64player_close(xm64player_t *player);

// Close a YM file
void ym64player_close(ym64player_t *player);

If you want to stop all audio functionality, you can close the audio subsytem and the mixer. This isn’t very common since you’re probably going to want to keep it open until the console shuts down.

void audio_close(void);
void mixer_close(void);

Search

Subscribe to the mailing list

Follow N64 Squid

  • RSS Feed
  • YouTube

Random posts