From 6ab6a1ac3cb1f73a6e3225e01365fe7b9d10c0dd Mon Sep 17 00:00:00 2001 From: Sven Eberhardt Date: Thu, 27 Aug 2015 21:44:23 -0400 Subject: [PATCH] Add script interface for some EFX sound modifiers. --- CMakeLists.txt | 6 +- docs/sdk/content.xml.in | 6 + docs/sdk/script/SoundModifiers.xml | 338 ++++++++++++ docs/sdk/script/fn/SetGlobalSoundModifier.xml | 60 +++ docs/sdk/script/fn/Sound.xml | 7 + docs/sdk/script/fn/SoundAt.xml | 7 + docs/sdk/script/index.xml | 1 + .../Environment.ocd/Ambience.ocd/Script.c | 24 +- src/game/C4Game.cpp | 26 +- src/game/C4Game.h | 2 + src/game/C4Viewport.cpp | 10 +- src/game/C4Viewport.h | 2 +- src/gamescript/C4GameScript.cpp | 59 ++- src/object/C4GameObjects.cpp | 3 + src/object/C4Object.cpp | 26 +- src/object/C4Object.h | 6 +- src/platform/C4MusicSystem.h | 2 + src/platform/C4SoundIncludes.h | 5 + src/platform/C4SoundInstance.cpp | 487 +++++++++++++++++ src/platform/C4SoundInstance.h | 103 ++++ src/platform/C4SoundModifiers.cpp | 342 ++++++++++++ src/platform/C4SoundModifiers.h | 129 +++++ src/platform/C4SoundSystem.cpp | 494 +----------------- src/platform/C4SoundSystem.h | 15 +- src/player/C4Player.cpp | 26 + src/player/C4Player.h | 3 + src/script/C4StringTable.cpp | 29 + src/script/C4StringTable.h | 29 + 28 files changed, 1752 insertions(+), 495 deletions(-) create mode 100644 docs/sdk/script/SoundModifiers.xml create mode 100644 docs/sdk/script/fn/SetGlobalSoundModifier.xml create mode 100644 src/platform/C4SoundInstance.cpp create mode 100644 src/platform/C4SoundInstance.h create mode 100644 src/platform/C4SoundModifiers.cpp create mode 100644 src/platform/C4SoundModifiers.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 680b88fab..c160e63ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -464,10 +464,14 @@ set(OC_CLONK_SOURCES src/platform/C4SoundLoaders.cpp src/platform/C4SoundLoaders.h src/platform/C4SoundIncludes.h + src/platform/C4SoundInstance.cpp + src/platform/C4SoundInstance.h + src/platform/C4SoundModifiers.cpp + src/platform/C4SoundModifiers.h src/platform/C4SoundSystem.cpp src/platform/C4SoundSystem.h src/platform/C4TimeMilliseconds.cpp - src/platform/C4TimeMilliseconds.h + src/platform/C4TimeMilliseconds.h src/platform/C4StdInProc.cpp src/platform/C4StdInProc.h src/platform/C4Video.cpp diff --git a/docs/sdk/content.xml.in b/docs/sdk/content.xml.in index 5e144315d..f3c34433a 100644 --- a/docs/sdk/content.xml.in +++ b/docs/sdk/content.xml.in @@ -113,6 +113,12 @@
  • Effects
  • Querying Game Data
  • Script Players
  • +
  • Sound Modifiers
  • +
  • Libraries + +
  • diff --git a/docs/sdk/script/SoundModifiers.xml b/docs/sdk/script/SoundModifiers.xml new file mode 100644 index 000000000..70c950fc5 --- /dev/null +++ b/docs/sdk/script/SoundModifiers.xml @@ -0,0 +1,338 @@ + + + + + Sound modifiers + Sound modifiers + + Usage + A number of different sound modifiers can be attached to sounds. Sound modifiers are accessed via script by passing a prop list defining them to the Sound script functions. Sample prop lists are already defined in the Ambience definition in Objects.ocd. If Objects.ocd is loaded, you can for example play the "Ding" sound with a reverb-effect: + Sound("Ding",,,,,,,Ambience.CaveModifier); + Custom modifiers may also be created using the prototype classes provided in the ambience object: + +// Play "Ding" sound with an extra-long reverb modifier +long_reverb_modifier = new Ambience.SoundModifier { + Type = C4SMT_Reverb, + Reverb_Decay_Time = 10000, +}; +Sound("Ding",,,,,,,long_reverb_modifier); +modifier->Release(); + + Sound modifiers are lazy-initialized, that is the filters are computed when the first sound with that effect is played. The engine also holds filters of any used modifiers attached to the used prop list in memory until released by the Release()-call provided by the ambience library, which wraps ChangeSoundModifier with appropriate parameters. + Since filter creation may cost some performance, it is recommended to create filter prop lists at round start and keep them for any effects. Please note that modifier lookup happens by prop list pointer only, not by its contents. Recreating and failing to release sound modifier prop lists therefore constitutes a memory-leak. + If sound modifiers are released, they are kept active until the last sound using them finishes. Note that for modifiers such as echo, this could still cut off sounds because the modifier outlasts the original sound (potentially forever for echo without decay). + Some existing modifiers may also be updated and updates reflected to the sounds played, even with those currently playing, by using the Update()-call such as in this example scenario script: + +static reverb_modifier; + +func Initialize() +{ + // Play "Ding" sound repeatedly and modify reverb + reverb_modifier = new Ambience.SoundModifier { + Type = C4SMT_Echo, + Echo_Feedback = 1000, + }; + Sound("Ding",,,,1,,,reverb_modifier); + ScheduleCall(nil, this.Timer, 30, 99999); + return true; +} + +func Timer() +{ + // Update effect every 30 frames + reverb_modifier.Echo_Feedback = Random(2) * 500; + reverb_modifier->Update(); + return true; +} + Note that runtime updating does not work for the reverb modifier in the openal-soft library. + + Global modifiers + Global modifiers can be set using the SetGlobalSoundModifier (see function documentation for example). These modifiers are applied to all sounds played in the viewport of a player or all players that do not have a modifier yet. Please note that it is not possible to combine multiple modifiers on a single sound. + + + + Property reference + The effect is selected from the Type-property, which may have the following values: + + + Constant + Effect + + + C4SMT_Reverb + Reverb effect caused by sound bouncing off walls in enclosed spaces. + + + C4SMT_Echo + Sound repeat as caused by loud sounds reflected in very large spaces. + + + C4SMT_Equalizer + Custom amplification of up to four definable frequency bands. Note: When running with OpenAL soft, only supported with version 1.16 or above (not shipped by default). + +
    + Each modifier has a number of parameters. These consult to standard parameters for the OpenAL EFX library by dividing all given integer values by 1000 to yield float values. + Reverb modifier + + + Property + Type + Default + Minmum + Maximum + Remarks + + + Reverb_Density + int + 1000 + 0 + 1000 + + + + Reverb_Diffusion + int + 1000 + 0 + 1000 + + + + Reverb_Gain + int + 316 + 0 + 1000 + + + + Reverb_GainHF + int + 1000 + 0 + 1000 + + + + Reverb_Decay_Time + int + 2910 + 100 + 20000 + + + + Reverb_Decay_HFRatio + int + 1300 + 100 + 20000 + + + + Reverb_Reflections_Gain + int + 500 + 0 + 3160 + + + + Reverb_Reflections_Delay + int + 15 + 0 + 300 + + + + Reverb_Late_Reverb_Gain + int + 706 + 0 + 10000 + + + + Reverb_Late_Reverb_Delay + int + 22 + 0 + 100 + + + + Reverb_Air_Absorption_GainHF + int + 994 + 892 + 1000 + + + + Reverb_Room_Rolloff_Factor + int + 0 + 0 + 10000 + + + + Reverb_Decay_HFLimit + bool + true + + + + +
    +
    + Echo modifier + + + Property + Type + Default + Minmum + Maximum + Description + + + Echo_Delay + int + 100 + 0 + 207 + Time delay for first, centered echo. + + + Echo_LRDelay + int + 100 + 0 + 404 + Time delay for secondary, panning echo. + + + Echo_Damping + int + 500 + 0 + 990 + Amount of high-frequency damping. + + + Echo_Feedback + int + 500 + 0 + 1000 + Amount of original signal fed into the echo. A value of 1000 would lead to an infinite echo. + + + Echo_Spread + int + -1000 + -1000 + +1000 + Controls the amount of panning left and right, with the sign determining if the first jump is left or right. A value of zero means no echo panning. + +
    +
    + Equalizer modifier + + + Property + Type + Default + Minmum + Maximum + Remarks + + + Equalizer_Low_Gain + int + 1000 + 126 + 7943 + + + + Equalizer_Low_Cutoff + int + 200000 + 50000 + 800000 + + + + Equalizer_Mid1_Gain + int + 1000 + 126 + 7943 + + + + Equalizer_Mid1_Center + int + 500000 + 200000 + 3000000 + + + + Equalizer_Mid1_Width + int + 1000 + 10 + 1000 + + + + Equalizer_Mid2_Gain + int + 1000 + 126 + 7943 + + + + Equalizer_Mid2_Center + int + 3000000 + 1000000 + 8000000 + + + + Equalizer_Mid2_Width + int + 1000 + 10 + 1000 + + + + Equalizer_High_Gain + int + 1000 + 126 + 7943 + + + + Equalizer_High_Cutoff + int + 6000000 + 4000000 + 16000000 + + +
    +
    +
    + Sven22015-07 +
    diff --git a/docs/sdk/script/fn/SetGlobalSoundModifier.xml b/docs/sdk/script/fn/SetGlobalSoundModifier.xml new file mode 100644 index 000000000..5f18a8fd1 --- /dev/null +++ b/docs/sdk/script/fn/SetGlobalSoundModifier.xml @@ -0,0 +1,60 @@ + + + + + + SetGlobalSoundModifier + Effects + Sound + 7.0 OC + + bool + + + proplist + name + Modifier to be applied to all sounds. + + + int + player + If non-nil: Modifier is applied to sounds played in viewports owned by this player. + + + + + Sets a sound modifier to be applied to all sounds played that do not have a modifier already set. + Modifier precendence from highest to lowest is: +
    • Modifier given as parameter to Sound or SoundAt
    • +
    • Modifier assigned to the player owning the viewport which has its center closest to the sound source
    • +
    • Global modifier (as set by SetGlobalSoundModifier(modifier, nil);)
    + Only one modifier is applied at the time. It is not possible to combine multiple modifiers.
    + + + func Timer() +{ + // Is there a player? + var player = GetPlayerByIndex(0, C4PT_User); + if (player >= 0) + { + // Is the player controlling a clonk in a cave? + var mod = nil; + var clonk = GetCursor(player); + if (clonk) if (clonk->GetMaterial() == Material("Tunnel")) + { + // Controlled clonk is in a cave - do some cave sounds! + mod = Ambience.CaveModifier; + } + SetGlobalSoundModifier(mod, player); + } +} + Scenario timer script: When this function is called, it sets a cave reverb sound modifier whenever the clonk of the first player is in front of tunnel background. + + + Sound + SoundAt + Sound modifiers +
    + Sven22015-08 +
    diff --git a/docs/sdk/script/fn/Sound.xml b/docs/sdk/script/fn/Sound.xml index b519dd856..72318fa5f 100644 --- a/docs/sdk/script/fn/Sound.xml +++ b/docs/sdk/script/fn/Sound.xml @@ -51,6 +51,12 @@ 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. + + proplist + modifier + Sound modifier for special effects such as reverb or echo. See Sound modifiers. + + 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. @@ -63,6 +69,7 @@ SoundAt Music + Sound modifiers Sven22002-08 diff --git a/docs/sdk/script/fn/SoundAt.xml b/docs/sdk/script/fn/SoundAt.xml index c2c1f67ea..4ad2fc0ce 100644 --- a/docs/sdk/script/fn/SoundAt.xml +++ b/docs/sdk/script/fn/SoundAt.xml @@ -49,11 +49,18 @@ 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. + + proplist + modifier + Sound modifier for special effects such as reverb or echo. See Sound modifiers. + + 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. Sound Music + Sound modifiers Sven22002-08 diff --git a/docs/sdk/script/index.xml b/docs/sdk/script/index.xml index e60705cfd..f9dd83669 100644 --- a/docs/sdk/script/index.xml +++ b/docs/sdk/script/index.xml @@ -46,6 +46,7 @@ Effects Querying Game Data Script Player (i.e. AI player) + Sound modifiers Libraries Shape diff --git a/planet/Objects.ocd/Environment.ocd/Ambience.ocd/Script.c b/planet/Objects.ocd/Environment.ocd/Ambience.ocd/Script.c index e3755a3a5..d3b521aaa 100644 --- a/planet/Objects.ocd/Environment.ocd/Ambience.ocd/Script.c +++ b/planet/Objects.ocd/Environment.ocd/Ambience.ocd/Script.c @@ -1,6 +1,6 @@ /** Ambience - Cares about the placement of purely visual objects. + Cares about the placement of purely visual objects and handles sound ambience The placement uses categories and thus is forward-compatible. */ @@ -105,4 +105,24 @@ CreateEnvironmentObjects("Temperate", Rectangle(LandscapeWidth()/2, 0, Landscape p_id->Place(amount_percentage, area); } -} \ No newline at end of file +} + +/* Sound ambience */ + +local SoundModifier, CaveModifier; + +func ReleaseSoundModifier() { return ChangeSoundModifier(this, true); } +func UpdateSoundModifier() { return ChangeSoundModifier(this, false); } // do not use + +func Definition() +{ + // Base sound modifier + SoundModifier = { + Release = Ambience.ReleaseSoundModifier, + Update = Ambience.UpdateSoundModifier, + }; + // Modifiers for different ambiences + CaveModifier = new SoundModifier { + Type = C4SMT_Reverb, + }; +} diff --git a/src/game/C4Game.cpp b/src/game/C4Game.cpp index 0ba73b559..43eac3858 100644 --- a/src/game/C4Game.cpp +++ b/src/game/C4Game.cpp @@ -86,6 +86,7 @@ static C4GameParameters GameParameters; static C4ScenarioParameterDefs GameScenarioParameterDefs; static C4ScenarioParameters GameStartupScenarioParameters; static C4RoundResults GameRoundResults; +static C4Value GameGlobalSoundModifier; C4Game::C4Game(): ScenarioParameterDefs(GameScenarioParameterDefs), @@ -101,7 +102,8 @@ C4Game::C4Game(): pFileMonitor(NULL), pSec1Timer(new C4GameSec1Timer()), fPreinited(false), StartupLogPos(0), QuitLogPos(0), - fQuitWithError(false) + fQuitWithError(false), + GlobalSoundModifier(GameGlobalSoundModifier) { Default(); } @@ -480,6 +482,9 @@ bool C4Game::Init() // Gamma pDraw->ApplyGamma(); + // Sound modifier from savegames + if (GlobalSoundModifier) SetGlobalSoundModifier(GlobalSoundModifier._getPropList()); + // Message board and upper board if (!Application.isEditor) { @@ -617,6 +622,7 @@ void C4Game::Clear() PlayerControlDefaultAssignmentSets.Clear(); PlayerControlDefs.Clear(); ::MeshMaterialManager.Clear(); + SetGlobalSoundModifier(NULL); Application.SoundSystem.Init(); // clear it up and re-init it for startup menu use // global fullscreen class is not cleared, because it holds the carrier window @@ -1654,6 +1660,7 @@ void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumber pComp->Value(mkNamingAdapt(mkStringAdaptMA(CurrentScenarioSection), "CurrentScenarioSection", "")); pComp->Value(mkNamingAdapt(fResortAnyObject, "ResortAnyObj", false)); pComp->Value(mkNamingAdapt(iMusicLevel, "MusicLevel", 100)); + pComp->Value(mkNamingAdapt(mkParAdapt(GlobalSoundModifier, numbers), "GlobalSoundModifier", C4Value())); pComp->Value(mkNamingAdapt(NextMission, "NextMission", StdCopyStrBuf())); pComp->Value(mkNamingAdapt(NextMissionText, "NextMissionText", StdCopyStrBuf())); pComp->Value(mkNamingAdapt(NextMissionDesc, "NextMissionDesc", StdCopyStrBuf())); @@ -3708,3 +3715,20 @@ void C4Game::SetDefaultGamma() pDraw->SetGamma(0x000000, 0x808080, 0xffffff, iRamp); } } + +void C4Game::SetGlobalSoundModifier(C4PropList *new_modifier) +{ + // set in prop list (for savegames) and in sound system:: + C4SoundModifier *mod; + if (new_modifier) + { + GlobalSoundModifier.SetPropList(new_modifier); + mod = ::Application.SoundSystem.Modifiers.Get(new_modifier, true); + } + else + { + GlobalSoundModifier.Set0(); + mod = NULL; + } + ::Application.SoundSystem.Modifiers.SetGlobalModifier(mod, NO_OWNER); +} diff --git a/src/game/C4Game.h b/src/game/C4Game.h index ab3d5ce7d..18c97dc57 100644 --- a/src/game/C4Game.h +++ b/src/game/C4Game.h @@ -87,6 +87,7 @@ public: C4KeyboardInput &KeyboardInput; C4FileMonitor *pFileMonitor; C4GameSec1Timer *pSec1Timer; + C4Value &GlobalSoundModifier; // contains proplist for sound modifier to be applied to all new sounds played char CurrentScenarioSection[C4MaxName+1]; char ScenarioFilename[_MAX_PATH+1]; @@ -285,6 +286,7 @@ protected: public: bool ToggleChart(); // chart dlg on/off void SetMusicLevel(int32_t iToLvl); // change game music volume; multiplied by config volume for real volume + void SetGlobalSoundModifier(C4PropList *modifier_props); }; extern C4Game Game; diff --git a/src/game/C4Viewport.cpp b/src/game/C4Viewport.cpp index 02fb9a542..b52f2fcf5 100644 --- a/src/game/C4Viewport.cpp +++ b/src/game/C4Viewport.cpp @@ -1006,7 +1006,7 @@ C4Viewport* C4ViewportList::GetViewport(int32_t iPlayer, C4Viewport* pPrev) return NULL; } -int32_t C4ViewportList::GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int32_t iAudibilityRadius) +int32_t C4ViewportList::GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int32_t iAudibilityRadius, int32_t *outPlayer) { // default audibility radius if (!iAudibilityRadius) iAudibilityRadius = C4AudibilityRadius; @@ -1015,8 +1015,12 @@ int32_t C4ViewportList::GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) { float distanceToCenterOfViewport = Distance(cvp->GetViewCenterX(),cvp->GetViewCenterY(),iX,iY); - iAudible = Max( iAudible, - Clamp(100-100*distanceToCenterOfViewport/C4AudibilityRadius,0,100) ); + int32_t audibility = Clamp(100 - 100 * distanceToCenterOfViewport / C4AudibilityRadius, 0, 100); + if (audibility > iAudible) + { + iAudible = audibility; + if (outPlayer) *outPlayer = cvp->Player; + } *iPan += (iX-(cvp->GetViewCenterX())) / 5; } *iPan = Clamp(*iPan, -100, 100); diff --git a/src/game/C4Viewport.h b/src/game/C4Viewport.h index 23181fdbf..1523f2828 100644 --- a/src/game/C4Viewport.h +++ b/src/game/C4Viewport.h @@ -145,7 +145,7 @@ public: #ifdef USE_WIN32_WINDOWS C4Viewport* GetViewport(HWND hwnd); #endif - int32_t GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int32_t iAudibilityRadius=0); + int32_t GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int32_t iAudibilityRadius = 0, int32_t *outPlayer = NULL); bool ViewportNextPlayer(); bool FreeScroll(C4Vec2D vScrollBy); // key callback: Scroll ownerless viewport by some offset diff --git a/src/gamescript/C4GameScript.cpp b/src/gamescript/C4GameScript.cpp index 49bfba87f..720a3ad8b 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, long iPitch) +static bool FnSoundAt(C4PropList * _this, C4String *szSound, long iX, long iY, Nillable iLevel, Nillable iAtPlayer, long iCustomFalloffDistance, long iPitch, C4PropList *modifier_props) { // play here? if (!iAtPlayer.IsNil()) @@ -503,6 +503,12 @@ static bool FnSoundAt(C4PropList * _this, C4String *szSound, long iX, long iY, N // default sound level if (iLevel.IsNil() || iLevel>100) iLevel=100; + // modifier from prop list + C4SoundModifier *modifier; + if (modifier_props) + modifier = Application.SoundSystem.Modifiers.Get(modifier_props, true); + else + modifier = NULL; // target object C4Object *pObj = Object(_this); if (pObj) @@ -515,7 +521,7 @@ static bool FnSoundAt(C4PropList * _this, C4String *szSound, long iX, long iY, N return true; } -static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillable iLevel, Nillable iAtPlayer, long iLoop, long iCustomFalloffDistance, long iPitch) +static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillable iLevel, Nillable iAtPlayer, long iLoop, long iCustomFalloffDistance, long iPitch, C4PropList *modifier_props) { // play here? if (!iAtPlayer.IsNil()) @@ -533,6 +539,12 @@ static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillabl // default sound level if (iLevel.IsNil() || iLevel>100) iLevel=100; + // modifier from prop list + C4SoundModifier *modifier; + if (modifier_props) + modifier = Application.SoundSystem.Modifiers.Get(modifier_props, true); + else + modifier = NULL; // target object C4Object *pObj = NULL; if (!fGlobal) pObj = Object(_this); @@ -549,7 +561,7 @@ static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillabl else { // try to play effect - StartSoundEffect(FnStringPar(szSound), !!iLoop, iLevel, pObj, iCustomFalloffDistance, iPitch); + StartSoundEffect(FnStringPar(szSound), !!iLoop, iLevel, pObj, iCustomFalloffDistance, iPitch, modifier); } } else @@ -560,6 +572,40 @@ static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillabl return true; } +static bool FnChangeSoundModifier(C4PropList * _this, C4PropList *modifier_props, bool release) +{ + // internal function to be used by sound library: Updates sound modifier + C4SoundModifier *modifier = Application.SoundSystem.Modifiers.Get(modifier_props, false); + // modifier not found. May be due to savegame resume. + // In that case, creation/updates will happen automatically next time Sound() is called + // always return true for sync safety because the internal C4SoundModifierList is not synchronized + if (!modifier) return true; + if (release) + modifier->Release(); + else + modifier->Update(); + return true; +} + +static bool FnSetGlobalSoundModifier(C4PropList * _this, C4PropList *modifier_props, Nillable at_player) +{ + // set modifier to be applied to all future sounds + if (at_player.IsNil()) + { + // no player given: Global modifier for all players. + Game.SetGlobalSoundModifier(modifier_props); + } + else + { + // modifier for all viewports of a player + C4Player *plr = ::Players.Get(at_player); + if (!plr) return false; + plr->SetSoundModifier(modifier_props); + } + // always true on valid params for sync safety + return true; +} + static bool FnMusic(C4PropList * _this, C4String *szSongname, bool fLoop, long iFadeTime_ms) { bool success; @@ -2648,6 +2694,8 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine) AddFunc(pEngine, "CheckConstructionSite", FnCheckConstructionSite); AddFunc(pEngine, "Sound", FnSound); AddFunc(pEngine, "SoundAt", FnSoundAt); + AddFunc(pEngine, "ChangeSoundModifier", FnChangeSoundModifier); + AddFunc(pEngine, "SetGlobalSoundModifier", FnSetGlobalSoundModifier); AddFunc(pEngine, "Music", FnMusic); AddFunc(pEngine, "MusicLevel", FnMusicLevel); AddFunc(pEngine, "SetPlayList", FnSetPlayList); @@ -2953,6 +3001,11 @@ C4ScriptConstDef C4ScriptGameConstMap[]= { "ATTACH_Back" ,C4V_Int, C4ATTACH_Back }, { "ATTACH_MoveRelative" ,C4V_Int, C4ATTACH_MoveRelative }, + // sound modifier type + { "C4SMT_Reverb" ,C4V_Int, C4SoundModifier::C4SMT_Reverb }, + { "C4SMT_Echo" ,C4V_Int, C4SoundModifier::C4SMT_Echo }, + { "C4SMT_Equalizer" ,C4V_Int, C4SoundModifier::C4SMT_Equalizer }, + { NULL, C4V_Nil, 0} }; diff --git a/src/object/C4GameObjects.cpp b/src/object/C4GameObjects.cpp index e89523011..0dba599db 100644 --- a/src/object/C4GameObjects.cpp +++ b/src/object/C4GameObjects.cpp @@ -510,7 +510,10 @@ void C4GameObjects::ResetAudibility() { for (C4Object *obj : *this) if (obj) + { obj->Audible = obj->AudiblePan = 0; + obj->AudiblePlayer = NO_OWNER; + } } void C4GameObjects::SetOCF() diff --git a/src/object/C4Object.cpp b/src/object/C4Object.cpp index 2770c1d4d..4e06205a6 100644 --- a/src/object/C4Object.cpp +++ b/src/object/C4Object.cpp @@ -193,7 +193,8 @@ void C4Object::Default() OnFire=0; InLiquid=0; EntranceStatus=0; - Audible=0; + Audible=AudiblePan=0; + AudiblePlayer = NO_OWNER; t_contact=0; OCF=0; Action.Default(); @@ -302,7 +303,7 @@ bool C4Object::Init(C4PropList *pDef, C4Object *pCreator, UpdateFace(true); // Initial audibility - Audible=::Viewports.GetAudibility(GetX(), GetY(), &AudiblePan); + Audible=::Viewports.GetAudibility(GetX(), GetY(), &AudiblePan, 0, &AudiblePlayer); // Initial OCF SetOCF(); @@ -1885,7 +1886,7 @@ void C4Object::Draw(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, f if (!IsVisible(iByPlayer, !!eDrawMode)) return; // Line - if (Def->Line) { DrawLine(cgo); return; } + if (Def->Line) { DrawLine(cgo, iByPlayer); return; } // background particles (bounds not checked) if (BackParticles) BackParticles->Draw(cgo, this); @@ -1905,7 +1906,7 @@ void C4Object::Draw(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, f fYStretchObject=true; // Set audibility - if (!eDrawMode) SetAudibilityAt(cgo, GetX(), GetY()); + if (!eDrawMode) SetAudibilityAt(cgo, GetX(), GetY(), iByPlayer); // Output boundary if (!fYStretchObject && !eDrawMode && !(Category & C4D_Parallax)) @@ -2201,15 +2202,15 @@ void C4Object::DrawTopFace(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDraw #endif } -void C4Object::DrawLine(C4TargetFacet &cgo) +void C4Object::DrawLine(C4TargetFacet &cgo, int32_t at_player) { // Nothing to draw if the object has less than two vertices if (Shape.VtxNum < 2) return; #ifndef USE_CONSOLE // Audibility - SetAudibilityAt(cgo, Shape.VtxX[0],Shape.VtxY[0]); - SetAudibilityAt(cgo, Shape.VtxX[Shape.VtxNum-1],Shape.VtxY[Shape.VtxNum-1]); + SetAudibilityAt(cgo, Shape.VtxX[0], Shape.VtxY[0], at_player); + SetAudibilityAt(cgo, Shape.VtxX[Shape.VtxNum - 1], Shape.VtxY[Shape.VtxNum - 1], at_player); // additive mode? PrepareDrawing(); // Draw line segments @@ -4147,13 +4148,18 @@ void C4Object::UpdateLight() if (Landscape.pFoW) Landscape.pFoW->Add(this); } -void C4Object::SetAudibilityAt(C4TargetFacet &cgo, int32_t iX, int32_t iY) +void C4Object::SetAudibilityAt(C4TargetFacet &cgo, int32_t iX, int32_t iY, int32_t player) { // target pos (parallax) float offX, offY, newzoom; GetDrawPosition(cgo, iX, iY, cgo.Zoom, offX, offY, newzoom); - Audible = Max(Audible, Clamp(100 - 100 * Distance(cgo.X + cgo.Wdt / 2, cgo.Y + cgo.Hgt / 2, offX, offY) / 700, 0, 100)); - AudiblePan = Clamp(200 * (offX - cgo.X - (cgo.Wdt / 2)) / cgo.Wdt, -100, 100); + int32_t audible_at_pos = Clamp(100 - 100 * Distance(cgo.X + cgo.Wdt / 2, cgo.Y + cgo.Hgt / 2, offX, offY) / 700, 0, 100); + if (audible_at_pos > Audible) + { + Audible = audible_at_pos; + AudiblePan = Clamp(200 * (offX - cgo.X - (cgo.Wdt / 2)) / cgo.Wdt, -100, 100); + AudiblePlayer = player; + } } bool C4Object::IsVisible(int32_t iForPlr, bool fAsOverlay) const diff --git a/src/object/C4Object.h b/src/object/C4Object.h index 384c924f8..b2b1c1138 100644 --- a/src/object/C4Object.h +++ b/src/object/C4Object.h @@ -136,7 +136,7 @@ public: int32_t Breath; int32_t InMat; // SyncClearance-NoSave // uint32_t Color; - int32_t Audible, AudiblePan; // NoSave // + int32_t Audible, AudiblePan, AudiblePlayer; // NoSave // int32_t lightRange; int32_t lightFadeoutRange; uint32_t lightColor; @@ -232,7 +232,7 @@ public: C4Real nxdir, C4Real nydir, C4Real nrdir, int32_t iController); void CompileFunc(StdCompiler *pComp, C4ValueNumbers *); virtual void Denumerate(C4ValueNumbers *); - void DrawLine(C4TargetFacet &cgo); + void DrawLine(C4TargetFacet &cgo, int32_t at_player); bool SetPhase(int32_t iPhase); void AssignRemoval(bool fExitContents=false); enum DrawMode { ODM_Normal=0, ODM_Overlay=1, ODM_BaseOnly=2 }; @@ -329,7 +329,7 @@ public: void SetAlive(bool Alive) { this->Alive = Alive; SetOCF(); } bool GetAlive() const { return Alive; } void UpdateLight(); - void SetAudibilityAt(C4TargetFacet &cgo, int32_t iX, int32_t iY); + void SetAudibilityAt(C4TargetFacet &cgo, int32_t iX, int32_t iY, int32_t player); bool IsVisible(int32_t iForPlr, bool fAsOverlay) const; // return whether an object is visible for the given player void SetRotation(int32_t nr); void PrepareDrawing() const; // set blit modulation and/or additive blitting diff --git a/src/platform/C4MusicSystem.h b/src/platform/C4MusicSystem.h index c55141455..76915de8f 100644 --- a/src/platform/C4MusicSystem.h +++ b/src/platform/C4MusicSystem.h @@ -78,6 +78,8 @@ private: ALCcontext* alcContext; public: void SelectContext(); + ALCcontext *GetContext() const { return alcContext; } + ALCdevice *GetDevice() const { return alcDevice; } #endif public: inline bool IsMODInitialized() {return MODInitialized;} diff --git a/src/platform/C4SoundIncludes.h b/src/platform/C4SoundIncludes.h index 507baccb1..940649615 100644 --- a/src/platform/C4SoundIncludes.h +++ b/src/platform/C4SoundIncludes.h @@ -31,8 +31,13 @@ #elif AUDIO_TK == AUDIO_TK_OPENAL # ifdef __APPLE__ # include +# include +# include +# include # else # include +# include +# include # endif typedef ALuint C4SoundHandle; # ifdef _WIN32 diff --git a/src/platform/C4SoundInstance.cpp b/src/platform/C4SoundInstance.cpp new file mode 100644 index 000000000..f53a60ef2 --- /dev/null +++ b/src/platform/C4SoundInstance.cpp @@ -0,0 +1,487 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000, Matthes Bender + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ + * Copyright (c) 2009-2013, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +/* Handles the sound bank and plays effects using DSoundX */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace C4SoundLoaders; + +C4SoundEffect::C4SoundEffect(): + Instances (0), + pSample (0), + FirstInst (NULL), + Next (NULL) +{ + Name[0]=0; +} + +C4SoundEffect::~C4SoundEffect() +{ + Clear(); +} + +void C4SoundEffect::Clear() +{ + while (FirstInst) RemoveInst(FirstInst); +#if AUDIO_TK == AUDIO_TK_FMOD + if (pSample) FSOUND_Sample_Free(pSample); +#elif AUDIO_TK == AUDIO_TK_SDL_MIXER + if (pSample) Mix_FreeChunk(pSample); +#elif AUDIO_TK == AUDIO_TK_OPENAL + if (pSample) alDeleteBuffers(1, &pSample); +#endif + pSample = 0; +} + +bool C4SoundEffect::Load(const char *szFileName, C4Group &hGroup) +{ + // Sound check + if (!Config.Sound.RXSound) return false; + // Locate sound in file + StdBuf WaveBuffer; + if (!hGroup.LoadEntry(szFileName, &WaveBuffer)) return false; + // load it from mem + if (!Load((BYTE*)WaveBuffer.getMData(), WaveBuffer.getSize())) return false; + // Set name + SCopy(szFileName,Name,C4MaxSoundName); + return true; +} + +bool C4SoundEffect::Load(BYTE *pData, size_t iDataLen, bool fRaw) +{ + // Sound check + if (!Config.Sound.RXSound) return false; + + SoundInfo info; + int32_t options = 0; + if (fRaw) + options |= SoundLoader::OPTION_Raw; + for (SoundLoader* loader = SoundLoader::first_loader; loader; loader = loader->next) + { + if (loader->ReadInfo(&info, pData, iDataLen)) + { + if (info.final_handle) + { + // loader supplied the handle specific to the sound system used; just assign to pSample + pSample = info.final_handle; + } + else + { +#if AUDIO_TK == AUDIO_TK_OPENAL + Application.MusicSystem.SelectContext(); + alGenBuffers(1, &pSample); + alBufferData(pSample, info.format, &info.sound_data[0], info.sound_data.size(), info.sample_rate); +#else + Log("SoundLoader does not provide a ready-made handle"); +#endif + } + SampleRate = info.sample_rate; + Length = info.sample_length*1000; + break; + } + } + *Name = '\0'; + return !!pSample; +} + +void C4SoundEffect::Execute() +{ + // check for instances that have stopped and volume changes + for (C4SoundInstance *pInst = FirstInst; pInst; ) + { + C4SoundInstance *pNext = pInst->pNext; + if (!pInst->Playing()) + RemoveInst(pInst); + else + pInst->Execute(); + pInst = pNext; + } +} + +C4SoundInstance *C4SoundEffect::New(bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier) +{ + // 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, iPitch, modifier)) { delete pInst; return NULL; } + // add to list + AddInst(pInst); + // return + return pInst; +} + +C4SoundInstance *C4SoundEffect::GetInstance(C4Object *pObj) +{ + for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) + if (pInst->getObj() == pObj) + return pInst; + return NULL; +} + +void C4SoundEffect::ClearPointers(C4Object *pObj) +{ + for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) + pInst->ClearPointers(pObj); +} + +int32_t C4SoundEffect::GetStartedInstanceCount(int32_t iX, int32_t iY, int32_t iRad) +{ + int32_t cnt = 0; + for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) + if (pInst->isStarted() && pInst->getObj() && pInst->Inside(iX, iY, iRad)) + cnt++; + return cnt; +} + +int32_t C4SoundEffect::GetStartedInstanceCount() +{ + int32_t cnt = 0; + for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) + if (pInst->isStarted() && pInst->Playing() && !pInst->getObj()) + cnt++; + return cnt; +} + +void C4SoundEffect::AddInst(C4SoundInstance *pInst) +{ + pInst->pNext = FirstInst; + FirstInst = pInst; + Instances++; +} +void C4SoundEffect::RemoveInst(C4SoundInstance *pInst) +{ + if (pInst == FirstInst) + FirstInst = pInst->pNext; + else + { + C4SoundInstance *pPos = FirstInst; + while (pPos && pPos->pNext != pInst) pPos = pPos->pNext; + if (pPos) + pPos->pNext = pInst->pNext; + } + delete pInst; + Instances--; +} + + +C4SoundInstance::C4SoundInstance(): + pEffect (NULL), + iVolume(0), iPan(0), iPitch(0), + player(NO_OWNER), + pitch_dirty(false), + iChannel (-1), + modifier(NULL), + has_local_modifier(false), + pNext (NULL) +{ +} + +C4SoundInstance::~C4SoundInstance() +{ + Clear(); +} + +void C4SoundInstance::Clear() +{ + Stop(); + iChannel = -1; + if (modifier) + { + modifier->DelRef(); + modifier = NULL; + has_local_modifier = false; + } +} + +bool C4SoundInstance::Create(C4SoundEffect *pnEffect, bool fLoop, int32_t inVolume, C4Object *pnObj, int32_t inNearInstanceMax, int32_t iFalloffDistance, int32_t inPitch, C4SoundModifier *modifier) +{ + // Sound check + if (!Config.Sound.RXSound || !pnEffect) return false; + // Already playing? Stop + if (Playing()) { Stop(); return false; } + // Set effect + pEffect = pnEffect; + // Set + tStarted = C4TimeMilliseconds::Now(); + iVolume = inVolume; iPan = 0; iChannel = -1; + iPitch = inPitch; pitch_dirty = (iPitch != 0); + iNearInstanceMax = inNearInstanceMax; + this->iFalloffDistance = iFalloffDistance; + pObj = pnObj; + fLooping = fLoop; + if ((this->modifier = modifier)) + { + modifier->AddRef(); + has_local_modifier = true; + } + SetPlayer(NO_OWNER); // may be updated on first execution + // Start + Execute(); + 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? + if (isStarted()) return true; + // don't bother if half the time is up and the sound is not looping + if (C4TimeMilliseconds::Now() > tStarted + pEffect->Length / 2 && !fLooping) + return false; + // do near-instances check + int32_t iNearInstances = pObj ? pEffect->GetStartedInstanceCount(pObj->GetX(), pObj->GetY(), C4NearSoundRadius) + : pEffect->GetStartedInstanceCount(); + // over maximum? + if (iNearInstances > iNearInstanceMax) return false; + // Start + return Start(); +} + +bool C4SoundInstance::Start() +{ +#if AUDIO_TK == AUDIO_TK_FMOD + // Start + if ((iChannel = FSOUND_PlaySound(FSOUND_FREE, pEffect->pSample)) == -1) + return false; + if (!FSOUND_SetLoopMode(iChannel, fLooping ? FSOUND_LOOP_NORMAL : FSOUND_LOOP_OFF)) + { Stop(); return false; } + // set position + if (C4TimeMilliseconds::Now() > tStarted + 20) + { + assert(pEffect->Length > 0); + int32_t iTime = (C4TimeMilliseconds::Now() - tStarted) % pEffect->Length; + FSOUND_SetCurrentPosition(iChannel, iTime / 10 * pEffect->SampleRate / 100); + } +#elif AUDIO_TK == AUDIO_TK_SDL_MIXER + // Be paranoid about SDL_Mixer initialisation + if (!Application.MusicSystem.MODInitialized) return false; + if ((iChannel = Mix_PlayChannel(-1, pEffect->pSample, fLooping? -1 : 0)) == -1) + return false; +#elif AUDIO_TK == AUDIO_TK_OPENAL + Application.MusicSystem.SelectContext(); + alGenSources(1, (ALuint*)&iChannel); + alSourcei(iChannel, AL_BUFFER, pEffect->pSample); + alSourcei(iChannel, AL_LOOPING, fLooping ? AL_TRUE : AL_FALSE); + if (modifier) modifier->ApplyTo(iChannel); + alSourcePlay(iChannel); +#else + return false; +#endif + // Safety: Don't execute if start failed, or Execute() would try to start again + if (!isStarted()) return false; + // Update volume + Execute(); + return true; +} + +bool C4SoundInstance::Stop() +{ + if (!pEffect) return false; + // Stop sound + bool fRet = true; +#if AUDIO_TK == AUDIO_TK_FMOD + if (Playing()) + fRet = !! FSOUND_StopSound(iChannel); +#elif AUDIO_TK == AUDIO_TK_SDL_MIXER + // iChannel == -1 will halt all channels. Is that right? + if (Playing()) + Mix_HaltChannel(iChannel); +#elif AUDIO_TK == AUDIO_TK_OPENAL + if (iChannel != -1) + { + if (Playing()) alSourceStop(iChannel); + ALuint c = iChannel; + alDeleteSources(1, &c); + } +#endif + iChannel = -1; + tStarted = 0; + fLooping = false; + return fRet; +} + +bool C4SoundInstance::Playing() +{ + if (!pEffect) return false; + if (fLooping) return true; +#if AUDIO_TK == AUDIO_TK_FMOD + return isStarted() ? FSOUND_GetCurrentSample(iChannel) == pEffect->pSample + : C4TimeMilliseconds::Now() < tStarted + pEffect->Length; +#elif AUDIO_TK == AUDIO_TK_SDL_MIXER + return Application.MusicSystem.MODInitialized && (iChannel != -1) && Mix_Playing(iChannel); +#elif AUDIO_TK == AUDIO_TK_OPENAL + if (iChannel == -1) + return false; + else + { + ALint state; + alGetSourcei(iChannel, AL_SOURCE_STATE, &state); + return state == AL_PLAYING; + } +#endif + return false; +} + +void C4SoundInstance::Execute() +{ + // Object deleted? + if (pObj && !pObj->Status) ClearPointers(pObj); + // initial values + int32_t iVol = iVolume * 256 * Config.Sound.SoundVolume / 100, iPan = C4SoundInstance::iPan; + // bound to an object? + if (pObj) + { + int iAudibility = pObj->Audible; + if (pObj->AudiblePlayer != player) SetPlayer(pObj->AudiblePlayer); + // apply custom falloff distance + if (iFalloffDistance) + { + iAudibility = Clamp(100 + (iAudibility - 100) * C4AudibilityRadius / iFalloffDistance, 0,100); + } + iVol = iVol * iAudibility / 100; + iPan += pObj->AudiblePan; + } + // sound off? + if (!iVol) + { + // stop, if started + if (isStarted()) + { +#if AUDIO_TK == AUDIO_TK_FMOD + FSOUND_StopSound(iChannel); +#elif AUDIO_TK == AUDIO_TK_SDL_MIXER + Mix_HaltChannel(iChannel); +#elif AUDIO_TK == AUDIO_TK_OPENAL + alDeleteSources(1, (ALuint*)&iChannel); +#endif + iChannel = -1; + } + } + else + { + // start + if (!isStarted()) + if (!CheckStart()) + return; + // set volume & panning +#if AUDIO_TK == AUDIO_TK_FMOD + FSOUND_SetVolume(iChannel, Clamp(iVol / 100, 0, 255)); + FSOUND_SetPan(iChannel, Clamp(256*(iPan+100)/200,0,255)); +#elif AUDIO_TK == AUDIO_TK_SDL_MIXER + Mix_Volume(iChannel, (iVol * MIX_MAX_VOLUME) / (100 * 256)); + Mix_SetPanning(iChannel, Clamp((100 - iPan) * 256 / 100, 0, 255), Clamp((100 + iPan) * 256 / 100, 0, 255)); +#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 + } +} + +void C4SoundInstance::SetVolumeByPos(int32_t x, int32_t y) +{ + int32_t vol_player = NO_OWNER; + iVolume = ::Viewports.GetAudibility(x, y, &iPan, 0, &vol_player); + if (vol_player != player) SetPlayer(vol_player); +} + +void C4SoundInstance::ClearPointers(C4Object *pDelete) +{ + if (!Playing()) { Stop(); return; } + if (pObj == pDelete) + { + // stop if looping (would most likely loop forever) + if (fLooping) + Stop(); + // otherwise: set volume by last position + else + SetVolumeByPos(pObj->GetX(), pObj->GetY()); + pObj = NULL; + } +} + +bool C4SoundInstance::Inside(int32_t iX, int32_t iY, int32_t iRad) +{ + return pObj && + (pObj->GetX() - iX) * (pObj->GetX() - iX) + (pObj->GetY() - iY) * (pObj->GetY() - iY) <= iRad * iRad; +} + +void C4SoundInstance::SetModifier(C4SoundModifier *new_modifier, bool is_global) +{ + // do not overwrite local modifier with global one + if (is_global) + { + if (has_local_modifier) return; + } + else + { + // this sound has its own modifier now and doesn't care for global ones + has_local_modifier = (new_modifier != NULL); + } + if (new_modifier != modifier) + { + // update modifier and ref-count + C4SoundModifier *old_modifier = modifier; + modifier = new_modifier; + if (modifier) modifier->AddRef(); + if (old_modifier) old_modifier->DelRef(); + // Apply new modifier + if (isStarted()) + { + if (modifier) + { + modifier->ApplyTo(iChannel); + } + else + { +#if AUDIO_TK == AUDIO_TK_OPENAL + alSource3i(iChannel, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); +#endif + } + } + } +} + +void C4SoundInstance::SetPlayer(int32_t new_player) +{ + // update player and associated sound modifier + player = new_player; + SetModifier(::Application.SoundSystem.Modifiers.GetGlobalModifier(player), true); +} diff --git a/src/platform/C4SoundInstance.h b/src/platform/C4SoundInstance.h new file mode 100644 index 000000000..34c0500bc --- /dev/null +++ b/src/platform/C4SoundInstance.h @@ -0,0 +1,103 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000, Matthes Bender + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ + * Copyright (c) 2009-2013, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +/* Helper classes for individual sounds and effects in sound system. */ + +#include + +#include + +class C4Object; +class C4SoundModifier; + +class C4SoundEffect +{ + friend class C4SoundInstance; +public: + C4SoundEffect(); + ~C4SoundEffect(); +public: + char Name[C4MaxSoundName+1]; + int32_t Instances; + int32_t SampleRate, Length; + C4SoundHandle pSample; + C4SoundInstance *FirstInst; + C4SoundEffect *Next; +public: + void Clear(); + 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, int32_t iPitch = 0, C4SoundModifier *modifier = NULL); + C4SoundInstance *GetInstance(C4Object *pObj); + void ClearPointers(C4Object *pObj); + int32_t GetStartedInstanceCount(int32_t iX, int32_t iY, int32_t iRad); // local + int32_t GetStartedInstanceCount(); // global +protected: + void AddInst(C4SoundInstance *pInst); + void RemoveInst(C4SoundInstance *pInst); +}; + +class C4SoundInstance +{ + friend class C4SoundEffect; + friend class C4SoundSystem; + friend class C4SoundModifier; +protected: + C4SoundInstance(); +public: + ~C4SoundInstance(); +protected: + C4SoundEffect *pEffect; + int32_t iVolume, iPan, iPitch, iChannel; + bool pitch_dirty; + C4TimeMilliseconds tStarted; + int32_t iNearInstanceMax; + bool fLooping; + C4Object *pObj; + int32_t iFalloffDistance; + C4SoundModifier *modifier; + bool has_local_modifier; + C4SoundInstance *pNext; + + // NO_OWNER or a player number signifying which player owns the viewport in which the sound is heard (best) + // Note that this does NOT correspond to the player number given in the Sound() script function + // (i.e. sounds played only for one player). For example, a global GUI sound would have player==NO_OWNER even if + // it is played for a specific player only. + int32_t player; +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, int32_t inPitch = 0, C4SoundModifier *modifier = NULL); + bool CheckStart(); + bool Start(); + bool Stop(); + bool Playing(); + 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); + bool Inside(int32_t iX, int32_t iY, int32_t iRad); + C4SoundModifier *GetModifier() const { return modifier; } + void SetModifier(C4SoundModifier *new_modifier, bool is_global); + void SetPlayer(int32_t new_player); +}; + diff --git a/src/platform/C4SoundModifiers.cpp b/src/platform/C4SoundModifiers.cpp new file mode 100644 index 000000000..ca66f7464 --- /dev/null +++ b/src/platform/C4SoundModifiers.cpp @@ -0,0 +1,342 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000, Matthes Bender + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ + * Copyright (c) 2015, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +/* Handles the sound bank and plays effects using DSoundX */ + +#include +#include +#include +#include +#include +#include +#include + +#if AUDIO_TK == AUDIO_TK_OPENAL +static LPALGENEFFECTS alGenEffects; +static LPALDELETEEFFECTS alDeleteEffects; +static LPALISEFFECT alIsEffect; +static LPALEFFECTI alEffecti; +static LPALEFFECTIV alEffectiv; +static LPALEFFECTF alEffectf; +static LPALEFFECTFV alEffectfv; +static LPALGETEFFECTI alGetEffecti; +static LPALGETEFFECTIV alGetEffectiv; +static LPALGETEFFECTF alGetEffectf; +static LPALGETEFFECTFV alGetEffectfv; +static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; +#endif + +C4SoundModifier::C4SoundModifier(C4PropList *in_props) : instance_count(0) +#if AUDIO_TK == AUDIO_TK_OPENAL + , effect(0u), slot(0u) +#endif +{ + props.SetPropList(in_props); + Application.SoundSystem.Modifiers.Add(this); +#if AUDIO_TK == AUDIO_TK_OPENAL + Application.MusicSystem.SelectContext(); + alGenEffects(1, &effect); + alGenAuxiliaryEffectSlots(1, &slot); +#endif +} + +C4SoundModifier::~C4SoundModifier() +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + // failsafe effect removal + if (alIsEffect(effect)) + alDeleteEffects(1, &effect); + if (alIsAuxiliaryEffectSlot(slot)) + alDeleteAuxiliaryEffectSlots(1, &slot); +#endif + Application.SoundSystem.Modifiers.Remove(this); +} + +void C4SoundModifier::Update() +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + // update AL effect slot + if (slot) + { + alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, effect); + } +#endif + // update from props and mark as not released + released = false; +} + +void C4SoundModifier::ApplyTo(ALuint source) +{ + // apply slot to source if valid + if (slot) alSource3i(source, AL_AUXILIARY_SEND_FILTER, slot, 0, AL_FILTER_NULL); +} + +float C4SoundModifier::GetFloatProp(C4PropertyName key, float ratio, float default_value) +{ + // get scaled property and fill in default value + C4PropList *p = props._getPropList(); + return float(p->GetPropertyInt(key, int32_t(default_value * ratio))) / ratio; +} + +bool C4SoundModifier::GetBoolProp(C4PropertyName key, bool default_value) +{ + // get scaled property and fill in default value + C4PropList *p = props._getPropList(); + return (p->GetPropertyInt(key, int32_t(default_value ? 1 : 0)) != 0); +} + +C4SoundModifierReverb::C4SoundModifierReverb(C4PropList *in_props) + : C4SoundModifier(in_props) +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); +#endif +} + +void C4SoundModifierReverb::Update() +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + // use the cave preset as default for the reverb modifier + Application.MusicSystem.SelectContext(); + alEffectf(effect, AL_REVERB_DENSITY, GetFloatProp(P_Reverb_Density, 1000, 1.0f)); + alEffectf(effect, AL_REVERB_DIFFUSION, GetFloatProp(P_Reverb_Diffusion, 1000, 1.0f)); + alEffectf(effect, AL_REVERB_GAIN, GetFloatProp(P_Reverb_Gain, 1000, 0.316f)); + alEffectf(effect, AL_REVERB_GAINHF, GetFloatProp(P_Reverb_GainHF, 1000, 1.0f)); + alEffectf(effect, AL_REVERB_DECAY_TIME, GetFloatProp(P_Reverb_Decay_Time, 1000, 2.91f)); + alEffectf(effect, AL_REVERB_DECAY_HFRATIO, GetFloatProp(P_Reverb_Decay_HFRatio, 1000, 1.3f)); + alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, GetFloatProp(P_Reverb_Reflections_Gain, 1000, 0.5f)); + alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, GetFloatProp(P_Reverb_Reflections_Delay, 1000, 0.015f)); + alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, GetFloatProp(P_Reverb_Late_Reverb_Gain, 1000, 0.706f)); + alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, GetFloatProp(P_Reverb_Late_Reverb_Delay, 1000, 0.022f)); + alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, GetFloatProp(P_Reverb_Air_Absorption_GainHF, 1000, 0.994f)); + alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, GetFloatProp(P_Reverb_Room_Rolloff_Factor, 1000, 0.0f)); + alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, GetBoolProp(P_Reverb_Decay_HFLimit, true) ? 1 : 0); +#endif + C4SoundModifier::Update(); +} + +C4SoundModifierEcho::C4SoundModifierEcho(C4PropList *in_props) + : C4SoundModifier(in_props) +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_ECHO); +#endif +} + +void C4SoundModifierEcho::Update() +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + // use default OpenAL echo preset + Application.MusicSystem.SelectContext(); + alEffectf(effect, AL_ECHO_DELAY, GetFloatProp(P_Echo_Delay, 1000, 0.1f)); + alEffectf(effect, AL_ECHO_LRDELAY, GetFloatProp(P_Echo_LRDelay, 1000, 0.1f)); + alEffectf(effect, AL_ECHO_DAMPING, GetFloatProp(P_Echo_Damping, 1000, 0.5f)); + alEffectf(effect, AL_ECHO_FEEDBACK, GetFloatProp(P_Echo_Feedback, 1000, 0.5f)); + alEffectf(effect, AL_ECHO_SPREAD, GetFloatProp(P_Echo_Spread, 1000, -1.0f)); +#endif + C4SoundModifier::Update(); +} + +C4SoundModifierEqualizer::C4SoundModifierEqualizer(C4PropList *in_props) + : C4SoundModifier(in_props) +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EQUALIZER); +#endif +} + +void C4SoundModifierEqualizer::Update() +{ +#if AUDIO_TK == AUDIO_TK_OPENAL + // use default OpenAL equalizer preset + Application.MusicSystem.SelectContext(); + alEffectf(effect, AL_EQUALIZER_LOW_GAIN, GetFloatProp(P_Equalizer_Low_Gain, 1000, 1.0f)); + alEffectf(effect, AL_EQUALIZER_LOW_CUTOFF, GetFloatProp(P_Equalizer_Low_Cutoff, 1000, 200.0f)); + alEffectf(effect, AL_EQUALIZER_MID1_GAIN, GetFloatProp(P_Equalizer_Mid1_Gain, 1000, 1.0f)); + alEffectf(effect, AL_EQUALIZER_MID1_CENTER, GetFloatProp(P_Equalizer_Mid1_Center, 1000, 500.0f)); + alEffectf(effect, AL_EQUALIZER_MID1_WIDTH, GetFloatProp(P_Equalizer_Mid1_Width, 1000, 1.0f)); + alEffectf(effect, AL_EQUALIZER_MID2_GAIN, GetFloatProp(P_Equalizer_Mid2_Gain, 1000, 1.0f)); + alEffectf(effect, AL_EQUALIZER_MID2_CENTER, GetFloatProp(P_Equalizer_Mid2_Center, 1000, 3000.0f)); + alEffectf(effect, AL_EQUALIZER_MID2_WIDTH, GetFloatProp(P_Equalizer_Mid2_Width, 1000, 1.0f)); + alEffectf(effect, AL_EQUALIZER_HIGH_GAIN, GetFloatProp(P_Equalizer_High_Gain, 1000, 1.0f)); + alEffectf(effect, AL_EQUALIZER_HIGH_CUTOFF, GetFloatProp(P_Equalizer_High_Cutoff, 1000, 6000.0f)); +#endif + C4SoundModifier::Update(); +} + +C4SoundModifierList::C4SoundModifierList() +{ + for (int32_t i = 0; i <= C4SoundModifier::C4SMT_Max; ++i) + is_effect_available[i] = false; +} + +void C4SoundModifierList::Init() +{ + is_initialized = false; +#if AUDIO_TK == AUDIO_TK_OPENAL +#define LOAD_ALPROC(x) ((void *&)(x) = alGetProcAddress(#x)) + Application.MusicSystem.SelectContext(); + if (!alcIsExtensionPresent(Application.MusicSystem.GetDevice(), "ALC_EXT_EFX")) + { + LogF("ALExt: No efx extensions available. Sound modifications disabled."); + return; + } + LOAD_ALPROC(alGenEffects); + LOAD_ALPROC(alDeleteEffects); + LOAD_ALPROC(alIsEffect); + LOAD_ALPROC(alEffecti); + LOAD_ALPROC(alEffectiv); + LOAD_ALPROC(alEffectf); + LOAD_ALPROC(alEffectfv); + LOAD_ALPROC(alGetEffecti); + LOAD_ALPROC(alGetEffectiv); + LOAD_ALPROC(alGetEffectf); + LOAD_ALPROC(alGetEffectfv); + LOAD_ALPROC(alGenAuxiliaryEffectSlots); + LOAD_ALPROC(alDeleteAuxiliaryEffectSlots); + LOAD_ALPROC(alIsAuxiliaryEffectSlot); + LOAD_ALPROC(alAuxiliaryEffectSloti); + LOAD_ALPROC(alAuxiliaryEffectSlotiv); + LOAD_ALPROC(alAuxiliaryEffectSlotf); + LOAD_ALPROC(alAuxiliaryEffectSlotfv); + LOAD_ALPROC(alGetAuxiliaryEffectSloti); + LOAD_ALPROC(alGetAuxiliaryEffectSlotiv); + LOAD_ALPROC(alGetAuxiliaryEffectSlotf); + LOAD_ALPROC(alGetAuxiliaryEffectSlotfv); + if (!alGenEffects) + { + LogF("ALExt: Error loading efx extensions. Sound modifications disabled."); + return; // safety + } + StdStrBuf sAvailableEffects(""); + StdStrBuf sUnavailableEffects(""); + ALenum test_effects[] = { AL_EFFECT_REVERB, AL_EFFECT_ECHO, AL_EFFECT_EQUALIZER, }; + ALuint effect = 0u; + alGenEffects(1, &effect); + ALenum err = alGetError(); + if (err != AL_NO_ERROR) + { + LogF("OpenAL alGenEffects Error: %s", alGetString(err)); + } + for (auto test_effect : test_effects) + { + alEffecti(effect, AL_EFFECT_TYPE, test_effect); + err = alGetError(); + bool is_ok = (err == AL_NO_ERROR); + is_effect_available[test_effect] = is_ok; + StdStrBuf *target_string; + if (!is_ok) target_string = &sUnavailableEffects; else target_string = &sAvailableEffects; + if (target_string->getLength()) target_string->Append(", "); + target_string->AppendFormat("%d", (int)test_effect); + if (!is_ok) target_string->AppendFormat(" (%s)", alGetString(err)); + } + if (alIsEffect(effect)) alDeleteEffects(1, &effect); + LogF("OpenAL extensions loaded. ON: %s. OFF: %s.", sAvailableEffects.getData(), sUnavailableEffects.getData()); +#undef LOAD_ALPROC +#else + // modifiers not supported + return; +#endif + is_initialized = true; +} + +void C4SoundModifierList::Clear() +{ + // Release all sounds. ref count should be zero now because sound instances should have been cleared before this call. + for (auto iter = sound_modifiers.begin(); iter != sound_modifiers.end(); ) + { + C4SoundModifier *modifier = *iter; + ++iter; + // release after iterator increase because deletion will modify the list + assert(modifier->GetRefCount() == 0); + modifier->Release(); + } +} + +C4SoundModifier *C4SoundModifierList::Get(class C4PropList *props, bool create_if_not_found) +{ + // find odifier by prop list + auto iter = std::find_if(sound_modifiers.begin(), sound_modifiers.end(), + [props](const C4SoundModifier *mod) { return mod->GetProps() == props; }); + if (iter != sound_modifiers.end()) return *iter; + // if not found, create if desired + if (!create_if_not_found) return NULL; + C4SoundModifier *modifier; + int32_t effect_type = props->GetPropertyInt(P_Type); + if (!is_effect_available[effect_type]) return NULL; // Not supported D: + switch (effect_type) + { + case C4SoundModifier::C4SMT_Reverb: + modifier = new C4SoundModifierReverb(props); + break; + case C4SoundModifier::C4SMT_Echo: + modifier = new C4SoundModifierEcho(props); + break; + case C4SoundModifier::C4SMT_Equalizer: + modifier = new C4SoundModifierEqualizer(props); + break; + default: + // invalid modifier + return NULL; + } + // initial parameter settings + modifier->Update(); + return modifier; +} + +void C4SoundModifierList::SetGlobalModifier(C4SoundModifier *new_modifier, int32_t player_number) +{ + // -1 based array access for NO_OWNER player number + int32_t global_modifier_index = player_number + 1; + if (global_modifier_index < 0) return; // safety + size_t index = static_cast(global_modifier_index); + if (index >= global_modifiers.size()) global_modifiers.resize(index+1); + if (new_modifier != global_modifiers[index]) + { + // Run new effects with new modifier + C4SoundModifier *old_modifier = global_modifiers[index]; + global_modifiers[index] = new_modifier; + // update existing sound effects first + for (C4SoundInstance *pInst = ::Application.SoundSystem.GetFirstInstance(); pInst; pInst = ::Application.SoundSystem.GetNextInstance(pInst)) + if (pInst->GetModifier() == old_modifier) // that check is not 100% correct but should cover all realistic use-cases + pInst->SetModifier(new_modifier, true); + // Reference counting + if (new_modifier) new_modifier->AddRef(); + if (old_modifier) old_modifier->DelRef(); + } +} + +C4SoundModifier *C4SoundModifierList::GetGlobalModifier(int32_t player_index) const +{ + // safety + int32_t list_index = player_index + 1; + if (list_index < 0 || static_cast(list_index) >= global_modifiers.size()) return NULL; + // get from player and fall back to global list + C4SoundModifier *result = global_modifiers[list_index]; + if (!result && list_index) result = global_modifiers[0]; + return result; +} diff --git a/src/platform/C4SoundModifiers.h b/src/platform/C4SoundModifiers.h new file mode 100644 index 000000000..051d7304c --- /dev/null +++ b/src/platform/C4SoundModifiers.h @@ -0,0 +1,129 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000, Matthes Bender + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ + * Copyright (c) 2015, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +/* Handles the sound bank and plays effects using FMOD */ + +#ifndef INC_C4SoundModifiers +#define INC_C4SoundModifiers + +#include +#include + +class C4SoundModifier +{ +public: + enum Type + { + C4SMT_None = 0x0, + C4SMT_Reverb = 0x1, + C4SMT_Echo = 0x4, + C4SMT_Equalizer = 0xc, + C4SMT_Max = 0xc // value of largest type + }; + + C4SoundModifier(C4PropList *in_props); + ~C4SoundModifier(); + +private: + // associated prop list for script interface + C4Value props; + + // number of sound instances currently using the modifier + int32_t instance_count; + + // set to true for sound modifiers released by script but with instance_count>0 + bool released; + +#if AUDIO_TK == AUDIO_TK_OPENAL +protected: + ALuint effect, slot; +#endif + +protected: + // get a property from the props proplist and divide by factor for float representation + float GetFloatProp(C4PropertyName key, float ratio, float default_value); + bool GetBoolProp(C4PropertyName key, bool default_value); + +public: + // update from props and mark as not released + virtual void Update(); + + // effect is deleted when marked for release and no instances are running + void Release() { if (!instance_count) delete this; else released = true; } + void AddRef() { ++instance_count; } + void DelRef() { if (!--instance_count && released) delete this; } + int32_t GetRefCount() const { return instance_count; } + + const C4PropList *GetProps() const { return props._getPropList(); } + +#if AUDIO_TK == AUDIO_TK_OPENAL + // apply to AL buffer + void ApplyTo(ALuint source); +#endif +}; + +// Reverb sound modifier: Adds effect of sound reflections in enclosed spaces +class C4SoundModifierReverb : public C4SoundModifier +{ +public: + C4SoundModifierReverb(C4PropList *in_props); + +public: + virtual void Update(); +}; + +// Echo: Repeats dampened version of input signal +class C4SoundModifierEcho : public C4SoundModifier +{ +public: + C4SoundModifierEcho(C4PropList *in_props); + +public: + virtual void Update(); +}; + +// Equalizer: Allows to specify low- mid- and high-frequency amplification and reduction +class C4SoundModifierEqualizer : public C4SoundModifier +{ +public: + C4SoundModifierEqualizer(C4PropList *in_props); + +public: + virtual void Update(); +}; + +// member of C4SoundSystem: Handles modifier management and EFX initialization +class C4SoundModifierList +{ +private: + bool is_initialized; + bool is_effect_available[C4SoundModifier::C4SMT_Max+1]; + std::list sound_modifiers; + std::vector global_modifiers; // global modifiers indexed by player number+1. Global modifier for all players in index 0. +public: + C4SoundModifierList(); + void Init(); + void Add(C4SoundModifier *new_modifier) { sound_modifiers.push_back(new_modifier); } + void Remove(C4SoundModifier *prev_modifier) { sound_modifiers.remove(prev_modifier); } + C4SoundModifier *Get(class C4PropList *props, bool create_if_not_found); + void Clear(); + void SetGlobalModifier(C4SoundModifier *new_modifier, int32_t player_index); + C4SoundModifier *GetGlobalModifier(int32_t player_index) const; + +}; + +#endif diff --git a/src/platform/C4SoundSystem.cpp b/src/platform/C4SoundSystem.cpp index 10c20c188..600271741 100644 --- a/src/platform/C4SoundSystem.cpp +++ b/src/platform/C4SoundSystem.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -29,466 +30,6 @@ #include #include -class C4SoundEffect -{ - friend class C4SoundInstance; -public: - C4SoundEffect(); - ~C4SoundEffect(); -public: - char Name[C4MaxSoundName+1]; - int32_t Instances; - int32_t SampleRate, Length; - C4SoundHandle pSample; - C4SoundInstance *FirstInst; - C4SoundEffect *Next; -public: - void Clear(); - 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, 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 - int32_t GetStartedInstanceCount(); // global -protected: - void AddInst(C4SoundInstance *pInst); - void RemoveInst(C4SoundInstance *pInst); -}; - -class C4SoundInstance -{ - friend class C4SoundEffect; -protected: - C4SoundInstance(); -public: - ~C4SoundInstance(); -protected: - C4SoundEffect *pEffect; - int32_t iVolume, iPan, iPitch, iChannel; - bool pitch_dirty; - C4TimeMilliseconds tStarted; - int32_t iNearInstanceMax; - bool fLooping; - C4Object *pObj; - int32_t iFalloffDistance; - C4SoundInstance *pNext; -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, int32_t inPitch = 0); - bool CheckStart(); - bool Start(); - bool Stop(); - bool Playing(); - 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); - bool Inside(int32_t iX, int32_t iY, int32_t iRad); -}; - -using namespace C4SoundLoaders; - -C4SoundEffect::C4SoundEffect(): - Instances (0), - pSample (0), - FirstInst (NULL), - Next (NULL) -{ - Name[0]=0; -} - -C4SoundEffect::~C4SoundEffect() -{ - Clear(); -} - -void C4SoundEffect::Clear() -{ - while (FirstInst) RemoveInst(FirstInst); -#if AUDIO_TK == AUDIO_TK_FMOD - if (pSample) FSOUND_Sample_Free(pSample); -#elif AUDIO_TK == AUDIO_TK_SDL_MIXER - if (pSample) Mix_FreeChunk(pSample); -#elif AUDIO_TK == AUDIO_TK_OPENAL - if (pSample) alDeleteBuffers(1, &pSample); -#endif - pSample = 0; -} - -bool C4SoundEffect::Load(const char *szFileName, C4Group &hGroup) -{ - // Sound check - if (!Config.Sound.RXSound) return false; - // Locate sound in file - StdBuf WaveBuffer; - if (!hGroup.LoadEntry(szFileName, &WaveBuffer)) return false; - // load it from mem - if (!Load((BYTE*)WaveBuffer.getMData(), WaveBuffer.getSize())) return false; - // Set name - SCopy(szFileName,Name,C4MaxSoundName); - return true; -} - -bool C4SoundEffect::Load(BYTE *pData, size_t iDataLen, bool fRaw) -{ - // Sound check - if (!Config.Sound.RXSound) return false; - - SoundInfo info; - int32_t options = 0; - if (fRaw) - options |= SoundLoader::OPTION_Raw; - for (SoundLoader* loader = SoundLoader::first_loader; loader; loader = loader->next) - { - if (loader->ReadInfo(&info, pData, iDataLen)) - { - if (info.final_handle) - { - // loader supplied the handle specific to the sound system used; just assign to pSample - pSample = info.final_handle; - } - else - { -#if AUDIO_TK == AUDIO_TK_OPENAL - Application.MusicSystem.SelectContext(); - alGenBuffers(1, &pSample); - alBufferData(pSample, info.format, &info.sound_data[0], info.sound_data.size(), info.sample_rate); -#else - Log("SoundLoader does not provide a ready-made handle"); -#endif - } - SampleRate = info.sample_rate; - Length = info.sample_length*1000; - break; - } - } - *Name = '\0'; - return !!pSample; -} - -void C4SoundEffect::Execute() -{ - // check for instances that have stopped and volume changes - for (C4SoundInstance *pInst = FirstInst; pInst; ) - { - C4SoundInstance *pNext = pInst->pNext; - if (!pInst->Playing()) - RemoveInst(pInst); - else - pInst->Execute(); - pInst = pNext; - } -} - -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, iPitch)) { delete pInst; return NULL; } - // add to list - AddInst(pInst); - // return - return pInst; -} - -C4SoundInstance *C4SoundEffect::GetInstance(C4Object *pObj) -{ - for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) - if (pInst->getObj() == pObj) - return pInst; - return NULL; -} - -void C4SoundEffect::ClearPointers(C4Object *pObj) -{ - for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) - pInst->ClearPointers(pObj); -} - -int32_t C4SoundEffect::GetStartedInstanceCount(int32_t iX, int32_t iY, int32_t iRad) -{ - int32_t cnt = 0; - for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) - if (pInst->isStarted() && pInst->getObj() && pInst->Inside(iX, iY, iRad)) - cnt++; - return cnt; -} - -int32_t C4SoundEffect::GetStartedInstanceCount() -{ - int32_t cnt = 0; - for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext) - if (pInst->isStarted() && pInst->Playing() && !pInst->getObj()) - cnt++; - return cnt; -} - -void C4SoundEffect::AddInst(C4SoundInstance *pInst) -{ - pInst->pNext = FirstInst; - FirstInst = pInst; - Instances++; -} -void C4SoundEffect::RemoveInst(C4SoundInstance *pInst) -{ - if (pInst == FirstInst) - FirstInst = pInst->pNext; - else - { - C4SoundInstance *pPos = FirstInst; - while (pPos && pPos->pNext != pInst) pPos = pPos->pNext; - if (pPos) - pPos->pNext = pInst->pNext; - } - delete pInst; - Instances--; -} - - -C4SoundInstance::C4SoundInstance(): - pEffect (NULL), - iVolume(0), iPan(0), iPitch(0), - pitch_dirty(false), - iChannel (-1), - pNext (NULL) -{ -} - -C4SoundInstance::~C4SoundInstance() -{ - Clear(); -} - -void C4SoundInstance::Clear() -{ - Stop(); - iChannel = -1; -} - -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; - // Already playing? Stop - if (Playing()) { Stop(); return false; } - // Set effect - pEffect = pnEffect; - // Set - tStarted = C4TimeMilliseconds::Now(); - iVolume = inVolume; iPan = 0; iChannel = -1; - iPitch = inPitch; pitch_dirty = (iPitch != 0); - iNearInstanceMax = inNearInstanceMax; - this->iFalloffDistance = iFalloffDistance; - pObj = pnObj; - fLooping = fLoop; - // Start - Execute(); - 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? - if (isStarted()) return true; - // don't bother if half the time is up and the sound is not looping - if (C4TimeMilliseconds::Now() > tStarted + pEffect->Length / 2 && !fLooping) - return false; - // do near-instances check - int32_t iNearInstances = pObj ? pEffect->GetStartedInstanceCount(pObj->GetX(), pObj->GetY(), C4NearSoundRadius) - : pEffect->GetStartedInstanceCount(); - // over maximum? - if (iNearInstances > iNearInstanceMax) return false; - // Start - return Start(); -} - -bool C4SoundInstance::Start() -{ -#if AUDIO_TK == AUDIO_TK_FMOD - // Start - if ((iChannel = FSOUND_PlaySound(FSOUND_FREE, pEffect->pSample)) == -1) - return false; - if (!FSOUND_SetLoopMode(iChannel, fLooping ? FSOUND_LOOP_NORMAL : FSOUND_LOOP_OFF)) - { Stop(); return false; } - // set position - if (C4TimeMilliseconds::Now() > tStarted + 20) - { - assert(pEffect->Length > 0); - int32_t iTime = (C4TimeMilliseconds::Now() - tStarted) % pEffect->Length; - FSOUND_SetCurrentPosition(iChannel, iTime / 10 * pEffect->SampleRate / 100); - } -#elif AUDIO_TK == AUDIO_TK_SDL_MIXER - // Be paranoid about SDL_Mixer initialisation - if (!Application.MusicSystem.MODInitialized) return false; - if ((iChannel = Mix_PlayChannel(-1, pEffect->pSample, fLooping? -1 : 0)) == -1) - return false; -#elif AUDIO_TK == AUDIO_TK_OPENAL - Application.MusicSystem.SelectContext(); - alGenSources(1, (ALuint*)&iChannel); - alSourcei(iChannel, AL_BUFFER, pEffect->pSample); - alSourcei(iChannel, AL_LOOPING, fLooping ? AL_TRUE : AL_FALSE); - alSourcePlay(iChannel); -#else - return false; -#endif - // Safety: Don't execute if start failed, or Execute() would try to start again - if (!isStarted()) return false; - // Update volume - Execute(); - return true; -} - -bool C4SoundInstance::Stop() -{ - if (!pEffect) return false; - // Stop sound - bool fRet = true; -#if AUDIO_TK == AUDIO_TK_FMOD - if (Playing()) - fRet = !! FSOUND_StopSound(iChannel); -#elif AUDIO_TK == AUDIO_TK_SDL_MIXER - // iChannel == -1 will halt all channels. Is that right? - if (Playing()) - Mix_HaltChannel(iChannel); -#elif AUDIO_TK == AUDIO_TK_OPENAL - if (iChannel != -1) - { - if (Playing()) alSourceStop(iChannel); - ALuint c = iChannel; - alDeleteSources(1, &c); - } -#endif - iChannel = -1; - tStarted = 0; - fLooping = false; - return fRet; -} - -bool C4SoundInstance::Playing() -{ - if (!pEffect) return false; - if (fLooping) return true; -#if AUDIO_TK == AUDIO_TK_FMOD - return isStarted() ? FSOUND_GetCurrentSample(iChannel) == pEffect->pSample - : C4TimeMilliseconds::Now() < tStarted + pEffect->Length; -#elif AUDIO_TK == AUDIO_TK_SDL_MIXER - return Application.MusicSystem.MODInitialized && (iChannel != -1) && Mix_Playing(iChannel); -#elif AUDIO_TK == AUDIO_TK_OPENAL - if (iChannel == -1) - return false; - else - { - ALint state; - alGetSourcei(iChannel, AL_SOURCE_STATE, &state); - return state == AL_PLAYING; - } -#endif - return false; -} - -void C4SoundInstance::Execute() -{ - // Object deleted? - if (pObj && !pObj->Status) ClearPointers(pObj); - // initial values - int32_t iVol = iVolume * 256 * Config.Sound.SoundVolume / 100, iPan = C4SoundInstance::iPan; - // bound to an object? - if (pObj) - { - int iAudibility = pObj->Audible; - // apply custom falloff distance - if (iFalloffDistance) - { - iAudibility = Clamp(100 + (iAudibility - 100) * C4AudibilityRadius / iFalloffDistance, 0,100); - } - iVol = iVol * iAudibility / 100; - iPan += pObj->AudiblePan; - } - // sound off? - if (!iVol) - { - // stop, if started - if (isStarted()) - { -#if AUDIO_TK == AUDIO_TK_FMOD - FSOUND_StopSound(iChannel); -#elif AUDIO_TK == AUDIO_TK_SDL_MIXER - Mix_HaltChannel(iChannel); -#elif AUDIO_TK == AUDIO_TK_OPENAL - alDeleteSources(1, (ALuint*)&iChannel); -#endif - iChannel = -1; - } - } - else - { - // start - if (!isStarted()) - if (!CheckStart()) - return; - // set volume & panning -#if AUDIO_TK == AUDIO_TK_FMOD - FSOUND_SetVolume(iChannel, Clamp(iVol / 100, 0, 255)); - FSOUND_SetPan(iChannel, Clamp(256*(iPan+100)/200,0,255)); -#elif AUDIO_TK == AUDIO_TK_SDL_MIXER - Mix_Volume(iChannel, (iVol * MIX_MAX_VOLUME) / (100 * 256)); - Mix_SetPanning(iChannel, Clamp((100 - iPan) * 256 / 100, 0, 255), Clamp((100 + iPan) * 256 / 100, 0, 255)); -#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 - } -} - -void C4SoundInstance::SetVolumeByPos(int32_t x, int32_t y) -{ - iVolume = ::Viewports.GetAudibility(x, y, &iPan); -} - -void C4SoundInstance::ClearPointers(C4Object *pDelete) -{ - if (!Playing()) { Stop(); return; } - if (pObj == pDelete) - { - // stop if looping (would most likely loop forever) - if (fLooping) - Stop(); - // otherwise: set volume by last position - else - SetVolumeByPos(pObj->GetX(), pObj->GetY()); - pObj = NULL; - } -} - -bool C4SoundInstance::Inside(int32_t iX, int32_t iY, int32_t iRad) -{ - return pObj && - (pObj->GetX() - iX) * (pObj->GetX() - iX) + (pObj->GetY() - iY) * (pObj->GetY() - iY) <= iRad * iRad; -} - - C4SoundSystem::C4SoundSystem(): FirstSound (NULL) { @@ -507,6 +48,8 @@ bool C4SoundSystem::Init() // Might be reinitialisation ClearEffects(); + // (re)init EFX + Modifiers.Init(); // Open sound file if (!SoundFile.IsOpen()) if (!Reloc.Open(SoundFile, C4CFN_Sound)) return false; @@ -521,6 +64,7 @@ bool C4SoundSystem::Init() void C4SoundSystem::Clear() { ClearEffects(); + Modifiers.Clear(); // Close sound file SoundFile.Close(); } @@ -583,7 +127,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, int32_t iPitch) +C4SoundInstance *C4SoundSystem::NewEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier) { // Sound not active if (!Config.Sound.RXSound) return NULL; @@ -591,7 +135,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, iPitch); + return csfx->New(fLoop, iVolume, pObj, iCustomFalloffDistance, iPitch, modifier); } C4SoundInstance *C4SoundSystem::FindInstance(const char *szSndName, C4Object *pObj) @@ -676,20 +220,38 @@ void C4SoundSystem::ClearPointers(C4Object *pObj) pEff->ClearPointers(pObj); } -C4SoundInstance *StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch) +C4SoundInstance *C4SoundSystem::GetFirstInstance() const +{ + // Return by searching through effect linked list. + for (C4SoundEffect *pSfx = FirstSound; pSfx; pSfx = pSfx->Next) + if (pSfx->FirstInst) return pSfx->FirstInst; + return NULL; +} + +C4SoundInstance *C4SoundSystem::GetNextInstance(C4SoundInstance *prev) const +{ + // Return by searching through instance linked list and parent linked list of effects + assert(prev && prev->pEffect); + if (prev->pNext) return prev->pNext; + for (C4SoundEffect *pSfx = prev->pEffect->Next; pSfx; pSfx = pSfx->Next) + if (pSfx->FirstInst) return pSfx->FirstInst; + return NULL; +} + +C4SoundInstance *StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier) { // Sound check if (!Config.Sound.RXSound) return NULL; // Start new - return Application.SoundSystem.NewEffect(szSndName, fLoop, iVolume, pObj, iCustomFalloffDistance, iPitch); + return Application.SoundSystem.NewEffect(szSndName, fLoop, iVolume, pObj, iCustomFalloffDistance, iPitch, modifier); } -C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume, int32_t iCustomFallofDistance, int32_t iPitch) +C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume, int32_t iCustomFallofDistance, int32_t iPitch, C4SoundModifier *modifier) { // Sound check if (!Config.Sound.RXSound) return NULL; // Create - C4SoundInstance *pInst = StartSoundEffect(szSndName, false, iVolume, NULL, iCustomFallofDistance, iPitch); + C4SoundInstance *pInst = StartSoundEffect(szSndName, false, iVolume, NULL, iCustomFallofDistance, iPitch, modifier); // Set volume by position if (pInst) pInst->SetVolumeByPos(iX, iY); // Return diff --git a/src/platform/C4SoundSystem.h b/src/platform/C4SoundSystem.h index 29c6280e7..841875210 100644 --- a/src/platform/C4SoundSystem.h +++ b/src/platform/C4SoundSystem.h @@ -21,6 +21,7 @@ #define INC_C4SoundSystem #include +#include const int32_t C4MaxSoundName=100, @@ -32,6 +33,7 @@ const int32_t SoundUnloadTime=60, SoundMaxUnloadSize=100000; class C4SoundInstance; class C4SoundEffect; +class C4SoundModifier; class C4SoundSystem { @@ -41,20 +43,24 @@ 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, int32_t iPitch = 0); + C4SoundInstance *NewEffect(const char *szSound, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iCustomFalloffDistance = 0, int32_t iPitch = 0, C4SoundModifier *modifier = NULL); C4SoundInstance *FindInstance(const char *szSound, C4Object *pObj); + C4SoundInstance *GetFirstInstance() const; + C4SoundInstance *GetNextInstance(C4SoundInstance *prev) const; bool Init(); void ClearPointers(C4Object *pObj); + + C4SoundModifierList Modifiers; protected: C4Group SoundFile; - C4SoundEffect *FirstSound; + C4SoundEffect *FirstSound; // TODO: Add a hash map for sound lookup. Also add a global list for all running sound instances. void ClearEffects(); C4SoundEffect* GetEffect(const char *szSound); 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, 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 *StartSoundEffect(const char *szSndName, bool fLoop = false, int32_t iVolume = 100, C4Object *pObj = NULL, int32_t iCustomFalloffDistance = 0, int32_t iPitch = 0, C4SoundModifier *modifier = NULL); +C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume = 100, int32_t iCustomFallofDistance = 0, int32_t iPitch = 0, C4SoundModifier *modifier = NULL); C4SoundInstance *GetSoundInstance(const char *szSndName, C4Object *pObj); void StopSoundEffect(const char *szSndName, C4Object *pObj); void SoundLevel(const char *szSndName, C4Object *pObj, int32_t iLevel); @@ -62,5 +68,4 @@ 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 diff --git a/src/player/C4Player.cpp b/src/player/C4Player.cpp index 77ff4c3d6..4bc5890f6 100644 --- a/src/player/C4Player.cpp +++ b/src/player/C4Player.cpp @@ -348,6 +348,9 @@ bool C4Player::Init(int32_t iNumber, int32_t iAtClient, const char *szAtClientNa // init graphs if (Game.pNetworkStatistics) CreateGraphs(); + // init sound mod + SetSoundModifier(SoundModifier._getPropList()); + return true; } @@ -852,6 +855,7 @@ void C4Player::Clear() CrewInfoList.Clear(); Menu.Clear(); BigIcon.Clear(); + SetSoundModifier(NULL); fFogOfWar=true; while (pMsgBoardQuery) { @@ -892,6 +896,7 @@ void C4Player::Default() ZoomLimitMinWdt=ZoomLimitMinHgt=ZoomLimitMaxWdt=ZoomLimitMaxHgt=ZoomWdt=ZoomHgt=0; ZoomLimitMinVal=ZoomLimitMaxVal=ZoomVal=Fix0; ViewLock = true; + SoundModifier.Set0(); } bool C4Player::Load(const char *szFilename, bool fSavegame) @@ -1100,6 +1105,8 @@ void C4Player::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) pComp->Value(mkNamingAdapt(mkParAdapt(Crew, numbers), "Crew" )); pComp->Value(mkNamingAdapt(CrewInfoList.iNumCreated, "CrewCreated", 0)); pComp->Value(mkNamingPtrAdapt( pMsgBoardQuery, "MsgBoardQueries" )); + pComp->Value(mkNamingAdapt(mkParAdapt(SoundModifier, numbers), "SoundModifier", C4Value())); + // Keys held down pComp->Value(Control); @@ -1874,3 +1881,22 @@ bool C4Player::GainScenarioAchievement(const char *achievement_id, int32_t value Achievements.SetValue(sAchvID.getData(), value, true); return true; } + +void C4Player::SetSoundModifier(C4PropList *new_modifier) +{ + // set modifier to be applied to all new sounds being played in a player's viewport + // update prop list parameter + C4SoundModifier *mod; + if (new_modifier) + { + SoundModifier.SetPropList(new_modifier); + mod = ::Application.SoundSystem.Modifiers.Get(new_modifier, true); + } + else + { + SoundModifier.Set0(); + mod = NULL; + } + // update in sound system + ::Application.SoundSystem.Modifiers.SetGlobalModifier(mod, Number); +} diff --git a/src/player/C4Player.h b/src/player/C4Player.h index 9abe754ac..c497809c6 100644 --- a/src/player/C4Player.h +++ b/src/player/C4Player.h @@ -137,6 +137,8 @@ public: class C4MessageBoardQuery *pMsgBoardQuery; // BigIcon C4FacetSurface BigIcon; + // Sound + C4Value SoundModifier; // Dynamic list C4Player *Next; @@ -205,6 +207,7 @@ public: bool IsInvisible() const; bool IsViewLocked() const { return ViewLock; } // return if view is fixed to cursor, so scrolling is not allowed void SetViewLocked(bool to_val); // lock or unlock free scrolling for player + void SetSoundModifier(C4PropList *new_modifier); // set modifier to be applied to all new sounds being played in a player's viewport protected: void ClearControl(); diff --git a/src/script/C4StringTable.cpp b/src/script/C4StringTable.cpp index f1acbe5a5..de71f1763 100644 --- a/src/script/C4StringTable.cpp +++ b/src/script/C4StringTable.cpp @@ -191,6 +191,35 @@ C4StringTable::C4StringTable() P[P_EditCursorCommands] = "EditCursorCommands"; P[P_IsPointContained] = "IsPointContained"; P[P_GetRandomPoint] = "GetRandomPoint"; + P[P_Type] = "Type"; + P[P_Reverb_Density] = "Reverb_Density"; + P[P_Reverb_Diffusion] = "Reverb_Diffusion"; + P[P_Reverb_Gain] = "Reverb_Gain"; + P[P_Reverb_GainHF] = "Reverb_GainHF"; + P[P_Reverb_Decay_Time] = "Reverb_Decay_Time"; + P[P_Reverb_Decay_HFRatio] = "Reverb_Decay_HFRatio"; + P[P_Reverb_Reflections_Gain] = "Reverb_Reflections_Gain"; + P[P_Reverb_Reflections_Delay] = "Reverb_Reflections_Delay"; + P[P_Reverb_Late_Reverb_Gain] = "Reverb_Late_Reverb_Gain"; + P[P_Reverb_Late_Reverb_Delay] = "Reverb_Late_Reverb_Delay"; + P[P_Reverb_Air_Absorption_GainHF] = "Reverb_Air_Absorption_GainHF"; + P[P_Reverb_Room_Rolloff_Factor] = "Reverb_Room_Rolloff_Factor"; + P[P_Reverb_Decay_HFLimit] = "Reverb_Decay_HFLimit"; + P[P_Echo_Delay] = "Echo_Delay"; + P[P_Echo_LRDelay] = "Echo_LRDelay"; + P[P_Echo_Damping] = "Echo_Damping"; + P[P_Echo_Feedback] = "Echo_Feedback"; + P[P_Echo_Spread] = "Echo_Spread"; + P[P_Equalizer_Low_Gain] = "Equalizer_Low_Gain"; + P[P_Equalizer_Low_Cutoff] = "Equalizer_Low_Cutoff"; + P[P_Equalizer_Mid1_Gain] = "Equalizer_Mid1_Gain"; + P[P_Equalizer_Mid1_Center] = "Equalizer_Mid1_Center"; + P[P_Equalizer_Mid1_Width] = "Equalizer_Mid1_Width"; + P[P_Equalizer_Mid2_Gain] = "Equalizer_Mid2_Gain"; + P[P_Equalizer_Mid2_Center] = "Equalizer_Mid2_Center"; + P[P_Equalizer_Mid2_Width] = "Equalizer_Mid2_Width"; + P[P_Equalizer_High_Gain] = "Equalizer_High_Gain"; + P[P_Equalizer_High_Cutoff] = "Equalizer_High_Cutoff"; P[DFA_WALK] = "WALK"; P[DFA_FLIGHT] = "FLIGHT"; P[DFA_KNEEL] = "KNEEL"; diff --git a/src/script/C4StringTable.h b/src/script/C4StringTable.h index 0d18d80b3..f804515ca 100644 --- a/src/script/C4StringTable.h +++ b/src/script/C4StringTable.h @@ -393,6 +393,35 @@ enum C4PropertyName P_EditCursorCommands, P_IsPointContained, P_GetRandomPoint, + P_Type, + P_Reverb_Density, + P_Reverb_Diffusion, + P_Reverb_Gain, + P_Reverb_GainHF, + P_Reverb_Decay_Time, + P_Reverb_Decay_HFRatio, + P_Reverb_Reflections_Gain, + P_Reverb_Reflections_Delay, + P_Reverb_Late_Reverb_Gain, + P_Reverb_Late_Reverb_Delay, + P_Reverb_Air_Absorption_GainHF, + P_Reverb_Room_Rolloff_Factor, + P_Reverb_Decay_HFLimit, + P_Echo_Delay, + P_Echo_LRDelay, + P_Echo_Damping, + P_Echo_Feedback, + P_Echo_Spread, + P_Equalizer_Low_Gain, + P_Equalizer_Low_Cutoff, + P_Equalizer_Mid1_Gain, + P_Equalizer_Mid1_Center, + P_Equalizer_Mid1_Width, + P_Equalizer_Mid2_Gain, + P_Equalizer_Mid2_Center, + P_Equalizer_Mid2_Width, + P_Equalizer_High_Gain, + P_Equalizer_High_Cutoff, // Default Action Procedures DFA_WALK, DFA_FLIGHT,