stk-code_catmod/src/audio/sfx_openal.cpp

468 lines
14 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2014 Joerg Henrichs
// Copyright (C) 2006-2014 Patrick Ammann <pammann@aro.ch>
// Copyright (C) 2009-2014 Marianne Gagnon
//
// This program 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 3
// of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#if HAVE_OGGVORBIS
#include "audio/sfx_openal.hpp"
#include "audio/sfx_buffer.hpp"
#include "config/user_config.hpp"
#include "modes/world.hpp"
#include "utils/vs.hpp"
#ifdef __APPLE__
# include <OpenAL/al.h>
#else
# include <AL/al.h>
#endif
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string>
SFXOpenAL::SFXOpenAL(SFXBuffer* buffer, bool positional, float gain,
bool owns_buffer)
: SFXBase()
{
m_sound_buffer = buffer;
m_sound_source = 0;
m_status = SFX_NOT_INITIALISED;
m_positional = positional;
m_default_gain = gain;
m_loop = false;
m_gain = -1.0f;
m_master_gain = 1.0f;
m_owns_buffer = owns_buffer;
m_play_time = 0.0f;
// Don't initialise anything else if the sfx manager was not correctly
// initialised. First of all the initialisation will not work, and it
// will not be used anyway.
if (SFXManager::get()->sfxAllowed())
{
init();
}
} // SFXOpenAL
//-----------------------------------------------------------------------------
/** Deletes the sfx source, and if it owns the buffer, also deletes the sound
* buffer. */
SFXOpenAL::~SFXOpenAL()
{
if (m_status!=SFX_UNKNOWN)
{
alDeleteSources(1, &m_sound_source);
}
if (m_owns_buffer && m_sound_buffer)
{
m_sound_buffer->unload();
delete m_sound_buffer;
}
} // ~SFXOpenAL
//-----------------------------------------------------------------------------
/** Initialises the sfx.
*/
bool SFXOpenAL::init()
{
m_status = SFX_UNKNOWN;
alGenSources(1, &m_sound_source );
if (!SFXManager::checkError("generating a source"))
return false;
assert( alIsBuffer(m_sound_buffer->getBufferID()) );
assert( alIsSource(m_sound_source) );
alSourcei (m_sound_source, AL_BUFFER, m_sound_buffer->getBufferID());
if (!SFXManager::checkError("attaching the buffer to the source"))
return false;
alSource3f(m_sound_source, AL_POSITION, 0.0, 0.0, 0.0);
alSource3f(m_sound_source, AL_VELOCITY, 0.0, 0.0, 0.0);
alSource3f(m_sound_source, AL_DIRECTION, 0.0, 0.0, 0.0);
alSourcef (m_sound_source, AL_ROLLOFF_FACTOR, m_sound_buffer->getRolloff());
alSourcef (m_sound_source, AL_MAX_DISTANCE, m_sound_buffer->getMaxDist());
if (m_gain < 0.0f)
{
alSourcef (m_sound_source, AL_GAIN, m_default_gain * m_master_gain);
}
else
{
alSourcef (m_sound_source, AL_GAIN, m_gain * m_master_gain);
}
if (m_positional) alSourcei (m_sound_source, AL_SOURCE_RELATIVE, AL_FALSE);
else alSourcei (m_sound_source, AL_SOURCE_RELATIVE, AL_TRUE);
alSourcei(m_sound_source, AL_LOOPING, m_loop ? AL_TRUE : AL_FALSE);
if(!SFXManager::checkError("setting up the source"))
return false;
m_status = SFX_STOPPED;
return true;
} // init
// ------------------------------------------------------------------------
/** Updates the status of a playing sfx. If the sound has been played long
* enough, mark it to be finished. This avoid (a potentially costly)
* call to openal.
* \param dt Time step size.
*/
void SFXOpenAL::updatePlayingSFX(float dt)
{
assert(m_status==SFX_PLAYING);
m_play_time += dt;
if(!m_loop && m_play_time > m_sound_buffer->getDuration())
m_status = SFX_STOPPED;
} // updatePlayingSFX
//-----------------------------------------------------------------------------
/** Queues up a change of the pitch of a sound effect to the sfx manager.
* \param factor Speedup/slowdown between 0.5 and 2.0
*/
void SFXOpenAL::setSpeed(float factor)
{
if(m_status==SFX_UNKNOWN || !SFXManager::get()->sfxAllowed()) return;
assert(!isnan(factor));
SFXManager::get()->queue(SFXManager::SFX_SPEED, this, factor);
} // setSpeed
//-----------------------------------------------------------------------------
/** Changes the pitch of a sound effect. Executed from the sfx manager thread.
* \param factor Speedup/slowdown between 0.5 and 2.0
*/
void SFXOpenAL::reallySetSpeed(float factor)
{
if(m_status==SFX_NOT_INITIALISED)
{
init();
if(m_status==SFX_UNKNOWN)
return;
}
//OpenAL only accepts pitches in the range of 0.5 to 2.0
if(factor > 2.0f)
{
factor = 2.0f;
}
if(factor < 0.5f)
{
factor = 0.5f;
}
alSourcef(m_sound_source,AL_PITCH,factor);
} // reallySetSpeed
//-----------------------------------------------------------------------------
/** Changes the volume of a sound effect.
* \param gain Volume adjustment between 0.0 (mute) and 1.0 (full volume).
*/
void SFXOpenAL::setVolume(float gain)
{
if(m_status==SFX_UNKNOWN || !SFXManager::get()->sfxAllowed()) return;
assert(!isnan(gain)) ;
SFXManager::get()->queue(SFXManager::SFX_VOLUME, this, gain);
} // setVolume
//-----------------------------------------------------------------------------
/** Changes the volume of a sound effect.
* \param gain Volume adjustment between 0.0 (mute) and 1.0 (full volume).
*/
void SFXOpenAL::reallySetVolume(float gain)
{
m_gain = m_default_gain * gain;
if(m_status==SFX_UNKNOWN) return;
if(m_status==SFX_NOT_INITIALISED)
{
init();
if(m_status==SFX_UNKNOWN)
return;
}
alSourcef(m_sound_source, AL_GAIN, m_gain * m_master_gain);
} // reallySetVolume
//-----------------------------------------------------------------------------
/** Schedules setting of the master volume.
* \param gain Gain value.
*/
void SFXOpenAL::setMasterVolume(float gain)
{
// This needs to be called even if sfx are disabled atm, so only exit
// in case that the sfx could not be loaded in the first place.
if(m_status==SFX_UNKNOWN) return;
SFXManager::get()->queue(SFXManager::SFX_MASTER_VOLUME, this, gain);
} // setMasterVolume
//-----------------------------------------------------------------------------
/** Sets the master volume.
* \param gain Master volume.
*/
void SFXOpenAL::reallySetMasterVolumeNow(float gain)
{
m_master_gain = gain;
if(m_status==SFX_UNKNOWN || m_status == SFX_NOT_INITIALISED) return;
alSourcef(m_sound_source, AL_GAIN,
(m_gain < 0.0f ? m_default_gain : m_gain) * m_master_gain);
SFXManager::checkError("setting volume");
} // reallySetMasterVolumeNow
//-----------------------------------------------------------------------------
/** Loops this sound effect.
*/
void SFXOpenAL::setLoop(bool status)
{
// Set the flag (even if sfx are disabled), so that the right settings
// are available on restart.
m_loop = status;
if (m_status == SFX_UNKNOWN || !SFXManager::get()->sfxAllowed()) return;
SFXManager::get()->queue(SFXManager::SFX_LOOP, this, status ? 1.0f : 0.0f);
} // setLoop
//-----------------------------------------------------------------------------
/** Loops this sound effect.
*/
void SFXOpenAL::reallySetLoop(bool status)
{
if(m_status==SFX_NOT_INITIALISED)
{
init();
if(m_status==SFX_UNKNOWN)
return;
}
alSourcei(m_sound_source, AL_LOOPING, status ? AL_TRUE : AL_FALSE);
SFXManager::checkError("looping");
} // reallySetLoop
//-----------------------------------------------------------------------------
/** Queues a stop for this effect to the sound manager.
*/
void SFXOpenAL::stop()
{
if (m_status == SFX_UNKNOWN || !SFXManager::get()->sfxAllowed()) return;
SFXManager::get()->queue(SFXManager::SFX_STOP, this);
} // stop
//-----------------------------------------------------------------------------
/** The sfx manager thread executes a stop for this sfx.
*/
void SFXOpenAL::reallyStopNow()
{
if(m_status==SFX_PLAYING || m_status==SFX_PAUSED)
{
m_status = SFX_STOPPED;
m_loop = false;
alSourcei(m_sound_source, AL_LOOPING, AL_FALSE);
alSourceStop(m_sound_source);
SFXManager::checkError("stoping");
}
} // reallyStopNow
//-----------------------------------------------------------------------------
/** Queues up a pause command for this sfx.
*/
void SFXOpenAL::pause()
{
SFXManager::get()->queue(SFXManager::SFX_PAUSE, this);
} // pause
//-----------------------------------------------------------------------------
/** Pauses a SFX that's currently played. Nothing happens it the effect is
* currently not being played.
*/
void SFXOpenAL::reallyPauseNow()
{
// Need to be tested again here, since this function can be called
// from pauseAll, and we have to make sure to only pause playing sfx.
if (m_status != SFX_PLAYING || !SFXManager::get()->sfxAllowed()) return;
m_status = SFX_PAUSED;
alSourcePause(m_sound_source);
SFXManager::checkError("pausing");
} // reallyPauseNow
//-----------------------------------------------------------------------------
/** Queues up a resume command for this sound effect.
*/
void SFXOpenAL::resume()
{
if (m_status != SFX_PLAYING || !SFXManager::get()->sfxAllowed()) return;
SFXManager::get()->queue(SFXManager::SFX_RESUME, this);
} // resume
//-----------------------------------------------------------------------------
/** Resumes a sound effect.
*/
void SFXOpenAL::reallyResumeNow()
{
if(m_status==SFX_NOT_INITIALISED)
{
init();
if(m_status==SFX_UNKNOWN)
return;
}
if(m_status==SFX_PAUSED)
{
alSourcePlay(m_sound_source);
SFXManager::checkError("resuming");
m_status = SFX_PLAYING;
}
} // reallyResumeNow
//-----------------------------------------------------------------------------
/** This actually queues up the sfx in the sfx manager. It will be started
* from a separate thread later (in this frame).
*/
void SFXOpenAL::play()
{
if (m_status == SFX_UNKNOWN || !SFXManager::get()->sfxAllowed()) return;
if(m_status==SFX_STOPPED)
m_play_time = 0.0f;
// Technically the sfx is only playing after the sfx thread starts it,
// but it is important to set this here since stk might decide to
// delete a sfx if it has finished playing (i.e. is in stopped state)
// - which can happen if the sfx thread had no time to actually start
// it yet.
m_status = SFX_PLAYING;
SFXManager::get()->queue(SFXManager::SFX_PLAY, this);
} // play
//-----------------------------------------------------------------------------
/** Plays this sound effect.
*/
void SFXOpenAL::reallyPlayNow()
{
if (!SFXManager::get()->sfxAllowed()) return;
if (m_status==SFX_NOT_INITIALISED)
{
// lazily create OpenAL source when needed
init();
// creation of OpenAL source failed, giving up
if (m_status==SFX_UNKNOWN) return;
}
alSourcePlay(m_sound_source);
SFXManager::checkError("playing");
} // reallyPlayNow
//-----------------------------------------------------------------------------
/** Sets the position where this sound effects is played.
* \param position Position of the sound effect.
*/
void SFXOpenAL::setPosition(const Vec3 &position)
{
if (m_status == SFX_UNKNOWN || !SFXManager::get()->sfxAllowed()) return;
SFXManager::get()->queue(SFXManager::SFX_POSITION, this, position);
} // setPosition
//-----------------------------------------------------------------------------
/** Sets the position where this sound effects is played.
* \param position Position of the sound effect.
*/
void SFXOpenAL::reallySetPosition(const Vec3 &position)
{
if(m_status==SFX_NOT_INITIALISED)
{
init();
if(m_status==SFX_UNKNOWN)
return;
}
if (!m_positional)
{
// in multiplayer, all sounds are positional, so in this case don't
// bug users with an error message if (note that 0 players is also
// possible, in cutscenes)
if (race_manager->getNumLocalPlayers() < 2)
{
Log::warn("SFX", "Position called on non-positional SFX");
}
return;
}
alSource3f(m_sound_source, AL_POSITION, position.getX(),
position.getY(), -position.getZ());
if (SFXManager::get()->getListenerPos().distance(position)
> m_sound_buffer->getMaxDist())
{
alSourcef(m_sound_source, AL_GAIN, 0);
}
else
{
alSourcef(m_sound_source, AL_GAIN,
(m_gain < 0.0f ? m_default_gain : m_gain) * m_master_gain);
}
SFXManager::checkError("positioning");
} // reallySetPosition
//-----------------------------------------------------------------------------
/** Queues up a delete request for this object. This is necessary to avoid
* a crash if the sfx manager thread might be delayed and access this object
* after it was deleted.
*/
void SFXOpenAL::deleteSFX()
{
SFXManager::get()->queue(SFXManager::SFX_DELETE, this);
} // deleteSFX
//-----------------------------------------------------------------------------
void SFXOpenAL::onSoundEnabledBack()
{
if (m_loop)
{
if (m_status==SFX_NOT_INITIALISED) init();
if (m_status!=SFX_UNKNOWN)
{
alSourcef(m_sound_source, AL_GAIN, 0);
play();
pause();
alSourcef(m_sound_source, AL_GAIN,
(m_gain < 0.0f ? m_default_gain : m_gain) * m_master_gain);
}
}
} // onSoundEnabledBack
//-----------------------------------------------------------------------------
void SFXOpenAL::setRolloff(float rolloff)
{
alSourcef (m_sound_source, AL_ROLLOFF_FACTOR, rolloff);
}
#endif //if HAVE_OGGVORBIS