/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Engine/StdH.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // sound event codes for prediction #define EVENT_SOUNDPLAY 0x0101 #define EVENT_SOUNDSTOP 0x0102 #define EVENT_SOUNDSETOFFSET 0x0103 extern FLOAT snd_fEarsDistance; extern FLOAT snd_fDelaySoundSpeed; extern FLOAT snd_fDopplerSoundSpeed; extern FLOAT snd_fPanStrength; extern FLOAT snd_fLRFilter; extern FLOAT snd_fBFilter; extern FLOAT snd_fUFilter; extern FLOAT snd_fDFilter; extern BOOL _bPredictionActive; // console variables for volume extern FLOAT snd_fSoundVolume; extern FLOAT snd_fMusicVolume; static CTString GetPred(CEntity*pen) { CTString str1; if (pen->IsPredictor()) { str1 = "predictor"; } else if (pen->IsPredicted()) { str1 = "predicted"; } else if (pen->en_ulFlags & ENF_WILLBEPREDICTED) { str1 = "will be predicted"; } else { str1 = "???"; } CTString str; str.PrintF("%08x-%s", pen, (const char *) str1); return str; } /* ==================================================== * * Class global methods */ /* * Constructor */ CSoundObject::CSoundObject() { so_pCsdLink = NULL; so_psdcDecoder = NULL; so_penEntity = NULL; so_slFlags = SOF_NONE; // clear sound settings so_spNew.sp_fLeftVolume = 1.0f; so_spNew.sp_fRightVolume = 1.0f; so_spNew.sp_slLeftFilter = 0x7FFF; so_spNew.sp_slRightFilter = 0x7FFF; so_spNew.sp_fPitchShift = 1.0f; so_spNew.sp_fPhaseShift = 0.0f; so_spNew.sp_fDelay = 0.0f; so_sp = so_spNew; so_fLeftOffset = 0.0f; so_fRightOffset = 0.0f; so_fOffsetDelta = 0.0f; so_fDelayed = 0.0f; so_fLastLeftVolume = 1.0f; so_fLastRightVolume = 1.0f; so_swLastLeftSample = 0; so_swLastRightSample = 0; // 3d effects so_sp3.sp3_fFalloff = 0.0f; so_sp3.sp3_fHotSpot = 0.0f; so_sp3.sp3_fMaxVolume = 0.0f; so_sp3.sp3_fPitch = 1.0f; } /* * Destructor */ CSoundObject::~CSoundObject() { Stop_internal(); } // copy from another object of same class void CSoundObject::Copy(CSoundObject &soOther) { Stop_internal(); so_sp = so_spNew = soOther.so_sp; so_sp3 = soOther.so_sp3; so_penEntity = NULL; so_slFlags = soOther.so_slFlags; if (soOther.so_slFlags&SOF_PLAY) { Play(soOther.so_pCsdLink, soOther.so_slFlags); } } // Set 3D parameters void CSoundObject::Set3DParameters( FLOAT fFalloff, FLOAT fHotSpot, FLOAT fMaxVolume, FLOAT fPitch) { ASSERT( (fFalloff > 0) && (fHotSpot >= 0)); ASSERT( fMaxVolume >= 0); ASSERT( fFalloff >= fHotSpot); ASSERT( fPitch > 0); CSoundObject *pso = this; // if the sound's entity is a predictor if (_bPredictionActive && so_penEntity!=NULL) { if (so_penEntity->IsPredictionHead()) { // get your prediction tail //CPrintF("SET3D: "); CEntity *pen = so_penEntity->GetPredictionTail(); if (pen!=so_penEntity) { pso = (CSoundObject *)( ((UBYTE*)pen) + (int(this)-int(so_penEntity)) ); } } } pso->so_sp3.sp3_fFalloff = fFalloff; pso->so_sp3.sp3_fHotSpot = fHotSpot; pso->so_sp3.sp3_fMaxVolume = fMaxVolume; pso->so_sp3.sp3_fPitch = fPitch; } /* ==================================================== * Sound control methods */ // get proper sound object for predicted events - return NULL the event is already predicted CSoundObject *CSoundObject::GetPredictionTail(ULONG ulTypeID, ULONG ulEventID) { // if the sound has an entity if (so_penEntity!=NULL) { //CPrintF(" {%s}", GetPred(so_penEntity)); // if the entity is temporary predictor if (so_penEntity->GetFlags()&ENF_TEMPPREDICTOR) { //CPrintF(" temppred\n"); // it must not play the sound return NULL; } SLONG slOffset = int(this)-int(so_penEntity); ULONG ulCRC; CRC_Start(ulCRC); CRC_AddLONG(ulCRC, slOffset); CRC_AddLONG(ulCRC, ulTypeID); CRC_Finish(ulCRC); // if the event is predicted if (so_penEntity->CheckEventPrediction(ulCRC, ulEventID)) { //CPrintF(" predicted\n"); // return nothing return NULL; } CEntity *pen = so_penEntity; // find eventual prediction tail sound object if (pen->IsPredictor()) { pen = pen->GetPredictionTail(); if (pen!=so_penEntity) { //CPrintF(" ROUTED\n"); return (CSoundObject *)( ((UBYTE*)pen) + slOffset ); } } } // if no specific prediction states - use this object //CPrintF(" ORIGINAL\n"); return this; } /* * Play */ void CSoundObject::Play(CSoundData *pCsdLink, SLONG slFlags) { // synchronize access to sounds CTSingleLock slSounds( &_pSound->sl_csSound, TRUE); //CPrintF("PLAY: '%s'", (const char*)pCsdLink->GetName().FileName()); // get prediction tail CSoundObject *psoTail = GetPredictionTail(EVENT_SOUNDPLAY, (ULONG)pCsdLink); // if the event is predicted if (psoTail==NULL) { // do nothing; return; } // play the sound in the given object psoTail->Play_internal(pCsdLink, slFlags); } // play sound - internal function - doesn't account for prediction void CSoundObject::Play_internal( CSoundData *pCsdLink, SLONG slFlags) { ASSERT(so_penEntity==NULL || !so_penEntity->IsPredictor()); // check if should continue with new sound BOOL bContinue = ((slFlags&SOF_SMOOTHCHANGE) && (so_slFlags&SOF_PREPARE) && (so_slFlags&SOF_PLAY)); Stop_internal(); // mark new data as referenced once more pCsdLink->AddReference(); // mark old data as referenced once less so_pCsdLink->RemReference(); // store init SoundData so_pCsdLink = pCsdLink; // add to link list so_pCsdLink->AddObjectLink(*this); // store flags so_slFlags = slFlags; // if should continue with new sound if (bContinue) { // play buffer immediately so_slFlags = so_slFlags | SOF_PREPARE | SOF_PLAY; } else { // play buffer so_slFlags = (so_slFlags & ~(SOF_PREPARE|SOF_PAUSED)) | SOF_PLAY; } // if the sound data is encoded if (so_pCsdLink->sd_ulFlags&SDF_ENCODED) { // create decoder if (so_pCsdLink->sd_ulFlags&SDF_STREAMING) { so_psdcDecoder = new CSoundDecoder(so_pCsdLink->GetName()); } else { ASSERT(FALSE); // nonstreaming not supported anymore } } // remember starting parameters so_sp = so_spNew; // initialize mixer temporary variables if (!(slFlags&SOF_LOADED)) { so_fLastLeftVolume = so_sp.sp_fLeftVolume; so_fLastRightVolume = so_sp.sp_fRightVolume; so_fLeftOffset = 0.0f; so_fRightOffset = 0.0f; so_fOffsetDelta = 0.0f; so_fDelayed = 0.0f; if (!bContinue) { so_swLastLeftSample = 0; so_swLastRightSample = 0; } else { // adjust for master volume if(so_slFlags&SOF_MUSIC) { so_fLastLeftVolume *= snd_fMusicVolume; so_fLastRightVolume *= snd_fMusicVolume; } else { so_fLastLeftVolume *= snd_fSoundVolume; so_fLastRightVolume *= snd_fSoundVolume; } } } } // hard set sound offset in seconds void CSoundObject::SetOffset( FLOAT fOffset) { // synchronize access to sounds CTSingleLock slSounds( &_pSound->sl_csSound, TRUE); // get prediction tail //CPrintF("SETOFF: "); CSoundObject *psoTail = GetPredictionTail(EVENT_SOUNDSETOFFSET, 0); // if the event is predicted if (psoTail==NULL) { // do nothing; return; } // if sound not playing if (psoTail->so_pCsdLink==NULL) { // do nothing return; } // safety check ASSERT( fOffset>=0); if( fOffset<0) { CPrintF( "BUG: Trying to set negative offset (%.2g) in sound '%s' !\n", fOffset, (const char *) (CTString&)psoTail->so_pCsdLink->GetName()); fOffset = 0.0f; } // on the other hand, don't set offset for real - might be source for some bugs! return; // update sound offsets CPrintF("Setting offset: %g\n", fOffset); psoTail->so_fLeftOffset = psoTail->so_fRightOffset = psoTail->so_pCsdLink->sd_wfeFormat.nSamplesPerSec*fOffset; } /* * Stop */ void CSoundObject::Stop(void) { // synchronize access to sounds CTSingleLock slSounds( &_pSound->sl_csSound, TRUE); //CPrintF("STOP"); if (so_pCsdLink!=NULL) { //CPrintF(" '%s'", (const char*)so_pCsdLink->GetName().FileName()); } CSoundObject *psoTail = this; // get prediction tail psoTail = GetPredictionTail(EVENT_SOUNDSTOP, (ULONG)so_pCsdLink); // if the event is predicted if (psoTail==NULL) { // do nothing; return; } psoTail->Stop_internal(); } void CSoundObject::Stop_internal(void) { // sound is stopped so_slFlags &= ~(SOF_PLAY|SOF_PREPARE|SOF_PAUSED); // destroy decoder if exists if( so_psdcDecoder!=NULL) { delete so_psdcDecoder; so_psdcDecoder = NULL; } // if added in link list, remove it from list if( IsHooked()) { ASSERT(so_pCsdLink != NULL); so_pCsdLink->RemoveObjectLink(*this); // remove reference from SoundData so_pCsdLink->RemReference(); // clear SoundData link so_pCsdLink = NULL; } } // Update all 3d effects void CSoundObject::Update3DEffects(void) { // if not 3d sound if( !(so_slFlags & SOF_3D)) { // do nothing; return; } // if (!(so_slFlags&SOF_PREPARE)) { // if the sound's entity is a predictor /* if (so_penEntity!=NULL && so_penEntity->IsPredictor()) { // kill the sound so_slFlags&=~SOF_PLAY; //CPrintF("Update canceled %s (%s)\n", (const char*)so_pCsdLink->GetName(), GetPred(so_penEntity)); // do nothing; return; } */ //CPrintF("Update PASSED %s (%s)\n", (const char*)so_pCsdLink->GetName(), GetPred(so_penEntity)); // } // total parameters (accounting for all listeners) FLOAT fTLVolume = 0, fTRVolume = 0; FLOAT fTLFilter = UpperLimit(0.0f), fTRFilter = UpperLimit(0.0f); FLOAT fTLDelay = UpperLimit(0.0f), fTRDelay = UpperLimit(0.0f); FLOAT fTPitchShift = 0; // get your position parameters FLOAT3D vPosition(0,0,0); FLOAT3D vSpeed(0,0,0); if (so_penEntity!=NULL) { vPosition = so_penEntity->en_plPlacement.pl_PositionVector; if (so_penEntity->en_ulPhysicsFlags&EPF_MOVABLE) { CMovableEntity *penMovable = (CMovableEntity *)so_penEntity; vSpeed = penMovable->en_vCurrentTranslationAbsolute; } } // for each listener INDEX ctEffectiveListeners = 0; {FOREACHINLIST( CSoundListener, sli_lnInActiveListeners, _pSound->sl_lhActiveListeners, itsli) { CSoundListener &sli = *itsli; // if local, but not of this listener if ((so_slFlags&SOF_LOCAL) && so_penEntity!=sli.sli_penEntity) { // don't add this listener continue; } // calculated parameters for this listener FLOAT fLVolume, fRVolume; FLOAT fLFilter, fRFilter; FLOAT fLDelay , fRDelay ; FLOAT fPitchShift; // calculate distance from listener FLOAT3D vAbsDelta = vPosition - sli.sli_vPosition; FLOAT fAbsDelta = vAbsDelta.Length(); // if too far away if (fAbsDelta>so_sp3.sp3_fFalloff) { // don't add this listener continue; } // calculate distance falloff factor FLOAT fDistanceFactor; if( fAbsDelta <= so_sp3.sp3_fHotSpot) { fDistanceFactor = 1; } else { fDistanceFactor = (so_sp3.sp3_fFalloff - fAbsDelta) / (so_sp3.sp3_fFalloff - so_sp3.sp3_fHotSpot); } ASSERT(fDistanceFactor>=0 && fDistanceFactor<=+1); // calculate volumetric influence // NOTE: decoded sounds must be treated as volumetric FLOAT fNonVolumetric = 1.0f; FLOAT fNonVolumetricAdvanced = 1.0f; if( (so_slFlags & SOF_VOLUMETRIC) || so_psdcDecoder!=NULL) { fNonVolumetric = 1.0f-fDistanceFactor; fNonVolumetricAdvanced = 0.0f; } ASSERT(fNonVolumetric>=0 && fNonVolumetric<=+1); // find doppler effect pitch shift fPitchShift = 1.0f; if (fAbsDelta>0.001f) { FLOAT3D vObjectDirection = vAbsDelta/fAbsDelta; FLOAT fObjectSpeed = vSpeed%vObjectDirection; // negative towards listener FLOAT fListenerSpeed = sli.sli_vSpeed%vObjectDirection; // positive towards object fPitchShift = (snd_fDopplerSoundSpeed+fListenerSpeed*fNonVolumetricAdvanced)/ (snd_fDopplerSoundSpeed+fObjectSpeed*fNonVolumetricAdvanced); } // find position of sound relative to viewer orientation FLOAT3D vRelative = vAbsDelta*!sli.sli_mRotation; // find distances from left and right ear FLOAT fLDistance = (FLOAT3D(-snd_fEarsDistance*fNonVolumetricAdvanced/2,0,0)-vRelative).Length(); FLOAT fRDistance = (FLOAT3D(+snd_fEarsDistance*fNonVolumetricAdvanced/2,0,0)-vRelative).Length(); // calculate sound delay to each ear fLDelay = fLDistance/snd_fDelaySoundSpeed; fRDelay = fRDistance/snd_fDelaySoundSpeed; // calculate relative sound directions FLOAT fLRFactor=0; // positive right FLOAT fFBFactor=0; // positive front FLOAT fUDFactor=0; // positive up if (fAbsDelta>0.001f) { FLOAT3D vDir = vRelative/fAbsDelta; fLRFactor = +vDir(1); fFBFactor = -vDir(3); fUDFactor = +vDir(2); } ASSERT(fLRFactor>=-1.1 && fLRFactor<=+1.1); ASSERT(fFBFactor>=-1.1 && fFBFactor<=+1.1); ASSERT(fUDFactor>=-1.1 && fUDFactor<=+1.1); // calculate panning influence factor FLOAT fPanningFactor= fNonVolumetric*snd_fPanStrength; ASSERT(fPanningFactor>=0 && fPanningFactor<=+1); // calc volume for left and right channel FLOAT fVolume = so_sp3.sp3_fMaxVolume * fDistanceFactor; if( fLRFactor > 0) { fLVolume = (1-fLRFactor*fPanningFactor) * fVolume; fRVolume = fVolume; } else { fLVolume = fVolume; fRVolume = (1+fLRFactor*fPanningFactor) * fVolume; } // calculate filters FLOAT fListenerFilter = sli.sli_fFilter; if (so_slFlags&SOF_NOFILTER) { fListenerFilter = 0.0f; } fLFilter = fRFilter = 1+fListenerFilter; if( fLRFactor > 0) { fLFilter += fLRFactor*snd_fLRFilter*fNonVolumetricAdvanced; } else { fRFilter -= fLRFactor*snd_fLRFilter*fNonVolumetricAdvanced; } if( fFBFactor<0) { fLFilter -= snd_fBFilter*fFBFactor*fNonVolumetricAdvanced; fRFilter -= snd_fBFilter*fFBFactor*fNonVolumetricAdvanced; } if( fUDFactor>0) { fLFilter += snd_fUFilter*fUDFactor*fNonVolumetricAdvanced; fRFilter += snd_fUFilter*fUDFactor*fNonVolumetricAdvanced; } else { fLFilter -= snd_fDFilter*fUDFactor*fNonVolumetricAdvanced; fRFilter -= snd_fDFilter*fUDFactor*fNonVolumetricAdvanced; } // adjust calculated volume to the one of listener fLVolume *= sli.sli_fVolume; fRVolume *= sli.sli_fVolume; // update parameters for all listener fTLVolume = Max( fTLVolume, fLVolume); fTRVolume = Max( fTRVolume, fRVolume); fTLDelay = Min( fTLDelay , fLDelay ); fTRDelay = Min( fTRDelay , fRDelay ); fTLFilter = Min( fTLFilter, fLFilter); fTRFilter = Min( fTRFilter, fRFilter); fTPitchShift += fPitchShift; ctEffectiveListeners++; }} fTPitchShift /= ctEffectiveListeners; // calculate 2d parameters FLOAT fPitchShift = fTPitchShift * so_sp3.sp3_fPitch; FLOAT fPhaseShift = fTLDelay-fTRDelay; FLOAT fDelay = Min( fTRDelay,fTLDelay); // CPrintF("V:%f %f F:%f %f P:%f S:%f\n", // fTLVolume, fTRVolume, // fTLFilter, fTRFilter, // fPhaseShift, // fPitchShift); // set sound parameters fTLVolume = Clamp( fTLVolume, SL_VOLUME_MIN, SL_VOLUME_MAX); fTRVolume = Clamp( fTRVolume, SL_VOLUME_MIN, SL_VOLUME_MAX); SetVolume( fTLVolume, fTRVolume); if( fTLVolume>0 || fTRVolume>0) { // do safety clamping fTLFilter = ClampDn( fTLFilter, 1.0f); fTRFilter = ClampDn( fTRFilter, 1.0f); fDelay = ClampDn( fDelay, 0.0f); fPitchShift = ClampDn( fPitchShift, 0.001f); fPhaseShift = Clamp( fPhaseShift, -1.0f, +1.0f); // set sound params SetFilter( fTLFilter, fTRFilter); SetDelay( fDelay); SetPitch( fPitchShift); SetPhase( fPhaseShift); } } // Prepare sound void CSoundObject::PrepareSound(void) { ASSERT(so_penEntity==NULL || !so_penEntity->IsPredictor()); so_fLastLeftVolume = so_spNew.sp_fLeftVolume; so_fLastRightVolume = so_spNew.sp_fRightVolume; // adjust for master volume if(so_slFlags&SOF_MUSIC) { so_fLastLeftVolume *= snd_fMusicVolume; so_fLastRightVolume *= snd_fMusicVolume; } else { so_fLastLeftVolume *= snd_fSoundVolume; so_fLastRightVolume *= snd_fSoundVolume; } } // Obtain sound and play it for this object void CSoundObject::Play_t(const CTFileName &fnmSound, SLONG slFlags) // throw char * { // obtain it (adds one reference) CSoundData *ptd = _pSoundStock->Obtain_t(fnmSound); // set it as data (adds one more reference, and remove old reference) Play(ptd, slFlags); // release it (removes one reference) _pSoundStock->Release(ptd); // total reference count +1+1-1 = +1 for new data -1 for old data } // read/write functions void CSoundObject::Read_t(CTStream *pistr) // throw char * { int iDroppedOut; // load file name CTFileName fnmSound; *pistr >> fnmSound; // load object preferences *pistr >> iDroppedOut; *pistr >> so_slFlags; *pistr >> so_spNew.sp_fLeftVolume; *pistr >> so_spNew.sp_fRightVolume; *pistr >> so_spNew.sp_slLeftFilter; *pistr >> so_spNew.sp_slRightFilter; *pistr >> so_spNew.sp_fPitchShift; *pistr >> so_spNew.sp_fPhaseShift; *pistr >> so_spNew.sp_fDelay; *pistr >> so_fDelayed; *pistr >> so_fLastLeftVolume; *pistr >> so_fLastRightVolume; *pistr >> so_swLastLeftSample; *pistr >> so_swLastRightSample; *pistr >> so_fLeftOffset; *pistr >> so_fRightOffset; *pistr >> so_fOffsetDelta; // load 3D parameters so_penEntity = NULL; *pistr >> so_sp3.sp3_fFalloff; *pistr >> so_sp3.sp3_fHotSpot; *pistr >> so_sp3.sp3_fMaxVolume; *pistr >> so_sp3.sp3_fPitch; // update current state so_sp = so_spNew; // Obtain and play object (sound) if ( fnmSound != "" && (so_slFlags&SOF_PLAY)) { Play_t( fnmSound, so_slFlags|SOF_LOADED); } } void CSoundObject::Write_t(CTStream *pistr) // throw char * { int iDroppedOut=0; // save file name if (so_pCsdLink!=NULL) { *pistr << (so_pCsdLink->GetName()); } else { *pistr << CTFILENAME(""); } // save object preferences *pistr << iDroppedOut; *pistr << so_slFlags; *pistr << so_spNew.sp_fLeftVolume; *pistr << so_spNew.sp_fRightVolume; *pistr << so_spNew.sp_slLeftFilter; *pistr << so_spNew.sp_slRightFilter; *pistr << so_spNew.sp_fPitchShift; *pistr << so_spNew.sp_fPhaseShift; *pistr << so_spNew.sp_fDelay; *pistr << so_fDelayed; *pistr << so_fLastLeftVolume; *pistr << so_fLastRightVolume; *pistr << so_swLastLeftSample; *pistr << so_swLastRightSample; *pistr << so_fLeftOffset; *pistr << so_fRightOffset; *pistr << so_fOffsetDelta; // save 3D parameters *pistr << so_sp3.sp3_fFalloff; *pistr << so_sp3.sp3_fHotSpot; *pistr << so_sp3.sp3_fMaxVolume; *pistr << so_sp3.sp3_fPitch; }