Add script interface for some EFX sound modifiers.

lights3
Sven Eberhardt 2015-08-27 21:44:23 -04:00
parent d4154ab1eb
commit 6ab6a1ac3c
28 changed files with 1752 additions and 495 deletions

View File

@ -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

View File

@ -113,6 +113,12 @@
<li><emlink href="script/Effects.html">Effects</emlink></li>
<li><emlink href="script/GetXXVal.html">Querying Game Data</emlink></li>
<li><emlink href="script/ScriptPlayers.html">Script Players</emlink></li>
<li><emlink href="script/SoundModifiers.html">Sound Modifiers</emlink></li>
<li>Libraries
<ul>
<text><emlink href="script/Shape.html">Shape</emlink></text>
</ul>
</li>
<!-- Insert Functions here -->
</ul>
</li>

View File

@ -0,0 +1,338 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE doc
SYSTEM '../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../clonk.xsl"?>
<doc>
<title>Sound modifiers</title>
<h>Sound modifiers</h>
<part>
<h>Usage</h>
<text>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 <funclink>Sound</funclink> 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:</text>
<code>Sound("Ding",,,,,,,Ambience.CaveModifier);</code>
<text>Custom modifiers may also be created using the prototype classes provided in the ambience object:</text>
<code>// 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();</code>
<text>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 <funclink>ChangeSoundModifier</funclink> with appropriate parameters.</text>
<text>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.</text>
<text>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).</text>
<text>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:</text>
<code>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;
}</code>
<text>Note that runtime updating does not work for the reverb modifier in the openal-soft library.</text>
<part>
<h>Global modifiers</h>
<text>Global modifiers can be set using the <funclink>SetGlobalSoundModifier</funclink> (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.</text>
</part>
</part>
<part>
<h>Property reference</h>
<text>The effect is selected from the Type-property, which may have the following values:</text>
<table>
<rowh>
<col>Constant</col>
<col>Effect</col>
</rowh>
<row>
<col>C4SMT_Reverb</col>
<col>Reverb effect caused by sound bouncing off walls in enclosed spaces.</col>
</row>
<row>
<col>C4SMT_Echo</col>
<col>Sound repeat as caused by loud sounds reflected in very large spaces.</col>
</row>
<row>
<col>C4SMT_Equalizer</col>
<col>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).</col>
</row>
</table>
<text>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.</text>
<part><h>Reverb modifier</h>
<table>
<rowh>
<col>Property</col>
<col>Type</col>
<col>Default</col>
<col>Minmum</col>
<col>Maximum</col>
<col>Remarks</col>
</rowh>
<row>
<col>Reverb_Density</col>
<col>int</col>
<col>1000</col>
<col>0</col>
<col>1000</col>
<col></col>
</row>
<row>
<col>Reverb_Diffusion</col>
<col>int</col>
<col>1000</col>
<col>0</col>
<col>1000</col>
<col></col>
</row>
<row>
<col>Reverb_Gain</col>
<col>int</col>
<col>316</col>
<col>0</col>
<col>1000</col>
<col></col>
</row>
<row>
<col>Reverb_GainHF</col>
<col>int</col>
<col>1000</col>
<col>0</col>
<col>1000</col>
<col></col>
</row>
<row>
<col>Reverb_Decay_Time</col>
<col>int</col>
<col>2910</col>
<col>100</col>
<col>20000</col>
<col></col>
</row>
<row>
<col>Reverb_Decay_HFRatio</col>
<col>int</col>
<col>1300</col>
<col>100</col>
<col>20000</col>
<col></col>
</row>
<row>
<col>Reverb_Reflections_Gain</col>
<col>int</col>
<col>500</col>
<col>0</col>
<col>3160</col>
<col></col>
</row>
<row>
<col>Reverb_Reflections_Delay</col>
<col>int</col>
<col>15</col>
<col>0</col>
<col>300</col>
<col></col>
</row>
<row>
<col>Reverb_Late_Reverb_Gain</col>
<col>int</col>
<col>706</col>
<col>0</col>
<col>10000</col>
<col></col>
</row>
<row>
<col>Reverb_Late_Reverb_Delay</col>
<col>int</col>
<col>22</col>
<col>0</col>
<col>100</col>
<col></col>
</row>
<row>
<col>Reverb_Air_Absorption_GainHF</col>
<col>int</col>
<col>994</col>
<col>892</col>
<col>1000</col>
<col></col>
</row>
<row>
<col>Reverb_Room_Rolloff_Factor</col>
<col>int</col>
<col>0</col>
<col>0</col>
<col>10000</col>
<col></col>
</row>
<row>
<col>Reverb_Decay_HFLimit</col>
<col>bool</col>
<col>true</col>
<col></col>
<col></col>
<col></col>
</row>
</table>
</part>
<part><h>Echo modifier</h>
<table>
<rowh>
<col>Property</col>
<col>Type</col>
<col>Default</col>
<col>Minmum</col>
<col>Maximum</col>
<col>Description</col>
</rowh>
<row>
<col>Echo_Delay</col>
<col>int</col>
<col>100</col>
<col>0</col>
<col>207</col>
<col>Time delay for first, centered echo.</col>
</row>
<row>
<col>Echo_LRDelay</col>
<col>int</col>
<col>100</col>
<col>0</col>
<col>404</col>
<col>Time delay for secondary, panning echo.</col>
</row>
<row>
<col>Echo_Damping</col>
<col>int</col>
<col>500</col>
<col>0</col>
<col>990</col>
<col>Amount of high-frequency damping.</col>
</row>
<row>
<col>Echo_Feedback</col>
<col>int</col>
<col>500</col>
<col>0</col>
<col>1000</col>
<col>Amount of original signal fed into the echo. A value of 1000 would lead to an infinite echo.</col>
</row>
<row>
<col>Echo_Spread</col>
<col>int</col>
<col>-1000</col>
<col>-1000</col>
<col>+1000</col>
<col>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.</col>
</row>
</table>
</part>
<part><h>Equalizer modifier</h>
<table>
<rowh>
<col>Property</col>
<col>Type</col>
<col>Default</col>
<col>Minmum</col>
<col>Maximum</col>
<col>Remarks</col>
</rowh>
<row>
<col>Equalizer_Low_Gain</col>
<col>int</col>
<col>1000</col>
<col>126</col>
<col>7943</col>
<col></col>
</row>
<row>
<col>Equalizer_Low_Cutoff</col>
<col>int</col>
<col>200000</col>
<col>50000</col>
<col>800000</col>
<col></col>
</row>
<row>
<col>Equalizer_Mid1_Gain</col>
<col>int</col>
<col>1000</col>
<col>126</col>
<col>7943</col>
<col></col>
</row>
<row>
<col>Equalizer_Mid1_Center</col>
<col>int</col>
<col>500000</col>
<col>200000</col>
<col>3000000</col>
<col></col>
</row>
<row>
<col>Equalizer_Mid1_Width</col>
<col>int</col>
<col>1000</col>
<col>10</col>
<col>1000</col>
<col></col>
</row>
<row>
<col>Equalizer_Mid2_Gain</col>
<col>int</col>
<col>1000</col>
<col>126</col>
<col>7943</col>
<col></col>
</row>
<row>
<col>Equalizer_Mid2_Center</col>
<col>int</col>
<col>3000000</col>
<col>1000000</col>
<col>8000000</col>
<col></col>
</row>
<row>
<col>Equalizer_Mid2_Width</col>
<col>int</col>
<col>1000</col>
<col>10</col>
<col>1000</col>
<col></col>
</row>
<row>
<col>Equalizer_High_Gain</col>
<col>int</col>
<col>1000</col>
<col>126</col>
<col>7943</col>
<col></col>
</row>
<row>
<col>Equalizer_High_Cutoff</col>
<col>int</col>
<col>6000000</col>
<col>4000000</col>
<col>16000000</col>
<col></col>
</row>
</table>
</part>
</part>
<author>Sven2</author><date>2015-07</date>
</doc>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>SetGlobalSoundModifier</title>
<category>Effects</category>
<subcat>Sound</subcat>
<version>7.0 OC</version>
<syntax>
<rtype>bool</rtype>
<params>
<param>
<type>proplist</type>
<name>name</name>
<desc>Modifier to be applied to all sounds.</desc>
</param>
<param>
<type>int</type>
<name>player</name>
<desc>If non-nil: Modifier is applied to sounds played in viewports owned by this player.</desc>
<optional />
</param>
</params>
</syntax>
<desc>Sets a <emlink href="script/SoundModifiers.html">sound modifier</emlink> to be applied to all sounds played that do not have a modifier already set.</desc>
<remark>Modifier precendence from highest to lowest is:
<ul><li>Modifier given as parameter to <funclink>Sound</funclink> or <funclink>SoundAt</funclink></li>
<li>Modifier assigned to the player owning the viewport which has its center closest to the sound source</li>
<li>Global modifier (as set by SetGlobalSoundModifier(modifier, nil);)</li></ul>
Only one modifier is applied at the time. It is not possible to combine multiple modifiers.</remark>
<examples>
<example>
<code>func Timer()
{
// Is there a player?
var player = <funclink>GetPlayerByIndex</funclink>(0, C4PT_User);
if (player >= 0)
{
// Is the player controlling a clonk in a cave?
var mod = nil;
var clonk = <funclink>GetCursor</funclink>(player);
if (clonk) if (clonk-><funclink>GetMaterial</funclink>() == <funclink>Material</funclink>("Tunnel"))
{
// Controlled clonk is in a cave - do some cave sounds!
mod = Ambience.CaveModifier;
}
SetGlobalSoundModifier(mod, player);
}
}</code>
<text>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.</text>
</example>
</examples>
<related><funclink>Sound</funclink>
<funclink>SoundAt</funclink>
<emlink href="script/SoundModifiers.html">Sound modifiers</emlink></related>
</func>
<author>Sven2</author><date>2015-08</date>
</funcs>

