colobot/src/sound/oalsound/alsound.cpp

805 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2014, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsiteс.ch; http://colobot.info; http://github.com/colobot
*
* 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://gnu.org/licenses
*/
#include "sound/oalsound/alsound.h"
#include <algorithm>
#include <iomanip>
#include <boost/filesystem.hpp>
ALSound::ALSound()
{
m_enabled = false;
m_audioVolume = 1.0f;
m_musicVolume = 1.0f;
m_currentMusic = nullptr;
m_eye.LoadZero();
m_lookat.LoadZero();
m_previousMusic.fadeTime = 0.0f;
m_previousMusic.music = nullptr;
m_channels_limit = 2048;
}
ALSound::~ALSound()
{
CleanUp();
}
void ALSound::CleanUp()
{
if (m_enabled)
{
GetLogger()->Info("Unloading files and closing device...\n");
StopAll();
StopMusic();
for (auto channel : m_channels)
{
delete channel.second;
}
delete m_currentMusic;
for (auto item : m_oldMusic)
{
delete item.music;
}
delete m_previousMusic.music;
for (auto item : m_sounds)
{
delete item.second;
}
for (auto item : m_music)
{
delete item.second;
}
m_enabled = false;
alcDestroyContext(m_context);
alcCloseDevice(m_device);
}
}
bool ALSound::Create()
{
CleanUp();
if (m_enabled)
return true;
GetLogger()->Info("Opening audio device...\n");
m_device = alcOpenDevice(NULL);
if (!m_device)
{
GetLogger()->Error("Could not open audio device!\n");
return false;
}
m_context = alcCreateContext(m_device, NULL);
if (!m_context)
{
GetLogger()->Error("Could not create audio context!\n");
return false;
}
alcMakeContextCurrent(m_context);
alListenerf(AL_GAIN, m_audioVolume);
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
GetLogger()->Info("Done.\n");
m_enabled = true;
return true;
}
bool ALSound::GetEnable()
{
return m_enabled;
}
void ALSound::SetAudioVolume(int volume)
{
m_audioVolume = static_cast<float>(volume) / MAXVOLUME;
}
int ALSound::GetAudioVolume()
{
if ( !m_enabled )
return 0;
return m_audioVolume * MAXVOLUME;
}
void ALSound::SetMusicVolume(int volume)
{
m_musicVolume = static_cast<float>(volume) / MAXVOLUME;
if (m_currentMusic)
{
m_currentMusic->SetVolume(m_musicVolume);
}
}
int ALSound::GetMusicVolume()
{
if ( !m_enabled )
return 0.0f;
return m_musicVolume * MAXVOLUME;
}
bool ALSound::Cache(Sound sound, const std::string &filename)
{
Buffer *buffer = new Buffer();
if (buffer->LoadFromFile(filename, sound))
{
m_sounds[sound] = buffer;
return true;
}
return false;
}
bool ALSound::CacheMusic(const std::string &filename)
{
if (m_music.find("music/"+filename) == m_music.end())
{
Buffer *buffer = new Buffer();
if (buffer->LoadFromFile("music/"+filename, static_cast<Sound>(-1)))
{
m_music["music/"+filename] = buffer;
return true;
}
}
return false;
}
int ALSound::GetPriority(Sound sound)
{
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);
// Seeks a channel used which sound is stopped.
for (auto it : m_channels)
{
if (it.second->IsPlaying())
{
continue;
}
if (it.second->GetSoundType() != sound)
{
continue;
}
it.second->SetPriority(priority);
it.second->Reset();
channel = it.first;
bAlreadyLoaded = it.second->IsLoaded();
return true;
}
// just add a new channel if we dont have any
if (m_channels.size() == 0)
{
Channel *chn = new Channel();
// check if we channel ready to play music, if not report error
if (chn->IsReady())
{
chn->SetPriority(priority);
chn->Reset();
m_channels[1] = chn;
channel = 1;
bAlreadyLoaded = false;
return true;
}
delete chn;
GetLogger()->Error("Could not open channel to play sound!\n");
return false;
}
// Assigns new channel within limit
if (m_channels.size() < m_channels_limit)
{
auto it = m_channels.end();
it--;
int i = (*it).first;
while (++i)
{
if (m_channels.find(i) == m_channels.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);
chn->Reset();
m_channels[++i] = chn;
channel = i;
bAlreadyLoaded = false;
return true;
}
delete chn;
GetLogger()->Debug("Could not open additional channel to play sound!\n");
break;
}
}
}
int lowerOrEqual = -1;
for (auto it : m_channels)
{
if (it.second->GetPriority() < priority)
{
GetLogger()->Debug("Sound channel with lower priority will be reused.\n");
channel = it.first;
it.second->Reset();
return true;
}
if (it.second->GetPriority() <= priority)
lowerOrEqual = it.first;
}
if (lowerOrEqual != -1)
{
channel = lowerOrEqual;
m_channels[channel]->Reset();
GetLogger()->Debug("Sound channel with lower or equal priority will be reused.\n");
return true;
}
GetLogger()->Debug("Could not find free buffer to use.\n");
return false;
}
int ALSound::Play(Sound sound, float amplitude, float frequency, bool bLoop)
{
return Play(sound, m_eye, amplitude, frequency, bLoop);
}
int ALSound::Play(Sound sound, const Math::Vector &pos, float amplitude, float frequency, bool bLoop)
{
if (!m_enabled)
{
return -1;
}
if (m_sounds.find(sound) == m_sounds.end())
{
GetLogger()->Debug("Sound %d was not loaded!\n", sound);
return -1;
}
int channel;
bool bAlreadyLoaded = false;
if (!SearchFreeBuffer(sound, channel, bAlreadyLoaded))
{
return -1;
}
if (!bAlreadyLoaded)
{
if (!m_channels[channel]->SetBuffer(m_sounds[sound]))
{
m_channels[channel]->SetBuffer(nullptr);
return -1;
}
}
m_channels[channel]->SetPosition(pos);
m_channels[channel]->SetVolumeAtrib(1.0f);
// setting initial values
m_channels[channel]->SetStartAmplitude(amplitude);
m_channels[channel]->SetStartFrequency(frequency);
m_channels[channel]->SetChangeFrequency(1.0f);
m_channels[channel]->ResetOper();
m_channels[channel]->SetFrequency(frequency);
m_channels[channel]->SetVolume(powf(amplitude * m_channels[channel]->GetVolumeAtrib(), 0.2f) * m_audioVolume);
m_channels[channel]->SetLoop(bLoop);
if (!m_channels[channel]->Play())
{
m_channels_limit = m_channels.size() - 1;
GetLogger()->Debug("Changing channel limit to %u.\n", m_channels_limit);
auto it = m_channels.find(channel);
Channel *ch = it->second;
m_channels.erase(it);
delete ch;
return -1;
}
return channel | ((m_channels[channel]->GetId() & 0xffff) << 16);
}
bool ALSound::FlushEnvelope(int channel)
{
if (!CheckChannel(channel))
{
return false;
}
m_channels[channel]->ResetOper();
return true;
}
bool ALSound::AddEnvelope(int channel, float amplitude, float frequency, float time, SoundNext oper)
{
if (!CheckChannel(channel))
{
return false;
}
SoundOper op;
op.finalAmplitude = amplitude;
op.finalFrequency = frequency;
op.totalTime = time;
op.nextOper = oper;
op.currentTime = 0.0f;
m_channels[channel]->AddOper(op);
return true;
}
bool ALSound::Position(int channel, const Math::Vector &pos)
{
if (!CheckChannel(channel))
{
return false;
}
m_channels[channel]->SetPosition(pos);
return true;
}
bool ALSound::Frequency(int channel, float frequency)
{
if (!CheckChannel(channel))
{
return false;
}
m_channels[channel]->SetFrequency(frequency * m_channels[channel]->GetInitFrequency());
m_channels[channel]->SetChangeFrequency(frequency);
return true;
}
bool ALSound::Stop(int channel)
{
if (!CheckChannel(channel))
{
return false;
}
m_channels[channel]->Stop();
m_channels[channel]->ResetOper();
return true;
}
bool ALSound::StopAll()
{
if (!m_enabled)
{
return false;
}
for (auto channel : m_channels)
{
channel.second->Stop();
channel.second->ResetOper();
}
return true;
}
bool ALSound::MuteAll(bool bMute)
{
if (!m_enabled)
{
return false;
}
for (auto it : m_channels)
{
if (it.second->IsPlaying())
{
it.second->Mute(bMute);
}
}
return true;
}
void ALSound::FrameMove(float delta)
{
if (!m_enabled)
{
return;
}
float progress;
float volume, frequency;
for (auto it : m_channels)
{
if (!it.second->IsPlaying())
{
continue;
}
if (it.second->IsMuted())
{
it.second->SetVolume(0.0f);
continue;
}
if (!it.second->HasEnvelope())
continue;
SoundOper &oper = it.second->GetEnvelope();
oper.currentTime += delta;
progress = oper.currentTime / oper.totalTime;
progress = std::min(progress, 1.0f);
// setting volume
volume = progress * (oper.finalAmplitude - it.second->GetStartAmplitude());
volume = volume + it.second->GetStartAmplitude();
it.second->SetVolume(powf(volume * it.second->GetVolumeAtrib(), 0.2f) * m_audioVolume);
// 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);
if (oper.totalTime <= oper.currentTime)
{
if (oper.nextOper == SOPER_LOOP)
{
oper.currentTime = 0.0f;
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();
}
}
}
std::list<OldMusic> toRemove;
for (auto& it : m_oldMusic)
{
if (it.currentTime >= it.fadeTime)
{
delete it.music;
toRemove.push_back(it);
}
else
{
it.currentTime += delta;
it.music->SetVolume(((it.fadeTime-it.currentTime) / it.fadeTime) * m_musicVolume);
}
}
if (m_previousMusic.fadeTime > 0.0f) {
if (m_previousMusic.currentTime >= m_previousMusic.fadeTime)
{
m_previousMusic.music->Pause();
}
else
{
m_previousMusic.currentTime += delta;
m_previousMusic.music->SetVolume(((m_previousMusic.fadeTime-m_previousMusic.currentTime) / m_previousMusic.fadeTime) * m_musicVolume);
}
}
for (auto it : toRemove)
m_oldMusic.remove(it);
}
void ALSound::SetListener(const Math::Vector &eye, const Math::Vector &lookat)
{
m_eye = eye;
m_lookat = lookat;
Math::Vector forward = lookat - eye;
forward.Normalize();
float orientation[] = {forward.x, forward.y, forward.z, 0.f, -1.0f, 0.0f};
alListener3f(AL_POSITION, eye.x, eye.y, eye.z);
alListenerfv(AL_ORIENTATION, orientation);
}
bool ALSound::PlayMusic(int rank, bool bRepeat, float fadeTime)
{
std::stringstream filename;
filename << "music" << std::setfill('0') << std::setw(3) << rank << ".ogg";
return PlayMusic(filename.str(), bRepeat, fadeTime);
}
bool operator<(const OldMusic & l, const OldMusic & r)
{
return l.currentTime < r.currentTime;
}
bool operator==(const OldMusic & l, const OldMusic & r)
{
return l.currentTime == r.currentTime;
}
bool ALSound::PlayMusic(const std::string &filename, bool bRepeat, float fadeTime)
{
if (!m_enabled)
{
return false;
}
Buffer *buffer;
// check if we have music in cache
if (m_music.find("music/"+filename) == m_music.end())
{
GetLogger()->Debug("Music %s was not cached!\n", filename.c_str());
/* TODO: if (!boost::filesystem::exists("music/"+filename))
{
GetLogger()->Debug("Requested music %s was not found.\n", filename.c_str());
return false;
} */
buffer = new Buffer();
if (!buffer->LoadFromFile("music/"+filename, static_cast<Sound>(-1)))
{
return false;
}
m_music["music/"+filename] = buffer;
}
else
{
GetLogger()->Debug("Music loaded from cache\n");
buffer = m_music["music/"+filename];
}
if (m_currentMusic)
{
OldMusic old;
old.music = m_currentMusic;
old.fadeTime = fadeTime;
old.currentTime = 0.0f;
m_oldMusic.push_back(old);
}
m_currentMusic = new Channel();
m_currentMusic->SetBuffer(buffer);
m_currentMusic->SetVolume(m_musicVolume);
m_currentMusic->SetLoop(bRepeat);
m_currentMusic->Play();
return true;
}
bool ALSound::PlayPauseMusic(const std::string &filename, bool repeat)
{
if (m_previousMusic.fadeTime > 0.0f)
{
if(m_currentMusic)
{
OldMusic old;
old.music = m_currentMusic;
old.fadeTime = 2.0f;
old.currentTime = 0.0f;
m_oldMusic.push_back(old);
m_currentMusic = nullptr;
}
}
else
{
if (m_currentMusic)
{
m_previousMusic.music = m_currentMusic;
m_previousMusic.fadeTime = 2.0f;
m_previousMusic.currentTime = 0.0f;
m_currentMusic = nullptr;
}
}
return PlayMusic(filename, repeat);
}
void ALSound::StopPauseMusic()
{
if (m_previousMusic.fadeTime > 0.0f)
{
StopMusic();
m_currentMusic = m_previousMusic.music;
m_previousMusic.music = nullptr;
if(m_currentMusic != nullptr)
{
m_currentMusic->SetVolume(m_musicVolume);
if(m_previousMusic.currentTime >= m_previousMusic.fadeTime)
{
m_currentMusic->Play();
}
}
m_previousMusic.fadeTime = 0.0f;
}
}
bool ALSound::RestartMusic()
{
if (!m_enabled || !m_currentMusic)
{
return false;
}
m_currentMusic->Stop();
m_currentMusic->Play();
return true;
}
void ALSound::StopMusic(float fadeTime)
{
if (!m_enabled || !m_currentMusic)
{
return;
}
OldMusic old;
old.music = m_currentMusic;
old.fadeTime = fadeTime;
old.currentTime = 0.0f;
m_oldMusic.push_back(old);
m_currentMusic = nullptr;
}
bool ALSound::IsPlayingMusic()
{
if (!m_enabled || !m_currentMusic)
{
return false;
}
return m_currentMusic->IsPlaying();
}
void ALSound::SuspendMusic()
{
if (!m_enabled || !m_currentMusic)
{
return;
}
m_currentMusic->Stop();
}
bool ALSound::CheckChannel(int &channel)
{
int id = (channel >> 16) & 0xffff;
channel &= 0xffff;
if (!m_enabled)
{
return false;
}
if (m_channels.find(channel) == m_channels.end())
{
return false;
}
if (m_audioVolume == 0)
{
return false;
}
if (m_channels[channel]->GetId() != id)
{
return false;
}
return true;
}