How to: Apply Attenuation and Doppler 3D Audio Effects

This example demonstrates how to use the Microsoft Cross-Platform Audio Creation Tool (XACT) to apply attenuation and Doppler 3D positioning effects to sounds.

Introduction

This topic builds on How to: Apply Basic 3D Positional Effects to a Cue, which discusses the steps required to add 3D audio to your game, and which introduces concepts such as XACT projects, the audio engine, cues, and emitter and listener positions.

3D game audio typically implements at least three effects: speaker positioning, volume attenuation over distance, and Doppler pitch shifting. The basic 3D audio how-to discusses speaker positioning. This topic explains how to add attenuation and Doppler effects to 3D audio.

At design time, the sound designer defines the attenuation and Doppler effects and applies them to specific sounds. The designer uses XACT Runtime Parameter Controls (RPCs) to specify the relationships between game information, exposed as XACT variables, and resulting sound effects. To determine final attentuation and Doppler effects at run time, the XACT engine combines the designer's effects with calculations based on position and velocity of the audio source and listener.

Sound designers can add attenuation without requiring any additional code changes by the developer (assuming that the game already sets the emitter and listener positions). Doppler effect, however, requires that XACT know the velocity of the emitter and listener, so the game must set the Velocity and Velocity properties.

Attenuation

In 3D audio, attenuation refers to volume attenuation—the effect of having volume fade with distance between emitter and listener. By default, XACT does not automatically apply volume attenuation as a result of calculating distance, or Doppler effect as a result of relative emitter and listener velocity.

Because XACT is designed to partition and simplify the work of the programmer and sound designer—the programmer controls the position and velocity of game objects, and the sound designer determines how that affects sound—XACT determines emitted sound by combining effects specified by the sound designer with positional information supplied by the game. XACT exposes variables that represent game information the designer uses to modify sounds. These variables are set at run time either by the game or XACT itself. The designer uses XACT RPCs to define the relationships between these variables and sound effects.

The project tree region of the XACT tool shows the available variables:

Bb447686.advanced_audio_project_tree(en-US,XNAGameStudio.10).png

Figure 1.  XACT Variables

Global variables are shared by all active sound cues. Only one of each global variable exists, and is persisted the entire time the game is running. In contrast, a separate copy of each Cue Instance variable exists for every active cue. Cue instance variables persist only for the lifetime of an individual cue instance.

Global variables allow multiple audio elements to be controlled together, while cue instance variables provide individual control or information for each instance of a cue, and for each copy of the same cue.

Bb447686.advanced_audio_variables(en-US,XNAGameStudio.10).bmp

Figure 2.  Cue Instance Variables

The variables used for 3D audio effects are the cue instance variables Distance, DopplerPitchScalar, and OrientationAngle. XACT updates these variables automatically for the cue when the game calls Apply3D.

The variables provide the following information.

Distance
the distance between the emitter and listener.
DopplerPitchScalar
A value calculated by comparing the relative velocities of the emitter and listener; used for Doppler pitch shifting.
OrientationAngle
The angle difference between the emitter and listener.

Though XACT updates the cue's Distance variable, it doesn't, by default, adjust the volume for that cue. And it isn't the developer's job to try to determine and set the appropriate volume. (The code could query distance with Cue.GetVariable("Distance"), and then, based on distance, try to set the volume of the cue. However, volume can be set only for categories of cues, defined by the sound designer. See How to: Change Sound Volume Levels.)

Instead, the sound designer defines the attenuation, which is the relationship between distance and volume. To do so, the designer uses the XACT tool to create an RPC. An RPC defines the mapping between an input variable, such as Distance, and an output parameter, such as sound volume.

To define the Distance scale

  1. Click once on the Distance variable in the tree.
  2. The properties pane, below the project tree, will display properties for the Distance variable.

  3. Set MinimumValue and MaximumValue to values appropriate to the game's world scale (or whatever scale is used when calling Apply3D).
  4. Distances outside the specified range are clamped to the range, so the designer needn't worry about extreme values when determining the curve.

For example, a game uses a 1 m (one meter) game scale, and sounds trail off completely near 1000 m. MinimumValue would be 0, and MaximumValue 1000.

Bb447686.advanced_audio_distance(en-US,XNAGameStudio.10).png

Figure 3.  The Distance Variable

To create a Distance::Volume RPC

  1. Right-click the RPC Presets node in the project tree and select New RPC Preset.
  2. XACT creates and displays a new RPC, and opens it for editing.

  3. Set the Parameter drop-down to Sound::Volume. Set the Variable drop-down to Distance (the default is OrientationAngle).
  4. The RPC now adjusts volume based on distance. However, the curve is created flat, which means that volume will remain constant over distance. To change that, and define the attenuation, you must adjust the curve.

    In the curve plot window, distance is on the x-axis and volume, in decibels, is on the y-axis. To attenuate over distance, Y (volume) should decrease as X (distance) increases.

  5. To create a new control point, right-click anywhere on the plot line and click Add. The curve is split and a new adjustable control point is added. Add and adjust as many curve points as needed to define the attenuation.

Different sounds may have different attenuation characteristics. These two example curves produce significantly different attenuation. The first creates a simple constant ramp from full volume (+0 dB) to silence (−96 dB) over the full distance range. The second has a fast rolloff after a short distance, then a fade out to full distance.

Bb447686.advanced_audio_RPC_attenuation1(en-US,XNAGameStudio.10).png

Figure 4.  Attenuation RPC (Constant Rolloff)


Bb447686.advanced_audio_RPC_attenuation2(en-US,XNAGameStudio.10).png

Figure 5.  Attenuation RPC (Quick Rolloff)

