Layered Music - Part 2Following on from the
previous post, we have a multi-channel music file and need to play it correctly in-game.
Adjusting the channel volumes during playbackI use the
FMOD API for audio playback. So I'll be talking about my usage of that API specifically. I don't know what capabilities other audio systems have.
I won't discuss the work required to actually load and play the music file - so let's assume that's already working!
Our example music file has the following channels, representing 4 versions of the song, each in stereo. These are the
input channels.
1. Beach (Left)
2. Beach (Right)
3. Jungle (Left)
4. Jungle (Right)
5. Underground (Left)
6. Underground (Right)
7. Plains (Left)
8. Plains (Right)
And the game outputs in stereo, so there are two
output channels.
1. Left
2. Right
The function that's needed to alter which channels are heard is
Channel::setMixMatrix. This allows you to freely control how much of each of the input channels is heard on each of the output channels! For example:
Applying the above mix matrix sends 1.0 (full amount) of the Beach L channel to the Left speaker output, and 1.0 of the Beach R channel to the Right speaker output. All other channels send nothing to any of the output channels. So this will sound like just the Beach version of the music is playing on its own.
For each version of the music I define one of these mixes. Mine are
defined in XML as part of the data which describes all the sounds and music in the game. You can define it however you want, or hardcode it into the code, it's not important.
The above mix causes the Jungle version of the song to play.
And finally in order to fade between songs, I apply a new mix matrix every frame, where each value of the mix matrix is interpolated between the Beach mix and the Jungle mix. The mix below is 60% through the fade; so Beach has been reduced to a volume of 0.4 and Jungle has increased to a volume of 0.6.
Channel OrderingThere's one last thing to cover - a detail that I glossed over previously.
In
this image in the first post I showed that when exporting the multi-channel music file from Audacity, Beach L is being put into Channel 1, Beach R is being put into Channel 2, etc.
And in the mix matrix examples above, I said that Beach L is in Channel 1, Beach R is in Channel 2, etc.
However this isn't quite correct. The channel numbering is actually different between the Audacity export and the FMOD mix matrix.
We need to look at the
ogg format specification to see what the meaning of each of the channels is. We are using 8 channels which is interpreted by the format to mean the file is in 7.1 surround sound.
eight channels
the stream is 7.1 surround. channel order: front left, center, front right, side left, side right, rear left, rear right, LFE
And then we can look at the documentation for the
FMOD_SPEAKER enum which reveals the order in which FMOD refers to channels.
typedef enum {
FMOD_SPEAKER_FRONT_LEFT,
FMOD_SPEAKER_FRONT_RIGHT,
FMOD_SPEAKER_FRONT_CENTER,
FMOD_SPEAKER_LOW_FREQUENCY,
FMOD_SPEAKER_SURROUND_LEFT,
FMOD_SPEAKER_SURROUND_RIGHT,
FMOD_SPEAKER_BACK_LEFT,
FMOD_SPEAKER_BACK_RIGHT,
I exported Beach R in Channel 2, which in the ogg format represents the "center", which is actually Channel 3 on the FMOD side. So if I try to play the music file back I'll actually end up with a really weird combination of the left/right channels of different versions of the track playing at the same time.
I don't want to fix this by changing the way the mix matrices are laid out, because I like the mix matrices to be easy to read. So I'll fix it by exporting the file differently.
Beach L | FMOD Channel 1 ("FMOD_SPEAKER_FRONT_LEFT") | Export to Ogg Channel 1 ("front left") |
Beach R | FMOD Channel 2 ("FMOD_SPEAKER_FRONT_RIGHT") | Export to Ogg Channel 3 ("front right") |
Jungle L | FMOD Channel 3 ("FMOD_SPEAKER_FRONT_CENTER") | Export to Ogg Channel 2 ("center") |
Jungle R | FMOD Channel 4 ("FMOD_SPEAKER_LOW_FREQUENCY") | Export to Ogg Channel 8 ("LFE") |
Underground L | FMOD Channel 5 ("FMOD_SPEAKER_SURROUND_LEFT") | Export to Ogg Channel 6 ("rear left") |
Underground R | FMOD Channel 6 ("FMOD_SPEAKER_SURROUND_RIGHT") | Export to Ogg Channel 7 ("rear right") |
Plains L | FMOD Channel 7 ("FMOD_SPEAKER_BACK_LEFT") | Export to Ogg Channel 4 ("side left") |
Plains R | FMOD Channel 8 ("FMOD_SPEAKER_BACK_RIGHT") | Export to Ogg Channel 5 ("side right") |
The above mapping of the audio channels to the correct export channel looks like this in Audacity:
It's a mess but, hey, it works! I don't know if this will even be useful to anyone, but it's a look inside a small part of the game's development which is the whole point of this devlog.
The result, againHere's the final result of this whole thing:
The final numbers displayed in the black and white text at the top of the screen - all the 1.0s and 0.0s - are the current mix matrix. You can see how the matrix changes as it fades between each version of the song.
Thanks for reading!