1
0
forked from 0ad/0ad

# 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:
Ykkrosh 2010-04-04 21:24:39 +00:00
parent 7275071785
commit cfca28cab0
15 changed files with 276 additions and 61 deletions

View File

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

View File

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

View File

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

View File

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

View 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);

View File

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

View File

@ -0,0 +1 @@
Engine.RegisterInterface("Sound");

View 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);

View File

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

View File

@ -90,6 +90,9 @@ COMPONENT(ProjectileManager)
INTERFACE(Selectable)
COMPONENT(Selectable)
INTERFACE(SoundManager)
COMPONENT(SoundManager)
INTERFACE(Terrain)
COMPONENT(Terrain)

View 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)

View 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)

View 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

View File

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

View File

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