initial spatial audio implementation

main
cosmonaut 2021-10-24 16:12:42 -07:00
parent b6715102ad
commit 4fceb026da
2 changed files with 290 additions and 46 deletions

View File

@ -121,6 +121,10 @@ typedef struct FAudioGMS_SoundInstance
uint8_t isStatic; uint8_t isStatic;
uint8_t destroyOnFinish; uint8_t destroyOnFinish;
uint8_t is3D;
F3DAUDIO_EMITTER* emitter; /* must not be NULL if is3D */
float stereoAzimuth[2];
union union
{ {
FAudioGMS_StaticSound *staticSound; FAudioGMS_StaticSound *staticSound;
@ -128,12 +132,20 @@ typedef struct FAudioGMS_SoundInstance
} parent; } parent;
} FAudioGMS_SoundInstance; } FAudioGMS_SoundInstance;
static const float DOPPLER_SCALE = 1.0f;
static const float CURVE_DISTANCE_SCALER = 1.0f;
typedef struct FAudioGMS_Device typedef struct FAudioGMS_Device
{ {
FAudio* handle; FAudio* handle;
F3DAUDIO_HANDLE handle3D;
FAudioDeviceDetails deviceDetails; FAudioDeviceDetails deviceDetails;
FAudioMasteringVoice *masteringVoice; FAudioMasteringVoice *masteringVoice;
F3DAUDIO_LISTENER listener;
float speedOfSound;
FAudioGMS_StaticSound **staticSounds; FAudioGMS_StaticSound **staticSounds;
uint32_t staticSoundCount; uint32_t staticSoundCount;
IdStack staticSoundIndexStack; IdStack staticSoundIndexStack;
@ -143,9 +155,35 @@ typedef struct FAudioGMS_Device
IdStack soundInstanceIndexStack; IdStack soundInstanceIndexStack;
} FAudioGMS_Device; } FAudioGMS_Device;
static FAudioGMS_Device *device = NULL; static FAudioGMS_Device* device = NULL;
void FAudioGMS_Init() static inline FAudioGMS_StaticSound* FAudioGMS_INTERNAL_LookupStaticSound(uint32_t id)
{
if (id >= 0 && id < device->staticSoundCount)
{
return device->staticSounds[id];
}
else
{
Log("Invalid StaticSound ID!");
return NULL;
}
}
static inline FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_LookupSoundInstance(uint32_t id)
{
if (id >= 0 && id < device->soundInstanceCount)
{
return device->soundInstances[id];
}
else
{
Log("Invalid SoundInstance ID!");
return NULL;
}
}
void FAudioGMS_Init(double speedOfSound)
{ {
device = SDL_malloc(sizeof(FAudioGMS_Device)); device = SDL_malloc(sizeof(FAudioGMS_Device));
@ -218,6 +256,28 @@ void FAudioGMS_Init()
return; return;
} }
device->speedOfSound = speedOfSound;
F3DAudioInitialize(
device->deviceDetails.OutputFormat.dwChannelMask,
device->speedOfSound,
device->handle3D
);
device->listener.OrientFront.x = 0;
device->listener.OrientFront.y = 0;
device->listener.OrientFront.z = -1;
device->listener.OrientTop.x = 0;
device->listener.OrientTop.y = 1;
device->listener.OrientTop.z = 0;
device->listener.Position.x = 0;
device->listener.Position.y = 0;
device->listener.Position.z = 0;
device->listener.Velocity.x = 0;
device->listener.Velocity.y = 0;
device->listener.Velocity.z = 0;
device->listener.pCone = NULL;
device->staticSounds = NULL; device->staticSounds = NULL;
device->staticSoundCount = 0; device->staticSoundCount = 0;
IdStack_Init(&device->staticSoundIndexStack); IdStack_Init(&device->staticSoundIndexStack);
@ -240,6 +300,10 @@ static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance* in
FAudioSourceVoice_Stop(instance->handle, 0, 0); FAudioSourceVoice_Stop(instance->handle, 0, 0);
FAudioSourceVoice_FlushSourceBuffers(instance->handle); FAudioSourceVoice_FlushSourceBuffers(instance->handle);
FAudioVoice_DestroyVoice(instance->handle); FAudioVoice_DestroyVoice(instance->handle);
if (instance->is3D)
{
SDL_free(instance->emitter);
}
SDL_free(instance->dspSettings.pMatrixCoefficients); SDL_free(instance->dspSettings.pMatrixCoefficients);
SDL_free(instance); SDL_free(instance);
} }
@ -247,12 +311,17 @@ static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance* in
void FAudioGMS_SoundInstance_Destroy(double id) void FAudioGMS_SoundInstance_Destroy(double id)
{ {
FAudioGMS_INTERNAL_SoundInstance_Destroy(device->soundInstances[(uint32_t)id]); FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id);
if (instance != NULL)
{
FAudioGMS_INTERNAL_SoundInstance_Destroy(instance);
}
} }
void FAudioGMS_SoundInstance_DestroyWhenFinished(double id) void FAudioGMS_SoundInstance_DestroyWhenFinished(double id)
{ {
FAudioGMS_SoundInstance *instance = device->soundInstances[(uint32_t)id]; FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id);
if (instance != NULL) if (instance != NULL)
{ {
@ -274,30 +343,13 @@ static void FAudioGMS_INTERNAL_StaticSound_Destroy(FAudioGMS_StaticSound* sound)
void FAudioGMS_StaticSound_Destroy(double id) void FAudioGMS_StaticSound_Destroy(double id)
{ {
if (id >= 0 && id < device->staticSoundCount)
{
FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[(uint32_t)id]); FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[(uint32_t)id]);
}
void FAudioGMS_Update()
{
uint32_t i;
for (i = 0; i < device->soundInstanceCount; i += 1)
{
FAudioGMS_SoundInstance* instance = device->soundInstances[i];
if (instance != NULL)
{
if (instance->destroyOnFinish)
{
FAudioVoiceState state;
FAudioSourceVoice_GetState(instance->handle, &state, FAUDIO_VOICE_NOSAMPLESPLAYED);
if (state.BuffersQueued == 0)
{
FAudioGMS_INTERNAL_SoundInstance_Destroy(instance);
}
}
} }
else
{
Log("Invalid ID for destroy!");
} }
} }
@ -375,7 +427,7 @@ double FAudioGMS_StaticSound_LoadWAV(char *filePath)
sound->buffer.LoopBegin = 0; sound->buffer.LoopBegin = 0;
sound->buffer.LoopCount = 0; sound->buffer.LoopCount = 0;
sound->buffer.LoopLength = 0; sound->buffer.LoopLength = 0;
sound->buffer.pAudioData = pSampleData; sound->buffer.pAudioData = (uint8_t*) pSampleData;
sound->buffer.pContext = NULL; sound->buffer.pContext = NULL;
sound->buffer.PlayBegin = 0; sound->buffer.PlayBegin = 0;
sound->buffer.PlayLength = 0; sound->buffer.PlayLength = 0;
@ -563,25 +615,67 @@ static void FAudioGMS_INTERNAL_SoundInstance_SetPan(FAudioGMS_SoundInstance* ins
); );
} }
static void FAudioGMS_INTERNAL_SoundInstance_SetPitch(FAudioGMS_SoundInstance* instance, float pitch) static void FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(FAudioGMS_SoundInstance* instance)
{ {
instance->pitch = pitch; float doppler = 1.0f;
pitch -= 1.0; /* default pitch is 1.0 as per GM standard */ if (!instance->is3D || DOPPLER_SCALE == 0.0f)
pitch = SDL_max(-1.0, SDL_min(1.0, pitch)); {
doppler = 1.0f;
}
else
{
doppler = instance->dspSettings.DopplerFactor * DOPPLER_SCALE;
}
FAudioSourceVoice_SetFrequencyRatio( FAudioSourceVoice_SetFrequencyRatio(
instance->handle, instance->handle,
SDL_powf(2.0f, pitch), SDL_powf(2.0f, instance->pitch - 1) * doppler, /* FAudio expects pitch range to be -1.0 to 1.0 while GM uses 0.0 to 2.0 so we adjust here */
0 0
); );
} }
static void FAudioGMS_INTERNAL_SoundInstance_SetPitch(FAudioGMS_SoundInstance* instance, float pitch)
{
pitch = SDL_max(0.0, SDL_min(2.0, pitch));
instance->pitch = pitch;
FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(instance);
}
static void FAudioGMS_INTERNAL_SoundInstance_SetVolume(FAudioGMS_SoundInstance* instance, float volume) static void FAudioGMS_INTERNAL_SoundInstance_SetVolume(FAudioGMS_SoundInstance* instance, float volume)
{ {
FAudioVoice_SetVolume(instance->handle, volume, 0); FAudioVoice_SetVolume(instance->handle, volume, 0);
} }
static void FAudioGMS_INTERNAL_Apply3D(FAudioGMS_SoundInstance* instance)
{
F3DAUDIO_EMITTER* emitter = instance->emitter;
if (emitter == NULL)
{
Log("Sound instance does not have 3D data! Did you forget to initialize?");
return;
}
F3DAudioCalculate(
device->handle3D,
&device->listener,
emitter,
F3DAUDIO_CALCULATE_MATRIX | F3DAUDIO_CALCULATE_DOPPLER,
&instance->dspSettings
);
FAudioGMS_INTERNAL_SoundInstance_UpdatePitch(instance);
FAudioVoice_SetOutputMatrix(
instance->handle,
device->masteringVoice,
instance->dspSettings.SrcChannelCount,
instance->dspSettings.DstChannelCount,
instance->dspSettings.pMatrixCoefficients,
0
);
}
static FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound( static FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(
FAudioGMS_StaticSound *staticSound, FAudioGMS_StaticSound *staticSound,
double pan, double pan,
@ -646,6 +740,11 @@ static FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_SoundInstance_CreateFromStati
instance->soundState = SoundState_Stopped; instance->soundState = SoundState_Stopped;
instance->is3D = 0;
instance->emitter = NULL;
instance->stereoAzimuth[0] = 0.0f;
instance->stereoAzimuth[1] = 0.0f;
if (device->soundInstanceIndexStack.count > 0) if (device->soundInstanceIndexStack.count > 0)
{ {
instance->id = IdStack_Pop(&device->soundInstanceIndexStack); instance->id = IdStack_Pop(&device->soundInstanceIndexStack);
@ -663,6 +762,47 @@ static FAudioGMS_SoundInstance* FAudioGMS_INTERNAL_SoundInstance_CreateFromStati
return instance; return instance;
} }
static void FAudioGMS_INTERNAL_StaticSound_AddEmitter(FAudioGMS_SoundInstance* instance, float x, float y, float z)
{
instance->is3D = 1;
instance->emitter = SDL_malloc(sizeof(F3DAUDIO_EMITTER));
/* defaults based on XNA behavior */
instance->emitter->pCone = NULL;
instance->emitter->ChannelRadius = 1.0f;
instance->emitter->pChannelAzimuths = instance->stereoAzimuth;
instance->emitter->pVolumeCurve = NULL;
instance->emitter->pLFECurve = NULL;
instance->emitter->pLPFDirectCurve = NULL;
instance->emitter->pLPFReverbCurve = NULL;
instance->emitter->pReverbCurve = NULL;
instance->emitter->InnerRadius = 0;
instance->emitter->InnerRadiusAngle = 0;
instance->emitter->ChannelCount = instance->dspSettings.SrcChannelCount;
instance->emitter->CurveDistanceScaler = CURVE_DISTANCE_SCALER;
instance->emitter->DopplerScaler = 1.0f;
instance->emitter->Position.x = x;
instance->emitter->Position.y = y;
instance->emitter->Position.z = z;
instance->emitter->Velocity.x = 0;
instance->emitter->Velocity.y = 0;
instance->emitter->Velocity.z = 0;
instance->emitter->OrientTop.x = 0;
instance->emitter->OrientTop.y = 1;
instance->emitter->OrientTop.z = 0;
instance->emitter->OrientFront.x = 0;
instance->emitter->OrientFront.y = 0;
instance->emitter->OrientFront.z = 1;
FAudioGMS_INTERNAL_Apply3D(instance);
}
static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance* instance) static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance* instance)
{ {
if (instance->isStatic) if (instance->isStatic)
@ -693,7 +833,7 @@ static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance* insta
void FAudioGMS_StaticSound_PlayOneOff(double staticSoundID, double pan, double pitch, double volume, double reverb) void FAudioGMS_StaticSound_PlayOneOff(double staticSoundID, double pan, double pitch, double volume, double reverb)
{ {
FAudioGMS_StaticSound* staticSound = device->staticSounds[(uint32_t)staticSoundID]; FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
if (staticSound != NULL) if (staticSound != NULL)
{ {
@ -707,9 +847,26 @@ void FAudioGMS_StaticSound_PlayOneOff(double staticSoundID, double pan, double p
} }
} }
void FAudioGMS_StaticSound_PlayOneOffSpatial(double staticSoundID, double x, double y, double z, double pitch, double volume, double reverb)
{
FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
if (staticSound != NULL)
{
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound, 0, pitch, volume, reverb);
instance->destroyOnFinish = 1;
FAudioGMS_INTERNAL_StaticSound_AddEmitter(instance, x, y, z);
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
}
else
{
Log("StaticSound_PlayOneOffSpatial: Invalid static sound ID! Did you destroy this sound?");
}
}
double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch, double volume, double reverb, double loop) double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch, double volume, double reverb, double loop)
{ {
FAudioGMS_StaticSound* staticSound = device->staticSounds[(uint32_t)staticSoundID]; FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
if (staticSound != NULL) if (staticSound != NULL)
{ {
@ -725,9 +882,28 @@ double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch
} }
} }
double FAudioGMS_StaticSound_PlaySpatial(double staticSoundID, double x, double y, double z, double pitch, double volume, double reverb, double loop)
{
FAudioGMS_StaticSound* staticSound = FAudioGMS_INTERNAL_LookupStaticSound((uint32_t)staticSoundID);
if (staticSound != NULL)
{
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_SoundInstance_CreateFromStaticSound(staticSound, 0, pitch, volume, reverb);
instance->loop = (uint8_t)loop;
FAudioGMS_INTERNAL_StaticSound_AddEmitter(instance, x, y, z);
FAudioGMS_INTERNAL_SoundInstance_Play(instance);
return (double)instance->id;
}
else
{
Log("StaticSound_PlaySpatial: Invalid static sound ID! Did you destroy this sound?");
return -1;
}
}
void FAudioGMS_SoundInstance_Play(double id) void FAudioGMS_SoundInstance_Play(double id)
{ {
FAudioGMS_SoundInstance *instance = device->soundInstances[(uint32_t)id]; FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id);
if (instance != NULL) if (instance != NULL)
{ {
@ -737,7 +913,7 @@ void FAudioGMS_SoundInstance_Play(double id)
void FAudioGMS_SoundInstance_Pause(double id) void FAudioGMS_SoundInstance_Pause(double id)
{ {
FAudioGMS_SoundInstance* instance = device->soundInstances[(uint32_t)id]; FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id);
if (instance != NULL) if (instance != NULL)
{ {
@ -758,7 +934,7 @@ void FAudioGMS_SoundInstance_Pause(double id)
void FAudioGMS_SoundInstance_Stop(double id) void FAudioGMS_SoundInstance_Stop(double id)
{ {
FAudioGMS_SoundInstance* instance = device->soundInstances[(uint32_t)id]; FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)id);
if (instance != NULL) if (instance != NULL)
{ {
@ -773,19 +949,82 @@ void FAudioGMS_SoundInstance_Stop(double id)
} }
} }
void FAudioGMS_SoundInstance_Set3DPosition(double soundInstanceID, double x, double y, double z)
{
FAudioGMS_SoundInstance* instance = FAudioGMS_INTERNAL_LookupSoundInstance((uint32_t)soundInstanceID);
if (instance != NULL)
{
if (!instance->is3D)
{
Log("Not a 3D sound!");
return;
}
instance->emitter->Velocity.x = x - instance->emitter->Position.x;
instance->emitter->Velocity.y = y - instance->emitter->Position.y;
instance->emitter->Velocity.z = z - instance->emitter->Position.z;
instance->emitter->Position.x = x;
instance->emitter->Position.y = y;
instance->emitter->Position.z = z;
}
}
void FAudioGMS_SetListenerPosition(double x, double y, double z)
{
device->listener.Velocity.x = x - device->listener.Position.x;
device->listener.Velocity.y = y - device->listener.Position.y;
device->listener.Velocity.z = z - device->listener.Position.z;
device->listener.Position.x = x;
device->listener.Position.y = y;
device->listener.Position.z = z;
}
void FAudioGMS_Update()
{
uint32_t i;
for (i = 0; i < device->soundInstanceCount; i += 1)
{
FAudioGMS_SoundInstance* instance = device->soundInstances[i];
if (instance != NULL)
{
if (instance->is3D)
{
FAudioGMS_INTERNAL_Apply3D(instance);
}
if (instance->destroyOnFinish)
{
FAudioVoiceState state;
FAudioSourceVoice_GetState(instance->handle, &state, FAUDIO_VOICE_NOSAMPLESPLAYED);
if (state.BuffersQueued == 0)
{
FAudioGMS_INTERNAL_SoundInstance_Destroy(instance);
}
}
}
}
}
void FAudioGMS_Destroy() void FAudioGMS_Destroy()
{ {
uint32_t i = 0; uint32_t i = 0;
for (i = 0; i < device->staticSoundCount; i += 1)
{
FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[i]);
}
for (i = 0; i < device->soundInstanceCount; i += 1) for (i = 0; i < device->soundInstanceCount; i += 1)
{ {
FAudioGMS_INTERNAL_SoundInstance_Destroy(device->soundInstances[i]); FAudioGMS_INTERNAL_SoundInstance_Destroy(device->soundInstances[i]);
} }
for (i = 0; i < device->staticSoundCount; i += 1)
{
FAudioGMS_INTERNAL_StaticSound_Destroy(device->staticSounds[i]);
}
FAudio_Release(device->handle); FAudio_Release(device->handle);
SDL_free(device); SDL_free(device);
device = NULL; device = NULL;

View File

@ -39,20 +39,25 @@
extern "C" { extern "C" {
#endif /* __cplusplus */ #endif /* __cplusplus */
FAUDIOGMSAPI void FAudioGMS_Init(); FAUDIOGMSAPI void FAudioGMS_Init(double speedOfSound); /* recommend about 1500? for 2D game */
FAUDIOGMSAPI void FAudioGMS_Update();
FAUDIOGMSAPI double FAudioGMS_StaticSound_LoadWAV(char *filePath); /* returns a static sound ID */ FAUDIOGMSAPI double FAudioGMS_StaticSound_LoadWAV(char *filePath); /* returns a static sound ID */
FAUDIOGMSAPI void FAudioGMS_StaticSound_PlayOneOff(double staticSoundID, double pan, double pitch, double volume, double reverb); /* automatically frees itself when done! */ FAUDIOGMSAPI void FAudioGMS_StaticSound_PlayOneOff(double staticSoundID, double pan, double pitch, double volume, double reverb); /* automatically frees itself when done! */
FAUDIOGMSAPI void FAudioGMS_StaticSound_PlayOneOffSpatial(double staticSoundID, double x, double y, double z, double pitch, double volume, double reverb); /* automatically frees itself when done! */
FAUDIOGMSAPI double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch, double volume, double reverb, double loop); /* returns a sound instance ID. must be freed! */ FAUDIOGMSAPI double FAudioGMS_StaticSound_Play(double staticSoundID, double pan, double pitch, double volume, double reverb, double loop); /* returns a sound instance ID. must be freed! */
FAUDIOGMSAPI double FAudioGMS_StaticSound_PlaySpatial(double staticSoundID, double x, double y, double z, double pitch, double volume, double reverb, double loop); /* returns a sound instance ID. must be freed! */
FAUDIOGMSAPI void FAudioGMS_StaticSound_Destroy(double id);
FAUDIOGMSAPI void FAudioGMS_SoundInstance_Play(double id); FAUDIOGMSAPI void FAudioGMS_SoundInstance_Play(double id);
FAUDIOGMSAPI void FAudioGMS_SoundInstance_Pause(double id); FAUDIOGMSAPI void FAudioGMS_SoundInstance_Pause(double id);
FAUDIOGMSAPI void FAudioGMS_SoundInstance_Stop(double id); FAUDIOGMSAPI void FAudioGMS_SoundInstance_Stop(double id);
FAUDIOGMSAPI void FAudioGMS_SoundInstance_Set3DPosition(double soundInstanceID, double x, double y, double z);
FAUDIOGMSAPI void FAudioGMS_SoundInstance_Destroy(double id); FAUDIOGMSAPI void FAudioGMS_SoundInstance_Destroy(double id);
FAUDIOGMSAPI void FAudioGMS_SoundInstance_DestroyWhenFinished(double id); FAUDIOGMSAPI void FAudioGMS_SoundInstance_DestroyWhenFinished(double id);
FAUDIOGMSAPI void FAudioGMS_StaticSound_Destroy(double id);
FAUDIOGMSAPI void FAudioGMS_SetListenerPosition(double x, double y, double z);
FAUDIOGMSAPI void FAudioGMS_Update();
FAUDIOGMSAPI void FAudioGMS_Destroy(); FAUDIOGMSAPI void FAudioGMS_Destroy();
#ifdef __cplusplus #ifdef __cplusplus