/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "framework/FileSystem.h" #include "framework/Session.h" #include "renderer/RenderWorld.h" #include "sound/snd_local.h" /* ================== idSoundWorldLocal::Init ================== */ void idSoundWorldLocal::Init( idRenderWorld *renderWorld ) { rw = renderWorld; writeDemo = NULL; listenerAxis.Identity(); listenerPos.Zero(); listenerPrivateId = 0; listenerQU.Zero(); listenerArea = 0; listenerAreaName = "Undefined"; if (idSoundSystemLocal::useEFXReverb) { if (!soundSystemLocal.alIsAuxiliaryEffectSlot(listenerSlot)) { alGetError(); soundSystemLocal.alGenAuxiliaryEffectSlots(1, &listenerSlot); ALuint e = alGetError(); if (e != AL_NO_ERROR) { common->Warning("idSoundWorldLocal::Init: alGenAuxiliaryEffectSlots failed: 0x%x", e); listenerSlot = AL_EFFECTSLOT_NULL; } } if (!soundSystemLocal.alIsFilter(listenerFilter)) { alGetError(); soundSystemLocal.alGenFilters(1, &listenerFilter); ALuint e = alGetError(); if (e != AL_NO_ERROR) { common->Warning("idSoundWorldLocal::Init: alGenFilters failed: 0x%x", e); listenerFilter = AL_FILTER_NULL; } else { soundSystemLocal.alFilteri(listenerFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); // original EAX occusion value was -1150 // default OCCLUSIONLFRATIO is 0.25 // pow(10.0, (-1150*0.25)/2000.0) soundSystemLocal.alFilterf(listenerFilter, AL_LOWPASS_GAIN, 0.718208f); // pow(10.0, -1150/2000.0) soundSystemLocal.alFilterf(listenerFilter, AL_LOWPASS_GAINHF, 0.266073f); } } } gameMsec = 0; game44kHz = 0; pause44kHz = -1; lastAVI44kHz = 0; for ( int i = 0 ; i < SOUND_MAX_CLASSES ; i++ ) { soundClassFade[i].Clear(); } // fill in the 0 index spot idSoundEmitterLocal *placeHolder = new idSoundEmitterLocal; emitters.Append( placeHolder ); fpa[0] = fpa[1] = fpa[2] = fpa[3] = fpa[4] = fpa[5] = NULL; aviDemoPath = ""; aviDemoName = ""; localSound = NULL; slowmoActive = false; slowmoSpeed = 0; enviroSuitActive = false; } /* =============== idSoundWorldLocal::idSoundWorldLocal =============== */ idSoundWorldLocal::idSoundWorldLocal() { } /* =============== idSoundWorldLocal::~idSoundWorldLocal =============== */ idSoundWorldLocal::~idSoundWorldLocal() { Shutdown(); } /* =============== idSoundWorldLocal::Shutdown this is called from the main thread =============== */ void idSoundWorldLocal::Shutdown() { int i; if ( soundSystemLocal.currentSoundWorld == this ) { soundSystemLocal.currentSoundWorld = NULL; } AVIClose(); if (idSoundSystemLocal::useEFXReverb) { if (soundSystemLocal.alIsAuxiliaryEffectSlot(listenerSlot)) { soundSystemLocal.alAuxiliaryEffectSloti(listenerSlot, AL_EFFECTSLOT_EFFECT, AL_EFFECTSLOT_NULL); soundSystemLocal.alDeleteAuxiliaryEffectSlots(1, &listenerSlot); listenerSlot = AL_EFFECTSLOT_NULL; } if (soundSystemLocal.alIsFilter(listenerFilter)) { soundSystemLocal.alDeleteFilters(1, &listenerFilter); listenerFilter = AL_FILTER_NULL; } } for ( i = 0; i < emitters.Num(); i++ ) { if ( emitters[i] ) { delete emitters[i]; emitters[i] = NULL; } } localSound = NULL; } /* =================== idSoundWorldLocal::ClearAllSoundEmitters =================== */ void idSoundWorldLocal::ClearAllSoundEmitters() { int i; Sys_EnterCriticalSection(); AVIClose(); for ( i = 0; i < emitters.Num(); i++ ) { idSoundEmitterLocal *sound = emitters[i]; sound->Clear(); } localSound = NULL; Sys_LeaveCriticalSection(); } /* =================== idSoundWorldLocal::AllocLocalSoundEmitter =================== */ idSoundEmitterLocal *idSoundWorldLocal::AllocLocalSoundEmitter() { int i, index; idSoundEmitterLocal *def = NULL; index = -1; // never use the 0 index spot for ( i = 1 ; i < emitters.Num() ; i++ ) { def = emitters[i]; // check for a completed and freed spot if ( def->removeStatus >= REMOVE_STATUS_SAMPLEFINISHED ) { index = i; if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { common->Printf( "sound: recycling sound def %d\n", i ); } break; } } if ( index == -1 ) { // append a brand new one def = new idSoundEmitterLocal; // we need to protect this from the async thread Sys_EnterCriticalSection(); index = emitters.Append( def ); Sys_LeaveCriticalSection(); if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { common->Printf( "sound: appended new sound def %d\n", index ); } } def->Clear(); def->index = index; def->removeStatus = REMOVE_STATUS_ALIVE; def->soundWorld = this; return def; } /* =================== idSoundWorldLocal::AllocSoundEmitter this is called from the main thread =================== */ idSoundEmitter *idSoundWorldLocal::AllocSoundEmitter() { idSoundEmitterLocal *emitter = AllocLocalSoundEmitter(); if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { common->Printf( "AllocSoundEmitter = %i\n", emitter->index ); } if ( writeDemo ) { writeDemo->WriteInt( DS_SOUND ); writeDemo->WriteInt( SCMD_ALLOC_EMITTER ); writeDemo->WriteInt( emitter->index ); } return emitter; } /* =================== idSoundWorldLocal::StartWritingDemo this is called from the main thread =================== */ void idSoundWorldLocal::StartWritingDemo( idDemoFile *demo ) { writeDemo = demo; writeDemo->WriteInt( DS_SOUND ); writeDemo->WriteInt( SCMD_STATE ); // use the normal save game code to archive all the emitters WriteToSaveGame( writeDemo ); } /* =================== idSoundWorldLocal::StopWritingDemo this is called from the main thread =================== */ void idSoundWorldLocal::StopWritingDemo() { writeDemo = NULL; } /* =================== idSoundWorldLocal::ProcessDemoCommand this is called from the main thread =================== */ void idSoundWorldLocal::ProcessDemoCommand( idDemoFile *readDemo ) { int index; idSoundEmitterLocal *def; if ( !readDemo ) { return; } int dc; if ( !readDemo->ReadInt( dc ) ) { return; } switch( (soundDemoCommand_t)dc ) { case SCMD_STATE: // we need to protect this from the async thread // other instances of calling idSoundWorldLocal::ReadFromSaveGame do this while the sound code is muted // setting muted and going right in may not be good enough here, as we async thread may already be in an async tick (in which case we could still race to it) Sys_EnterCriticalSection(); ReadFromSaveGame( readDemo ); Sys_LeaveCriticalSection(); UnPause(); break; case SCMD_PLACE_LISTENER: { idVec3 origin; idMat3 axis; int listenerId; int gameTime; readDemo->ReadVec3( origin ); readDemo->ReadMat3( axis ); readDemo->ReadInt( listenerId ); readDemo->ReadInt( gameTime ); PlaceListener( origin, axis, listenerId, gameTime, "" ); }; break; case SCMD_ALLOC_EMITTER: readDemo->ReadInt( index ); if ( index < 1 || index > emitters.Num() ) { common->Error( "idSoundWorldLocal::ProcessDemoCommand: bad emitter number" ); } if ( index == emitters.Num() ) { // append a brand new one def = new idSoundEmitterLocal; emitters.Append( def ); } def = emitters[ index ]; def->Clear(); def->index = index; def->removeStatus = REMOVE_STATUS_ALIVE; def->soundWorld = this; break; case SCMD_FREE: { int immediate; readDemo->ReadInt( index ); readDemo->ReadInt( immediate ); EmitterForIndex( index )->Free( immediate != 0 ); } break; case SCMD_UPDATE: { idVec3 origin; int listenerId; soundShaderParms_t parms; readDemo->ReadInt( index ); readDemo->ReadVec3( origin ); readDemo->ReadInt( listenerId ); readDemo->ReadFloat( parms.minDistance ); readDemo->ReadFloat( parms.maxDistance ); readDemo->ReadFloat( parms.volume ); readDemo->ReadFloat( parms.shakes ); readDemo->ReadInt( parms.soundShaderFlags ); readDemo->ReadInt( parms.soundClass ); EmitterForIndex( index )->UpdateEmitter( origin, listenerId, &parms ); } break; case SCMD_START: { const idSoundShader *shader; int channel; float diversity; int shaderFlags; readDemo->ReadInt( index ); shader = declManager->FindSound( readDemo->ReadHashString() ); readDemo->ReadInt( channel ); readDemo->ReadFloat( diversity ); readDemo->ReadInt( shaderFlags ); EmitterForIndex( index )->StartSound( shader, (s_channelType)channel, diversity, shaderFlags ); } break; case SCMD_MODIFY: { int channel; soundShaderParms_t parms; readDemo->ReadInt( index ); readDemo->ReadInt( channel ); readDemo->ReadFloat( parms.minDistance ); readDemo->ReadFloat( parms.maxDistance ); readDemo->ReadFloat( parms.volume ); readDemo->ReadFloat( parms.shakes ); readDemo->ReadInt( parms.soundShaderFlags ); readDemo->ReadInt( parms.soundClass ); EmitterForIndex( index )->ModifySound( (s_channelType)channel, &parms ); } break; case SCMD_STOP: { int channel; readDemo->ReadInt( index ); readDemo->ReadInt( channel ); EmitterForIndex( index )->StopSound( (s_channelType)channel ); } break; case SCMD_FADE: { int channel; float to, over; readDemo->ReadInt( index ); readDemo->ReadInt( channel ); readDemo->ReadFloat( to ); readDemo->ReadFloat( over ); EmitterForIndex( index )->FadeSound((s_channelType)channel, to, over ); } break; } } /* =================== idSoundWorldLocal::CurrentShakeAmplitudeForPosition this is called from the main thread =================== */ float idSoundWorldLocal::CurrentShakeAmplitudeForPosition( const int time, const idVec3 &listererPosition ) { float amp = 0.0f; int localTime; if ( idSoundSystemLocal::s_constantAmplitude.GetFloat() >= 0.0f ) { return 0.0f; } localTime = soundSystemLocal.GetCurrent44kHzTime(); for ( int i = 1; i < emitters.Num(); i++ ) { idSoundEmitterLocal *sound = emitters[i]; if ( !sound->hasShakes ) { continue; } amp += FindAmplitude( sound, localTime, &listererPosition, SCHANNEL_ANY, true ); } return amp; } /* =================== idSoundWorldLocal::MixLoop Sum all sound contributions into finalMixBuffer, an unclamped float buffer holding all output channels. MIXBUFFER_SAMPLES samples will be created, with each sample consisting of 2 or 6 floats depending on numSpeakers. this is normally called from the sound thread, but also from the main thread for AVIdemo writing =================== */ void idSoundWorldLocal::MixLoop( int current44kHz, int numSpeakers, float *finalMixBuffer ) { int i, j; idSoundEmitterLocal *sound; // if noclip flying outside the world, leave silence if ( listenerArea == -1 ) { alListenerf( AL_GAIN, 0.0f ); return; } // update the listener position and orientation ALfloat listenerPosition[3]; listenerPosition[0] = -listenerPos.y; listenerPosition[1] = listenerPos.z; listenerPosition[2] = -listenerPos.x; ALfloat listenerOrientation[6]; listenerOrientation[0] = -listenerAxis[0].y; listenerOrientation[1] = listenerAxis[0].z; listenerOrientation[2] = -listenerAxis[0].x; listenerOrientation[3] = -listenerAxis[2].y; listenerOrientation[4] = listenerAxis[2].z; listenerOrientation[5] = -listenerAxis[2].x; alListenerf( AL_GAIN, 1.0f ); alListenerfv( AL_POSITION, listenerPosition ); alListenerfv( AL_ORIENTATION, listenerOrientation ); if (idSoundSystemLocal::useEFXReverb && soundSystemLocal.efxloaded) { ALuint effect = 0; idStr s(listenerArea); bool found = soundSystemLocal.EFXDatabase.FindEffect(s, &effect); if (!found) { s = listenerAreaName; found = soundSystemLocal.EFXDatabase.FindEffect(s, &effect); } if (!found) { s = "default"; found = soundSystemLocal.EFXDatabase.FindEffect(s, &effect); } // only update if change in settings if (found && listenerEffect != effect) { EFXprintf("Switching to EFX '%s' (#%u)\n", s.c_str(), effect); listenerEffect = effect; soundSystemLocal.alAuxiliaryEffectSloti(listenerSlot, AL_EFFECTSLOT_EFFECT, effect); } } // debugging option to mute all but a single soundEmitter if ( idSoundSystemLocal::s_singleEmitter.GetInteger() > 0 && idSoundSystemLocal::s_singleEmitter.GetInteger() < emitters.Num() ) { sound = emitters[idSoundSystemLocal::s_singleEmitter.GetInteger()]; if ( sound && sound->playing ) { // run through all the channels for ( j = 0; j < SOUND_MAX_CHANNELS ; j++ ) { idSoundChannel *chan = &sound->channels[j]; // see if we have a sound triggered on this channel if ( !chan->triggerState ) { chan->ALStop(); continue; } AddChannelContribution( sound, chan, current44kHz, numSpeakers, finalMixBuffer ); } } return; } for ( i = 1; i < emitters.Num(); i++ ) { sound = emitters[i]; if ( !sound ) { continue; } // if no channels are active, do nothing if ( !sound->playing ) { continue; } // run through all the channels for ( j = 0; j < SOUND_MAX_CHANNELS ; j++ ) { idSoundChannel *chan = &sound->channels[j]; // see if we have a sound triggered on this channel if ( !chan->triggerState ) { chan->ALStop(); continue; } AddChannelContribution( sound, chan, current44kHz, numSpeakers, finalMixBuffer ); } } // TODO port to OpenAL if ( false && enviroSuitActive ) { soundSystemLocal.DoEnviroSuit( finalMixBuffer, MIXBUFFER_SAMPLES, numSpeakers ); } } //============================================================================== /* =================== idSoundWorldLocal::AVIOpen this is called by the main thread =================== */ void idSoundWorldLocal::AVIOpen( const char *path, const char *name ) { aviDemoPath = path; aviDemoName = name; lastAVI44kHz = game44kHz - game44kHz % MIXBUFFER_SAMPLES; if ( idSoundSystemLocal::s_numberOfSpeakers.GetInteger() == 6 ) { fpa[0] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_left.raw" ); fpa[1] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_right.raw" ); fpa[2] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_center.raw" ); fpa[3] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_lfe.raw" ); fpa[4] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_backleft.raw" ); fpa[5] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_backright.raw" ); } else { fpa[0] = fileSystem->OpenFileWrite( aviDemoPath + "channel_left.raw" ); fpa[1] = fileSystem->OpenFileWrite( aviDemoPath + "channel_right.raw" ); } soundSystemLocal.SetMute( true ); } /* =================== idSoundWorldLocal::AVIUpdate this is called by the main thread writes one block of sound samples if enough time has passed This can be used to write wave files even if no sound hardware exists =================== */ void idSoundWorldLocal::AVIUpdate() { int numSpeakers; if ( game44kHz - lastAVI44kHz < MIXBUFFER_SAMPLES ) { return; } numSpeakers = idSoundSystemLocal::s_numberOfSpeakers.GetInteger(); float mix[MIXBUFFER_SAMPLES*6+16]; float *mix_p = (float *)((( intptr_t)mix + 15 ) & ~15); // SIMD align SIMDProcessor->Memset( mix_p, 0, MIXBUFFER_SAMPLES*sizeof(float)*numSpeakers ); MixLoop( lastAVI44kHz, numSpeakers, mix_p ); for ( int i = 0; i < numSpeakers; i++ ) { short outD[MIXBUFFER_SAMPLES]; for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { float s = mix_p[ j*numSpeakers + i]; if ( s < -32768.0f ) { outD[j] = -32768; } else if ( s > 32767.0f ) { outD[j] = 32767; } else { outD[j] = idMath::FtoiFast( s ); } } // write to file fpa[i]->Write( outD, MIXBUFFER_SAMPLES*sizeof(short) ); } lastAVI44kHz += MIXBUFFER_SAMPLES; return; } /* =================== idSoundWorldLocal::AVIClose =================== */ void idSoundWorldLocal::AVIClose( void ) { int i; if ( !fpa[0] ) { return; } // make sure the final block is written game44kHz += MIXBUFFER_SAMPLES; AVIUpdate(); game44kHz -= MIXBUFFER_SAMPLES; for ( i = 0; i < 6; i++ ) { if ( fpa[i] != NULL ) { fileSystem->CloseFile( fpa[i] ); fpa[i] = NULL; } } if ( idSoundSystemLocal::s_numberOfSpeakers.GetInteger() == 2 ) { // convert it to a wave file idFile *rL, *lL, *wO; idStr name; name = aviDemoPath + aviDemoName + ".wav"; wO = fileSystem->OpenFileWrite( name ); if ( !wO ) { common->Error( "Couldn't write %s", name.c_str() ); } name = aviDemoPath + "channel_right.raw"; rL = fileSystem->OpenFileRead( name ); if ( !rL ) { common->Error( "Couldn't open %s", name.c_str() ); } name = aviDemoPath + "channel_left.raw"; lL = fileSystem->OpenFileRead( name ); if ( !lL ) { common->Error( "Couldn't open %s", name.c_str() ); } int numSamples = rL->Length()/2; mminfo_t info; pcmwaveformat_t format; info.ckid = fourcc_riff; info.fccType = mmioFOURCC( 'W', 'A', 'V', 'E' ); info.cksize = (rL->Length()*2) - 8 + 4 + 16 + 8 + 8; info.dwDataOffset = 12; wO->Write( &info, 12 ); info.ckid = mmioFOURCC( 'f', 'm', 't', ' ' ); info.cksize = 16; wO->Write( &info, 8 ); format.wBitsPerSample = 16; format.wf.nAvgBytesPerSec = 44100*4; // sample rate * block align format.wf.nChannels = 2; format.wf.nSamplesPerSec = 44100; format.wf.wFormatTag = WAVE_FORMAT_TAG_PCM; format.wf.nBlockAlign = 4; // channels * bits/sample / 8 wO->Write( &format, 16 ); info.ckid = mmioFOURCC( 'd', 'a', 't', 'a' ); info.cksize = rL->Length() * 2; wO->Write( &info, 8 ); short s0, s1; for( i = 0; i < numSamples; i++ ) { lL->Read( &s0, 2 ); rL->Read( &s1, 2 ); wO->Write( &s0, 2 ); wO->Write( &s1, 2 ); } fileSystem->CloseFile( wO ); fileSystem->CloseFile( lL ); fileSystem->CloseFile( rL ); fileSystem->RemoveFile( aviDemoPath + "channel_right.raw" ); fileSystem->RemoveFile( aviDemoPath + "channel_left.raw" ); } soundSystemLocal.SetMute( false ); } //============================================================================== /* =================== idSoundWorldLocal::ResolveOrigin Find out of the sound is completely occluded by a closed door portal, or the virtual sound origin position at the portal closest to the listener. this is called by the main thread dist is the distance from the orignial sound origin to the current portal that enters soundArea def->distance is the distance we are trying to reduce. If there is no path through open portals from the sound to the listener, def->distance will remain set at maxDistance =================== */ static const int MAX_PORTAL_TRACE_DEPTH = 10; void idSoundWorldLocal::ResolveOrigin( const int stackDepth, const soundPortalTrace_t *prevStack, const int soundArea, const float dist, const idVec3& soundOrigin, idSoundEmitterLocal *def ) { if ( dist >= def->distance ) { // we can't possibly hear the sound through this chain of portals return; } if ( soundArea == listenerArea ) { float fullDist = dist + (soundOrigin - listenerQU).LengthFast(); if ( fullDist < def->distance ) { def->distance = fullDist; def->spatializedOrigin = soundOrigin; } return; } if ( stackDepth == MAX_PORTAL_TRACE_DEPTH ) { // don't spend too much time doing these calculations in big maps return; } soundPortalTrace_t newStack; newStack.portalArea = soundArea; newStack.prevStack = prevStack; int numPortals = rw->NumPortalsInArea( soundArea ); for( int p = 0; p < numPortals; p++ ) { exitPortal_t re = rw->GetPortal( soundArea, p ); float occlusionDistance = 0; // air blocking windows will block sound like closed doors if ( (re.blockingBits & ( PS_BLOCK_VIEW | PS_BLOCK_AIR ) ) ) { // we could just completely cut sound off, but reducing the volume works better // continue; occlusionDistance = idSoundSystemLocal::s_doorDistanceAdd.GetFloat(); } // what area are we about to go look at int otherArea = re.areas[0]; if ( re.areas[0] == soundArea ) { otherArea = re.areas[1]; } // if this area is already in our portal chain, don't bother looking into it const soundPortalTrace_t *prev; for ( prev = prevStack ; prev ; prev = prev->prevStack ) { if ( prev->portalArea == otherArea ) { break; } } if ( prev ) { continue; } // pick a point on the portal to serve as our virtual sound origin #if 1 idVec3 source; idPlane pl; re.w->GetPlane( pl ); float scale; idVec3 dir = listenerQU - soundOrigin; if ( !pl.RayIntersection( soundOrigin, dir, scale ) ) { source = re.w->GetCenter(); } else { source = soundOrigin + scale * dir; // if this point isn't inside the portal edges, slide it in for ( int i = 0 ; i < re.w->GetNumPoints() ; i++ ) { int j = ( i + 1 ) % re.w->GetNumPoints(); idVec3 edgeDir = (*(re.w))[j].ToVec3() - (*(re.w))[i].ToVec3(); idVec3 edgeNormal; edgeNormal.Cross( pl.Normal(), edgeDir ); idVec3 fromVert = source - (*(re.w))[j].ToVec3(); float d = edgeNormal * fromVert; if ( d > 0 ) { // move it in float div = edgeNormal.Normalize(); d /= div; source -= d * edgeNormal; } } } #else // clip the ray from the listener to the center of the portal by // all the portal edge planes, then project that point (or the original if not clipped) // onto the portal plane to get the spatialized origin idVec3 start = listenerQU; idVec3 mid = re.w->GetCenter(); bool wasClipped = false; for ( int i = 0 ; i < re.w->GetNumPoints() ; i++ ) { int j = ( i + 1 ) % re.w->GetNumPoints(); idVec3 v1 = (*(re.w))[j].ToVec3() - soundOrigin; idVec3 v2 = (*(re.w))[i].ToVec3() - soundOrigin; v1.Normalize(); v2.Normalize(); idVec3 edgeNormal; edgeNormal.Cross( v1, v2 ); idVec3 fromVert = start - soundOrigin; float d1 = edgeNormal * fromVert; if ( d1 > 0.0f ) { fromVert = mid - (*(re.w))[j].ToVec3(); float d2 = edgeNormal * fromVert; // move it in float f = d1 / ( d1 - d2 ); idVec3 clipped = start * ( 1.0f - f ) + mid * f; start = clipped; wasClipped = true; } } idVec3 source; if ( wasClipped ) { // now project it onto the portal plane idPlane pl; re.w->GetPlane( pl ); float f1 = pl.Distance( start ); float f2 = pl.Distance( soundOrigin ); float f = f1 / ( f1 - f2 ); source = start * ( 1.0f - f ) + soundOrigin * f; } else { source = soundOrigin; } #endif idVec3 tlen = source - soundOrigin; float tlenLength = tlen.LengthFast(); ResolveOrigin( stackDepth+1, &newStack, otherArea, dist+tlenLength+occlusionDistance, source, def ); } } /* =================== idSoundWorldLocal::PlaceListener this is called by the main thread =================== */ void idSoundWorldLocal::PlaceListener( const idVec3& origin, const idMat3& axis, const int listenerId, const int gameTime, const idStr& areaName ) { int current44kHzTime; if ( !soundSystemLocal.isInitialized ) { return; } if ( pause44kHz >= 0 ){ return; } if ( writeDemo ) { writeDemo->WriteInt( DS_SOUND ); writeDemo->WriteInt( SCMD_PLACE_LISTENER ); writeDemo->WriteVec3( origin ); writeDemo->WriteMat3( axis ); writeDemo->WriteInt( listenerId ); writeDemo->WriteInt( gameTime ); } current44kHzTime = soundSystemLocal.GetCurrent44kHzTime(); // we usually expect gameTime to be increasing by 16 or 32 msec, but when // a cinematic is fast-forward skipped through, it can jump by a significant // amount, while the hardware 44kHz position will not have changed accordingly, // which would make sounds (like long character speaches) continue from the // old time. Fix this by killing all non-looping sounds if ( gameTime > gameMsec + 500 ) { OffsetSoundTime( - ( gameTime - gameMsec ) * 0.001f * 44100.0f ); } gameMsec = gameTime; if ( fpa[0] ) { // exactly 30 fps so the wave file can be used for exact video frames game44kHz = idMath::FtoiFast( gameMsec * ( ( 1000.0f / 60.0f ) / 16.0f ) * 0.001f * 44100.0f ); } else { // the normal 16 msec / frame game44kHz = idMath::FtoiFast( gameMsec * 0.001f * 44100.0f ); } listenerPrivateId = listenerId; listenerQU = origin; // Doom units listenerPos = origin * DOOM_TO_METERS; // meters listenerAxis = axis; listenerAreaName = areaName; listenerAreaName.ToLower(); if ( rw ) { listenerArea = rw->PointInArea( listenerQU ); // where are we? } else { listenerArea = 0; } if ( listenerArea < 0 ) { return; } ForegroundUpdate( current44kHzTime ); } /* ================== idSoundWorldLocal::ForegroundUpdate ================== */ void idSoundWorldLocal::ForegroundUpdate( int current44kHzTime ) { int j, k; idSoundEmitterLocal *def; if ( !soundSystemLocal.isInitialized ) { return; } Sys_EnterCriticalSection(); // if we are recording an AVI demo, don't use hardware time if ( fpa[0] ) { current44kHzTime = lastAVI44kHz; } // // check to see if each sound is visible or not // speed up by checking maxdistance to origin // although the sound may still need to play if it has // just become occluded so it can ramp down to 0 // for ( j = 1; j < emitters.Num(); j++ ) { def = emitters[j]; if ( def->removeStatus >= REMOVE_STATUS_SAMPLEFINISHED ) { continue; } // see if our last channel just finished def->CheckForCompletion( current44kHzTime ); if ( !def->playing ) { continue; } // update virtual origin / distance, etc def->Spatialize( listenerPos, listenerArea, rw ); // per-sound debug options if ( idSoundSystemLocal::s_drawSounds.GetInteger() && rw ) { if ( def->distance < def->maxDistance || idSoundSystemLocal::s_drawSounds.GetInteger() > 1 ) { idBounds ref; ref.Clear(); ref.AddPoint( idVec3( -10, -10, -10 ) ); ref.AddPoint( idVec3( 10, 10, 10 ) ); float vis = (1.0f - (def->distance / def->maxDistance)); // draw a box rw->DebugBounds( idVec4( vis, 0.25f, vis, vis ), ref, def->origin ); // draw an arrow to the audible position, possible a portal center if ( def->origin != def->spatializedOrigin ) { rw->DebugArrow( colorRed, def->origin, def->spatializedOrigin, 4 ); } // draw the index idVec3 textPos = def->origin; textPos[2] -= 8; rw->DrawText( va("%i", def->index), textPos, 0.1f, idVec4(1,0,0,1), listenerAxis ); textPos[2] += 8; // run through all the channels for ( k = 0; k < SOUND_MAX_CHANNELS ; k++ ) { idSoundChannel *chan = &def->channels[k]; // see if we have a sound triggered on this channel if ( !chan->triggerState ) { continue; } char text[1024]; float min = chan->parms.minDistance; float max = chan->parms.maxDistance; const char *defaulted = chan->leadinSample->defaultSound ? "(DEFAULTED)" : ""; sprintf( text, "%s (%i/%i %i/%i)%s", chan->soundShader->GetName(), (int)def->distance, (int)def->realDistance, (int)min, (int)max, defaulted ); rw->DrawText( text, textPos, 0.1f, idVec4(1,0,0,1), listenerAxis ); textPos[2] += 8; } } } } Sys_LeaveCriticalSection(); // // the sound meter // if ( idSoundSystemLocal::s_showLevelMeter.GetInteger() ) { const idMaterial *gui = declManager->FindMaterial( "guis/assets/soundmeter/audiobg", false ); if ( gui ) { const shaderStage_t *foo = gui->GetStage(0); if ( !foo->texture.cinematic ) { ((shaderStage_t *)foo)->texture.cinematic = new idSndWindow; } } } // // optionally dump out the generated sound // if ( fpa[0] ) { AVIUpdate(); } } /* =================== idSoundWorldLocal::OffsetSoundTime =================== */ void idSoundWorldLocal::OffsetSoundTime( int offset44kHz ) { int i, j; for ( i = 0; i < emitters.Num(); i++ ) { if ( emitters[i] == NULL ) { continue; } for ( j = 0; j < SOUND_MAX_CHANNELS; j++ ) { idSoundChannel *chan = &emitters[i]->channels[ j ]; if ( !chan->triggerState ) { continue; } chan->trigger44kHzTime += offset44kHz; } } } /* =================== idSoundWorldLocal::WriteToSaveGame =================== */ void idSoundWorldLocal::WriteToSaveGame( idFile *savefile ) { int i, j, num, currentSoundTime; const char *name; // the game soundworld is always paused at this point, save that time down if ( pause44kHz > 0 ) { currentSoundTime = pause44kHz; } else { currentSoundTime = soundSystemLocal.GetCurrent44kHzTime(); } // write listener data savefile->WriteVec3(listenerQU); savefile->WriteMat3(listenerAxis); savefile->WriteInt(listenerPrivateId); savefile->WriteInt(gameMsec); savefile->WriteInt(game44kHz); savefile->WriteInt(currentSoundTime); num = emitters.Num(); savefile->WriteInt(num); for ( i = 1; i < emitters.Num(); i++ ) { idSoundEmitterLocal *def = emitters[i]; if ( def->removeStatus != REMOVE_STATUS_ALIVE ) { int skip = -1; savefile->Write( &skip, sizeof( skip ) ); continue; } savefile->WriteInt(i); // Write the emitter data savefile->WriteVec3( def->origin ); savefile->WriteInt( def->listenerId ); WriteToSaveGameSoundShaderParams( savefile, &def->parms ); savefile->WriteFloat( def->amplitude ); savefile->WriteInt( def->ampTime ); for (int k = 0; k < SOUND_MAX_CHANNELS; k++) WriteToSaveGameSoundChannel( savefile, &def->channels[k] ); savefile->WriteFloat( def->distance ); savefile->WriteBool( def->hasShakes ); savefile->WriteInt( def->lastValidPortalArea ); savefile->WriteFloat( def->maxDistance ); savefile->WriteBool( def->playing ); savefile->WriteFloat( def->realDistance ); savefile->WriteInt( def->removeStatus ); savefile->WriteVec3( def->spatializedOrigin ); // write the channel data for( j = 0; j < SOUND_MAX_CHANNELS; j++ ) { idSoundChannel *chan = &def->channels[ j ]; // Write out any sound commands for this def if ( chan->triggerState && chan->soundShader && chan->leadinSample ) { savefile->WriteInt( j ); // write the pointers out separately name = chan->soundShader->GetName(); savefile->WriteString( name ); name = chan->leadinSample->name; savefile->WriteString( name ); } } // End active channels with -1 int end = -1; savefile->WriteInt( end ); } // new in Doom3 v1.2 savefile->Write( &slowmoActive, sizeof( slowmoActive ) ); savefile->Write( &slowmoSpeed, sizeof( slowmoSpeed ) ); savefile->Write( &enviroSuitActive, sizeof( enviroSuitActive ) ); } /* =================== idSoundWorldLocal::WriteToSaveGameSoundShaderParams =================== */ void idSoundWorldLocal::WriteToSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ) { saveGame->WriteFloat(params->minDistance); saveGame->WriteFloat(params->maxDistance); saveGame->WriteFloat(params->volume); saveGame->WriteFloat(params->shakes); saveGame->WriteInt(params->soundShaderFlags); saveGame->WriteInt(params->soundClass); } /* =================== idSoundWorldLocal::WriteToSaveGameSoundChannel =================== */ void idSoundWorldLocal::WriteToSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ) { saveGame->WriteBool( ch->triggerState ); saveGame->WriteUnsignedChar( 0 ); saveGame->WriteUnsignedChar( 0 ); saveGame->WriteUnsignedChar( 0 ); saveGame->WriteInt( ch->trigger44kHzTime ); saveGame->WriteInt( ch->triggerGame44kHzTime ); WriteToSaveGameSoundShaderParams( saveGame, &ch->parms ); saveGame->WriteInt( 0 /* ch->leadinSample */ ); saveGame->WriteInt( ch->triggerChannel ); saveGame->WriteInt( 0 /* ch->soundShader */ ); saveGame->WriteInt( 0 /* ch->decoder */ ); saveGame->WriteFloat(ch->diversity ); saveGame->WriteFloat(ch->lastVolume ); for (int m = 0; m < 6; m++) saveGame->WriteFloat( ch->lastV[m] ); saveGame->WriteInt( ch->channelFade.fadeStart44kHz ); saveGame->WriteInt( ch->channelFade.fadeEnd44kHz ); saveGame->WriteFloat( ch->channelFade.fadeStartVolume ); saveGame->WriteFloat( ch->channelFade.fadeEndVolume ); } /* =================== idSoundWorldLocal::ReadFromSaveGame =================== */ void idSoundWorldLocal::ReadFromSaveGame( idFile *savefile ) { int i, num, handle, listenerId, gameTime, channel; int savedSoundTime, currentSoundTime, soundTimeOffset; idSoundEmitterLocal *def; idVec3 origin; idMat3 axis; idStr soundShader; ClearAllSoundEmitters(); savefile->ReadVec3( origin ); savefile->ReadMat3( axis ); savefile->ReadInt( listenerId ); savefile->ReadInt( gameTime ); savefile->ReadInt( game44kHz ); savefile->ReadInt( savedSoundTime ); // we will adjust the sound starting times from those saved with the demo currentSoundTime = soundSystemLocal.GetCurrent44kHzTime(); soundTimeOffset = currentSoundTime - savedSoundTime; // at the end of the level load we unpause the sound world and adjust the sound starting times once more pause44kHz = currentSoundTime; // place listener PlaceListener( origin, axis, listenerId, gameTime, "Undefined" ); // make sure there are enough // slots to read the saveGame in. We don't shrink the list // if there are extras. savefile->ReadInt( num ); while( emitters.Num() < num ) { def = new idSoundEmitterLocal; def->index = emitters.Append( def ); def->soundWorld = this; } // read in the state for ( i = 1; i < num; i++ ) { savefile->ReadInt( handle ); if ( handle < 0 ) { continue; } if ( handle != i ) { common->Error( "idSoundWorldLocal::ReadFromSaveGame: index mismatch" ); } def = emitters[i]; def->removeStatus = REMOVE_STATUS_ALIVE; def->playing = true; // may be reset by the first UpdateListener savefile->ReadVec3( def->origin ); savefile->ReadInt( def->listenerId ); ReadFromSaveGameSoundShaderParams( savefile, &def->parms ); savefile->ReadFloat( def->amplitude ); savefile->ReadInt( def->ampTime ); for (int k = 0; k < SOUND_MAX_CHANNELS; k++) ReadFromSaveGameSoundChannel( savefile, &def->channels[k] ); savefile->ReadFloat( def->distance ); savefile->ReadBool( def->hasShakes ); savefile->ReadInt( def->lastValidPortalArea ); savefile->ReadFloat( def->maxDistance ); savefile->ReadBool( def->playing ); savefile->ReadFloat( def->realDistance ); savefile->ReadInt( (int&)def->removeStatus ); savefile->ReadVec3( def->spatializedOrigin ); // read the individual channels savefile->ReadInt( channel ); while ( channel >= 0 ) { if ( channel > SOUND_MAX_CHANNELS ) { common->Error( "idSoundWorldLocal::ReadFromSaveGame: channel > SOUND_MAX_CHANNELS" ); } idSoundChannel *chan = &def->channels[channel]; if ( !chan->decoder ) { // The pointer in the save file is not valid, so we grab a new one chan->decoder = idSampleDecoder::Alloc(); } savefile->ReadString( soundShader ); chan->soundShader = declManager->FindSound( soundShader ); savefile->ReadString( soundShader ); // load savegames with s_noSound 1 if ( soundSystemLocal.soundCache ) { chan->leadinSample = soundSystemLocal.soundCache->FindSound( soundShader, false ); } else { chan->leadinSample = NULL; } // adjust the hardware start time chan->trigger44kHzTime += soundTimeOffset; // make sure we start up the hardware voice if needed chan->triggered = chan->triggerState; chan->openalStreamingOffset = currentSoundTime - chan->trigger44kHzTime; // adjust the hardware fade time if ( chan->channelFade.fadeStart44kHz != 0 ) { chan->channelFade.fadeStart44kHz += soundTimeOffset; chan->channelFade.fadeEnd44kHz += soundTimeOffset; } // next command savefile->ReadInt( channel ); } } if ( session->GetSaveGameVersion() >= 17 ) { savefile->Read( &slowmoActive, sizeof( slowmoActive ) ); savefile->Read( &slowmoSpeed, sizeof( slowmoSpeed ) ); savefile->Read( &enviroSuitActive, sizeof( enviroSuitActive ) ); } else { slowmoActive = false; slowmoSpeed = 0; enviroSuitActive = false; } } /* =================== idSoundWorldLocal::ReadFromSaveGameSoundShaderParams =================== */ void idSoundWorldLocal::ReadFromSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ) { saveGame->ReadFloat(params->minDistance); saveGame->ReadFloat(params->maxDistance); saveGame->ReadFloat(params->volume); saveGame->ReadFloat(params->shakes); saveGame->ReadInt(params->soundShaderFlags); saveGame->ReadInt(params->soundClass); } /* =================== idSoundWorldLocal::ReadFromSaveGameSoundChannel =================== */ void idSoundWorldLocal::ReadFromSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ) { saveGame->ReadBool( ch->triggerState ); char tmp; int i; saveGame->ReadChar( tmp ); saveGame->ReadChar( tmp ); saveGame->ReadChar( tmp ); saveGame->ReadInt( ch->trigger44kHzTime ); saveGame->ReadInt( ch->triggerGame44kHzTime ); ReadFromSaveGameSoundShaderParams( saveGame, &ch->parms ); saveGame->ReadInt( i ); ch->leadinSample = NULL; saveGame->ReadInt( ch->triggerChannel ); saveGame->ReadInt( i ); ch->soundShader = NULL; saveGame->ReadInt( i ); ch->decoder = NULL; saveGame->ReadFloat(ch->diversity ); saveGame->ReadFloat(ch->lastVolume ); for (int m = 0; m < 6; m++) saveGame->ReadFloat( ch->lastV[m] ); saveGame->ReadInt( ch->channelFade.fadeStart44kHz ); saveGame->ReadInt( ch->channelFade.fadeEnd44kHz ); saveGame->ReadFloat( ch->channelFade.fadeStartVolume ); saveGame->ReadFloat( ch->channelFade.fadeEndVolume ); } /* =================== idSoundWorldLocal::EmitterForIndex =================== */ idSoundEmitter *idSoundWorldLocal::EmitterForIndex( int index ) { if ( index == 0 ) { return NULL; } if ( index >= emitters.Num() ) { common->Error( "idSoundWorldLocal::EmitterForIndex: %i > %i", index, emitters.Num() ); } return emitters[index]; } /* =============== idSoundWorldLocal::StopAllSounds this is called from the main thread =============== */ void idSoundWorldLocal::StopAllSounds() { for ( int i = 0; i < emitters.Num(); i++ ) { idSoundEmitterLocal * def = emitters[i]; def->StopSound( SCHANNEL_ANY ); } } /* =============== idSoundWorldLocal::Pause =============== */ void idSoundWorldLocal::Pause( void ) { if ( pause44kHz >= 0 ) { common->Warning( "idSoundWorldLocal::Pause: already paused" ); return; } pause44kHz = soundSystemLocal.GetCurrent44kHzTime(); } /* =============== idSoundWorldLocal::UnPause =============== */ void idSoundWorldLocal::UnPause( void ) { int offset44kHz; if ( pause44kHz < 0 ) { common->Warning( "idSoundWorldLocal::UnPause: not paused" ); return; } offset44kHz = soundSystemLocal.GetCurrent44kHzTime() - pause44kHz; OffsetSoundTime( offset44kHz ); pause44kHz = -1; } /* =============== idSoundWorldLocal::IsPaused =============== */ bool idSoundWorldLocal::IsPaused( void ) { return ( pause44kHz >= 0 ); } /* =============== idSoundWorldLocal::PlayShaderDirectly start a music track this is called from the main thread =============== */ void idSoundWorldLocal::PlayShaderDirectly( const char *shaderName, int channel ) { if ( localSound && channel == -1 ) { localSound->StopSound( SCHANNEL_ANY ); } else if ( localSound ) { localSound->StopSound( channel ); } if ( !shaderName || !shaderName[0] ) { return; } const idSoundShader *shader = declManager->FindSound( shaderName ); if ( !shader ) { return; } if ( !localSound ) { localSound = AllocLocalSoundEmitter(); } static idRandom rnd; float diversity = rnd.RandomFloat(); localSound->StartSound( shader, ( channel == -1 ) ? SCHANNEL_ONE : channel , diversity, SSF_GLOBAL ); // in case we are at the console without a game doing updates, force an update ForegroundUpdate( soundSystemLocal.GetCurrent44kHzTime() ); } /* =============== idSoundWorldLocal::CalcEars Determine the volumes from each speaker for a given sound emitter =============== */ void idSoundWorldLocal::CalcEars( int numSpeakers, idVec3 spatializedOrigin, idVec3 listenerPos, idMat3 listenerAxis, float ears[6], float spatialize ) { idVec3 svec = spatializedOrigin - listenerPos; idVec3 ovec; ovec[0] = svec * listenerAxis[0]; ovec[1] = svec * listenerAxis[1]; ovec[2] = svec * listenerAxis[2]; ovec.Normalize(); if ( numSpeakers == 6 ) { static idVec3 speakerVector[6] = { idVec3( 0.707f, 0.707f, 0.0f ), // front left idVec3( 0.707f, -0.707f, 0.0f ), // front right idVec3( 0.707f, 0.0f, 0.0f ), // front center idVec3( 0.0f, 0.0f, 0.0f ), // sub idVec3( -0.707f, 0.707f, 0.0f ), // rear left idVec3( -0.707f, -0.707f, 0.0f ) // rear right }; for ( int i = 0 ; i < 6 ; i++ ) { if ( i == 3 ) { ears[i] = idSoundSystemLocal::s_subFraction.GetFloat(); // subwoofer continue; } float dot = ovec * speakerVector[i]; ears[i] = (idSoundSystemLocal::s_dotbias6.GetFloat() + dot) / ( 1.0f + idSoundSystemLocal::s_dotbias6.GetFloat() ); if ( ears[i] < idSoundSystemLocal::s_minVolume6.GetFloat() ) { ears[i] = idSoundSystemLocal::s_minVolume6.GetFloat(); } } } else { float dot = ovec.y; float dotBias = idSoundSystemLocal::s_dotbias2.GetFloat(); // when we are inside the minDistance, start reducing the amount of spatialization // so NPC voices right in front of us aren't quieter that off to the side dotBias += ( idSoundSystemLocal::s_spatializationDecay.GetFloat() - dotBias ) * ( 1.0f - spatialize ); ears[0] = (idSoundSystemLocal::s_dotbias2.GetFloat() + dot) / ( 1.0f + dotBias ); ears[1] = (idSoundSystemLocal::s_dotbias2.GetFloat() - dot) / ( 1.0f + dotBias ); if ( ears[0] < idSoundSystemLocal::s_minVolume2.GetFloat() ) { ears[0] = idSoundSystemLocal::s_minVolume2.GetFloat(); } if ( ears[1] < idSoundSystemLocal::s_minVolume2.GetFloat() ) { ears[1] = idSoundSystemLocal::s_minVolume2.GetFloat(); } ears[2] = ears[3] = ears[4] = ears[5] = 0.0f; } } /* =============== idSoundWorldLocal::AddChannelContribution Adds the contribution of a single sound channel to finalMixBuffer this is called from the async thread Mixes MIXBUFFER_SAMPLES samples starting at current44kHz sample time into finalMixBuffer =============== */ void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSoundChannel *chan, int current44kHz, int numSpeakers, float *finalMixBuffer ) { int j; float volume; // // get the sound definition and parameters from the entity // soundShaderParms_t *parms = &chan->parms; // assume we have a sound triggered on this channel assert( chan->triggerState ); // fetch the actual wave file and see if it's valid idSoundSample *sample = chan->leadinSample; if ( sample == NULL ) { return; } // if you don't want to hear all the beeps from missing sounds if ( sample->defaultSound && !idSoundSystemLocal::s_playDefaultSound.GetBool() ) { return; } // get the actual shader const idSoundShader *shader = chan->soundShader; // this might happen if the foreground thread just deleted the sound emitter if ( !shader ) { return; } float maxd = parms->maxDistance; float mind = parms->minDistance; int mask = shader->speakerMask; bool omni = ( parms->soundShaderFlags & SSF_OMNIDIRECTIONAL) != 0; bool looping = ( parms->soundShaderFlags & SSF_LOOPING ) != 0; bool global = ( parms->soundShaderFlags & SSF_GLOBAL ) != 0; bool noOcclusion = ( parms->soundShaderFlags & SSF_NO_OCCLUSION ) || !idSoundSystemLocal::s_useOcclusion.GetBool(); // speed goes from 1 to 0.2 if ( idSoundSystemLocal::s_slowAttenuate.GetBool() && slowmoActive && !chan->disallowSlow ) { maxd *= slowmoSpeed; } // stereo samples are always omni if ( sample->objectInfo.nChannels == 2 ) { omni = true; } // if the sound is playing from the current listener, it will not be spatialized at all if ( sound->listenerId == listenerPrivateId ) { global = true; } // // see if it's in range // // convert volumes from decibels to float scale // leadin volume scale for shattering lights // this isn't exactly correct, because the modified volume will get applied to // some initial chunk of the loop as well, because the volume is scaled for the // entire mix buffer if ( shader->leadinVolume && current44kHz - chan->trigger44kHzTime < sample->LengthIn44kHzSamples() ) { volume = soundSystemLocal.dB2Scale( shader->leadinVolume ); } else { volume = soundSystemLocal.dB2Scale( parms->volume ); } // global volume scale volume *= soundSystemLocal.dB2Scale( idSoundSystemLocal::s_volume.GetFloat() ); // volume fading float fadeDb = chan->channelFade.FadeDbAt44kHz( current44kHz ); volume *= soundSystemLocal.dB2Scale( fadeDb ); fadeDb = soundClassFade[parms->soundClass].FadeDbAt44kHz( current44kHz ); volume *= soundSystemLocal.dB2Scale( fadeDb ); // // if it's a global sound then // it's not affected by distance or occlusion // float spatialize = 1; idVec3 spatializedOriginInMeters; if ( !global ) { float dlen; if ( noOcclusion ) { // use the real origin and distance spatializedOriginInMeters = sound->origin * DOOM_TO_METERS; dlen = sound->realDistance; } else { // use the possibly portal-occluded origin and distance spatializedOriginInMeters = sound->spatializedOrigin * DOOM_TO_METERS; dlen = sound->distance; } // reduce volume based on distance if ( dlen >= maxd ) { volume = 0.0f; } else if ( dlen > mind ) { float frac = idMath::ClampFloat( 0.0f, 1.0f, 1.0f - ((dlen - mind) / (maxd - mind))); if ( idSoundSystemLocal::s_quadraticFalloff.GetBool() ) { frac *= frac; } volume *= frac; } else if ( mind > 0.0f ) { // we tweak the spatialization bias when you are inside the minDistance spatialize = dlen / mind; } } // // if it is a private sound, set the volume to zero // unless we match the listenerId // if ( parms->soundShaderFlags & SSF_PRIVATE_SOUND ) { if ( sound->listenerId != listenerPrivateId ) { volume = 0; } } if ( parms->soundShaderFlags & SSF_ANTI_PRIVATE_SOUND ) { if ( sound->listenerId == listenerPrivateId ) { volume = 0; } } // // do we have anything to add? // if ( volume < SND_EPSILON && chan->lastVolume < SND_EPSILON ) { return; } chan->lastVolume = volume; // // fetch the sound from the cache as 44kHz, 16 bit samples // int offset = current44kHz - chan->trigger44kHzTime; float inputSamples[MIXBUFFER_SAMPLES*2+16]; float *alignedInputSamples = (float *) ( ( ( (intptr_t)inputSamples ) + 15 ) & ~15 ); // // allocate and initialize hardware source // if ( sound->removeStatus < REMOVE_STATUS_SAMPLEFINISHED ) { if ( !alIsSource( chan->openalSource ) ) { chan->openalSource = soundSystemLocal.AllocOpenALSource( chan, !chan->leadinSample->hardwareBuffer || !chan->soundShader->entries[0]->hardwareBuffer || looping, chan->leadinSample->objectInfo.nChannels == 2 ); } if ( alIsSource( chan->openalSource ) ) { // stop source if needed.. if ( chan->triggered ) { alSourceStop( chan->openalSource ); } // update source parameters if ( global || omni ) { alSourcei( chan->openalSource, AL_SOURCE_RELATIVE, AL_TRUE); alSource3f( chan->openalSource, AL_POSITION, 0.0f, 0.0f, 0.0f ); alSourcef( chan->openalSource, AL_GAIN, ( volume ) < ( 1.0f ) ? ( volume ) : ( 1.0f ) ); } else { alSourcei( chan->openalSource, AL_SOURCE_RELATIVE, AL_FALSE); alSource3f( chan->openalSource, AL_POSITION, -spatializedOriginInMeters.y, spatializedOriginInMeters.z, -spatializedOriginInMeters.x ); alSourcef( chan->openalSource, AL_GAIN, ( volume ) < ( 1.0f ) ? ( volume ) : ( 1.0f ) ); } alSourcei( chan->openalSource, AL_LOOPING, ( looping && chan->soundShader->entries[0]->hardwareBuffer ) ? AL_TRUE : AL_FALSE ); #if 1 alSourcef( chan->openalSource, AL_REFERENCE_DISTANCE, mind ); alSourcef( chan->openalSource, AL_MAX_DISTANCE, maxd ); #endif alSourcef( chan->openalSource, AL_PITCH, ( slowmoActive && !chan->disallowSlow ) ? ( slowmoSpeed ) : ( 1.0f ) ); if (idSoundSystemLocal::useEFXReverb) { if (enviroSuitActive) { alSourcei(chan->openalSource, AL_DIRECT_FILTER, listenerFilter); alSource3i(chan->openalSource, AL_AUXILIARY_SEND_FILTER, listenerSlot, 0, listenerFilter); } else { alSource3i(chan->openalSource, AL_AUXILIARY_SEND_FILTER, listenerSlot, 0, AL_FILTER_NULL); } } if ( ( !looping && chan->leadinSample->hardwareBuffer ) || ( looping && chan->soundShader->entries[0]->hardwareBuffer ) ) { // handle uncompressed (non streaming) single shot and looping sounds if ( chan->triggered ) { alSourcei( chan->openalSource, AL_BUFFER, looping ? chan->soundShader->entries[0]->openalBuffer : chan->leadinSample->openalBuffer ); } } else { ALint finishedbuffers; ALuint buffers[3]; // handle streaming sounds (decode on the fly) both single shot AND looping if ( chan->triggered ) { alSourcei( chan->openalSource, AL_BUFFER, 0 ); alDeleteBuffers( 3, &chan->lastopenalStreamingBuffer[0] ); chan->lastopenalStreamingBuffer[0] = chan->openalStreamingBuffer[0]; chan->lastopenalStreamingBuffer[1] = chan->openalStreamingBuffer[1]; chan->lastopenalStreamingBuffer[2] = chan->openalStreamingBuffer[2]; alGenBuffers( 3, &chan->openalStreamingBuffer[0] ); buffers[0] = chan->openalStreamingBuffer[0]; buffers[1] = chan->openalStreamingBuffer[1]; buffers[2] = chan->openalStreamingBuffer[2]; finishedbuffers = 3; } else { alGetSourcei( chan->openalSource, AL_BUFFERS_PROCESSED, &finishedbuffers ); alSourceUnqueueBuffers( chan->openalSource, finishedbuffers, &buffers[0] ); if ( finishedbuffers == 3 ) { chan->triggered = true; } } for ( j = 0; j < finishedbuffers; j++ ) { chan->GatherChannelSamples( chan->openalStreamingOffset * sample->objectInfo.nChannels, MIXBUFFER_SAMPLES * sample->objectInfo.nChannels, alignedInputSamples ); for ( int i = 0; i < ( MIXBUFFER_SAMPLES * sample->objectInfo.nChannels ); i++ ) { if ( alignedInputSamples[i] < -32768.0f ) ((short *)alignedInputSamples)[i] = -32768; else if ( alignedInputSamples[i] > 32767.0f ) ((short *)alignedInputSamples)[i] = 32767; else ((short *)alignedInputSamples)[i] = idMath::FtoiFast( alignedInputSamples[i] ); } alBufferData( buffers[j], chan->leadinSample->objectInfo.nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, alignedInputSamples, MIXBUFFER_SAMPLES * sample->objectInfo.nChannels * sizeof( short ), 44100 ); chan->openalStreamingOffset += MIXBUFFER_SAMPLES; } if ( finishedbuffers ) { alSourceQueueBuffers( chan->openalSource, finishedbuffers, &buffers[0] ); } } // (re)start if needed.. if ( chan->triggered ) { alSourcePlay( chan->openalSource ); chan->triggered = false; } } } else { if ( slowmoActive && !chan->disallowSlow ) { idSlowChannel slow = sound->GetSlowChannel( chan ); slow.AttachSoundChannel( chan ); if ( sample->objectInfo.nChannels == 2 ) { // need to add a stereo path, but very few samples go through this memset( alignedInputSamples, 0, sizeof( alignedInputSamples[0] ) * MIXBUFFER_SAMPLES * 2 ); } else { slow.GatherChannelSamples( offset, MIXBUFFER_SAMPLES, alignedInputSamples ); } sound->SetSlowChannel( chan, slow ); } else { sound->ResetSlowChannel( chan ); // if we are getting a stereo sample adjust accordingly if ( sample->objectInfo.nChannels == 2 ) { // we should probably check to make sure any looping is also to a stereo sample... chan->GatherChannelSamples( offset*2, MIXBUFFER_SAMPLES*2, alignedInputSamples ); } else { chan->GatherChannelSamples( offset, MIXBUFFER_SAMPLES, alignedInputSamples ); } } // // work out the left / right ear values // float ears[6]; if ( global || omni ) { // same for all speakers for ( int i = 0 ; i < 6 ; i++ ) { ears[i] = idSoundSystemLocal::s_globalFraction.GetFloat() * volume; } ears[3] = idSoundSystemLocal::s_subFraction.GetFloat() * volume; // subwoofer } else { CalcEars( numSpeakers, spatializedOriginInMeters, listenerPos, listenerAxis, ears, spatialize ); for ( int i = 0 ; i < 6 ; i++ ) { ears[i] *= volume; } } // if the mask is 0, it really means do every channel if ( !mask ) { mask = 255; } // cleared mask bits set the mix volume to zero for ( int i = 0 ; i < 6 ; i++ ) { if ( !(mask & ( 1 << i ) ) ) { ears[i] = 0; } } // if sounds are generally normalized, using a mixing volume over 1.0 will // almost always cause clipping noise. If samples aren't normalized, there // is a good call to allow overvolumes if ( idSoundSystemLocal::s_clipVolumes.GetBool() && !( parms->soundShaderFlags & SSF_UNCLAMPED ) ) { for ( int i = 0 ; i < 6 ; i++ ) { if ( ears[i] > 1.0f ) { ears[i] = 1.0f; } } } // if this is the very first mixing block, set the lastV // to the current volume if ( current44kHz == chan->trigger44kHzTime ) { for ( j = 0 ; j < 6 ; j++ ) { chan->lastV[j] = ears[j]; } } if ( numSpeakers == 6 ) { if ( sample->objectInfo.nChannels == 1 ) { SIMDProcessor->MixSoundSixSpeakerMono( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); } else { SIMDProcessor->MixSoundSixSpeakerStereo( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); } } else { if ( sample->objectInfo.nChannels == 1 ) { SIMDProcessor->MixSoundTwoSpeakerMono( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); } else { SIMDProcessor->MixSoundTwoSpeakerStereo( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); } } for ( j = 0 ; j < 6 ; j++ ) { chan->lastV[j] = ears[j]; } } soundSystemLocal.soundStats.activeSounds++; } /* =============== idSoundWorldLocal::FindAmplitude this is called from the main thread if listenerPosition is NULL, this is being used for shader parameters, like flashing lights and glows based on sound level. Otherwise, it is being used for the screen-shake on a player. This doesn't do the portal-occlusion currently, because it would have to reset all the defs which would be problematic in multiplayer =============== */ float idSoundWorldLocal::FindAmplitude( idSoundEmitterLocal *sound, const int localTime, const idVec3 *listenerPosition, const s_channelType channel, bool shakesOnly ) { int i, j; soundShaderParms_t *parms; float volume; int activeChannelCount; static const int AMPLITUDE_SAMPLES = MIXBUFFER_SAMPLES/8; float sourceBuffer[AMPLITUDE_SAMPLES]; float sumBuffer[AMPLITUDE_SAMPLES]; // work out the distance from the listener to the emitter float dlen; if ( !sound->playing ) { return 0; } if ( listenerPosition ) { // this doesn't do the portal spatialization idVec3 dist = sound->origin - *listenerPosition; dlen = dist.Length(); dlen *= DOOM_TO_METERS; } else { dlen = 1; } activeChannelCount = 0; for ( i = 0; i < SOUND_MAX_CHANNELS ; i++ ) { idSoundChannel *chan = &sound->channels[ i ]; if ( !chan->triggerState ) { continue; } if ( channel != SCHANNEL_ANY && chan->triggerChannel != channel) { continue; } parms = &chan->parms; int localTriggerTimes = chan->trigger44kHzTime; bool looping = ( parms->soundShaderFlags & SSF_LOOPING ) != 0; // check for screen shakes float shakes = parms->shakes; if ( shakesOnly && shakes <= 0.0f ) { continue; } // // calculate volume // if ( !listenerPosition ) { // just look at the raw wav data for light shader evaluation volume = 1.0; } else { volume = parms->volume; volume = soundSystemLocal.dB2Scale( volume ); if ( shakesOnly ) { volume *= shakes; } if ( listenerPosition && !( parms->soundShaderFlags & SSF_GLOBAL ) ) { // check for overrides float maxd = parms->maxDistance; float mind = parms->minDistance; if ( dlen >= maxd ) { volume = 0.0f; } else if ( dlen > mind ) { float frac = idMath::ClampFloat( 0, 1, 1.0f - ((dlen - mind) / (maxd - mind))); if ( idSoundSystemLocal::s_quadraticFalloff.GetBool() ) { frac *= frac; } volume *= frac; } } } if ( volume <= 0 ) { continue; } // // fetch the sound from the cache // this doesn't handle stereo samples correctly... // if ( !listenerPosition && chan->parms.soundShaderFlags & SSF_NO_FLICKER ) { // the NO_FLICKER option is to allow a light to still play a sound, but // not have it effect the intensity for ( j = 0 ; j < (AMPLITUDE_SAMPLES); j++ ) { sourceBuffer[j] = j & 1 ? 32767.0f : -32767.0f; } } else { int offset = (localTime - localTriggerTimes); // offset in samples int size = ( looping ? chan->soundShader->entries[0]->LengthIn44kHzSamples() : chan->leadinSample->LengthIn44kHzSamples() ); short *amplitudeData = (short *)( looping ? chan->soundShader->entries[0]->amplitudeData : chan->leadinSample->amplitudeData ); if ( amplitudeData ) { // when the amplitudeData is present use that fill a dummy sourceBuffer // this is to allow for amplitude based effect on hardware audio solutions if ( looping ) offset %= size; if ( offset < size ) { for ( j = 0 ; j < (AMPLITUDE_SAMPLES); j++ ) { sourceBuffer[j] = j & 1 ? amplitudeData[ ( offset / 512 ) * 2 ] : amplitudeData[ ( offset / 512 ) * 2 + 1 ]; } } } else { // get actual sample data chan->GatherChannelSamples( offset, AMPLITUDE_SAMPLES, sourceBuffer ); } } activeChannelCount++; if ( activeChannelCount == 1 ) { // store to the buffer for( j = 0; j < AMPLITUDE_SAMPLES; j++ ) { sumBuffer[ j ] = volume * sourceBuffer[ j ]; } } else { // add to the buffer for( j = 0; j < AMPLITUDE_SAMPLES; j++ ) { sumBuffer[ j ] += volume * sourceBuffer[ j ]; } } } if ( activeChannelCount == 0 ) { return 0.0; } float high = -32767.0f; float low = 32767.0f; // use a 20th of a second for( i = 0; i < (AMPLITUDE_SAMPLES); i++ ) { float fabval = sumBuffer[i]; if ( high < fabval ) { high = fabval; } if ( low > fabval ) { low = fabval; } } float sout; sout = atan( (high - low) / 32767.0f) / DEG2RAD(45); return sout; } /* ================= idSoundWorldLocal::FadeSoundClasses fade all sounds in the world with a given shader soundClass to is in Db (sigh), over is in seconds ================= */ void idSoundWorldLocal::FadeSoundClasses( const int soundClass, const float to, const float over ) { if ( soundClass < 0 || soundClass >= SOUND_MAX_CLASSES ) { common->Error( "idSoundWorldLocal::FadeSoundClasses: bad soundClass %i", soundClass ); } idSoundFade *fade = &soundClassFade[ soundClass ]; int length44kHz = soundSystemLocal.MillisecondsToSamples( over * 1000 ); // if it is already fading to this volume at this rate, don't change it if ( fade->fadeEndVolume == to && fade->fadeEnd44kHz - fade->fadeStart44kHz == length44kHz ) { return; } int start44kHz; if ( fpa[0] ) { // if we are recording an AVI demo, don't use hardware time start44kHz = lastAVI44kHz + MIXBUFFER_SAMPLES; } else { start44kHz = soundSystemLocal.GetCurrent44kHzTime() + MIXBUFFER_SAMPLES; } // fade it fade->fadeStartVolume = fade->FadeDbAt44kHz( start44kHz ); fade->fadeStart44kHz = start44kHz; fade->fadeEnd44kHz = start44kHz + length44kHz; fade->fadeEndVolume = to; } /* ================= idSoundWorldLocal::SetSlowmo ================= */ void idSoundWorldLocal::SetSlowmo( bool active ) { slowmoActive = active; } /* ================= idSoundWorldLocal::SetSlowmoSpeed ================= */ void idSoundWorldLocal::SetSlowmoSpeed( float speed ) { slowmoSpeed = speed; } /* ================= idSoundWorldLocal::SetEnviroSuit ================= */ void idSoundWorldLocal::SetEnviroSuit( bool active ) { enviroSuitActive = active; }