Directional or Projected Sound with SDL

Hey

I’ve modified the typical “play” function for SDL to support directional or
projected sound and volume changes. In other words, the function can change
the balance between left and right speaker and adjust the overall volume.
This is done independantly of each channel, so that each channel is
unaffected by the balance and volume of other sound channels

This function will load wave files to the memory and then scale the right or
left channel in memory before playing. It’s a terrible design, but I
understand SDL doesn’t support this feature on it’s own. This is presently
the best I’ve managed to come up with.
The soundfiles must be in stereo for the balance to work. I could make the
function duplicate the mono channel for mono files. This will be on the TODO
list.

This function works for smaller wave files without any noticable realtime
lag, but only because I have a dual core x gazillion Hz processor and a few
gig ram.

There are no handling of oposite endianesses. It works on my system with my
wave files, but that may be a coincidence. The function needs a lot more
error and situation handling.

I’d love to have comments on the code, and if anyone knows a better way to
solve the issue, please let me know.

I chose to let balance range from -1 to 1 so that a simple sine function of
the angle of an object in 2D/3D can be used as input.
With a linear attenuation of volume reaching 0 at 500, for example:

volume = (500.0-distance) / 500.0;
if (volume < 0.0) volume = 0.0;
PlaySoundStereo(“mysound.wav”, cos(angle), volume)

void PlaySoundStereo(char *file, float balance, float volume)
{
//float position; //-1 to 1 -1 = left, 0 = center, 1 = right
//float volume //0 to 1

float leftvolume;
float rightvolume;

int index;
SDL_AudioSpec wave;
Uint8 *data;
Uint32 dlen;
SDL_AudioCVT cvt;
Uint32 i;
short *int16;
char *int8;
int leftchannel = 1;

leftvolume = volume * ((1.0f - balance ) / 2.0f);
rightvolume = volume * ((balance + 1.0f) / 2.0f);

/* Look for an empty (or finished) sound slot */
for ( index=0; index<NUM_SOUNDS; ++index ) {
if ( sounds[index].dpos == sounds[index].dlen ) {
break;
}
}
if ( index == NUM_SOUNDS )
return;

/* Load the sound file and convert it to 16-bit stereo at 22kHz */
if ( SDL_LoadWAV(file, &wave, &data, &dlen) == NULL ) {
fprintf(stderr, “Couldn’t load %s: %s\n”, file, SDL_GetError());
return;
}

//// START - Ingvar’s code for resampling sound file in memory ("data")
according to balance and volume. /////////////
if (wave.channels == 1) {
//No balance adjustments for mono, yet.
if (wave.format == AUDIO_S8) {
int8 = (char
) (data);
for (i=0; i<dlen/4; i++) {
(*int8) = (char)((float)(int8) * volume);
int8++;
}
} else {
int16 = (short
) (data);
for (i=0; i<dlen/2; i++) {
(*int16) = (short)((float)(int16) * volume);
int16++;
}
}
} else if (wave.channels == 2) {
/

wave.format:
AUDIO_S8 - Signed 8-bit samples
AUDIO_U16 or AUDIO_U16LSB - Unsigned 16-bit little-endian samples
AUDIO_S16 or AUDIO_S16LSB - Signed 16-bit little-endian samples
AUDIO_U16MSB - Unsigned 16-bit big-endian samples
AUDIO_S16MSB - Signed 16-bit big-endian samples
AUDIO_U16SYS - Either AUDIO_U16LSB or AUDIO_U16MSB depending on you
systems endianness
AUDIO_S16SYS - Either AUDIO_S16LSB or AUDIO_S16MSB depending on you
*/

if (wave.format == AUDIO_S8) {
  int8 = (char*) (data);
  for (i=0; i<dlen/4; i++) {
    if (leftchannel) {
      (*int8) = (char)((float)(*int8) * leftvolume);
    } else {
      (*int8) = (char)((float)(*int8) * rightvolume);
    }
    int8++;
    leftchannel = !leftchannel;
  }
} else {
  int16 = (short*) (data);
  for (i=0; i<dlen/2; i++) {
    if (leftchannel) {
      (*int16) = (short)((float)(*int16) * leftvolume);
    } else {
      (*int16) = (short)((float)(*int16) * rightvolume);
    }
    int16++;
    leftchannel = !leftchannel;
  }
}

}
//// END - Ingvar’s code for resampling sound file in memory ("*data")
according to balance and volume. /////////////

SDL_BuildAudioCVT(&cvt, wave.format, wave.channels, wave.freq, AUDIO_S16,
2, 22050);
cvt.buf = malloc(dlen*cvt.len_mult);
memcpy(cvt.buf, data, dlen);
cvt.len = dlen;
SDL_ConvertAudio(&cvt);
SDL_FreeWAV(data);

/* Put the sound data in the slot (it starts playing immediately) */
if ( sounds[index].data ) {
free(sounds[index].data);
}
SDL_LockAudio();
sounds[index].data = cvt.buf;
sounds[index].dlen = cvt.len_cvt;
sounds[index].dpos = 0;
SDL_UnlockAudio();
}