When finished, simply close the RPC window. The new RPC is called "RPC Preset", but can be renamed (to "Attenuation", for example) to make it easier to find in the RPC list. The name given to the RPC does not affect the RPC or the game code. To rename the RPC, right-click it and click Rename.

To apply the RPC to sounds

The RPC is now defined, but no sounds are configured to use it. By default, sounds do not use any of the defined RPCs. Some sounds will not need attenuation (for example, narration or tutorial instructions), while others may need several different RPC effects.

  1. Double-click on a sound bank listed in Sound Banks in the project tree.
  2. The sound bank opens, showing the sounds and cues defined in that bank.

  3. From RPC Presets in the project tree, drag the desired RPC onto the sound that will use that RPC. Do this for as many sounds as will use the attenuation effect.
  4. You can apply more than one RPC to a sound. RPC curves that modify elements such as pitch and volume are additive, so if multiple parameters influence the sound, their results will be combined.

To remove an RPC from a sound

  • Right-click the sound and click Attach/Detach RPC(s), or
  • Right-click the RPC in the project tree and click Attach/Detach Sounds.

At this point, the attenuation RPC is defined and applied to the appropriate sounds. XACT will now use the curve to adjust emitter volume based on the distance between emitter and listener. Game code sets the AudioEmitter.Position and AudioListener.Position properties to specify the 3D location of the game objects. When the code then calls Apply3D, XACT adjusts volume appropriately. See How to: Apply Basic 3D Positional Effects to a Cue for a description of the steps needed to use 3D positioning in a game.

Doppler Pitch Shifting

Doppler pitch shifting is the pitch shifting effect when a listener and an emitter are moving towards or away from each other. The relative velocities of the emitter and listener are compared and the result used to pitch up or down the emitter's sound.

Doppler pitch shifting is applied to sounds in the same way that attenuation is, except that the RPC adjusts pitch based on relative velocity rather than adjusting volume based on distance. The Doppler RPC ties the DopplerPitchScalar input variable to the Sound::Pitch output parameter.

To add Doppler pitch shifting

  1. Set the scale of the DopplerPitchScalar variable. The default scale of 0–4 is appropriate, so leave MinimumValue at 0 and MaximumValue at 4.
  2. The scale of DopplerPitchScalar is not based on the velocity scale used by the game. Instead, this value is the result of an XACT calculation to determine the pitch adjustment that should be applied due to Doppler effect. 0 indicates down 1 octave, 1 indicates unity pitch, and 4 indicates up 2 octaves.

  3. Create a new RPC by right-clicking RPC Presets and clicking New RPC Preset.
  4. Set Parameter to Sound::Pitch, and Variable to DopplerPitchScalar.
  5. Define the Doppler curve effect. The y-axis is the pitch, in semitones, and ranges from down one octave (−12 semitones) to up one octave (+12 semitones).
  6. For normal or "ideal" Doppler effect, the curve would look like this:

    Bb447686.advanced_audio_RPC_doppler(en-US,XNAGameStudio.10).png

    Figure 6.  Doppler RPC

  7. Close the RPC window and, if desired, rename the RPC.
  8. Associate the RPC with sounds. First, open the sound bank. Then, drag the RPC from the project tree onto the desired sound or sounds.

Code Changes to Support Doppler Pitch Shifting

XACT derives DopplerPitchScalar based on the relative velocities of the emitter and listener. The game informs XACT of these velocities by setting the AudioListener.Velocity and AudioEmitter.Velocity properties before calling Apply3D.

The AudioEmitter.DopplerScale property can be used to exaggerate (values > 1.0) or diminish (values < 1.0) the Doppler effect.

The following example moves an object left and right. The gamepad triggers can be used to turn emitter and listener velocities on and off. Combined with an XACT project that defines RPCs as described in this topic, the code demonstrates the steps needed to enable attenuation and Doppler effects.

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace Advanced3DAudio
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        ContentManager content;
        
        //Standard audio objects
        AudioEngine engine;
        SoundBank soundBank;
        WaveBank waveBank;

        AudioEmitter emitter = new AudioEmitter();
        AudioListener listener = new AudioListener();
        float maxEmitterDistance = 150.0f;
        float maxVelocity = 30.0f;

        //Cue to play in 3D
        Cue cue3d;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            content = new ContentManager(Services);
        }

        protected override void Initialize()
        {
            base.Initialize();

            //Initialize audio objects
            engine = new AudioEngine("Content\\Audio\\Advanced3DAudio.xgs");
            soundBank = new SoundBank(engine, "Content\\Audio\\Sound Bank.xsb");
            waveBank = new WaveBank(engine, "Content\\Audio\\Wave Bank.xwb");

            //Can only apply 3d to GetCue, not PlayCue
            cue3d = soundBank.GetCue("cue3d");

            emitter.Position = Vector3.Backward;
            listener.Position = Vector3.Zero;

            //Must call Apply3D before calling Play
            cue3d.Apply3D(listener, emitter);
            cue3d.Play();

        }
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            if (loadAllContent)
            {
            }
        }
        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            if (unloadAllContent)
            {
                content.Unload();
            }
        }
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            //Move the sound left and right out to maximum distance.
            emitter.Position = new Vector3(
                (float)Math.Cos(gameTime.TotalGameTime.TotalSeconds / 5.0f) *
                maxEmitterDistance, 0.0f, 1.0f);

            //Add velocity with left or right triggers.
            emitter.Velocity = new Vector3(maxVelocity *
                GamePad.GetState(PlayerIndex.One).Triggers.Left, 0.0f, 0.0f);
            listener.Velocity = new Vector3(-maxVelocity *
                GamePad.GetState(PlayerIndex.One).Triggers.Right, 0.0f, 0.0f);

            //Apply the settings to the cue.
            cue3d.Apply3D(listener, emitter);

            base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }
    }
}
Show: