From d92df758a8aabdd320fc9b1273129d06a33506d7 Mon Sep 17 00:00:00 2001 From: Sven Eberhardt Date: Sun, 9 Aug 2015 18:40:42 -0400 Subject: [PATCH] Added pitch parameter to Sound() script function and allow pitch and sound level to be modified for sounds that have already been started. Sound() called when the instance is already running used to fail. Now, it always succeeds (also for script sync safety) but updates the sound level and pitch parameters. SoundAt has not been modified since it allows creation of multiple concurrent sound instance of the same effect without object context. --- docs/sdk/script/fn/Sound.xml | 7 ++++ docs/sdk/script/fn/SoundAt.xml | 6 ++++ src/gamescript/C4GameScript.cpp | 30 +++++++++++----- src/platform/C4SoundSystem.cpp | 63 ++++++++++++++++++++++++++------- src/platform/C4SoundSystem.h | 8 +++-- 5 files changed, 89 insertions(+), 25 deletions(-) diff --git a/docs/sdk/script/fn/Sound.xml b/docs/sdk/script/fn/Sound.xml index 25b060411..b519dd856 100644 --- a/docs/sdk/script/fn/Sound.xml +++ b/docs/sdk/script/fn/Sound.xml @@ -45,9 +45,16 @@ The further away the sound effect from the player, the more quiet it is played. By default, the sound will not be hearable anymore in a distance of 700 pixels. A custom distance can be specified here. + + int + pitch + Pitch modification between -90 and 1000. Values larger than zero let the sound play the faster and at a higher pitch. The resulting speed multiplication factor is (pitch + 100) / 100. + + Plays a sound. The specified sound file has to be available in the group Sound.ocg, in the active scenario file, or in any loaded object definition. The audibility of object local sounds will depend on the position of the object relative to the visible viewports. + When a sound effect is already played in the same context (calling object or globally), it is not played again. Instead, only the volume and pitch parameters are updated. This feature can be used to adjust sound parameters live e.g. to tune the pitch of a motor sound based on its speed. FindObject(Find_ID(WindGenerator))->Sound("Fanfare", false, 50); diff --git a/docs/sdk/script/fn/SoundAt.xml b/docs/sdk/script/fn/SoundAt.xml index 6ebd975ef..c2c1f67ea 100644 --- a/docs/sdk/script/fn/SoundAt.xml +++ b/docs/sdk/script/fn/SoundAt.xml @@ -43,6 +43,12 @@ The further away the sound effect from the player, the more quiet it is played. By default, the sound will not be hearable anymore in a distance of 700 pixels. A custom distance can be specified here. + + int + pitch + Pitch modification between -90 and 1000. Values larger than zero let the sound play the faster and at a higher pitch. The resulting speed multiplication factor is (pitch + 100) / 100. + + Plays a sound at the specified position. The specified sound file has to be available in the group Sound.ocg, in the active scenario file, or in any loaded object definition. diff --git a/src/gamescript/C4GameScript.cpp b/src/gamescript/C4GameScript.cpp index 6b7c4fa9f..49bfba87f 100644 --- a/src/gamescript/C4GameScript.cpp +++ b/src/gamescript/C4GameScript.cpp @@ -485,7 +485,7 @@ static C4Void FnBlastFree(C4PropList * _this, long iX, long iY, long iLevel, Nil return C4Void(); } -static bool FnSoundAt(C4PropList * _this, C4String *szSound, long iX, long iY, Nillable iLevel, Nillable iAtPlayer, long iCustomFalloffDistance) +static bool FnSoundAt(C4PropList * _this, C4String *szSound, long iX, long iY, Nillable iLevel, Nillable iAtPlayer, long iCustomFalloffDistance, long iPitch) { // play here? if (!iAtPlayer.IsNil()) @@ -510,12 +510,12 @@ static bool FnSoundAt(C4PropList * _this, C4String *szSound, long iX, long iY, N iX += pObj->GetX(); iY += pObj->GetY(); } - StartSoundEffectAt(FnStringPar(szSound),iX,iY,iLevel,iCustomFalloffDistance); + StartSoundEffectAt(FnStringPar(szSound), iX, iY, iLevel, iCustomFalloffDistance, iPitch); // always return true (network safety!) return true; } -static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillable iLevel, Nillable iAtPlayer, long iLoop, long iCustomFalloffDistance) +static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillable iLevel, Nillable iAtPlayer, long iLoop, long iCustomFalloffDistance, long iPitch) { // play here? if (!iAtPlayer.IsNil()) @@ -536,14 +536,26 @@ static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillabl // target object C4Object *pObj = NULL; if (!fGlobal) pObj = Object(_this); - // already playing? - if (iLoop >= 0 && GetSoundInstance(FnStringPar(szSound), pObj)) - return false; - // try to play effect + // play/stop? if (iLoop >= 0) - StartSoundEffect(FnStringPar(szSound),!!iLoop,iLevel,pObj, iCustomFalloffDistance); + { + // already playing? + C4SoundInstance *inst = GetSoundInstance(FnStringPar(szSound), pObj); + if (inst) + { + // then just update parameters + SoundUpdate(inst, iLevel, iPitch); + } + else + { + // try to play effect + StartSoundEffect(FnStringPar(szSound), !!iLoop, iLevel, pObj, iCustomFalloffDistance, iPitch); + } + } else - StopSoundEffect(FnStringPar(szSound),pObj); + { + StopSoundEffect(FnStringPar(szSound), pObj); + } // always return true (network safety!) return true; } diff --git a/src/platform/C4SoundSystem.cpp b/src/platform/C4SoundSystem.cpp index b49cef86c..286507423 100644 --- a/src/platform/C4SoundSystem.cpp +++ b/src/platform/C4SoundSystem.cpp @@ -47,7 +47,7 @@ public: bool Load(const char *szFileName, C4Group &hGroup); bool Load(BYTE *pData, size_t iDataLen, bool fRaw=false); // load directly from memory void Execute(); - C4SoundInstance *New(bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iCustomFalloffDistance = 0); + C4SoundInstance *New(bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iCustomFalloffDistance = 0, int32_t iPitch = 0); C4SoundInstance *GetInstance(C4Object *pObj); void ClearPointers(C4Object *pObj); int32_t GetStartedInstanceCount(int32_t iX, int32_t iY, int32_t iRad); // local @@ -66,7 +66,8 @@ public: ~C4SoundInstance(); protected: C4SoundEffect *pEffect; - int32_t iVolume, iPan, iChannel; + int32_t iVolume, iPan, iPitch, iChannel; + bool pitch_dirty; C4TimeMilliseconds tStarted; int32_t iNearInstanceMax; bool fLooping; @@ -77,7 +78,7 @@ public: C4Object *getObj() const { return pObj; } bool isStarted() const { return iChannel != -1; } void Clear(); - bool Create(C4SoundEffect *pEffect, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iNearInstanceMax = 0, int32_t iFalloffDistance = 0); + bool Create(C4SoundEffect *pEffect, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iNearInstanceMax = 0, int32_t iFalloffDistance = 0, int32_t inPitch = 0); bool CheckStart(); bool Start(); bool Stop(); @@ -85,6 +86,7 @@ public: void Execute(); void SetVolume(int32_t inVolume) { iVolume = inVolume; } void SetPan(int32_t inPan) { iPan = inPan; } + void SetPitch(int32_t inPitch); void SetVolumeByPos(int32_t x, int32_t y); void SetObj(C4Object *pnObj) { pObj = pnObj; } void ClearPointers(C4Object *pObj); @@ -185,13 +187,13 @@ void C4SoundEffect::Execute() } } -C4SoundInstance *C4SoundEffect::New(bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance) +C4SoundInstance *C4SoundEffect::New(bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch) { // check: too many instances? if (!fLoop && Instances >= C4MaxSoundInstances) return NULL; // create & init sound instance C4SoundInstance *pInst = new C4SoundInstance(); - if (!pInst->Create(this, fLoop, iVolume, pObj, 0, iCustomFalloffDistance)) { delete pInst; return NULL; } + if (!pInst->Create(this, fLoop, iVolume, pObj, 0, iCustomFalloffDistance, iPitch)) { delete pInst; return NULL; } // add to list AddInst(pInst); // return @@ -254,7 +256,8 @@ void C4SoundEffect::RemoveInst(C4SoundInstance *pInst) C4SoundInstance::C4SoundInstance(): pEffect (NULL), - iVolume (0), iPan (0), + iVolume(0), iPan(0), iPitch(0), + pitch_dirty(false), iChannel (-1), pNext (NULL) { @@ -271,7 +274,7 @@ void C4SoundInstance::Clear() iChannel = -1; } -bool C4SoundInstance::Create(C4SoundEffect *pnEffect, bool fLoop, int32_t inVolume, C4Object *pnObj, int32_t inNearInstanceMax, int32_t iFalloffDistance) +bool C4SoundInstance::Create(C4SoundEffect *pnEffect, bool fLoop, int32_t inVolume, C4Object *pnObj, int32_t inNearInstanceMax, int32_t iFalloffDistance, int32_t inPitch) { // Sound check if (!Config.Sound.RXSound || !pnEffect) return false; @@ -282,6 +285,7 @@ bool C4SoundInstance::Create(C4SoundEffect *pnEffect, bool fLoop, int32_t inVolu // Set tStarted = C4TimeMilliseconds::Now(); iVolume = inVolume; iPan = 0; iChannel = -1; + iPitch = inPitch; pitch_dirty = (iPitch != 0); iNearInstanceMax = inNearInstanceMax; this->iFalloffDistance = iFalloffDistance; pObj = pnObj; @@ -291,6 +295,13 @@ bool C4SoundInstance::Create(C4SoundEffect *pnEffect, bool fLoop, int32_t inVolu return true; } +void C4SoundInstance::SetPitch(int32_t inPitch) +{ + // set pitch and update on next call to Execute + iPitch = inPitch; + pitch_dirty = true; +} + bool C4SoundInstance::CheckStart() { // already started? @@ -441,6 +452,12 @@ void C4SoundInstance::Execute() #elif AUDIO_TK == AUDIO_TK_OPENAL alSource3f(iChannel, AL_POSITION, Clamp(float(iPan)/100.0f, -1.0f, +1.0f), 0, 0.7f); alSourcef(iChannel, AL_GAIN, float(iVol) / (100.0f*256.0f)); + if (pitch_dirty) + { + // set pitch; map -90..+100 range to 0.1f..2.0f + alSourcef(iChannel, AL_PITCH, Max(float(iPitch + 100) / 100.0f, 0.1f)); + pitch_dirty = false; + } #endif } } @@ -566,7 +583,7 @@ C4SoundEffect* C4SoundSystem::GetEffect(const char *szSndName) return pSfx; // Is still NULL if nothing is found } -C4SoundInstance *C4SoundSystem::NewEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance) +C4SoundInstance *C4SoundSystem::NewEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch) { // Sound not active if (!Config.Sound.RXSound) return NULL; @@ -574,7 +591,7 @@ C4SoundInstance *C4SoundSystem::NewEffect(const char *szSndName, bool fLoop, int C4SoundEffect *csfx; if (!(csfx=GetEffect(szSndName))) return NULL; // Play - return csfx->New(fLoop, iVolume, pObj, iCustomFalloffDistance); + return csfx->New(fLoop, iVolume, pObj, iCustomFalloffDistance, iPitch); } C4SoundInstance *C4SoundSystem::FindInstance(const char *szSndName, C4Object *pObj) @@ -659,20 +676,20 @@ void C4SoundSystem::ClearPointers(C4Object *pObj) pEff->ClearPointers(pObj); } -C4SoundInstance *StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance) +C4SoundInstance *StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch) { // Sound check if (!Config.Sound.RXSound) return NULL; // Start new - return Application.SoundSystem.NewEffect(szSndName, fLoop, iVolume, pObj, iCustomFalloffDistance); + return Application.SoundSystem.NewEffect(szSndName, fLoop, iVolume, pObj, iCustomFalloffDistance, iPitch); } -C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume, int32_t iCustomFallofDistance) +C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume, int32_t iCustomFallofDistance, int32_t iPitch) { // Sound check if (!Config.Sound.RXSound) return NULL; // Create - C4SoundInstance *pInst = StartSoundEffect(szSndName, false, iVolume, NULL, iCustomFallofDistance); + C4SoundInstance *pInst = StartSoundEffect(szSndName, false, iVolume, NULL, iCustomFallofDistance, iPitch); // Set volume by position if (pInst) pInst->SetVolumeByPos(iX, iY); // Return @@ -704,6 +721,7 @@ void SoundLevel(const char *szSndName, C4Object *pObj, int32_t iLevel) pInst->SetVolume(iLevel); pInst->Execute(); } + void SoundPan(const char *szSndName, C4Object *pObj, int32_t iPan) { // Find instance @@ -713,3 +731,22 @@ void SoundPan(const char *szSndName, C4Object *pObj, int32_t iPan) pInst->SetPan(iPan); pInst->Execute(); } + +void SoundPitch(const char *szSndName, C4Object *pObj, int32_t iPitch) +{ + // Find instance + C4SoundInstance *pInst = Application.SoundSystem.FindInstance(szSndName, pObj); + if (!pInst) return; + // Set pitch + pInst->SetPitch(iPitch); + pInst->Execute(); +} + +void SoundUpdate(C4SoundInstance *pInst, int32_t iLevel, int32_t iPitch) +{ + // Set sound data + pInst->SetVolume(iLevel); + pInst->SetPitch(iPitch); + // Ensure it's reflected in audio engine + pInst->Execute(); +} diff --git a/src/platform/C4SoundSystem.h b/src/platform/C4SoundSystem.h index a5797159e..29c6280e7 100644 --- a/src/platform/C4SoundSystem.h +++ b/src/platform/C4SoundSystem.h @@ -41,7 +41,7 @@ public: void Clear(); void Execute(); int32_t LoadEffects(C4Group &hGroup); - C4SoundInstance *NewEffect(const char *szSound, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iCustomFalloffDistance = 0); + C4SoundInstance *NewEffect(const char *szSound, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iCustomFalloffDistance = 0, int32_t iPitch = 0); C4SoundInstance *FindInstance(const char *szSound, C4Object *pObj); bool Init(); void ClearPointers(C4Object *pObj); @@ -53,12 +53,14 @@ protected: int32_t RemoveEffect(const char *szFilename); }; -C4SoundInstance *StartSoundEffect(const char *szSndName, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj=NULL, int32_t iCustomFalloffDistance=0); -C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume = 100, int32_t iCustomFallofDistance=0); +C4SoundInstance *StartSoundEffect(const char *szSndName, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iCustomFalloffDistance = 0, int32_t iPitch = 0); +C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume = 100, int32_t iCustomFallofDistance = 0, int32_t iPitch = 0); C4SoundInstance *GetSoundInstance(const char *szSndName, C4Object *pObj); void StopSoundEffect(const char *szSndName, C4Object *pObj); void SoundLevel(const char *szSndName, C4Object *pObj, int32_t iLevel); void SoundPan(const char *szSndName, C4Object *pObj, int32_t iPan); +void SoundPitch(const char *szSndName, C4Object *pObj, int32_t iPitch); +void SoundUpdate(C4SoundInstance *inst, int32_t iLevel, int32_t iPitch); #endif