Hey

Hehe, cool little function :slight_smile: I do panning during mixing, and don’t
have a use for an all-in-one function myself, but I’ll comment on the
code since you asked.

I’ve modified the typical “play” function for SDL to support
directional or projected sound and volume changes. In other words,
the function can change the balance between left and right speaker
and adjust the overall volume.

I want to point out something you may or may not already know.:
Panning isn’t the same thing as directional audio. 3D (directional)
audio uses positional cues such as phase and reflections (echoes) in
addition to panning and attenuation. This means it needs a concept of
a sound space, to properly create the reflections, and some sort of
HRTF (see head-related transfer function on Wikipedia). It is quite
complicated and most code does not do it terribly well. I don’t mean
this as a criticism–it’s hard to do well! But for an example, you
can look at OpenAL, if you haven’t before.

In other words, you’re doing panning and attenuation, and this can be
used as a crude form of directional audio, but it is not what people
normally mean when they say directional audio.

This is done independantly of each channel, so that each channel is
unaffected by the balance and volume of other sound channels

This function will load wave files to the memory and then scale the
right or left channel in memory before playing. It’s a terrible
design, but I understand SDL doesn’t support this feature on it’s
own. This is presently the best I’ve managed to come up with.
The soundfiles must be in stereo for the balance to work. I could
make the function duplicate the mono channel for mono files. This
will be on the TODO list.

This function works for smaller wave files without any noticable
realtime lag, but only because I have a dual core x gazillion Hz
processor and a few gig ram.

There are no handling of oposite endianesses. It works on my system
with my wave files, but that may be a coincidence. The function
needs a lot more error and situation handling.

I’d love to have comments on the code, and if anyone knows a better
way to solve the issue, please let me know.

I have a couple comments on your code. I’ll cut out the parts I’m not
responding to. Also, sorry, I suck at this email client and I screwed
up the quoting a little. I had to reinstall everything here due to a
hard drive dying . . . ;-(

I chose to let balance range from -1 to 1 so that a simple sine
function of the angle of an object in 2D/3D can be used as input.
With a linear attenuation of volume reaching 0 at 500, for example:

volume = (500.0-distance) / 500.0;
if (volume < 0.0) volume = 0.0;
PlaySoundStereo(“mysound.wav”, cos(angle), volume)

Attenuation can be modeled a few ways, such as logarithmically, again
depending on the sound space, but it’s not normally linear.

leftvolume = volume * ((1.0f - balance ) / 2.0f);
rightvolume = volume * ((balance + 1.0f) / 2.0f);

Oops! When you pan something, you need to use a non-linear “panning
law!” Normally one uses the square root panning law, though it is not
written in stone. Here’s how it works: A^2 + B^2 = C^2, where A and
B are the channel volumes and C is the total volume. Hence, when you
hard-pan something left or right, the volume on one channel is 1, and
the volume on the other is 0; when it is in the center, the volume
should be sqrt(2) on both channels. This creates a 3dB drop per
channel, which gives you a perceived 0dB center sound.

So, you need to take the square root of both sides. (Other panning
laws exist, such as sine; you can search for panning law on Google.)

/* Load the sound file and convert it to 16-bit stereo at 22kHz */

Does the existing function hard-code 22khz as well?

if ( SDL_LoadWAV(file, &wave, &data, &dlen) == NULL ) {
fprintf(stderr, “Couldn’t load %s: %s\n”, file, SDL_GetError());
return;
}

Hey, you have some error handling :wink:

//// START - Ingvar’s code for resampling sound file in memory
("*data") according to balance and volume. /////////////

Technically, if this is C (meaning C90), you should stick to /* */
comments and not // comments.

Anyway, that is all I had to say, so I snipped the rest of the code.

-CrystalOn Aug 27, 2008, at 4:22 AM, Ingvar Tjostheim wrote: