350 lines
14 KiB
C++
350 lines
14 KiB
C++
|
/* ========================================================================
|
||
|
$File: $
|
||
|
$Date: $
|
||
|
$Revision: $
|
||
|
$Creator: Casey Muratori $
|
||
|
$Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $
|
||
|
======================================================================== */
|
||
|
|
||
|
internal void
|
||
|
OutputTestSineWave(game_state *GameState, game_sound_output_buffer *SoundBuffer, int ToneHz)
|
||
|
{
|
||
|
int16 ToneVolume = 3000;
|
||
|
int WavePeriod = SoundBuffer->SamplesPerSecond/ToneHz;
|
||
|
|
||
|
int16 *SampleOut = SoundBuffer->Samples;
|
||
|
for(int SampleIndex = 0;
|
||
|
SampleIndex < SoundBuffer->SampleCount;
|
||
|
++SampleIndex)
|
||
|
{
|
||
|
// TODO(casey): Draw this out for people
|
||
|
#if 1
|
||
|
real32 SineValue = sinf(GameState->tSine);
|
||
|
int16 SampleValue = (int16)(SineValue * ToneVolume);
|
||
|
#else
|
||
|
int16 SampleValue = 0;
|
||
|
#endif
|
||
|
*SampleOut++ = SampleValue;
|
||
|
*SampleOut++ = SampleValue;
|
||
|
|
||
|
#if 1
|
||
|
GameState->tSine += Tau32*1.0f/(real32)WavePeriod;
|
||
|
if(GameState->tSine > Tau32)
|
||
|
{
|
||
|
GameState->tSine -= Tau32;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal playing_sound *
|
||
|
PlaySound(audio_state *AudioState, sound_id SoundID)
|
||
|
{
|
||
|
TIMED_FUNCTION();
|
||
|
|
||
|
if(!AudioState->FirstFreePlayingSound)
|
||
|
{
|
||
|
AudioState->FirstFreePlayingSound = PushStruct(AudioState->PermArena, playing_sound);
|
||
|
AudioState->FirstFreePlayingSound->Next = 0;
|
||
|
}
|
||
|
|
||
|
playing_sound *PlayingSound = AudioState->FirstFreePlayingSound;
|
||
|
AudioState->FirstFreePlayingSound = PlayingSound->Next;
|
||
|
|
||
|
PlayingSound->SamplesPlayed = 0;
|
||
|
// TODO(casey): Should these default to 0.5f/0.5f for centerred?
|
||
|
PlayingSound->CurrentVolume = PlayingSound->TargetVolume = V2(1.0f, 1.0f);
|
||
|
PlayingSound->dCurrentVolume = V2(0, 0);
|
||
|
PlayingSound->ID = SoundID;
|
||
|
PlayingSound->dSample = 1.0f;
|
||
|
|
||
|
PlayingSound->Next = AudioState->FirstPlayingSound;
|
||
|
AudioState->FirstPlayingSound = PlayingSound;
|
||
|
|
||
|
return(PlayingSound);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
ChangeVolume(audio_state *AudioState, playing_sound *Sound, real32 FadeDurationInSeconds, v2 Volume)
|
||
|
{
|
||
|
if(Sound)
|
||
|
{
|
||
|
if(FadeDurationInSeconds <= 0.0f)
|
||
|
{
|
||
|
Sound->CurrentVolume = Sound->TargetVolume = Volume;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
real32 OneOverFade = 1.0f / FadeDurationInSeconds;
|
||
|
Sound->TargetVolume = Volume;
|
||
|
Sound->dCurrentVolume = OneOverFade*(Sound->TargetVolume - Sound->CurrentVolume);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
ChangePitch(audio_state *AudioState, playing_sound *Sound, real32 dSample)
|
||
|
{
|
||
|
if(Sound)
|
||
|
{
|
||
|
Sound->dSample = dSample;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
OutputPlayingSounds(audio_state *AudioState,
|
||
|
game_sound_output_buffer *SoundBuffer, game_assets *Assets,
|
||
|
memory_arena *TempArena)
|
||
|
{
|
||
|
TIMED_FUNCTION();
|
||
|
|
||
|
temporary_memory MixerMemory = BeginTemporaryMemory(TempArena);
|
||
|
|
||
|
u32 GenerationID = BeginGeneration(Assets);
|
||
|
|
||
|
Assert((SoundBuffer->SampleCount & 3) == 0);
|
||
|
u32 ChunkCount = SoundBuffer->SampleCount / 4;
|
||
|
|
||
|
__m128 *RealChannel0 = PushArray(TempArena, ChunkCount, __m128, 16);
|
||
|
__m128 *RealChannel1 = PushArray(TempArena, ChunkCount, __m128, 16);
|
||
|
|
||
|
real32 SecondsPerSample = 1.0f / (real32)SoundBuffer->SamplesPerSecond;
|
||
|
#define AudioStateOutputChannelCount 2
|
||
|
|
||
|
__m128 One = _mm_set1_ps(1.0f);
|
||
|
__m128 Zero = _mm_set1_ps(0.0f);
|
||
|
|
||
|
// NOTE(casey): Clear out the mixer channels
|
||
|
{
|
||
|
__m128 *Dest0 = RealChannel0;
|
||
|
__m128 *Dest1 = RealChannel1;
|
||
|
for(u32 SampleIndex = 0;
|
||
|
SampleIndex < ChunkCount;
|
||
|
++SampleIndex)
|
||
|
{
|
||
|
_mm_store_ps((float *)Dest0++, Zero);
|
||
|
_mm_store_ps((float *)Dest1++, Zero);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NOTE(casey): Sum all sounds
|
||
|
for(playing_sound **PlayingSoundPtr = &AudioState->FirstPlayingSound;
|
||
|
*PlayingSoundPtr;
|
||
|
)
|
||
|
{
|
||
|
playing_sound *PlayingSound = *PlayingSoundPtr;
|
||
|
bool32 SoundFinished = false;
|
||
|
|
||
|
uint32 TotalChunksToMix = ChunkCount;
|
||
|
__m128 *Dest0 = RealChannel0;
|
||
|
__m128 *Dest1 = RealChannel1;
|
||
|
|
||
|
while(TotalChunksToMix && !SoundFinished)
|
||
|
{
|
||
|
loaded_sound *LoadedSound = GetSound(Assets, PlayingSound->ID, GenerationID);
|
||
|
if(LoadedSound)
|
||
|
{
|
||
|
sound_id NextSoundInChain = GetNextSoundInChain(Assets, PlayingSound->ID);
|
||
|
PrefetchSound(Assets, NextSoundInChain);
|
||
|
|
||
|
v2 Volume = PlayingSound->CurrentVolume;
|
||
|
v2 dVolume = SecondsPerSample*PlayingSound->dCurrentVolume;
|
||
|
v2 dVolumeChunk = 4.0f*dVolume;
|
||
|
real32 dSample = PlayingSound->dSample;
|
||
|
// TODO(casey): If you use the 1.9f version, it can clearly repro a bug
|
||
|
// in the accuracy of the samples played calcs!
|
||
|
// real32 dSample = PlayingSound->dSample*1.9f;
|
||
|
real32 dSampleChunk = 4.0f*dSample;
|
||
|
|
||
|
// NOTE(casey): Channel 0
|
||
|
__m128 MasterVolume0 = _mm_set1_ps(AudioState->MasterVolume.E[0]);
|
||
|
__m128 Volume0 = _mm_setr_ps(Volume.E[0] + 0.0f*dVolume.E[0],
|
||
|
Volume.E[0] + 1.0f*dVolume.E[0],
|
||
|
Volume.E[0] + 2.0f*dVolume.E[0],
|
||
|
Volume.E[0] + 3.0f*dVolume.E[0]);
|
||
|
__m128 dVolume0 = _mm_set1_ps(dVolume.E[0]);
|
||
|
__m128 dVolumeChunk0 = _mm_set1_ps(dVolumeChunk.E[0]);
|
||
|
|
||
|
// NOTE(casey): Channel 1
|
||
|
__m128 MasterVolume1 = _mm_set1_ps(AudioState->MasterVolume.E[1]);
|
||
|
__m128 Volume1 = _mm_setr_ps(Volume.E[1] + 0.0f*dVolume.E[1],
|
||
|
Volume.E[1] + 1.0f*dVolume.E[1],
|
||
|
Volume.E[1] + 2.0f*dVolume.E[1],
|
||
|
Volume.E[1] + 3.0f*dVolume.E[1]);
|
||
|
__m128 dVolume1 = _mm_set1_ps(dVolume.E[1]);
|
||
|
__m128 dVolumeChunk1 = _mm_set1_ps(dVolumeChunk.E[1]);
|
||
|
|
||
|
Assert(PlayingSound->SamplesPlayed >= 0.0f);
|
||
|
|
||
|
u32 ChunksToMix = TotalChunksToMix;
|
||
|
r32 RealChunksRemainingInSound =
|
||
|
(LoadedSound->SampleCount - RoundReal32ToInt32(PlayingSound->SamplesPlayed)) / dSampleChunk;
|
||
|
u32 ChunksRemainingInSound = RoundReal32ToInt32(RealChunksRemainingInSound);
|
||
|
if(ChunksToMix > ChunksRemainingInSound)
|
||
|
{
|
||
|
ChunksToMix = ChunksRemainingInSound;
|
||
|
}
|
||
|
|
||
|
u32 VolumeEndsAt[AudioStateOutputChannelCount] = {};
|
||
|
for(u32 ChannelIndex = 0;
|
||
|
ChannelIndex < ArrayCount(VolumeEndsAt);
|
||
|
++ChannelIndex)
|
||
|
{
|
||
|
if(dVolumeChunk.E[ChannelIndex] != 0.0f)
|
||
|
{
|
||
|
real32 DeltaVolume = (PlayingSound->TargetVolume.E[ChannelIndex] -
|
||
|
Volume.E[ChannelIndex]);
|
||
|
u32 VolumeChunkCount = (u32)(((DeltaVolume / dVolumeChunk.E[ChannelIndex]) + 0.5f));
|
||
|
if(ChunksToMix > VolumeChunkCount)
|
||
|
{
|
||
|
ChunksToMix = VolumeChunkCount;
|
||
|
VolumeEndsAt[ChannelIndex] = VolumeChunkCount;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO(casey): Handle stereo!
|
||
|
real32 BeginSamplePosition = PlayingSound->SamplesPlayed;
|
||
|
real32 EndSamplePosition = BeginSamplePosition + ChunksToMix*dSampleChunk;
|
||
|
real32 LoopIndexC = (EndSamplePosition - BeginSamplePosition) / (r32)ChunksToMix;
|
||
|
for(u32 LoopIndex = 0;
|
||
|
LoopIndex < ChunksToMix;
|
||
|
++LoopIndex)
|
||
|
{
|
||
|
real32 SamplePosition = BeginSamplePosition + LoopIndexC*(r32)LoopIndex;
|
||
|
// TODO(casey): Move volume up here to explicit.
|
||
|
#if 1
|
||
|
__m128 SamplePos = _mm_setr_ps(SamplePosition + 0.0f*dSample,
|
||
|
SamplePosition + 1.0f*dSample,
|
||
|
SamplePosition + 2.0f*dSample,
|
||
|
SamplePosition + 3.0f*dSample);
|
||
|
__m128i SampleIndex = _mm_cvttps_epi32(SamplePos);
|
||
|
__m128 Frac = _mm_sub_ps(SamplePos, _mm_cvtepi32_ps(SampleIndex));
|
||
|
|
||
|
__m128 SampleValueF = _mm_setr_ps(LoadedSound->Samples[0][((int32 *)&SampleIndex)[0]],
|
||
|
LoadedSound->Samples[0][((int32 *)&SampleIndex)[1]],
|
||
|
LoadedSound->Samples[0][((int32 *)&SampleIndex)[2]],
|
||
|
LoadedSound->Samples[0][((int32 *)&SampleIndex)[3]]);
|
||
|
__m128 SampleValueC = _mm_setr_ps(LoadedSound->Samples[0][((int32 *)&SampleIndex)[0] + 1],
|
||
|
LoadedSound->Samples[0][((int32 *)&SampleIndex)[1] + 1],
|
||
|
LoadedSound->Samples[0][((int32 *)&SampleIndex)[2] + 1],
|
||
|
LoadedSound->Samples[0][((int32 *)&SampleIndex)[3] + 1]);
|
||
|
|
||
|
__m128 SampleValue = _mm_add_ps(_mm_mul_ps(_mm_sub_ps(One, Frac), SampleValueF),
|
||
|
_mm_mul_ps(Frac, SampleValueC));
|
||
|
#else
|
||
|
__m128 SampleValue = _mm_setr_ps(LoadedSound->Samples[0][RoundReal32ToInt32(SamplePosition + 0.0f*dSample)],
|
||
|
LoadedSound->Samples[0][RoundReal32ToInt32(SamplePosition + 1.0f*dSample)],
|
||
|
LoadedSound->Samples[0][RoundReal32ToInt32(SamplePosition + 2.0f*dSample)],
|
||
|
LoadedSound->Samples[0][RoundReal32ToInt32(SamplePosition + 3.0f*dSample)]);
|
||
|
#endif
|
||
|
|
||
|
__m128 D0 = _mm_load_ps((float *)&Dest0[0]);
|
||
|
__m128 D1 = _mm_load_ps((float *)&Dest1[0]);
|
||
|
|
||
|
D0 = _mm_add_ps(D0, _mm_mul_ps(_mm_mul_ps(MasterVolume0, Volume0), SampleValue));
|
||
|
D1 = _mm_add_ps(D1, _mm_mul_ps(_mm_mul_ps(MasterVolume1, Volume1), SampleValue));
|
||
|
|
||
|
_mm_store_ps((float *)&Dest0[0], D0);
|
||
|
_mm_store_ps((float *)&Dest1[0], D1);
|
||
|
|
||
|
++Dest0;
|
||
|
++Dest1;
|
||
|
Volume0 = _mm_add_ps(Volume0, dVolumeChunk0);
|
||
|
Volume1 = _mm_add_ps(Volume1, dVolumeChunk1);
|
||
|
}
|
||
|
|
||
|
PlayingSound->CurrentVolume.E[0] = ((real32 *)&Volume0)[0];
|
||
|
PlayingSound->CurrentVolume.E[1] = ((real32 *)&Volume1)[1];
|
||
|
for(u32 ChannelIndex = 0;
|
||
|
ChannelIndex < ArrayCount(VolumeEndsAt);
|
||
|
++ChannelIndex)
|
||
|
{
|
||
|
if(ChunksToMix == VolumeEndsAt[ChannelIndex])
|
||
|
{
|
||
|
PlayingSound->CurrentVolume.E[ChannelIndex] =
|
||
|
PlayingSound->TargetVolume.E[ChannelIndex];
|
||
|
PlayingSound->dCurrentVolume.E[ChannelIndex] = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PlayingSound->SamplesPlayed = EndSamplePosition;
|
||
|
Assert(TotalChunksToMix >= ChunksToMix);
|
||
|
TotalChunksToMix -= ChunksToMix;
|
||
|
|
||
|
if(ChunksToMix == ChunksRemainingInSound)
|
||
|
{
|
||
|
if(IsValid(NextSoundInChain))
|
||
|
{
|
||
|
PlayingSound->ID = NextSoundInChain;
|
||
|
Assert(PlayingSound->SamplesPlayed >= LoadedSound->SampleCount);
|
||
|
PlayingSound->SamplesPlayed -= (real32)LoadedSound->SampleCount;
|
||
|
if(PlayingSound->SamplesPlayed < 0)
|
||
|
{
|
||
|
PlayingSound->SamplesPlayed = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SoundFinished = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
LoadSound(Assets, PlayingSound->ID);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(SoundFinished)
|
||
|
{
|
||
|
*PlayingSoundPtr = PlayingSound->Next;
|
||
|
PlayingSound->Next = AudioState->FirstFreePlayingSound;
|
||
|
AudioState->FirstFreePlayingSound = PlayingSound;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayingSoundPtr = &PlayingSound->Next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NOTE(casey): Convert to 16-bit
|
||
|
{
|
||
|
__m128 *Source0 = RealChannel0;
|
||
|
__m128 *Source1 = RealChannel1;
|
||
|
|
||
|
__m128i *SampleOut = (__m128i *)SoundBuffer->Samples;
|
||
|
for(u32 SampleIndex = 0;
|
||
|
SampleIndex < ChunkCount;
|
||
|
++SampleIndex)
|
||
|
{
|
||
|
__m128 S0 = _mm_load_ps((float *)Source0++);
|
||
|
__m128 S1 = _mm_load_ps((float *)Source1++);
|
||
|
|
||
|
__m128i L = _mm_cvtps_epi32(S0);
|
||
|
__m128i R = _mm_cvtps_epi32(S1);
|
||
|
|
||
|
__m128i LR0 = _mm_unpacklo_epi32(L, R);
|
||
|
__m128i LR1 = _mm_unpackhi_epi32(L, R);
|
||
|
|
||
|
__m128i S01 = _mm_packs_epi32(LR0, LR1);
|
||
|
|
||
|
*SampleOut++ = S01;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EndGeneration(Assets, GenerationID);
|
||
|
EndTemporaryMemory(MixerMemory);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
InitializeAudioState(audio_state *AudioState, memory_arena *PermArena)
|
||
|
{
|
||
|
AudioState->PermArena = PermArena;
|
||
|
AudioState->FirstPlayingSound = 0;
|
||
|
AudioState->FirstFreePlayingSound = 0;
|
||
|
|
||
|
AudioState->MasterVolume = V2(1.0f, 1.0f);
|
||
|
}
|