View File

@ -51,6 +51,12 @@
<desc>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.</desc>
<optional />
</param>
<param>
<type>proplist</type>
<name>modifier</name>
<desc>Sound modifier for special effects such as reverb or echo. See <emlink href="script/SoundModifiers.html">Sound modifiers</emlink>.</desc>
<optional />
</param>
</params>
</syntax>
<desc>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.</desc>
@ -63,6 +69,7 @@
</examples>
<related><funclink>SoundAt</funclink></related>
<related><funclink>Music</funclink></related>
<related><emlink href="script/SoundModifiers.html">Sound modifiers</emlink></related>
</func>
<author>Sven2</author><date>2002-08</date>
</funcs>

View File

@ -49,11 +49,18 @@
<desc>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.</desc>
<optional />
</param>
<param>
<type>proplist</type>
<name>modifier</name>
<desc>Sound modifier for special effects such as reverb or echo. See <emlink href="script/SoundModifiers.html">Sound modifiers</emlink>.</desc>
<optional />
</param>
</params>
</syntax>
<desc>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.</desc>
<related><funclink>Sound</funclink></related>
<related><funclink>Music</funclink></related>
<related><emlink href="script/SoundModifiers.html">Sound modifiers</emlink></related>
</func>
<author>Sven2</author><date>2002-08</date>
</funcs>

