Audio in “Momentum”

 

In This Post
  • Sound Design and Mechanics implemented in Momentum
  • Code samples and video demo.

Onto the article:

When I first started working on the idea for the speed-based platforming game that would become Momentum, I knew that properly utilizing audio feedback in the game would be just as important as the visual feedback. The game is about going really fast, and when you’re going really fast, there’s a lot of sounds being thrown at you; the sound of the wind rushing by you, all the sounds of the various things you’re passing rushing by you, the roar of your engine, etc. Sound is important to help immerse the player in the feeling of speed, so I knew properly utilizing sound would do a lot to make our game as good as it could be.

 

In the beginning of the project, I focused mainly on the core engine architecture (discussed in more detail in this post). After the first two milestones, where the engine was in place, and we had things moving on a screen, we moved towards getting gameplay in place. At this time, I refactored the rudimentary audio system we had to be far more flexible. Our engine used the FMOD Sound System API for its audio, instead of the FMOD Studio API; both I and the other sound designer on the team had already been working in the engine for a couple of months, and bringing on the Studio integration would have been somewhat of a waste of time, considering that Studio uses its own middleware. Sound System lets you use the low-level C++ functionality, which we can utilize much better for what we wanted to do.

 

In my refactoring, I decided to wrap the FMOD interface in our own audio system; essentially just putting all the C++ functionality in our own functions in our system, and calling those (which call the FMOD functions). This way, there’s never any FMOD code anywhere else in the engine, and in the future, if we ever wanted to bring a non-dev sound designer on the team, we could switch to Studio without much difficulty; we would just change the body of the functions to use the Studio API, and not change our system’s interface, so we wouldn’t have to rewrite a lot of code. Here’s an example of our system interface:

//All content © 2014 DigiPen (USA) Corporation, all rights reserved.
/******************************************************************************/
/*!
\par    Team Continuum
\file   soundmanager.h
\author Izzy Abdus-Sabur
\par    i.abdussabur@digipen.edu
\date   10/11/2014

\brief  Contains interface of the audio system and the soundemitter
component.

*/
/******************************************************************************/
#pragma once


#include "../System.h"
#include "../../../GameTypes/Component/component.h"
#include "../../ResourceLoader/resourceloader.h"

#define MAX_CHANNELS 32
#define MAX_DSPS 32
#define MAX_GROUPS 4
namespace Global
{
  /// \brief  The background volume.
  extern float bgVolume;

  /// \brief  The effects volume.
  extern float fxVolume;
}

namespace FMOD
{
  class Sound;
  class System;
  class Channel;
  class ChannelGroup;
  class DSP;
}

namespace Engine
{
  /// \brief  Values that represent SoundTypes.
  enum SoundTypes
  {
    ST_SONG = 0,
    ST_EFFECT
  };

  enum ChannelGroupType
  {
    CG_BGSound = 0,
    CG_SFX,
    CG_GameSounds,
    CG_Extra
  };

///////////////////////////////////////////////////////////////////////////////
/// \class SoundEmitter
///
/// \brief The component through which sound is played.
///
/// \author Izzy Abdus-Sabur
/// \date 2014/10
///////////////////////////////////////////////////////////////////////////////
  class SoundEmitter : public Component
  {
  public:
    SoundEmitter();
    ~SoundEmitter();

    virtual SoundEmitter* Initialize(void);
    virtual void Serialize(Serializer& file);
    virtual void ReverseSerialize(ReverseSerializer& file);

    ///////////////////////////////////////////////////////////////////////////////
    /// \brief  Given a message of a filename, play the music file associated
    ///         with that filename.
    ///
    /// \param  message
    ///         A message containing a filename.
    ///
    /// \return void
    ///////////////////////////////////////////////////////////////////////////////
    void Play(Message &message);

    /// \brief  Returns whether or not the soundemitter is currently paused.
    bool isPaused();

    /// \brief  Pauses the soundemitter.
    void Pause();

    /// \brief  Unpauses the soundemitter.
    void UnPause();

    /// \brief  The sound.
    FMOD::Sound *sound;

    // \brief   An int associated with the enum SoundTypes.
    int soundType;

    /// \brief  The channel id for this sound emitter.
    int channelID;

    /// \brief  The channel group this sound emitter is playing through.
    int channelGroupID;

    /// \brief  Whether or not the sound associated with this sound emitter has been played.
    bool firstPlayed;

    /// \brief  The path to the current sound file.
    std::string soundpath;
  };


  ///////////////////////////////////////////////////////////////////////////////
  /// \class Audio
  ///
  /// \brief the master audio system that is updated during the game loop,
  ///        you have to do all audio manipulation through this system
  ///
  /// \author Izzy Abdus-Sabur
  /// \date 2015/01/14
  ///////////////////////////////////////////////////////////////////////////////
  class Audio : public System
  {
  public:
    Audio();
    ~Audio();

    virtual void Initialize();

    virtual void Update();

    /// \brief  This gives a unique channel ID, and marks that ID number as in use.
    int giveChannel();

    /// \brief  This releases the channel ID from use.
    void releaseChannel(int channel);

    /// \brief  This gives out a DSP to be used that isn't already in use.
    int giveDSP();

    /// \brief  This releases the DSP at the given index from memory.
    void releaseDSP(int index);

