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 AudioEmitter.Velocity and AudioListener.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 as placeholders for runtime game information such as position and velocity. The sound designer uses XACT RPCs to define the relationships between those variables and sound effects. The variables are then set at run time, either by the game or XACT itself, and produce the desired sounds.

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

Bb447686.advanced_audio_project_tree(en-US,XNAGameStudio.20).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.20).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.

VariableDescription
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. It isn't the developer's job to try to determine and set the appropriate volume. 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.

(The programmer might be tempted to query distance with Cue.GetVariable("Distance"), and then, based on distance, try to set the volume of the cue. However, volume can normally be set only for categories of cues, defined by the sound designer. See How To: Change Sound Volume Levels. The game code would also have to calculate the proper volume change based on distance. To avoid these issues, simply use an RPC as described in this topic and let XACT perform attenuation automatically, as designed.)

To Create an Attenuation RPC

  1. Define an appropriate scale for the Distance variable.
  2. Create an RPC that adjusts the Sound::Volume parameter based on Distance.
  3. Apply the RPC to desired sounds.

Each step is discussed further, following.

To define the Distance scale

  1. Click once on the Distance variable in the tree.

    The properties pane, below the project tree, will display properties for the Distance variable. Look for the section named General.

  2. Inside the General section, find Variable Range. The left box is the minimum value, and the right box is the maximum value. Enter values into these boxes that are appropriate to the game's world scale (or whatever scale is used when calling Apply3D).

    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. The minimum value would be 0, and the maximum value would be 1000.

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

    Figure 3.  The Variable Range

To create a Distance::Volume RPC

  1. Right-click the RPC Presets node in the project tree and select New RPC Preset.

    XACT creates and displays a new RPC, and opens it for editing.

  2. Set the Variable drop-down to Distance (the default is OrientationAngle). Set the Object drop-down to Sound. Set the Parameter drop-down to Volume.
  3. 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.

    To create a new control point, double-click anywhere on the plot line. 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.20).png

    Figure 4.  Attenuation RPC (Constant Rolloff)

     

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

    Figure 5.  Attenuation RPC (Quick Rolloff)

  4. 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.

    The sound bank opens, showing the sounds and cues defined in that bank.

  2. 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.

    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.

    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.

  2. Create a new RPC by right-clicking RPC Presets and clicking New RPC Preset.
  3. Set Variable to DopplerPitchScalar, Object to Sound, and Parameter to Pitch.
  4. 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).

    For normal Doppler effect, the beginning of the curve would look like this:

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

    Figure 6.  Doppler RPC

    However, this curve limits the effect to +12 semitones. To enable a wider range effect, make the curve half as steep (+12 semitones at 4.0 rather than 2.0) and set the emitter's Doppler scale (AudioEmitter.DopplerScale) to 2.0f.

  5. Close the RPC window and, if desired, rename the RPC.
  6. 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.

The Complete Sample

The code in this tutorial illustrates the technique described in the text. A complete code sample for this tutorial is available for you to download, including full source code and any additional supporting files required by the sample.

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

namespace Advanced3DAudio
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;

        // Audio objects
        AudioEngine engine;
        SoundBank soundBank;
        WaveBank waveBank;

        // 3D audio objects
        AudioEmitter emitter = new AudioEmitter();
        AudioListener listener = new AudioListener();
        Cue cue;

        float maxEmitterDistance = 150.0f;
        float maxVelocity = 30.0f;

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

        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");

            // Set emitter and listener position.
            emitter.Position = Vector3.Backward;
            listener.Position = Vector3.Zero;

            // Get the cue and play it.
            // For 3D cues, you must call Apply3D before calling Play.
            cue = soundBank.GetCue("buzz");
            cue.Apply3D(listener, emitter);
            cue.Play();
        }

        protected override void LoadContent()
        {
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            // Allow the game to exit.
            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 3D settings to the cue.
            cue.Apply3D(listener, emitter);

            // Update the audio engine.
            engine.Update();

            base.Update(gameTime);
        }

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

Community Additions

ADD
Show: