3-D Sound Example 8 |
Mission :Let's change a little more of our source code to incorporate 3d sound
positioning!
Download the Expansion Pack!!
We are now going to cover the math that will enable us to dampen sounds according to their 3d
positions. In order to use 3d sound, we must have a DSP version that supports Stereo
mode. If the card doesn't support stereo mode, we can use the 3d distance to dampen the sound, but it won't achieve the
awesome 3d effect.Distance = sqrt((x*x)+(y*y)+(z*z));As you can see, it's only the distance formula for 2d points with the addition of the z extent. Now that we know how far the sound is, we will use that distance as an index in our Cos lookup table. Our lookup table can be however large we want. The bigger it is, the more precise our calculations will be. I decided to make it 500. Our lookup table isn't an ordinary table. We actually have it filled with Cos calculations from 0 to Pi/2. That produces a fall-off curve from 1 to 0 or in our usage from 100% to 0%! Now our sample will be as follows:
sample*= DampTable[Distance];By doing this multimplication, we are dampening the sound according to how far it is from us. In Mono mode, we will simply send this sample on its way after this little procedure. In Stereo mode, we have 2 channels to program, Left and Right. We now have to figure out which side of us the sound originated from. We only need to check the sign of the x extent of the 3d point to find this out. I decided to use the typical right handed coordinate system which places z positive in front of us, x positive to the right and y positive up. I then refer to the side the sound is on as the Major and the remaining side as the Minor side. Our little figure shows that our sound is originating from the left side.
By now we have our major side calculated, but how do we figure out what to send the other channel? If
we set the minor channel the same as the major, we could just as well be in Mono mode! We now
have to calculate the Sin of the angle between our left/right to the sound with the x axis being
0 at the right and 180 degrees at the left. Knowing the Sin of this angle, we can dampen the Minor
channel according to where it is located according to its x and z extents. I say the Sin of the angle
between our left/right because the sin fluxuates between 0 and 1 from 0 to 90 degrees and then it
goes from 1 to 0 from 90 to 180 degrees, so the two sides perfectly mirror one another! If you are
paying attention, you will realize that we have our dapening equation right at our finger tips! It
will be something like this:
sample*= DampTable[Distance]; minorsample = sample* Sin(Theta);We first use the same calculations that we used for the major side, then dampen that according where the sound is. Close your eyes and imaging a sound directly off your left shoulder. As it circles around you proceding right, your right ear starts to hear more of the sound until both left and right ears hear the same intensity. This will occur when the sound is directly in front of you, or in our case 90 degrees. As it proceeds toward your right, your left ear hears less and less until it hears nothing when the sound is located directly off your right shoulder. This may not be mathmatically perfect, but hey its darn close! Now that we have some idea how we are going to be using the sample data, let's get into the source code to make it happen!
typedef struct XYZLocation
{ long x,y,z;
XYZLocation()
{x=y=z=0;
}
};
typedef struct SampleHeader
{unsigned long Position;
unsigned long Length;
unsigned short SoundNumber;
XYZLocation *Location; // NEW to tutorial #8
long OldSample; //used for stereo mode
SampleHeader *Next;
SampleHeader *Previous;
SampleHeader()
{Position=Length=SoundNumber=0;
Next=Previous=NULL;
}
}Sampler,*Sample_ptr;
void Enable3dSound();
void Disable3dSound();
long Get3dSampleMono(Sample_ptr S);
long Get3dSampleStereo(Sample_ptr S);
long (SB16::*GrabSample)(Sample_ptr);
void Play(int sound_num,XYZLocation *);
void CreateDampTable();
char Sound3d,LeftChannel;
long *DampTable;
|
SB16::SB16()
{ ...
Sound3d=0; //Default non-3d oriented sound
LeftChannel=0; //Default starting with left in 3d mixing
MixingFunction=&SB16::FillBuffer8; //default 8 bit mixing scheme
GetSample=&SB16::GetSample8;
...
} |
void SB16::SetPtrFunctions()
{ if(ModeBits==16)
{MixingFunction=&SB16::FillBuffer16;
if(Sound3d)
{GrabSample=&SB16::GetSample16; //3d 16 Mono&Stereo
if(ModeStereoMono)
{GetSample=&SB16::Get3dSampleStereo; //3d 16 Stereo
}
else
{GetSample=&SB16::Get3dSampleMono; //3d 16 Mono
}
}
else
{GetSample=&SB16::GetSample16; //Non 3d Mono&Stereo
}
}
else
{MixingFunction=&SB16::FillBuffer8; //default 8 bit mixing scheme
if(Sound3d)
{GrabSample=&SB16::GetSample8; //3d 8 Mono&Stereo
if(ModeStereoMono)
{GetSample=&SB16::Get3dSampleStereo; //3d 8 Stereo
}
else
{GetSample=&SB16::Get3dSampleMono; //3d 8 Mono
}
}
else
{GetSample=&SB16::GetSample8;
}
}
}
|
void SB16::Enable3dSound()
{Sound3d=1;
CreateDampTable();
SetPtrFunctions();
}
void SB16::Disable3dSound()
{Sound3d=0;
SetPtrFunctions();
} |
void SB16::CreateDampTable()
{ DampTable = new long[500];
//making 500 increments of the + end of the Cos wave, from 0 -> Pi/2.
float step= (1.57079632679)/500.0,angle=0.0;
for(short k=0;k<500;k++)
{DampTable[k]=(long)(cos(angle)*0x10000); //angle in radians 0x10000 makes it 16:16 fixed
angle+=step;
}
} |
void SB16::Play(int sound_num,XYZLocation *P)
{SampleTail->Next = new SampleHeader();
SampleTail->Next->Previous=SampleTail;
SampleTail->Next->Next = NULL;
SampleTail->Next->Position=0;
SampleTail->Next->Location=P; //NEW!
SampleTail->Next->Length = Sounds[sound_num].Length;
SampleTail->Next->SoundNumber = sound_num;
SampleTail = SampleTail->Next;
} |
//ClipChar Changed
//ClipShort Changed
// Play function changed
//Added Leftchannel^=1 deal to 2 mixing routines above!
long SB16::Get3dSampleMono(Sample_ptr S)
{ long sample;
double Distance;
sample=(this->*GrabSample)(S);
if((S->Location->z > 500)||(S->Location->y >500)||(S->Location->x > 500))
{S->OldSample=0; //Too far away, return silence!
return 0;
}
Distance=sqrt((S->Location->x*S->Location->x)+
(S->Location->y*S->Location->y)+
(S->Location->z*S->Location->z));
if(Distance >500.0)
{S->OldSample=0; //Still too far away?
return 0;
}
else
{sample*=DampTable[Distance];
}
return sample;
} |
long SB16::Get3dSampleStereo(Sample_ptr S)
{ long sample;
double Distance,Sin,temp,hyp;
if(LeftChannel) //get sample : Left Channel
{sample=(this->*GrabSample)(S);
if((S->Location->z > 500)||(S->Location->y >500)||(S->Location->x > 500))
{S->OldSample=0;
return 0;
}
Distance=sqrt((S->Location->x*S->Location->x)+
(S->Location->y*S->Location->y)+
(S->Location->z*S->Location->z));
if(Distance >500.0)
{S->OldSample=0;
return 0;
}
hyp=sqrt((S->Location->x*S->Location->x)+(S->Location->z*S->Location->z));
if(hyp == 0)
{hyp = 1;
}
Sin=S->Location->z/hyp;
if(Sin < 0)
{Sin*=-1; //We don't want to invert our samples
}
//ASSERT: Sound is within range so lets start crunching numbers :)
temp=sample*DampTable[(short)Distance];
if(S->Location->x > 0) //positive x? if so it's to the right of us!
{/* Sound is to the right*/
S->OldSample=(long)temp;
return (long)((double)(temp*Sin));
}
else//Sound is to the left of us!
{ S->OldSample=(long)((double)(temp*Sin));
return (long)temp;
}
}
else //Everything was calculated when we did the left channel!
{return S->OldSample;
}
} |
void SB16::FillBuffer8()
{unsigned short i;
unsigned char *start=(unsigned char*)dma.MK_FP(dma.phys>>4,0);
SideToFill^=1;
if(SideToFill)
{ start+=HALFBUFFSIZE;
}
for(i=0;i<HALFBUFFSIZE;i++,start++)
{*start=ClipChar(MixSamples())+128;
LeftChannel^=1;
}
}
void SB16::FillBuffer16()
{char *start=(char*)dma.MK_FP(dma.phys>>4,0);
short sample=0,i;
SideToFill^=1;
if(SideToFill)
{ start+=HALFBUFFSIZE;
}
for(i=0;i<(HALFBUFFSIZE>>1);i++,start+=2)
{sample=ClipShort(MixSamples()); //variable clipping function?
*start= lobyte(sample);
*(start+1)=hibyte(sample);
LeftChannel^=1;
} |
char SB16::ClipChar(long num)
{if(Sound3d)
{ num>>=16;
}
if(num > 127)
{num = 127;
}
if(num < -128)
{num = -128;
}
return (char)num;
}
short SB16::ClipShort(long num)
{ if(Sound3d)
{ num>>=16;
}
if(num > 32767)
{num = 32767;
}
if(num < -32768)
{num = -32768;
}
return (short)num;
} |
