Background music

In this section we're going to add some music and sound effects to the game to make it feel more alive.

First we'll put some sound effects when the ball hits a paddle, and then we'll add some background music.

Audio in agb

In agb, audio is managed through the Mixer. Create a mixer from the Gba struct, passing through the frequency you intend to use. For this section, we'll use 32768Hz.

Get yourself a mixer by adding this near the beginning of your main function.


#![allow(unused)]
fn main() {
use agb::sound::mixer::Frequency;

let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
}

In order to update the mixer and keep it playing audio constantly without skipping, you need to call the mixer.frame() method every frame.

It is best to call this right before frame.commit(). So let's do this now and add:


#![allow(unused)]
fn main() {
mixer.frame(); // new code here
frame.commit();
}

Generating the wav files

agb can only play wav files. You can download the file from here, or generate the same sound yourself on sfxr.

The final file should go in the sfx directory in your game.

The file must be a 32768Hz wav file. Any other frequency will result in the sound being played at a different speed than what you would expect. You can use ffmpeg to convert to a file with the correct frequency with a command similar to this:

ffmpeg -i ~/Downloads/laserShoot.wav -ar 32768 sfx/ball-paddle-hit.wav

Importing the sound effect

Import the wav file using include_wav!().


#![allow(unused)]
fn main() {
use agb::{include_wav, mixer::SoundData};

static BALL_PADDLE_HIT: SoundData = include_wav!("sfx/ball-paddle-hit.wav");
}

Playing the sound effect

To play a sound effect, you need to create a SoundChannel.


#![allow(unused)]
fn main() {
use agb::sound::mixer::SoundChannel;

let hit_sound = SoundChannel::new(BALL_PADDLE_HIT);
mixer.play_sound(hit_sound);
}

We'll do this in a separate function:


#![allow(unused)]
fn main() {
fn play_hit(mixer: &mut Mixer) {
    let hit_sound = SoundChannel::new(BALL_PADDLE_HIT);
    mixer.play_sound(hit_sound);
}
}

and add the play_hit(&mut mixer) call where you handle the ball paddle hits.

Background music

Because the GBA doesn't have much spare CPU to use, we can't store compressed audio as background music and instead have to store it as uncompressed. Uncompressed music takes up a lot of space, and the maximum cartridge size is only 32MB, so that's as much as you can use.

Therefore, most commercial games for the GBA use tracker music for the background music instead. These work like MIDI files, where rather than storing the whole piece, you instead store the instruments and which notes to play and when. With this, you can reduce the size of the final ROM dramatically.

Composing tracker music is a topic of itself, but you can use the example here for this example. Copy this to sfx/bgm.xm and we'll see how to play this using agb.

Firstly you'll need to add another crate, agb-tracker.

cargo add agb_tracker

Then import the file:


#![allow(unused)]
fn main() {
use agb_tracker::{Track, include_xm};

static BGM: Track = include_xm!("sfx/bgm.xm");
}

You can create the tracker near where you enable the mixer:


#![allow(unused)]
fn main() {
use agb_tracker::Tracker;

let mut tracker = Tracker::new(&BGM);
}

and then to actually play the tracker, every frame you need to call .step(&mut mixer). So put the call to tracker.step() above the call to mixer.frame()


#![allow(unused)]
fn main() {
tracker.step(&mut mixer);
mixer.frame();
}

You will now have background music playing.

What we did

We've now got some sound effects playing when the ball hits the paddle, and some background music playing. Next we'll add score tracking and finish off the game.

Exercise

Add a new sound effect for when the ball hits the wall rather than a paddle.