colobot/src/sound/oalsound/alsound.cpp

725 lines
17 KiB
C++

/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2018, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.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>
CALSound::CALSound()
: m_enabled(false),
m_audioVolume(1.0f),
m_musicVolume(1.0f),
m_channelsLimit(2048),
m_device{},
m_context{},
m_thread("Music loading thread")
{
}
CALSound::~CALSound()
{
CleanUp();
}
void CALSound::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 CALSound::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 CALSound::GetEnable()
{
return m_enabled;
}
void CALSound::SetAudioVolume(int volume)
{
m_audioVolume = static_cast<float>(volume) / MAXVOLUME;
}
int CALSound::GetAudioVolume()
{
if ( !m_enabled )
return 0;
return m_audioVolume * MAXVOLUME;
}
void CALSound::SetMusicVolume(int volume)
{
m_musicVolume = static_cast<float>(volume) / MAXVOLUME;
if (m_currentMusic)
{
m_currentMusic->SetVolume(m_musicVolume);
}
}
int CALSound::GetMusicVolume()
{
if ( !m_enabled )
return 0.0f;
return m_musicVolume * MAXVOLUME;
}
bool CALSound::Cache(SoundType sound, const std::string &filename)
{
auto buffer = MakeUnique<CBuffer>();
if (buffer->LoadFromFile(filename, sound))
{
m_sounds[sound] = std::move(buffer);
return true;
}
return false;
}
void CALSound::CacheMusic(const std::string &filename)
{
m_thread.Start([this, filename]()
{
if (m_music.find(filename) == m_music.end())
{
auto buffer = MakeUnique<CBuffer>();
if (buffer->LoadFromFile(filename, static_cast<SoundType>(-1)))
{
m_music[filename] = std::move(buffer);
}
}
});
}
bool CALSound::IsCached(SoundType sound)
{
return m_sounds.find(sound) != m_sounds.end();
}
bool CALSound::IsCachedMusic(const std::string &filename)
{
return m_music.find(filename) != m_music.end();
}
int CALSound::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 CALSound::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<CChannel>();
// 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<CChannel>();
// 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 CALSound::Play(SoundType sound, float amplitude, float frequency, bool loop)
{
return Play(sound, Math::Vector{}, true, amplitude, frequency, loop);
}
int CALSound::Play(SoundType sound, const Math::Vector &pos, float amplitude, float frequency, bool loop)
{
return Play(sound, pos, false, amplitude, frequency, loop);
}
int CALSound::Play(SoundType sound, const Math::Vector &pos, bool relativeToListener, 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;
}
}
CChannel* chn = m_channels[channel].get();
chn->SetPosition(pos, relativeToListener);
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);
chn->Mute(false);
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 CALSound::FlushEnvelope(int channel)
{
if (!CheckChannel(channel))
{
return false;
}
m_channels[channel]->ResetOper();
return true;
}
bool CALSound::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 CALSound::Position(int channel, const Math::Vector &pos)
{
if (!CheckChannel(channel))
{
return false;
}
m_channels[channel]->SetPosition(pos);
return true;
}
bool CALSound::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 CALSound::Stop(int channel)
{
if (!CheckChannel(channel))
{
return false;
}
m_channels[channel]->Stop();
m_channels[channel]->ResetOper();
return true;
}
bool CALSound::StopAll()
{
if (!m_enabled)
{
return false;
}
for (auto& channel : m_channels)
{
channel.second->Stop();
channel.second->ResetOper();
}
return true;
}
bool CALSound::MuteAll(bool mute)
{
if (!m_enabled)
{
return false;
}
for (auto& it : m_channels)
{
if (it.second->IsPlaying())
{
it.second->Mute(mute);
}
}
return true;
}
void CALSound::FrameMove(float rTime)
{
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 += rTime;
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 += rTime;
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 += rTime;
m_previousMusic.music->SetVolume(((m_previousMusic.fadeTime-m_previousMusic.currentTime) / m_previousMusic.fadeTime) * m_musicVolume);
}
}
}
void CALSound::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);
}
void CALSound::PlayMusic(const std::string &filename, bool repeat, float fadeTime)
{
if (!m_enabled)
{
return;
}
m_thread.Start([this, filename, repeat, fadeTime]()
{
CBuffer* buffer = nullptr;
// check if we have music in cache
if (m_music.find(filename) == m_music.end())
{
GetLogger()->Debug("Music %s was not cached!\n", filename.c_str());
auto newBuffer = MakeUnique<CBuffer>();
buffer = newBuffer.get();
if (!newBuffer->LoadFromFile(filename, static_cast<SoundType>(-1)))
{
return;
}
m_music[filename] = std::move(newBuffer);
}
else
{
GetLogger()->Debug("Music loaded from cache\n");
buffer = m_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<CChannel>();
m_currentMusic->SetBuffer(buffer);
m_currentMusic->SetVolume(m_musicVolume);
m_currentMusic->SetLoop(repeat);
m_currentMusic->Play();
});
}
void CALSound::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;
}
}
PlayMusic(filename, repeat);
}
void CALSound::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;
}
}
void CALSound::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 CALSound::IsPlayingMusic()
{
if (!m_enabled || m_currentMusic == nullptr)
{
return false;
}
return m_currentMusic->IsPlaying();
}
bool CALSound::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;
}