View File

@ -46,6 +46,7 @@
<text><emlink href="script/Effects.html">Effects</emlink></text>
<text><emlink href="script/GetXXVal.html">Querying Game Data</emlink></text>
<text><emlink href="script/ScriptPlayers.html">Script Player (i.e. AI player)</emlink></text>
<text><emlink href="script/SoundModifiers.html">Sound modifiers</emlink></text>
<h id="Infos">Libraries</h>
<text><emlink href="script/Shape.html">Shape</emlink></text>
</part>

View File

@ -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);
}
}
}
/* 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,
};
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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<int32_t>(100-100*distanceToCenterOfViewport/C4AudibilityRadius,0,100) );
int32_t audibility = Clamp<int32_t>(100 - 100 * distanceToCenterOfViewport / C4AudibilityRadius, 0, 100);
if (audibility > iAudible)
{
iAudible = audibility;
if (outPlayer) *outPlayer = cvp->Player;
}
*iPan += (iX-(cvp->GetViewCenterX())) / 5;
}
*iPan = Clamp<int32_t>(*iPan, -100, 100);

View File

@ -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

View File

@ -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<long> iLevel, Nillable<long> iAtPlayer, long iCustomFalloffDistance, long iPitch)
static bool FnSoundAt(C4PropList * _this, C4String *szSound, long iX, long iY, Nillable<long> iLevel, Nillable<long> 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<long> iLevel, Nillable<long> iAtPlayer, long iLoop, long iCustomFalloffDistance, long iPitch)
static bool FnSound(C4PropList * _this, C4String *szSound, bool fGlobal, Nillable<long> iLevel, Nillable<long> 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<long> 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}
};

View File

@ -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()

View File

@ -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<int>(Audible, Clamp(100 - 100 * Distance(cgo.X + cgo.Wdt / 2, cgo.Y + cgo.Hgt / 2, offX, offY) / 700, 0, 100));
AudiblePan = Clamp<int>(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<int>(200 * (offX - cgo.X - (cgo.Wdt / 2)) / cgo.Wdt, -100, 100);
AudiblePlayer = player;
}
}
bool C4Object::IsVisible(int32_t iForPlr, bool fAsOverlay) const

View File

@ -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

View File

@ -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;}

View File

@ -31,8 +31,13 @@
#elif AUDIO_TK == AUDIO_TK_OPENAL
# ifdef __APPLE__
# include <OpenAL/al.h>
# include <OpenAL/alc.h>
# include <OpenAL/alext.h>
# include <OpenAL/efx-presets.h>
# else
# include <al.h>
# include <alc.h>
# include <alext.h>
# endif
typedef ALuint C4SoundHandle;
# ifdef _WIN32

View File

@ -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 <C4Include.h>
#include <C4SoundInstance.h>
#include <C4Random.h>
#include <C4Object.h>
#include <C4Game.h>
#include <C4Config.h>
#include <C4Application.h>
#include <C4Viewport.h>
#include <C4SoundIncludes.h>
#include <C4SoundLoaders.h>
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<int32_t>(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>(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>(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);
}

View File

@ -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 <C4Include.h>
#include <C4SoundSystem.h>
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);
};

View File

@ -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 <C4Include.h>
#include <C4SoundModifiers.h>
#include <C4SoundSystem.h>
#include <C4SoundInstance.h>
#include <C4SoundIncludes.h>
#include <C4Application.h>
#include <C4Value.h>
#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<size_t>(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<size_t>(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;
}

View File

@ -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 <C4SoundIncludes.h>
#include <C4PropList.h>
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<C4SoundModifier *> sound_modifiers;
std::vector<C4SoundModifier *> 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

View File

@ -20,6 +20,7 @@
#include <C4Include.h>
#include <C4SoundSystem.h>
#include <C4SoundInstance.h>
#include <C4Random.h>
#include <C4Object.h>
#include <C4Game.h>
@ -29,466 +30,6 @@
#include <C4SoundIncludes.h>
#include <C4SoundLoaders.h>
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<int32_t>(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>(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>(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

View File

@ -21,6 +21,7 @@
#define INC_C4SoundSystem
#include <C4Group.h>
#include <C4SoundModifiers.h>
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

View File

@ -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);
}

View File

@ -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();

View File

@ -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";

View File

@ -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,