colobot/src/sound/oalsound/alsound.cpp

691 lines
17 KiB
C++
Raw Normal View History

2012-09-20 18:38:14 +00:00
// * This file is part of the COLOBOT source code
// * Copyright (C) 2001-2008, Daniel ROUX & EPSITEC SA, www.epsitec.ch
// * Copyright (C) 2012 Polish Portal of Colobot (PPC)
// *
// * 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, see http://www.gnu.org/licenses/.
#include "alsound.h"
#define MIN(a, b) (a > b ? b : a)
ALSound::ALSound()
{
mEnabled = false;
m3D = false;
mAudioVolume = 1.0f;
mMusicVolume = 1.0f;
mCurrentMusic = nullptr;
mEye.LoadZero();
mLookat.LoadZero();
2012-09-20 18:38:14 +00:00
}
ALSound::~ALSound()
{
CleanUp();
}
void ALSound::CleanUp()
{
if (mEnabled) {
GetLogger()->Info("Unloading files and closing device...\n");
StopAll();
StopMusic();
for (auto channel : mChannels) {
delete channel.second;
}
2012-09-20 18:38:14 +00:00
if (mCurrentMusic) {
delete mCurrentMusic;
}
for (auto item : mSounds) {
2012-09-20 18:38:14 +00:00
delete item.second;
}
2012-09-20 18:38:14 +00:00
2013-04-13 14:06:35 +00:00
for (auto item : mMusic) {
delete item.second;
}
2012-09-20 18:38:14 +00:00
mEnabled = false;
alcDestroyContext(mContext);
alcCloseDevice(mDevice);
2012-09-20 18:38:14 +00:00
}
}
bool ALSound::Create(bool b3D)
{
CleanUp();
if (mEnabled)
return true;
GetLogger()->Info("Opening audio device...\n");
mDevice = alcOpenDevice(NULL);
if (!mDevice) {
GetLogger()->Error("Could not open audio device!\n");
2012-09-20 18:38:14 +00:00
return false;
}
mContext = alcCreateContext(mDevice, NULL);
if (!mContext) {
GetLogger()->Error("Could not create audio context!\n");
return false;
}
alcMakeContextCurrent(mContext);
alListenerf(AL_GAIN, mAudioVolume);
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
mCurrentMusic = new Channel();
GetLogger()->Info("Done.\n");
2012-09-20 18:38:14 +00:00
mEnabled = true;
return true;
}
void ALSound::SetSound3D(bool bMode)
{
m3D = bMode;
}
bool ALSound::GetSound3D()
2012-09-20 18:38:14 +00:00
{
return m3D;
2012-09-20 18:38:14 +00:00
}
bool ALSound::GetSound3DCap()
2012-09-20 18:38:14 +00:00
{
// TODO stub! need to be implemented
return true;
}
bool ALSound::GetEnable()
2012-09-20 18:38:14 +00:00
{
return mEnabled;
}
void ALSound::SetAudioVolume(int volume)
{
mAudioVolume = static_cast<float>(volume) / MAXVOLUME;
2012-09-20 18:38:14 +00:00
}
int ALSound::GetAudioVolume()
2012-09-20 18:38:14 +00:00
{
if ( !mEnabled )
return 0;
return mAudioVolume * MAXVOLUME;
2012-09-20 18:38:14 +00:00
}
void ALSound::SetMusicVolume(int volume)
{
mMusicVolume = static_cast<float>(volume) / MAXVOLUME;
if (mCurrentMusic) {
mCurrentMusic->SetVolume(mMusicVolume);
}
2012-09-20 18:38:14 +00:00
}
int ALSound::GetMusicVolume()
2012-09-20 18:38:14 +00:00
{
if ( !mEnabled )
return 0.0f;
2012-09-20 18:38:14 +00:00
return mMusicVolume * MAXVOLUME;
2012-09-20 18:38:14 +00:00
}
bool ALSound::Cache(Sound sound, std::string filename)
{
Buffer *buffer = new Buffer();
if (buffer->LoadFromFile(filename, sound)) {
mSounds[sound] = buffer;
return true;
}
return false;
}
2013-04-13 14:06:35 +00:00
bool ALSound::CacheMusic(std::string filename)
{
2013-05-02 14:37:11 +00:00
if(mMusic.find(filename) == mMusic.end()) {
Buffer *buffer = new Buffer();
std::stringstream file;
file << m_soundPath << "/" << filename;
if (buffer->LoadFromFile(file.str(), static_cast<Sound>(-1))) {
mMusic[filename] = buffer;
return true;
}
2013-04-13 14:06:35 +00:00
}
return false;
}
int ALSound::GetPriority(Sound sound)
2012-09-20 18:38:14 +00:00
{
if ( sound == SOUND_FLYh ||
sound == SOUND_FLY ||
sound == SOUND_MOTORw ||
sound == SOUND_MOTORt ||
sound == SOUND_MOTORr ||
sound == SOUND_MOTORs ||
sound == SOUND_SLIDE ||
sound == SOUND_ERROR )
{
return 30;
}
if ( sound == SOUND_CONVERT ||
sound == SOUND_ENERGY ||
sound == SOUND_DERRICK ||
sound == SOUND_STATION ||
sound == SOUND_REPAIR ||
sound == SOUND_RESEARCH ||
sound == SOUND_BURN ||
sound == SOUND_BUILD ||
sound == SOUND_TREMBLE ||
sound == SOUND_NUCLEAR ||
sound == SOUND_EXPLO ||
sound == SOUND_EXPLOl ||
sound == SOUND_EXPLOlp ||
sound == SOUND_EXPLOp ||
sound == SOUND_EXPLOi )
{
return 20;
}
if ( sound == SOUND_BLUP ||
sound == SOUND_INSECTs ||
sound == SOUND_INSECTa ||
sound == SOUND_INSECTb ||
sound == SOUND_INSECTw ||
sound == SOUND_INSECTm ||
sound == SOUND_PSHHH ||
sound == SOUND_EGG )
{
return 0;
}
return 10;
}
bool ALSound::SearchFreeBuffer(Sound sound, int &channel, bool &bAlreadyLoaded)
{
int priority = GetPriority(sound);
2012-09-20 18:38:14 +00:00
// Seeks a channel used which sound is stopped.
for (auto it : mChannels) {
if (it.second->IsPlaying())
continue;
if (it.second->GetSoundType() != sound)
continue;
it.second->SetPriority(priority);
channel = it.first;
bAlreadyLoaded = it.second->IsLoaded();
2012-09-20 18:38:14 +00:00
return true;
}
// just add a new channel if we dont have any
if (mChannels.size() == 0) {
Channel *chn = new Channel();
// check if we channel ready to play music, if not report error
if (chn->IsReady()) {
chn->SetPriority(priority);
mChannels[1] = chn;
channel = 1;
bAlreadyLoaded = false;
return true;
}
delete chn;
2013-05-01 10:12:32 +00:00
GetLogger()->Error("Could not open channel to play sound!\n");
2012-09-20 18:38:14 +00:00
return false;
}
// Seeks a channel completely free.
if (mChannels.size() < 64) {
auto it = mChannels.end();
it--;
int i = (*it).first;
while (++i) {
if (mChannels.find(i) == mChannels.end()) {
Channel *chn = new Channel();
// check if channel is ready to play music, if not destroy it and seek free one
if (chn->IsReady()) {
chn->SetPriority(priority);
mChannels[++i] = chn;
channel = i;
bAlreadyLoaded = false;
return true;
}
delete chn;
2013-05-01 10:12:32 +00:00
GetLogger()->Warn("Could not open additional channel to play sound!\n");
2012-09-20 18:38:14 +00:00
}
}
}
int lowerOrEqual = -1;
for (auto it : mChannels) {
if (it.second->GetPriority() < priority) {
2013-05-01 10:12:32 +00:00
GetLogger()->Debug("Sound channel with lower priority will be reused.\n");
2012-09-20 18:38:14 +00:00
channel = it.first;
return true;
}
if (it.second->GetPriority() <= priority)
lowerOrEqual = it.first;
}
if (lowerOrEqual != -1) {
channel = lowerOrEqual;
2013-05-01 10:12:32 +00:00
GetLogger()->Debug("Sound channel with lower or equal priority will be reused.\n");
2012-09-20 18:38:14 +00:00
return true;
}
GetLogger()->Warn("Could not find free buffer to use.\n");
return false;
}
int ALSound::Play(Sound sound, float amplitude, float frequency, bool bLoop)
{
return Play(sound, mEye, amplitude, frequency, bLoop);
2012-09-20 18:38:14 +00:00
}
int ALSound::Play(Sound sound, Math::Vector pos, float amplitude, float frequency, bool bLoop)
{
if (!mEnabled) {
2012-09-20 18:38:14 +00:00
return -1;
}
2012-09-20 18:38:14 +00:00
if (mSounds.find(sound) == mSounds.end()) {
GetLogger()->Warn("Sound %d was not loaded!\n", sound);
return -1;
}
int channel;
bool bAlreadyLoaded = false;
2012-09-20 18:38:14 +00:00
if (!SearchFreeBuffer(sound, channel, bAlreadyLoaded))
return -1;
if (!bAlreadyLoaded) {
if (!mChannels[channel]->SetBuffer(mSounds[sound])) {
mChannels[channel]->SetBuffer(nullptr);
return -1;
}
2012-09-20 18:38:14 +00:00
}
2013-04-11 23:46:30 +00:00
2012-09-20 18:38:14 +00:00
Position(channel, pos);
if (!m3D) {
ComputeVolumePan2D(channel, pos);
} else {
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetVolumeAtrib(1.0f);
}
2012-09-20 18:38:14 +00:00
// setting initial values
mChannels[channel]->SetStartAmplitude(amplitude);
2012-09-20 18:38:14 +00:00
mChannels[channel]->SetStartFrequency(frequency);
mChannels[channel]->SetChangeFrequency(1.0f);
mChannels[channel]->ResetOper();
mChannels[channel]->SetFrequency(frequency);
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetVolume(powf(amplitude * mChannels[channel]->GetVolumeAtrib(), 0.2f) * mAudioVolume);
mChannels[channel]->SetLoop(bLoop);
2012-09-20 18:38:14 +00:00
mChannels[channel]->Play();
2012-09-20 18:38:14 +00:00
return channel;
}
bool ALSound::FlushEnvelope(int channel)
{
if (mChannels.find(channel) == mChannels.end()) {
return false;
}
mChannels[channel]->ResetOper();
return true;
}
bool ALSound::AddEnvelope(int channel, float amplitude, float frequency, float time, SoundNext oper)
{
if (!mEnabled)
return false;
if (mChannels.find(channel) == mChannels.end()) {
return false;
}
2012-09-20 18:38:14 +00:00
SoundOper op;
op.finalAmplitude = amplitude;
op.finalFrequency = frequency;
op.totalTime = time;
op.nextOper = oper;
op.currentTime = 0.0f;
2012-09-20 18:38:14 +00:00
mChannels[channel]->AddOper(op);
return true;
2012-09-20 18:38:14 +00:00
}
bool ALSound::Position(int channel, Math::Vector pos)
{
if (!mEnabled)
return false;
if (mChannels.find(channel) == mChannels.end()) {
return false;
}
if (m3D) {
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetPan(pos);
} else {
ComputeVolumePan2D(channel, pos);
2013-04-11 23:46:30 +00:00
if (!mChannels[channel]->HasEnvelope()) {
float volume = mChannels[channel]->GetStartAmplitude();
mChannels[channel]->SetVolume(powf(volume * mChannels[channel]->GetVolumeAtrib(), 0.2f) * mAudioVolume);
}
}
2012-09-20 18:38:14 +00:00
return true;
}
bool ALSound::Frequency(int channel, float frequency)
{
if (!mEnabled)
return false;
if (mChannels.find(channel) == mChannels.end()) {
return false;
}
mChannels[channel]->SetFrequency(frequency * mChannels[channel]->GetInitFrequency());
mChannels[channel]->SetChangeFrequency(frequency);
2012-09-20 18:38:14 +00:00
return true;
}
bool ALSound::Stop(int channel)
{
if (!mEnabled)
return false;
if (mChannels.find(channel) == mChannels.end()) {
return false;
}
mChannels[channel]->Stop();
mChannels[channel]->ResetOper();
return true;
}
bool ALSound::StopAll()
{
if (!mEnabled)
return false;
for (auto channel : mChannels) {
channel.second->Stop();
channel.second->ResetOper();
}
return true;
}
bool ALSound::MuteAll(bool bMute)
{
if (!mEnabled)
return false;
for (auto it : mChannels) {
if (it.second->IsPlaying()) {
it.second->Mute(bMute);
}
}
2012-09-20 18:38:14 +00:00
if (bMute) {
mCurrentMusic->SetVolume(0.0f);
} else {
mCurrentMusic->SetVolume(mMusicVolume);
2012-09-20 18:38:14 +00:00
}
return true;
}
2012-09-20 18:38:14 +00:00
void ALSound::FrameMove(float delta)
{
if (!mEnabled)
return;
float progress;
float volume, frequency;
for (auto it : mChannels) {
if (!it.second->IsPlaying()) {
2012-09-20 18:38:14 +00:00
continue;
}
if (it.second->IsMuted()) {
it.second->SetVolume(0.0f);
continue;
}
2012-09-20 18:38:14 +00:00
if (!it.second->HasEnvelope())
continue;
SoundOper &oper = it.second->GetEnvelope();
oper.currentTime += delta;
progress = oper.currentTime / oper.totalTime;
2012-09-20 18:38:14 +00:00
progress = MIN(progress, 1.0f);
2012-09-20 18:38:14 +00:00
// setting volume
volume = progress * (oper.finalAmplitude - it.second->GetStartAmplitude());
2013-04-11 23:46:30 +00:00
volume = volume + it.second->GetStartAmplitude();
it.second->SetVolume(powf(volume * it.second->GetVolumeAtrib(), 0.2f) * mAudioVolume);
// setting frequency
frequency = progress;
frequency *= oper.finalFrequency - it.second->GetStartFrequency();
frequency += it.second->GetStartFrequency();
frequency *= it.second->GetChangeFrequency();
frequency = (frequency * it.second->GetInitFrequency());
it.second->SetFrequency(frequency);
2012-09-20 18:38:14 +00:00
if (oper.totalTime <= oper.currentTime) {
2012-09-20 18:38:14 +00:00
if (oper.nextOper == SOPER_LOOP) {
oper.currentTime = 0.0f;
2012-09-20 18:38:14 +00:00
it.second->Play();
} else {
it.second->SetStartAmplitude(oper.finalAmplitude);
it.second->SetStartFrequency(oper.finalFrequency);
if (oper.nextOper == SOPER_STOP) {
it.second->Stop();
}
it.second->PopEnvelope();
2012-09-20 18:38:14 +00:00
}
}
}
}
void ALSound::SetListener(Math::Vector eye, Math::Vector lookat)
{
mEye = eye;
mLookat = lookat;
if (m3D) {
float orientation[] = {lookat.x, lookat.y, lookat.z, 0.f, 1.f, 0.f};
alListener3f(AL_POSITION, eye.x, eye.y, eye.z);
alListenerfv(AL_ORIENTATION, orientation);
} else {
float orientation[] = {0.0f, 0.0f, 0.0f, 0.f, 1.f, 0.f};
alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f);
alListenerfv(AL_ORIENTATION, orientation);
2013-04-11 23:46:30 +00:00
// recalculate sound position
for (auto it : mChannels) {
if (it.second->IsPlaying()) {
Math::Vector pos = it.second->GetPosition();
ComputeVolumePan2D(it.first, pos);
2013-04-11 23:46:30 +00:00
if (!it.second->HasEnvelope()) {
float volume = it.second->GetStartAmplitude();
it.second->SetVolume(powf(volume * it.second->GetVolumeAtrib(), 0.2f) * mAudioVolume);
}
}
}
}
2012-09-20 18:38:14 +00:00
}
bool ALSound::PlayMusic(int rank, bool bRepeat)
{
std::stringstream filename;
filename << "music" << std::setfill('0') << std::setw(3) << rank << ".ogg";
return PlayMusic(filename.str(), bRepeat);
}
bool ALSound::PlayMusic(std::string filename, bool bRepeat)
2012-09-20 18:38:14 +00:00
{
if (!mEnabled) {
return false;
}
std::stringstream file;
file << m_soundPath << "/" << filename;
2013-04-13 14:06:35 +00:00
// check if we have music in cache
if (mMusic.find(filename) == mMusic.end()) {
GetLogger()->Warn("Music %s was not cached!\n", filename.c_str());
if (!boost::filesystem::exists(file.str())) {
GetLogger()->Warn("Requested music %s was not found.\n", filename.c_str());
return false;
}
Buffer *buffer = new Buffer();
buffer->LoadFromFile(file.str(), static_cast<Sound>(-1));
mCurrentMusic->SetBuffer(buffer);
} else {
GetLogger()->Debug("Music loaded from cache\n");
mCurrentMusic->SetBuffer(mMusic[filename]);
}
mCurrentMusic->SetVolume(mMusicVolume);
mCurrentMusic->SetLoop(bRepeat);
mCurrentMusic->Play();
2012-09-20 18:38:14 +00:00
return true;
}
bool ALSound::RestartMusic()
{
if (!mEnabled || !mCurrentMusic) {
return false;
}
mCurrentMusic->Stop();
mCurrentMusic->Play();
2012-09-20 18:38:14 +00:00
return true;
}
void ALSound::StopMusic()
{
if (!mEnabled || !mCurrentMusic) {
return;
}
2012-09-20 18:38:14 +00:00
SuspendMusic();
}
bool ALSound::IsPlayingMusic()
{
if (!mEnabled || !mCurrentMusic) {
return false;
}
return mCurrentMusic->IsPlaying();
2012-09-20 18:38:14 +00:00
}
void ALSound::SuspendMusic()
{
if (!mEnabled || !mCurrentMusic) {
return;
}
mCurrentMusic->Stop();
2012-09-20 18:38:14 +00:00
}
void ALSound::ComputeVolumePan2D(int channel, Math::Vector &pos)
{
float dist, a, g;
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetPosition(pos);
if (VectorsEqual(pos, mEye)) {
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetVolumeAtrib(1.0f); // maximum volume
mChannels[channel]->SetPan(Math::Vector()); // at the center
return;
}
dist = Distance(pos, mEye);
if ( dist >= 110.0f ) { // very far?
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetVolumeAtrib(0.0f); // silence
mChannels[channel]->SetPan(Math::Vector()); // at the center
return;
} else if ( dist <= 10.0f ) { // very close?
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetVolumeAtrib(1.0f); // maximum volume
mChannels[channel]->SetPan(Math::Vector()); // at the center
return;
}
2013-04-11 23:46:30 +00:00
mChannels[channel]->SetVolumeAtrib(1.0f - ((dist - 10.0f) / 100.0f));
Math::Vector one = Math::Vector(1.0f, 0.0f, 0.0f);
float angle_a = Angle(Math::Vector(mLookat.x - mEye.x, mLookat.z - mEye.z, 0.0f), one);
float angle_g = Angle(Math::Vector(pos.x - mEye.x, pos.z - mEye.z, 0.0f), one);
a = fmodf(angle_a, Math::PI * 2.0f);
g = fmodf(angle_g, Math::PI * 2.0f);
if ( a < 0.0f ) {
a += Math::PI * 2.0f;
}
if ( g < 0.0f ) {
g += Math::PI * 2.0f;
}
if ( a < g ) {
if (a + Math::PI * 2.0f - g < g - a ) {
a += Math::PI * 2.0f;
}
} else {
if ( g + Math::PI * 2.0f - a < a - g ) {
g += Math::PI * 2.0f;
}
}
mChannels[channel]->SetPan( Math::Vector(0.0f, 0.0f, sinf(g - a)) );
}