    /// \brief  The core FMOD system. Created in Initialize function
    FMOD::System *system;


    bool givenChannels[MAX_CHANNELS];
    FMOD::Channel* channels[MAX_CHANNELS];
    FMOD::ChannelGroup* groups[MAX_GROUPS];
    FMOD::DSP *dsp_list[MAX_DSPS];
    

    void pauseChannelGroup(int chID);
    void unpauseChannelGroup(int chID);

    /// \brief  This stops all sound playing at the master level; this should
    ///         only be used when changing levels.
    void cutoffSound();

  private:
    /// \brief  This is a helper function that creates the channel groups upon
    ///         system initialization.
    void assignChannelGroups();
  };

  extern Audio* AUDIO;
  
}


In the example link, you’ll see that no functions return or take in any FMOD structs; to play music or activate effects, you would only use our interface. However, if you want to specifically do certain things utilizing the FMOD API, you have to go through the system; as such, the only other files using FMOD functions are the components utilizing sound system’s other added FMOD-specific abilities.

 

So after doing this to make the system more flexible, I moved on beginning to implement the first of the more “advanced” audio effects we could use. The first thing I did was add the FMOD DSP functionality to our system, and utilized that to put a low-pass filter on all sound. Said low pass filter’s cutoff frequency was linked to the player’s current speed; as the player’s speed increased, the cutoff frequency would go up. The output of this was that when the player wasn’t moving, all sound was muffled, and when the player started to pick up speed, they would hear all sound as it was naturally meant to be; this was created to incentivize the player to move faster, as if moving faster was the natural thing to do (the point of the game). This had to be tweaked a little bit so that the effect was dramatic enough that the player would take notice the moment they started moving; during the course of our game, the max speed of the player gets higher (through powerups), so to hear audio as clear as possible you would have to collect as many powerups as possible. Further examples:
BgLowPassLogic* BgLowPassLogic::Initialize(void)
{
  obj_owner->owner->hookEvent(Engine::EV_Update, ComponentFunctionPtrFast(Update));

  // this creates the dsp (using FMOD's type defines), and makes sure it's active
  Engine::AUDIO->system->createDSPByType(FMOD_DSP_TYPE_LOWPASS_SIMPLE, &Engine::AUDIO->dsp_list[dspIndex]);
  Engine::AUDIO->dsp_list[dspIndex]->setBypass(false);

  // then assigns the dsp to the correct channel group
  Engine::AUDIO->groups[Engine::CG_BGSound]->addDSP(0, Engine::AUDIO->dsp_list[dspIndex]);

  // then initializes the correct cutoff frequency
  Engine::AUDIO->dsp_list[dspIndex]->setParameterFloat(FMOD_DSP_LOWPASS_SIMPLE_CUTOFF, startCutoff);

  return this;
}

 

void BgLowPassLogic::Update(Engine::Message &mes)
{
  mes;

  // getting the player's Body component and PlayerController component
  Engine::Body *playerBody = CompFromObj(Engine::Body, playerPointer);
  PlayerController* player = CompFromObj(PlayerController, playerPointer);

  // reading the player's current velocity
  float curPlayerVel = abs(playerBody->GetVelocity().GetX());

  // calculating the change to the cutoff frequency
  float cuttoffAddition = speedCutoffScale*(curPlayerVel - player->maxGroundSpeed / 2);

  // if this is negative, make it 0
  if (cuttoffAddition < 0)
    cuttoffAddition = 0;

  // then set the cutoff parameter
  Engine::AUDIO->dsp_list[dspIndex]->setParameterFloat(FMOD_DSP_LOWPASS_SIMPLE_CUTOFF, (startCutoff + (cuttoffAddition)));
  
}
All of this logic is on a component that would go on a background “manager” object, it functioned simply by being present in the level, because it accesses the audio system directly. In addition to this (as you might’ve seen above), I added the FMOD ChannelGroup functionality, allowing us to apply the previous effect to a specific channel group, instead of all sound.

 

An additional effect to help incentivize the player to move faster was the addition of mixing the background track using game logic. Normally, in game audio, any soundtrack will have already been mixed; all the layers will have been added together and the track will have been exported. In our game, however, all layers are exported individually, and the layers are mixed at run-time together using a state machine. The way we are utilizing this technology is to slowly add additional layers, or more advanced versions of previous layers, to the background track, as the player picks up speed powerups. Every time they pick up enough, and cross a certain threshold, another background track is added. Here’s a video showing this (and the low pass functionality) in action. The video given is from an extremely early build of our game, so a lot of the appearance is placeholder, and the song in the video was quickly thrown together to test this functionality. Both songs used are not owned or created by me. I just picked a song I liked, so that when it worked, the result was rewarding.
 
 

This basically covers all the audio functionality currently in the game; I have a lot planned for later, like adding 2D positional sound to the game, so that as the player passes enemies, or anything that would emit sound, they can literally hear that sound source passing from ear to ear. I also plan to utilize FMOD’s runtime spectrum analysis on the background track, and use that data to change visual effects(imagine something like the Itunes or Windows Media Player visualizer), either to change particle emitters, or to change the color of the background/stage platforms. The previous effect would actually work hand in hand with the layering effect, so that even the visuals of the game would react to the player getting faster.

 

All in all, I have a lot of fun stuff planned for the game, to make the experience of going really really fast as fun and immersive as possible.