/* ======================================================================== $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); }