/* * 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 #include 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(volume) / MAXVOLUME; } int CALSound::GetAudioVolume() { if ( !m_enabled ) return 0; return m_audioVolume * MAXVOLUME; } void CALSound::SetMusicVolume(int volume) { m_musicVolume = static_cast(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(); 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(); if (buffer->LoadFromFile(filename, static_cast(-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(); // 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(); // 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(); buffer = newBuffer.get(); if (!newBuffer->LoadFromFile(filename, static_cast(-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(); 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; }