Added support for terrain specific sfx. As an example

this has been used for the wooden terrain in snowtuxpeak.
Note that atm changing the pitch is used to make the sfx
depend on speed, but since I am not too happy with this
I might add support for a customisable pause between
looping the sfx.


git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@5527 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk 2010-06-20 23:01:23 +00:00
parent 8aa404bf8f
commit f1438d6354
6 changed files with 297 additions and 107 deletions

View File

@ -142,7 +142,7 @@ void SFXManager::loadSfx()
* based on a routine by Peter Mulholland, used with permission (quote :
* "Feel free to use")
*/
bool loadVorbisBuffer(const char *name, ALuint buffer)
bool SFXManager::loadVorbisBuffer(const std::string &name, ALuint buffer)
{
const int ogg_endianness = (IS_LITTLE_ENDIAN ? 0 : 1);
@ -158,7 +158,7 @@ bool loadVorbisBuffer(const char *name, ALuint buffer)
return false;
}
file = fopen(name, "rb");
file = fopen(name.c_str(), "rb");
if(!file)
{
@ -242,48 +242,51 @@ const char *SFXManager::getCustomTagName(int id)
*/
// -----------------------------------------------------------------------------
/*
addSingleSfx()
Introduces a mechanism by which one can load sound effects beyond the basic
enumerated types. This will be used when loading custom sound effects for
individual karts (character voices) so that we can avoid creating an
enumeration for each effect, for each kart.
\param sfxFile must be an absolute pathname
\return whether loading this sound effect was successful
/** Introduces a mechanism by which one can load sound effects beyond the basic
* enumerated types. This will be used when loading custom sound effects for
* individual karts (character voices) so that we can avoid creating an
* enumeration for each effect, for each kart.
* \param sfx_name
* \param sfxFile must be an absolute pathname
* \return whether loading this sound effect was successful
*/
bool SFXManager::addSingleSfx(const char* sfx_name,
std::string sfx_file,
bool positional,
float rolloff,
float gain)
bool SFXManager::addSingleSfx(const std::string &sfx_name,
const std::string &sfx_file,
bool positional,
float rolloff,
float gain)
{
std::string filename;
SFXBufferInfo sfx_info;
sfx_info.m_sfx_rolloff = rolloff;
sfx_info.m_sfx_positional = positional;
sfx_info.m_sfx_gain = gain;
SFXBufferInfo sfx_info;
sfx_info.m_rolloff = rolloff;
sfx_info.m_positional = positional;
sfx_info.m_gain = gain;
if(!m_initialized)
{
// Even if not initialised store the (useless) data in the mapping to
// avoid later warnings/errors.
m_all_sfx_types[sfx_name] = sfx_info;
return false;
}
if(UserConfigParams::m_verbosity>=5)
printf("Loading SFX %s\n", sfx_file.c_str());
alGetError(); // clear errors from previously
alGenBuffers(1, &sfx_info.m_sfx_buffer);
alGenBuffers(1, &sfx_info.m_buffer);
if (!checkError("generating a buffer"))
{
return false;
}
if (!loadVorbisBuffer(sfx_name, sfx_info.m_sfx_buffer))
assert( alIsBuffer(sfx_info.m_buffer) );
if (!loadVorbisBuffer(sfx_file, sfx_info.m_buffer))
{
fprintf(stderr, "Could not load sound effect %s\n", sfx_name);
fprintf(stderr, "Could not load sound effect %s\n", sfx_file.c_str());
return false;
}
@ -293,50 +296,48 @@ bool SFXManager::addSingleSfx(const char* sfx_name,
} // addSingleSFX
//----------------------------------------------------------------------------
void SFXManager::loadSingleSfx(const XMLNode* node)
/** Loads a single sfx from the XML specification.
* \param node The XML node with the data for this sfx.
*/
void SFXManager::loadSingleSfx(const XMLNode* node,
const std::string &path)
{
std::string filename;
std::string sfx_name;
SFXBufferInfo sfxInfo;
if (node->get("filename", &filename) == 0)
{
fprintf(stderr, "/!\\ The 'filename' attribute is mandatory in the SFX XML file!\n");
fprintf(stderr,
"/!\\ The 'filename' attribute is mandatory in the SFX XML file!\n");
return;
}
std::string sfx_name;
if (node->get("name", &sfx_name) == 0)
{
fprintf(stderr, "/!\\ The 'name' attribute is mandatory in the SFX XML file!\n");
fprintf(stderr,
"/!\\ The 'name' attribute is mandatory in the SFX XML file!\n");
return;
}
node->get("rolloff", &sfxInfo.m_sfx_rolloff );
node->get("positional", &sfxInfo.m_sfx_positional );
node->get("volume", &sfxInfo.m_sfx_gain );
std::string path = file_manager->getSFXFile(filename);
if(UserConfigParams::m_verbosity>=5)
printf("Loading SFX %s\n", path.c_str());
// Only try loading if the sound manager was properly initialised.
if(m_initialized)
if(m_all_sfx_types.find(sfx_name)!=m_all_sfx_types.end())
{
alGenBuffers(1, &sfxInfo.m_sfx_buffer);
if (!checkError("generating a buffer")) return;
fprintf(stderr,
"There is already a sfx named '%s' installed - new one is ignored.\n",
sfx_name.c_str());
return;
}
assert( alIsBuffer(sfxInfo.m_sfx_buffer) );
SFXBufferInfo sfx_info;
node->get("rolloff", &sfx_info.m_rolloff );
node->get("positional", &sfx_info.m_positional );
node->get("volume", &sfx_info.m_gain );
// Only use the filename if no full path is specified. This is used
// to load terrain specific sfx.
const std::string full_path = (path=="") ? file_manager->getSFXFile(filename)
: path;
addSingleSfx(sfx_name, full_path, sfx_info.m_positional, sfx_info.m_rolloff,
sfx_info.m_gain);
if (!loadVorbisBuffer(path.c_str(), sfxInfo.m_sfx_buffer))
{
fprintf(stderr, "Could not load sound effect %s\n", path.c_str());
return;
}
} // if m_initialized
m_all_sfx_types[sfx_name.c_str()] = sfxInfo;
/*
std::map<std::string, SFXBufferInfo>::iterator i = m_all_sfx_types.begin();
for (; i != m_all_sfx_types.end(); i++ )
@ -362,12 +363,12 @@ SFXBase* SFXManager::createSoundSource(const SFXBufferInfo& info,
if (race_manager->getNumLocalPlayers() < 2)
{
positional = info.m_sfx_positional;
positional = info.m_positional;
}
assert( alIsBuffer(info.m_sfx_buffer) );
assert( alIsBuffer(info.m_buffer) );
SFXBase* sfx = new SFXOpenAL(info.m_sfx_buffer, positional, info.m_sfx_rolloff, info.m_sfx_gain);
SFXBase* sfx = new SFXOpenAL(info.m_buffer, positional, info.m_rolloff, info.m_gain);
// debugging
/*printf("newSfx(): id:%d buffer:%p, rolloff:%f, gain:%f %p\n", id, m_sfx_buffers[id], m_sfx_rolloff[id], m_sfx_gain[id], p);*/
@ -380,8 +381,8 @@ SFXBase* SFXManager::createSoundSource(const SFXBufferInfo& info,
} // createSoundSource
//----------------------------------------------------------------------------
SFXBase* SFXManager::createSoundSource(const char* name, const bool addToSFXList)
SFXBase* SFXManager::createSoundSource(const std::string &name,
const bool addToSFXList)
{
std::map<std::string, SFXBufferInfo>::iterator i = m_all_sfx_types.find(name);
if ( i == m_all_sfx_types.end() )
@ -401,6 +402,36 @@ SFXBase* SFXManager::createSoundSource(const char* name, const bool addToSFXList
return createSoundSource( i->second, addToSFXList );
} // createSoundSource
//----------------------------------------------------------------------------
/** Returns true if a sfx with the given name exists.
* \param name The internal name of the sfx (not the name of the ogg file)
*/
bool SFXManager::soundExist(const std::string &name)
{
return m_all_sfx_types.find(name) != m_all_sfx_types.end();
} // soundExist
//----------------------------------------------------------------------------
/** This function removes a sfx buffer info entry from the mapping, and
* frees the openal buffer.
* \param name The name of the mapping entry to remove.
*/
void SFXManager::deleteSFXMapping(const std::string &name)
{
std::map<std::string, SFXBufferInfo>::iterator i;
i = m_all_sfx_types.find(name);
if(i==m_all_sfx_types.end())
{
fprintf(stderr, "SFXManager::deleteSFXMapping : Warning: sfx not found in list.\n");
return;
}
(*i).second.freeBuffer();
m_all_sfx_types.erase(i);
} // deleteSFXMapping
//----------------------------------------------------------------------------
/** Delete a sound effect object, and removes it from the internal list of
* all SFXs. This call deletes the object, and removes it from the list of
@ -453,6 +484,10 @@ void SFXManager::resumeAll()
} // resumeAll
//-----------------------------------------------------------------------------
/** Returns whether or not an openal error has occurred. If so, an error
* message is printed containing the given context.
* \param context Context to specify in the error message.
*/
bool SFXManager::checkError(const std::string &context)
{
// Check (and clear) the error flag
@ -468,6 +503,9 @@ bool SFXManager::checkError(const std::string &context)
} // checkError
//-----------------------------------------------------------------------------
/** Sets the master volume for all sound effects.
* \param gain The volume to set.
*/
void SFXManager::setMasterSFXVolume(float gain)
{
if (gain > 1.0) gain = 1.0f;
@ -531,18 +569,22 @@ void SFXManager::positionListener(const Vec3 &position, const Vec3 &front)
}
//-----------------------------------------------------------------------------
/** Positional sound is cool, but creating a new object just to play a simple
* menu sound is not. This function allows for 'quick sounds' in a single call.
* \param sound_type Internal name of the sfx to play.
*/
void SFXManager::quickSound(const char* soundType)
void SFXManager::quickSound(const std::string &sound_type)
{
if(!sfxAllowed()) return;
std::map<std::string, SFXBase*>::iterator sound = m_quick_sounds.find(soundType);
std::map<std::string, SFXBase*>::iterator sound = m_quick_sounds.find(sound_type);
if (sound == m_quick_sounds.end())
{
// sound not yet in our local list of quick sounds
SFXBase* newSound = sfx_manager->createSoundSource(soundType, false);
SFXBase* newSound = sfx_manager->createSoundSource(sound_type, false);
newSound->play();
m_quick_sounds[soundType] = newSound;
m_quick_sounds[sound_type] = newSound;
}
else
{

View File

@ -77,17 +77,17 @@ private:
{
private:
public:
ALuint m_sfx_buffer;
bool m_sfx_positional;
float m_sfx_rolloff;
float m_sfx_gain;
ALuint m_buffer;
bool m_positional;
float m_rolloff;
float m_gain;
SFXBufferInfo()
{
m_sfx_buffer = 0;
m_sfx_gain = 1.0f;
m_sfx_rolloff = 0.1f;
m_sfx_positional = false;
m_buffer = 0;
m_gain = 1.0f;
m_rolloff = 0.1f;
m_positional = false;
}
@ -95,8 +95,8 @@ private:
* and the OpenAL source must not be deleted on a copy */
void freeBuffer()
{
alDeleteBuffers(1, &m_sfx_buffer);
m_sfx_buffer = 0;
alDeleteBuffers(1, &m_buffer);
m_buffer = 0;
}
~SFXBufferInfo()
{
@ -121,27 +121,30 @@ private:
void loadSfx();
void loadSingleSfx(const XMLNode* node);
bool loadVorbisBuffer(const std::string &name,
ALuint buffer);
public:
SFXManager();
virtual ~SFXManager();
bool sfxAllowed();
bool addSingleSfx( const char* sfx_name,
std::string filename,
bool positional,
float rolloff,
float gain);
void loadSingleSfx(const XMLNode* node,
const std::string &path=std::string(""));
bool addSingleSfx(const std::string &sfx_name,
const std::string &filename,
bool positional,
float rolloff,
float gain);
SFXBase* createSoundSource(const SFXBufferInfo& info,
const bool addToSFXList=true);
SFXBase* createSoundSource(const char* name,
SFXBase* createSoundSource(const std::string &name,
const bool addToSFXList=true);
void deleteSFX(SFXBase *sfx);
void deleteSFXMapping(const std::string &name);
void pauseAll();
void resumeAll();
bool soundExist(const std::string &name);
void setMasterSFXVolume(float gain);
float getMasterSFXVolume() const { return m_master_gain; }
@ -149,10 +152,7 @@ public:
static const std::string getErrorString(int err);
void positionListener(const Vec3 &position, const Vec3 &front);
/** Positional sound is cool, but creating a new object just to play a simple
menu sound is not. This function allows for 'quick sounds' in a single call.*/
void quickSound(const char* soundName);
void quickSound(const std::string &soundName);
};

View File

@ -22,6 +22,7 @@
#include <stdexcept>
#include "audio/sfx_base.hpp"
#include "config/user_config.hpp"
#include "config/stk_config.hpp"
#include "graphics/irr_driver.hpp"
@ -76,6 +77,25 @@ Material::Material(const XMLNode *node, int index)
s.c_str());
else
m_graphical_effect = GE_NONE;
// Terrain-specifc sound effect
for(unsigned int i=0; i<node->getNumNodes(); i++)
{
const XMLNode *sfx= node->getNode(i);
if(sfx->getName()!="sfx")
{
printf("Warning: unknown node type '%s' for texture '%s' - ignored.\n",
sfx->getName().c_str(), m_texname);
continue;
}
if(m_sfx_name!="")
{
printf("Warning: more than one sfx specified for texture '%s' - ignored.\n",
m_texname.c_str());
continue;
}
initCustomSFX(sfx);
} // for i <node->getNumNodes()
install(/*is_full_path*/false);
} // Material
@ -93,11 +113,9 @@ Material::Material(const std::string& fname, int index, bool is_full_path)
} // Material
//-----------------------------------------------------------------------------
Material::~Material()
{
} // ~Material
//-----------------------------------------------------------------------------
/** Inits all material data with the default settings.
* \param Index of this material in the material_manager index array.
*/
void Material::init(unsigned int index)
{
m_index = index;
@ -115,8 +133,13 @@ void Material::init(unsigned int index)
m_resetter = false;
m_max_speed_fraction = 1.0f;
m_slowdown = 1.0f;
m_sfx_name = "";
m_sfx_min_speed = 0.0f;
m_sfx_max_speed = 30;
m_sfx_min_pitch = 1.0f;
m_sfx_max_pitch = 1.0f;
m_graphical_effect = GE_NONE;
}
} // init
//-----------------------------------------------------------------------------
void Material::install(bool is_full_path)
@ -127,6 +150,78 @@ void Material::install(bool is_full_path)
// now set the name to the basename, so that all tests work as expected
m_texname = StringUtils::getBasename(m_texname);
} // install
//-----------------------------------------------------------------------------
Material::~Material()
{
// If a special sfx is installed (that isn't part of stk itself), the
// entry needs to be removed from the sfx_manager's mapping, since other
// tracks might use the same name.
if(m_sfx_name!="" && m_sfx_name==m_texname)
{
sfx_manager->deleteSFXMapping(m_sfx_name);
}
} // ~Material
//-----------------------------------------------------------------------------
/** Initialise the data structures for a custom sfx to be played when a
* kart is driving on that particular material.
* \param sfx The xml node containing the information for this sfx.
*/
void Material::initCustomSFX(const XMLNode *sfx)
{
m_sfx_name="";
// The name of the 'name' attribute must be the same as the one
// used in sfx_manager, since the sfx_manager will be reading this
// xml node, too.
sfx->get("name", &m_sfx_name);
if(m_sfx_name=="") m_sfx_name = m_texname;
sfx->get("min-speed", &m_sfx_min_speed);
sfx->get("max-speed", &m_sfx_max_speed);
sfx->get("min-pitch", &m_sfx_min_pitch);
sfx->get("max-pitch", &m_sfx_max_pitch);
m_sfx_pitch_per_speed = (m_sfx_max_pitch - m_sfx_min_pitch)
/ (m_sfx_max_speed - m_sfx_min_speed);
if(!sfx_manager->soundExist(m_sfx_name))
{
std::string filename;
sfx->get("filename", &filename);
// The directory for the track was added to the model search path
// so just misuse the getModelFile function
const std::string full_path = file_manager->getModelFile(filename);
sfx_manager->loadSingleSfx(sfx, full_path);
}
} // initCustomSFX
//-----------------------------------------------------------------------------
/** Adjusts the pitch of the given sfx depending on the given speed.
* \param sfx The sound effect to adjust.
* \param speed The speed of the kart.
*/
void Material::setSFXSpeed(SFXBase *sfx, float speed) const
{
if(sfx->getStatus()==SFXManager::SFX_STOPED)
{
if(speed<m_sfx_min_speed) return;
sfx->play();
}
else if(sfx->getStatus()==SFXManager::SFX_PLAYING)
{
if(speed<m_sfx_min_speed)
{
sfx->stop();
return;
}
}
if(speed > m_sfx_max_speed)
{
sfx->speed(m_sfx_max_pitch);
return;
}
float f = m_sfx_pitch_per_speed * (speed-m_sfx_min_speed) + m_sfx_min_pitch;
sfx->speed(f);
} // setSFXSpeed
//-----------------------------------------------------------------------------
/** Sets the appropriate flags in an irrlicht SMaterial.

View File

@ -26,6 +26,7 @@
using namespace irr;
class XMLNode;
class SFXBase;
/**
* \ingroup graphics
@ -39,6 +40,9 @@ private:
video::ITexture *m_texture;
unsigned int m_index;
std::string m_texname;
/** Name of a special sfx to play when a kart is on this terrain, or
* "" if no special sfx exists. */
std::string m_sfx_name;
GraphicalEffect m_graphical_effect;
bool m_zipper;
bool m_resetter;
@ -63,15 +67,29 @@ private:
float m_slowdown;
/** Maximum speed at which no more slow down occurs. */
float m_max_speed_fraction;
void init (unsigned int index);
void install (bool is_full_path=false);
/** The minimum speed at which a special sfx is started to be played. */
float m_sfx_min_speed;
/** The speed at which the maximum pitch is used. */
float m_sfx_max_speed;
/** The minimum pitch to be used (at minimum speed). */
float m_sfx_min_pitch;
/** The maximum pitch to be used (at maximum speed). */
float m_sfx_max_pitch;
/** (max_pitch-min_pitch) / (max_speed - min_speed). Used to adjust
* the pitch of a sfx depending on speed of the kart.
*/
float m_sfx_pitch_per_speed;
void init (unsigned int index);
void install (bool is_full_path=false);
void initCustomSFX(const XMLNode *sfx);
public:
Material(const XMLNode *node, int index);
Material(const std::string& fname, int index,
bool is_full_path=false);
~Material ();
void setSFXSpeed(SFXBase *sfx, float speed) const;
void setMaterialProperties(video::SMaterial *m) const;
/** Returns the ITexture associated with this material. */
video::ITexture *getTexture() const { return m_texture; }
@ -92,6 +110,10 @@ public:
bool hasSmoke () const { return m_graphical_effect==GE_SMOKE;}
/** Returns true if this material should have water splashes. */
bool hasWaterSplash () const { return m_graphical_effect==GE_WATER;}
/** Returns the name of a special sfx to play while a kart is on this
* terrain. The string will be "" if no special sfx exists. */
const std::string &
getSFXName () const { return m_sfx_name; }
} ;

View File

@ -98,9 +98,6 @@ Kart::Kart (const std::string& ident, int position,
// Set position and heading:
m_reset_transform = init_transform;
// Neglecting the roll resistance (which is small for high speeds compared
// to the air resistance), maximum speed is reached when the engine
// power equals the air resistance force, resulting in this formula:
m_max_speed = m_kart_properties->getMaxSpeed();
m_max_speed_reverse_ratio = m_kart_properties->getMaxSpeedReverseRatio();
m_speed = 0.0f;
@ -120,11 +117,12 @@ Kart::Kart (const std::string& ident, int position,
}
}*/
m_engine_sound = sfx_manager->createSoundSource(m_kart_properties->getEngineSfxType());
m_beep_sound = sfx_manager->createSoundSource( "beep" );
m_crash_sound = sfx_manager->createSoundSource( "crash" );
m_goo_sound = sfx_manager->createSoundSource( "goo" );
m_skid_sound = sfx_manager->createSoundSource( "skid" );
m_engine_sound = sfx_manager->createSoundSource(m_kart_properties->getEngineSfxType());
m_beep_sound = sfx_manager->createSoundSource( "beep" );
m_crash_sound = sfx_manager->createSoundSource( "crash" );
m_goo_sound = sfx_manager->createSoundSource( "goo" );
m_skid_sound = sfx_manager->createSoundSource( "skid" );
m_terrain_sound = NULL;
if(!m_engine_sound)
{
@ -409,6 +407,12 @@ void Kart::reset()
m_max_speed_reduction = 0.0f;
m_power_reduction = 1.0f;
m_slipstream_mode = SS_NONE;
m_last_material = NULL;
if(m_terrain_sound)
{
m_terrain_sound->stop();
sfx_manager->deleteSFX(m_terrain_sound);
}
m_controls.m_steer = 0.0f;
m_controls.m_accel = 0.0f;
@ -734,7 +738,7 @@ void Kart::update(float dt)
{
m_body->getBroadphaseHandle()->m_collisionFilterGroup = old_group;
}
const Material* material=getMaterial();
const Material* material=TerrainInfo::getMaterial();
m_power_reduction = 1.0f;
if (getHoT()==Track::NOHIT) // kart falling off the track
{
@ -746,6 +750,28 @@ void Kart::update(float dt)
}
else if(material)
{
// Stop a terrain specific sfx if the terrain has changed
if(m_last_material!=material)
{
if(m_terrain_sound)
{
m_terrain_sound->stop();
sfx_manager->deleteSFX(m_terrain_sound);
}
const std::string s = material->getSFXName();
if(s!="")
{
m_terrain_sound = sfx_manager->createSoundSource(s);
m_terrain_sound->position(getXYZ());
m_terrain_sound->play();
m_terrain_sound->loop();
}
else
m_terrain_sound = NULL;
}
if(m_terrain_sound) material->setSFXSpeed(m_terrain_sound, m_speed);
m_last_material = material;
// Sometimes the material can be 0. This can happen if a kart is above
// another kart (e.g. mass collision, or one kart falling on another
// kart). Bullet does not have any triangle information in this case,

View File

@ -186,10 +186,15 @@ private:
SFXBase *m_beep_sound;
SFXBase *m_engine_sound;
SFXBase *m_crash_sound;
SFXBase *m_terrain_sound;
SFXBase *m_skid_sound;
SFXBase *m_goo_sound;
float m_time_last_crash;
/** Stores the last material, used to detect if a kart enteres a new
* terrain and might stop/start terrain specific sfx. */
const Material *m_last_material;
float handleSlipstream(float dt);
void updatePhysics(float dt);