Skip to content

Instantly share code, notes, and snippets.

@DevWouter
Last active February 17, 2022 11:02
Show Gist options
  • Save DevWouter/4c77532fc10c220709f4ad1920e46a6b to your computer and use it in GitHub Desktop.
Save DevWouter/4c77532fc10c220709f4ad1920e46a6b to your computer and use it in GitHub Desktop.
////////////////////////////////////////////////////////////////
// Below is an example of how Audio drivers work in video games
// and how you interact with them. It's a bit simplified but if
// you have no prior knowledge it should give you some general
// idea how it works and what the limitations are.
//
// A more complex example would go more in to details about:
// - How a DspNode works (here we only use it)
// - Events in music files (for example: "Loop this part 4 times
// and then start back at the beginning")
// - MultiPlexing (for example: surround->stereo while keeping
// the illusion of surround)
////////////////////////////////////////////////////////////////
// Create the audioSystem and retrieve our first audio driver
// listed by the OS.
var audioSystem = new AudioSystem();
var driver = audioSystem.GetDrivers().First();
////////////////////////////////////////////////////////////////
// Initializing the driver and normally we can provide it some parameters.
// What it does:
// - It creates the buffers that the hardware reads to play sound
// - They are multiple ones
// - These buffers are small (as in 100ms of data)
// - It does multiplexing depending on whether the hardware wants mono/stereo/surround
// - From this point forward you application is already playing sound.
// - But the buffers are empty so you won't hear anything
// - It will swap in the next buffer once the hardware is done with the previous buffer
// - Fun trick: Try pausing any application using a process manager while it has
// dedicated access to the hardware, you will often hear it loop the last swapped
// in buffer.
driver.Init();
////////////////////////////////////////////////////////////////
// Tell the driver how many channels we expect to *hear* at the same time
// With `3` we can only play 3 channels at the time. Normally in games we
// use a higher number
const int maxChannels = 3;
driver.SetMaxChannels(maxChannels);
////////////////////////////////////////////////////////////////
// Setting the mix matrix of channels. This is naive version
// but it gives you a good idea of the challenge when mixing
// multiple voices/channels.
// The max volume the hardware is always 100% and if we go above
// that we get clipping (a distortion in the output). Below is a
// really safe example where we assume that all tracks might
// require the max volume. normally we take a bit more risk.
float[][] mixMatrix = new float[maxChannels][maxChannels]{
{ 1.0f, 0.0f, 0.0f},
{ 0.5f, 0.5f, 0.0f},
{ 0.4f, 0.3f, 0.3f},
};
// The below example is a bit more risky since the mix config
// when all channels play goes to 120%. However clipping occurs
// when all track require require the max volume and with more
// channels that is less likely to happen.
// The first row also uses only 70% to prevent the user from
// having to constantly changing the volume when a lot of things
// on the screen happen.
float[][] altMixMatrix = new float[maxChannels][maxChannels]{
{ 0.7f, 0.0f, 0.0f}, // Max volume of speaker = 70%, mostly to prevent volume flucation
{ 0.5f, 0.4f, 0.0f}, // Max volume of speaker = 90%, the first channel becomes a bit softer
{ 0.4f, 0.4f, 0.4f}, // Max volume of speaker = 120%, we risk clipping
};
driver.SetMixMatrix(mixMatrix);
////////////////////////////////////////////////////////////////
// Create the DSP (Digital signal processor) for the music
// It's the default one but we add a processor that can perform music fade.
// Each DspNode receives an array of bits and is allowed to modify it.
var dsp_msc = driver.CreateDsp();
var dsp_msc_fade = dsp.CreateProcessorNode<FadeProcessor>();
var dsp_msc_in = dsp.GetInputNode();
var dsp_msc_out = dsp.GetOutputNode();
dsp_msc.BreakConnection(dsp_msc_in, dsp_msc_out);
dsp_msc.CreateConnection(dsp_msc_in, dsp_msc_fade);
dsp_msc.CreateConnection(dsp_msc_fade, dsp_out);
// Diagram of the DSP for music
//
// (IN)---(FADER)---(OUT)
//
////////////////////////////////////////////////////////////////
// Creating the "sound" audio banks
// sounds means we try avoid unloading it since we need it all the time)
var snd_explosion = driver.CreateSoundFromFile("sfx/explosion.wav");
var snd_bulletfire = driver.CreateSoundFromFile("sfx/bullet_fire.wav");
var snd_footsteps = driver.CreateSoundFromFile("sfx/footsteps.wav");
////////////////////////////////////////////////////////////////
// Creating the "music" audio banks
// Meaning we don't fully load the file and stream what we need from disk.
var msc_relax = driver.CreateStreamFromFile("music/relax-music.mp3");
var msc_fight = driver.CreateStreamFromFile("music/fight-music.mp3");
////////////////////////////////////////////////////////////////
// Create the group that plays music
// and by setting them to the max priority we always include them in the mix
var msc_group = driver.CreateChannelGroup();
msc_group.SetPriority(256);
msc_group.SetDsp(dsp_msc);
////////////////////////////////////////////////////////////////
// Start playing some music
var chl_msc_ctrl = msc_group.Add(msc_relax);
chl_msc_ctrl.SetDspAttribute(dsp_fade, "fade-in", 3_000); // Fade in 3 seconds
chl_msc_ctrl.SetLooping(true);
chl_msc_ctrl.SetPosition(0); // Start at the beginning
chl_msc_ctrl.Play();
////////////////////////////////////////////////////////////////
// Example of how we switch music.
Events.OnEnteringCombat += () => {
// Send stop signal to channel.
// The channel will be removed and released once stopped.
var old_ctrl = chl_msc_ctrl;
var fadeOutDuration = 5_000;
old_ctrl.SetDsp(dsp_fade, "fade-out", fadeOutDuration);
old_ctrl.Stop(new {Delay=fadeOutDuration});
// Start thew new music
chl_msc_ctrl = msc_group.Add(msc_fight);
chl_msc_ctrl.SetDspAttribute(dsp_fade, "fade-in", 3_000); // Fade in 3 seconds
chl_msc_ctrl.SetLooping(true);
chl_msc_ctrl.SetPosition(0); // Start at the beginning
chl_msc_ctrl.Play();
};
////////////////////////////////////////////////////////////////
// Creating the DSP for sound effects
var dsp_sfx = driver.CreateDsp();
var dsp_sfx_in = dsp_sfx.GetInputNode();
var dsp_sfx_out = dsp_sfx.GetOutputNode();
var dsp_sfx_3dPosition = dsp_sfx.CreateProcessorNode<Pos3dProcessor>();
var dsp_sfx_echo = dsp_sfx.CreateProcessorNode<EchoProcessor>();
var dsp_sfx_mix = dsp_sfx.CreateMixNode();
// The player should hear the music instantly with 3d positioning, but also an echo
dsp_sfx.BreakConnection(dsp_sfx_in, dsp_out);
dsp_sfx.CreateConnection(dsp_sfx_in, dsp_sfx_3dPosition);
dsp_sfx.CreateConnection(dsp_sfx_3dPosition, dsp_sfx_mix);
dsp_sfx.CreateConnection(dsp_sfx_3dPosition, dsp_sfx_echo);
dsp_sfx.CreateConnection(dsp_sfx_echo, dsp_sfx_out);
dsp_sfx.CreateConnection(dsp_sfx_mix, dsp_sfx_out);
// We also setup the default parameters
dsp_sfx_mix.SetAttribute("MixMatrix", new []{0.8f, 0.2f});
dsp_sfx_echo.SetAttribute("Delay", 200); // 200ms
// Diagram of the DSP for SFX
// /---(ECHO)---\
// (IN)---(3D)---| |---(MIX)---(OUT)
// \------------/
////////////////////////////////////////////////////////////////
// Create the sfx group
// It has a lower priority since we rather miss a sound effect than music.
var sfx_group = driver.CreateChannelGroup();
sfx_group.SetPriority(128);
sfx_group.SetDsp(dsp_sfx);
Events.PlayerMove += ()=>{
dsp_sfx_3dPosition.SetAttribute("OutPos", Player.Pos);
var chl = sfx_group.Add(snd_footsteps);
chl.SetDspAttribute(dsp_sfx_3dPosition, "InPos", Player.Pos);
// Override the other effects since the player is nearby
chl.SetDspAttribute(dsp_sfx_echo, "Delay", 50);
chl.SetDspAttribute("MixMatrix", new []{0.9f, 0.1});
// Since we are not looping, the channel can use the DSP to determine when the track
// has been stopped and when it can be removed.
chl.Play();
}
Events.EnemyMoves += (enemy)=>{
var chl = sfx_group.Add(snd_footsteps);
chl.SetDspAttribute(dsp_sfx_3dPosition, "InPos", enemy.Pos);
chl.SetVolume(0.5f); // Enemy is farther away so make foot steps more silent.
chl.Play();
}
Events.PlayerFiresGun += () => {
var chl = sfx_group.Add(snd_bulletfire);
chl.SetDspAttribute(dsp_sfx_3dPosition, "InPos", Player.Pos);
chl.Play();
};
Events.ExplodingBarrel += (barrel)=>{
// The barrel explodes in many pieces. We now try to play 5 channels/voices
// but keep in mind that we only allow 3 at the same time
// And since we always have music, that leaves us with only two if nobody moves or shoots.
for(var i = 0; i < 5; ++i){
var pos = barrel.Pos + Vector3f.RandomOffset(min: 0.2f, max: 1.0f);
var chl = sfx_group.Add(snd_explosion);
chl.SetDspAttribute(dsp_sfx_3dPosition, "InPos", pos);
chl.Play();
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment