colobot/src/sound/oalsound/alsound.cpp

779 lines
17 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 "common/make_unique.h"
#include <algorithm>
#include <iomanip>
#include <boost/filesystem.hpp>
ALSound::ALSound()
: m_enabled(false),
m_audioVolume(1.0f),
m_musicVolume(1.0f),
m_channelsLimit(2048),
m_device{},
m_context{}
{
}
ALSound::~ALSound()
{
CleanUp();
}
void ALSound::CleanUp()
{
if (m_enabled)
{
GetLogger()->Info("Unloading files and closing device...\n");
StopAll();
StopMusic();
m_channels.clear();
m_currentMusic.reset();
m_oldMusic.clear();
m_previousMusic.music.reset();
m_sounds.clear();
m_music.clear();
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(nullptr);
if (!m_device)
{
GetLogger()->Error("Could not open audio device!\n");
return false;
}
m_context = alcCreateContext(m_device, nullptr);
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(SoundType sound, const std::string &filename)
{
auto buffer = MakeUnique<Buffer>();
if (buffer->LoadFromFile(filename, sound))
{
m_sounds[sound] = std::move(buffer);
return true;
}
return false;
}
bool ALSound::CacheMusic(const std::string &filename)
{
if (m_music.find("music/"+filename) == m_music.end())
{
auto buffer = MakeUnique<Buffer>();
if (buffer->LoadFromFile("music/"+filename, static_cast<SoundType>(-1)))
{
m_music["music/"+filename] = std::move(buffer);
return true;
}
}
return false;
}
bool ALSound::IsCached(SoundType sound)
{
return m_sounds.find(sound) != m_sounds.end();
}
bool ALSound::IsCachedMusic(const std::string &filename)
{
return m_music.find("music/"+filename) != m_music.end();
}
int ALSound::GetPriority(SoundType 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(SoundType sound, int &channel, bool &alreadyLoaded)
{
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;
alreadyLoaded = it.second->IsLoaded();
return true;
}
// just add a new channel if we dont have any
if (m_channels.size() == 0)
{
auto chn = MakeUnique<Channel>();
// check if we channel ready to play music, if not report error
if (chn->IsReady())
{
chn->SetPriority(priority);
chn->Reset();
channel = 1;
m_channels[channel] = std::move(chn);
alreadyLoaded = false;
return true;
}
GetLogger()->Error("Could not open channel to play sound!\n");
return false;
}
// Assigns new channel within limit
if (m_channels.size() < m_channelsLimit)
{
auto it = m_channels.end();
it--;
int i = (*it).first;
while (++i)
{
if (m_channels.find(i) == m_channels.end())
{
auto chn = MakeUnique<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] = std::move(chn);
channel = i;
alreadyLoaded = false;
return true;
}
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(SoundType sound, float amplitude, float frequency, bool loop)
{
return Play(sound, m_eye, amplitude, frequency, loop);
}
int ALSound::Play(SoundType sound, const Math::Vector &pos, float amplitude, float frequency, bool loop)
{
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 alreadyLoaded = false;
if (!SearchFreeBuffer(sound, channel, alreadyLoaded))
{
return -1;
}
if (!alreadyLoaded)
{
if (!m_channels[channel]->SetBuffer(m_sounds[sound].get()))
{
m_channels[channel]->SetBuffer(nullptr);
return -1;
}
}
Channel* chn = m_channels[channel].get();
chn->SetPosition(pos);
chn->SetVolumeAtrib(1.0f);
// setting initial values
chn->SetStartAmplitude(amplitude);
chn->SetStartFrequency(frequency);
chn->SetChangeFrequency(1.0f);
chn->ResetOper();
chn->SetFrequency(frequency);
chn->SetVolume(powf(amplitude * chn->GetVolumeAtrib(), 0.2f) * m_audioVolume);
chn->SetLoop(loop);
if (!chn->Play())
{
m_channelsLimit = m_channels.size() - 1;
GetLogger()->Debug("Changing channel limit to %u.\n", m_channelsLimit);
m_channels.erase(channel);
return -1;
}
return channel | ((chn->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 mute)
{
if (!m_enabled)
{
return false;
}
for (auto& it : m_channels)
{
if (it.second->IsPlaying())
{
it.second->Mute(mute);
}
}
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();
}
}
}
auto it = m_oldMusic.begin();
while (it != m_oldMusic.end())
{
if (it->currentTime >= it->fadeTime)
{
it = m_oldMusic.erase(it);
}
else
{
it->currentTime += delta;
it->music->SetVolume(((it->fadeTime-it->currentTime) / it->fadeTime) * m_musicVolume);
++it;
}
}
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);
}
}
}
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 repeat, float fadeTime)
{
std::stringstream filename;
filename << "music" << std::setfill('0') << std::setw(3) << rank << ".ogg";
return PlayMusic(filename.str(), repeat, fadeTime);
}
bool ALSound::PlayMusic(const std::string &filename, bool repeat, float fadeTime)
{
if (!m_enabled)
{
return false;
}
Buffer *buffer = nullptr;
// 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;
} */
auto newBuffer = MakeUnique<Buffer>();
buffer = newBuffer.get();
if (!newBuffer->LoadFromFile("music/"+filename, static_cast<SoundType>(-1)))
{
return false;
}
m_music["music/"+filename] = std::move(newBuffer);
}
else
{
GetLogger()->Debug("Music loaded from cache\n");
buffer = m_music["music/"+filename].get();
}
if (m_currentMusic)
{
OldMusic old;
old.music = std::move(m_currentMusic);
old.fadeTime = fadeTime;
old.currentTime = 0.0f;
m_oldMusic.push_back(std::move(old));
}
m_currentMusic = MakeUnique<Channel>();
m_currentMusic->SetBuffer(buffer);
m_currentMusic->SetVolume(m_musicVolume);
m_currentMusic->SetLoop(repeat);
m_currentMusic->Play();
return true;
}
bool ALSound::PlayPauseMusic(const std::string &filename, bool repeat)
{
if (m_previousMusic.fadeTime > 0.0f)
{
if (m_currentMusic != nullptr)
{
OldMusic old;
old.music = std::move(m_currentMusic);
old.fadeTime = 2.0f;
old.currentTime = 0.0f;
m_oldMusic.push_back(std::move(old));
}
}
else
{
if (m_currentMusic != nullptr)
{
m_previousMusic.music = std::move(m_currentMusic);
m_previousMusic.fadeTime = 2.0f;
m_previousMusic.currentTime = 0.0f;
}
}
return PlayMusic(filename, repeat);
}
void ALSound::StopPauseMusic()
{
if (m_previousMusic.fadeTime > 0.0f)
{
StopMusic();
m_currentMusic = std::move(m_previousMusic.music);
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 == nullptr)
{
return false;
}
m_currentMusic->Stop();
m_currentMusic->Play();
return true;
}
void ALSound::StopMusic(float fadeTime)
{
if (!m_enabled || m_currentMusic == nullptr)
{
return;
}
OldMusic old;
old.music = std::move(m_currentMusic);
old.fadeTime = fadeTime;
old.currentTime = 0.0f;
m_oldMusic.push_back(std::move(old));
}
bool ALSound::IsPlayingMusic()
{
if (!m_enabled || m_currentMusic == nullptr)
{
return false;
}
return m_currentMusic->IsPlaying();
}
void ALSound::SuspendMusic()
{
if (!m_enabled || m_currentMusic == nullptr)
{
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;
}