# Initial audio integration with new simulation system.
Support sound group playback from simulation scripts. See #486. Update some sound group files to sound better for rough testing. Clean up CSoundGroup a tiny bit. This was SVN commit r7437.
This commit is contained in:
parent
7275071785
commit
cfca28cab0
@ -12,8 +12,8 @@
|
||||
<GainUpper>1</GainUpper>
|
||||
<GainLower>0</GainLower>
|
||||
<RandPitch>1</RandPitch>
|
||||
<PitchUpper>0.5</PitchUpper>
|
||||
<PitchLower>0</PitchLower>
|
||||
<PitchUpper>1.0</PitchUpper>
|
||||
<PitchLower>0.8</PitchLower>
|
||||
<Threshold>10</Threshold>
|
||||
<Decay>3</Decay>
|
||||
<Replacement>death_11.ogg</Replacement>
|
||||
|
@ -12,11 +12,11 @@
|
||||
<GainUpper>1</GainUpper>
|
||||
<GainLower>0</GainLower>
|
||||
<RandPitch>1</RandPitch>
|
||||
<PitchUpper>0.5</PitchUpper>
|
||||
<PitchLower>0</PitchLower>
|
||||
<PitchUpper>1.0</PitchUpper>
|
||||
<PitchLower>0.8</PitchLower>
|
||||
<Threshold>10</Threshold>
|
||||
<Decay>3</Decay>
|
||||
<Replacement>hstep_dirt_MN_11.ogg</Replacement>
|
||||
<Path>audio/actor/human/movement/</Path>
|
||||
<Sound>hstep_dirt11.ogg</Sound>
|
||||
<Sound>hstep_dirt_MN_11.ogg</Sound>
|
||||
</SoundGroup>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<RandPitch>1</RandPitch>
|
||||
<PitchUpper>1.1</PitchUpper>
|
||||
<PitchLower>0.9</PitchLower>
|
||||
<Threshold>1</Threshold>
|
||||
<Threshold>100</Threshold>
|
||||
<Decay>3</Decay>
|
||||
<Replacement>arrowfly_28.ogg</Replacement>
|
||||
<Path>audio/attack/weapon/</Path>
|
||||
|
@ -37,6 +37,8 @@ Health.prototype.Reduce = function(amount)
|
||||
// might get called multiple times)
|
||||
if (this.hitpoints)
|
||||
{
|
||||
PlaySound("death", this.entity);
|
||||
|
||||
if (this.template.DeathType == "corpse")
|
||||
this.CreateCorpse();
|
||||
|
||||
|
17
binaries/data/mods/public/simulation/components/Sound.js
Normal file
17
binaries/data/mods/public/simulation/components/Sound.js
Normal file
@ -0,0 +1,17 @@
|
||||
function Sound() {}
|
||||
|
||||
Sound.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
||||
Sound.prototype.PlaySoundGroup = function(name)
|
||||
{
|
||||
if (name in this.template.SoundGroups)
|
||||
{
|
||||
var cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager);
|
||||
if (cmpSoundManager)
|
||||
cmpSoundManager.PlaySoundGroup(this.template.SoundGroups[name], this.entity);
|
||||
}
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Sound, "Sound", Sound);
|
@ -66,6 +66,7 @@ UnitAI.prototype.Walk = function(x, z)
|
||||
return;
|
||||
|
||||
this.SelectAnimation("walk", false, cmpMotion.GetSpeed());
|
||||
PlaySound("walk", this.entity);
|
||||
|
||||
cmpMotion.MoveToPoint(x, z, 0, 0);
|
||||
|
||||
@ -200,12 +201,14 @@ UnitAI.prototype.OnMotionChanged = function(msg)
|
||||
var cmpResourceSupply = Engine.QueryInterface(this.gatherTarget, IID_ResourceSupply);
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
|
||||
this.gatherTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "GatherTimeout", 1000, {});
|
||||
// Get the animation/sound type name
|
||||
var type = cmpResourceSupply.GetType();
|
||||
var typename = "gather_" + (type.specific || type.generic);
|
||||
|
||||
this.gatherTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "GatherTimeout", 1000, {"typename": typename});
|
||||
|
||||
// Start the gather animation
|
||||
var type = cmpResourceSupply.GetType();
|
||||
var anim = "gather_" + (type.specific || type.generic);
|
||||
this.SelectAnimation(anim);
|
||||
this.SelectAnimation(typename);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -385,6 +388,9 @@ UnitAI.prototype.AttackTimeout = function(data)
|
||||
|
||||
// Play the attack animation
|
||||
this.SelectAnimation("melee", false, 1);
|
||||
// Play the sound
|
||||
// TODO: these sounds should be triggered by the animation instead
|
||||
PlaySound("attack", this.entity);
|
||||
|
||||
// Hit the target
|
||||
cmpAttack.PerformAttack(this.attackTarget);
|
||||
@ -410,7 +416,11 @@ UnitAI.prototype.RepairTimeout = function(data)
|
||||
|
||||
var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder);
|
||||
|
||||
// Play the sound
|
||||
PlaySound("build", this.entity);
|
||||
|
||||
// Repair/build the target
|
||||
// TODO: these sounds should be triggered by the animation instead
|
||||
var status = cmpBuilder.PerformBuilding(this.repairTarget);
|
||||
|
||||
// If the target is fully built and repaired, then stop and go back to idle
|
||||
@ -439,6 +449,10 @@ UnitAI.prototype.GatherTimeout = function(data)
|
||||
|
||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
|
||||
// Play the gather_* sound
|
||||
// TODO: these sounds should be triggered by the animation instead
|
||||
PlaySound(data.typename, this.entity);
|
||||
|
||||
// Gather from the target
|
||||
var status = cmpResourceGatherer.PerformGather(this.gatherTarget);
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
Engine.RegisterInterface("Sound");
|
13
binaries/data/mods/public/simulation/helpers/Sound.js
Normal file
13
binaries/data/mods/public/simulation/helpers/Sound.js
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Simple wrapper function for playing sounds that are associated with entities
|
||||
* @param name Typically one of 'walk', 'run', 'attack', 'death', 'build',
|
||||
* 'gather_fruit', 'gather_grain', 'gather_wood', 'gather_stone', 'gather_metal'
|
||||
*/
|
||||
function PlaySound(name, ent)
|
||||
{
|
||||
var cmpSound = Engine.QueryInterface(ent, IID_Sound);
|
||||
if (cmpSound)
|
||||
cmpSound.PlaySoundGroup(name);
|
||||
}
|
||||
|
||||
Engine.RegisterGlobal("PlaySound", PlaySound);
|
@ -63,6 +63,7 @@ public:
|
||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
|
||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, noParam);
|
||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
|
||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
|
||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
|
||||
|
||||
// Add scripted system components:
|
||||
|
@ -90,6 +90,9 @@ COMPONENT(ProjectileManager)
|
||||
INTERFACE(Selectable)
|
||||
COMPONENT(Selectable)
|
||||
|
||||
INTERFACE(SoundManager)
|
||||
COMPONENT(SoundManager)
|
||||
|
||||
INTERFACE(Terrain)
|
||||
COMPONENT(Terrain)
|
||||
|
||||
|
123
source/simulation2/components/CCmpSoundManager.cpp
Normal file
123
source/simulation2/components/CCmpSoundManager.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "simulation2/system/Component.h"
|
||||
#include "ICmpSoundManager.h"
|
||||
|
||||
#include "ps/CLogger.h"
|
||||
#include "simulation2/MessageTypes.h"
|
||||
#include "simulation2/components/ICmpPosition.h"
|
||||
#include "sound/SoundGroup.h"
|
||||
|
||||
class CCmpSoundManager : public ICmpSoundManager
|
||||
{
|
||||
public:
|
||||
static void ClassInit(CComponentManager& componentManager)
|
||||
{
|
||||
componentManager.SubscribeToMessageType(MT_Update);
|
||||
}
|
||||
|
||||
DEFAULT_COMPONENT_ALLOCATOR(SoundManager)
|
||||
|
||||
const CSimContext* m_Context;
|
||||
|
||||
std::map<std::wstring, CSoundGroup*> m_SoundGroups;
|
||||
|
||||
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
|
||||
{
|
||||
m_Context = &context;
|
||||
}
|
||||
|
||||
virtual void Deinit(const CSimContext& UNUSED(context))
|
||||
{
|
||||
for (std::map<std::wstring, CSoundGroup*>::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it)
|
||||
delete it->second;
|
||||
m_SoundGroups.clear();
|
||||
}
|
||||
|
||||
virtual void Serialize(ISerializer& UNUSED(serialize))
|
||||
{
|
||||
// Do nothing here - sounds are purely local, and don't need to be preserved across saved games etc
|
||||
// (If we add music support in here then we might want to save the music state, though)
|
||||
}
|
||||
|
||||
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
|
||||
{
|
||||
Init(context, paramNode);
|
||||
}
|
||||
|
||||
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
|
||||
{
|
||||
switch (msg.GetType())
|
||||
{
|
||||
case MT_Update:
|
||||
{
|
||||
// Update all the sound groups
|
||||
// TODO: is it sensible to do this once per simulation turn, not once per renderer frame
|
||||
// or on some other timer?
|
||||
const CMessageUpdate& msgData = static_cast<const CMessageUpdate&> (msg);
|
||||
float t = msgData.turnLength.ToFloat();
|
||||
for (std::map<std::wstring, CSoundGroup*>::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it)
|
||||
if (it->second)
|
||||
it->second->Update(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void PlaySoundGroup(std::wstring name, entity_id_t source)
|
||||
{
|
||||
// Make sure the sound group is loaded
|
||||
CSoundGroup* group;
|
||||
if (m_SoundGroups.find(name) == m_SoundGroups.end())
|
||||
{
|
||||
group = new CSoundGroup();
|
||||
if (!group->LoadSoundGroup(L"audio/" + name))
|
||||
{
|
||||
LOGERROR(L"Failed to load sound group '%ls'", name.c_str());
|
||||
delete group;
|
||||
group = NULL;
|
||||
}
|
||||
// Cache the sound group (or the null, if it failed)
|
||||
m_SoundGroups[name] = group;
|
||||
}
|
||||
else
|
||||
{
|
||||
group = m_SoundGroups[name];
|
||||
}
|
||||
|
||||
// Failed to load group -> do nothing
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
// Find the source's position, if possible
|
||||
// (TODO: we should do something more sensible if there's no position available)
|
||||
CVector3D sourcePos(0, 0, 0);
|
||||
if (source != INVALID_ENTITY)
|
||||
{
|
||||
CmpPtr<ICmpPosition> cmpPosition(*m_Context, source);
|
||||
if (!cmpPosition.null() && cmpPosition->IsInWorld())
|
||||
sourcePos = CVector3D(cmpPosition->GetPosition());
|
||||
}
|
||||
|
||||
group->PlayNext(sourcePos);
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_COMPONENT_TYPE(SoundManager)
|
26
source/simulation2/components/ICmpSoundManager.cpp
Normal file
26
source/simulation2/components/ICmpSoundManager.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "ICmpSoundManager.h"
|
||||
|
||||
#include "simulation2/system/InterfaceScripted.h"
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(SoundManager)
|
||||
DEFINE_INTERFACE_METHOD_2("PlaySoundGroup", void, ICmpSoundManager, PlaySoundGroup, std::wstring, entity_id_t)
|
||||
END_INTERFACE_WRAPPER(SoundManager)
|
39
source/simulation2/components/ICmpSoundManager.h
Normal file
39
source/simulation2/components/ICmpSoundManager.h
Normal file
@ -0,0 +1,39 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_ICMPSOUNDMANAGER
|
||||
#define INCLUDED_ICMPSOUNDMANAGER
|
||||
|
||||
#include "simulation2/system/Interface.h"
|
||||
|
||||
/**
|
||||
* Interface to the engine's sound system.
|
||||
*/
|
||||
class ICmpSoundManager : public IComponent
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Start playing audio defined by a sound group file.
|
||||
* @param name VFS path of sound group .xml, relative to audio/
|
||||
* @param source entity emitting the sound (used for positioning)
|
||||
*/
|
||||
virtual void PlaySoundGroup(std::wstring name, entity_id_t source) = 0;
|
||||
|
||||
DECLARE_INTERFACE_TYPE(SoundManager)
|
||||
};
|
||||
|
||||
#endif // INCLUDED_ICMPSOUNDMANAGER
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -146,7 +146,7 @@ void CSoundGroup::PlayNext(const CVector3D& position)
|
||||
UploadPropertiesAndPlay(hs, position);
|
||||
}
|
||||
|
||||
playtimes[m_index] = 0.0f;
|
||||
playtimes.at(m_index) = 0.0f;
|
||||
m_index++;
|
||||
m_Intensity++;
|
||||
if(m_Intensity > m_IntensityThreshold)
|
||||
@ -216,10 +216,7 @@ bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML)
|
||||
if (XeroFile.Load(pathnameXML) != PSRETURN_OK)
|
||||
return false;
|
||||
|
||||
// adjust the path name for resources if necessary
|
||||
//m_Name = XMLfile + directorypath;
|
||||
|
||||
//Define elements used in XML file
|
||||
// Define elements used in XML file
|
||||
#define EL(x) int el_##x = XeroFile.GetElementID(#x)
|
||||
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
|
||||
EL(soundgroup);
|
||||
@ -263,106 +260,85 @@ bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML)
|
||||
{
|
||||
SetGain(CStr(child.GetText()).ToFloat());
|
||||
}
|
||||
|
||||
if(child_name == el_looping)
|
||||
else if(child_name == el_looping)
|
||||
{
|
||||
if(CStr(child.GetText()).ToInt() == 1)
|
||||
SetFlag(eLoop);
|
||||
}
|
||||
|
||||
if(child_name == el_omnipresent)
|
||||
else if(child_name == el_omnipresent)
|
||||
{
|
||||
if(CStr(child.GetText()).ToInt() == 1)
|
||||
SetFlag(eOmnipresent);
|
||||
}
|
||||
|
||||
if(child_name == el_pitch)
|
||||
else if(child_name == el_pitch)
|
||||
{
|
||||
this->m_Pitch = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_priority)
|
||||
else if(child_name == el_priority)
|
||||
{
|
||||
this->m_Priority = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_randorder)
|
||||
else if(child_name == el_randorder)
|
||||
{
|
||||
if(CStr(child.GetText()).ToInt() == 1)
|
||||
SetFlag(eRandOrder);
|
||||
}
|
||||
|
||||
if(child_name == el_randgain)
|
||||
else if(child_name == el_randgain)
|
||||
{
|
||||
if(CStr(child.GetText()).ToInt() == 1)
|
||||
SetFlag(eRandGain);
|
||||
}
|
||||
|
||||
if(child_name == el_gainupper)
|
||||
else if(child_name == el_gainupper)
|
||||
{
|
||||
this->m_GainUpper = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_gainlower)
|
||||
else if(child_name == el_gainlower)
|
||||
{
|
||||
this->m_GainLower = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_randpitch)
|
||||
else if(child_name == el_randpitch)
|
||||
{
|
||||
if(CStr(child.GetText()).ToInt() == 1)
|
||||
SetFlag(eRandPitch);
|
||||
}
|
||||
|
||||
if(child_name == el_pitchupper)
|
||||
else if(child_name == el_pitchupper)
|
||||
{
|
||||
this->m_PitchUpper = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_pitchlower)
|
||||
else if(child_name == el_pitchlower)
|
||||
{
|
||||
this->m_PitchLower = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_conegain)
|
||||
else if(child_name == el_conegain)
|
||||
{
|
||||
this->m_ConeOuterGain = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_coneinner)
|
||||
else if(child_name == el_coneinner)
|
||||
{
|
||||
this->m_ConeInnerAngle = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_coneouter)
|
||||
else if(child_name == el_coneouter)
|
||||
{
|
||||
this->m_ConeOuterAngle = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_sound)
|
||||
else if(child_name == el_sound)
|
||||
{
|
||||
CStrW szTemp(child.GetText());
|
||||
this->filenames.push_back(szTemp);
|
||||
|
||||
this->filenames.push_back(szTemp);
|
||||
}
|
||||
if(child_name == el_path)
|
||||
else if(child_name == el_path)
|
||||
{
|
||||
m_filepath = CStrW(child.GetText());
|
||||
|
||||
}
|
||||
if(child_name == el_threshold)
|
||||
else if(child_name == el_threshold)
|
||||
{
|
||||
//m_intensity_threshold = CStr(child.GetText()).ToFloat();
|
||||
m_IntensityThreshold = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_decay)
|
||||
else if(child_name == el_decay)
|
||||
{
|
||||
//m_intensity_threshold = CStr(child.GetText()).ToFloat();
|
||||
m_Decay = CStr(child.GetText()).ToFloat();
|
||||
}
|
||||
|
||||
if(child_name == el_replacement)
|
||||
else if(child_name == el_replacement)
|
||||
{
|
||||
m_intensity_file = CStrW(child.GetText());
|
||||
}
|
||||
@ -370,5 +346,4 @@ bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML)
|
||||
|
||||
Reload();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
@ -66,12 +66,13 @@ enum eSndGrpFlags
|
||||
eRandGain = 0x02,
|
||||
eRandPitch = 0x04,
|
||||
eLoop = 0x08,
|
||||
eOmnipresent = 0x10
|
||||
eOmnipresent = 0x10
|
||||
};
|
||||
|
||||
|
||||
class CSoundGroup
|
||||
{
|
||||
NONCOPYABLE(CSoundGroup);
|
||||
public:
|
||||
CSoundGroup(const VfsPath& pathnameXML);
|
||||
CSoundGroup(void);
|
||||
@ -94,10 +95,10 @@ public:
|
||||
void Update(float TimeSinceLastFrame);
|
||||
|
||||
// Set a flag using a value from eSndGrpFlags
|
||||
inline void SetFlag(int flag){ m_Flags |= flag; }
|
||||
inline void SetFlag(int flag) { m_Flags |= flag; }
|
||||
|
||||
// Test flag, returns true if flag is set.
|
||||
inline bool TestFlag(int flag) { return (m_Flags & flag) != 0;}
|
||||
inline bool TestFlag(int flag) { return (m_Flags & flag) != 0; }
|
||||
|
||||
private:
|
||||
void SetGain(float gain);
|
||||
|
Loading…
Reference in New Issue
Block a user