diff --git a/src/audio/ALAudio.cpp b/src/audio/ALAudio.cpp index fe955202..bb57ebd5 100644 --- a/src/audio/ALAudio.cpp +++ b/src/audio/ALAudio.cpp @@ -61,6 +61,14 @@ void ALSpeaker::setPitch(float pitch) { AL_CHECK(alSourcef(source, AL_PITCH, pitch)); } +bool ALSpeaker::isLoop() const { + return AL::getSourcei(source, AL_LOOPING) == AL_TRUE; +} + +void ALSpeaker::setLoop(bool loop) { + AL_CHECK(alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE)); +} + void ALSpeaker::play() { AL_CHECK(alSourcePlay(source)); } @@ -240,5 +248,4 @@ void ALAudio::setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, } void ALAudio::update(double delta) { - } diff --git a/src/audio/ALAudio.h b/src/audio/ALAudio.h index 164d6671..fca96659 100644 --- a/src/audio/ALAudio.h +++ b/src/audio/ALAudio.h @@ -58,6 +58,9 @@ namespace audio { float getPitch() const override; void setPitch(float pitch) override; + bool isLoop() const override; + void setLoop(bool loop) override; + void play() override; void pause() override; void stop() override; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index aa8d1db6..cea25063 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -6,35 +6,100 @@ #include "NoAudio.h" namespace audio { - extern Backend* backend; + static speakerid_t nextId = 1; + static Backend* backend; + static std::unordered_map> speakers; } -audio::Backend* audio::backend = nullptr; +using namespace audio; void audio::initialize(bool enabled) { if (enabled) { - audio::backend = ALAudio::create(); + backend = ALAudio::create(); } - if (audio::backend == nullptr) { + if (backend == nullptr) { std::cerr << "could not to initialize audio" << std::endl; - audio::backend = NoAudio::create(); + backend = NoAudio::create(); } } +Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { + return backend->createSound(pcm, keepPCM); +} + void audio::setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up ) { - audio::backend->setListener(position, velocity, lookAt, up); + backend->setListener(position, velocity, lookAt, up); +} + +void remove_lower_priority_speaker(int priority) { + for (auto it = speakers.begin(); it != speakers.end();) { + if (it->second->getPriority() < priority && it->second->isPaused()) { + speakers.erase(it); + return; + } + it++; + } + for (auto it = speakers.begin(); it != speakers.end();) { + if (it->second->getPriority() < priority) { + speakers.erase(it); + return; + } + it++; + } +} + +speakerid_t audio::play( + Sound* sound, + glm::vec3 position, + float volume, + float pitch, + bool loop, + int priority +) { + Speaker* speaker = sound->newInstance(priority); + if (speaker == nullptr) { + remove_lower_priority_speaker(priority); + speaker = sound->newInstance(priority); + } + if (speaker == nullptr) { + return 0; + } + speakerid_t id = nextId++; + speakers[id].reset(speaker); + speaker->setPosition(position); + speaker->setVolume(volume); + speaker->setPitch(pitch); + speaker->setLoop(loop); + speaker->play(); + return id; +} + +Speaker* audio::get(speakerid_t id) { + auto found = speakers.find(id); + if (found == speakers.end()) { + return nullptr; + } + return found->second.get(); } void audio::update(double delta) { - audio::backend->update(delta); + backend->update(delta); + + for (auto it = speakers.begin(); it != speakers.end();) { + if (it->second->isStopped()) { + speakers.erase(it); + } else { + it++; + } + } } void audio::close() { - delete audio::backend; - audio::backend = nullptr; + delete backend; + backend = nullptr; } diff --git a/src/audio/audio.h b/src/audio/audio.h index 7b16eec1..9ebea79a 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -11,8 +11,13 @@ namespace audio { /// @brief duration unit is second using duration_t = float; + constexpr inline int PRIORITY_LOW = 0; + constexpr inline int PRIORITY_NORMAL = 5; + constexpr inline int PRIORITY_HIGH = 10; + class Speaker; + /// @brief Audio speaker states enum class State { playing, paused, @@ -84,6 +89,14 @@ namespace audio { /// @param pitch new pitch multiplier (must be positive) virtual void setPitch(float pitch) = 0; + /// @brief Check if speaker audio is in loop + /// @return true if audio is in loop + virtual bool isLoop() const = 0; + + /// @brief Enable/disable audio loop + /// @param loop loop mode + virtual void setLoop(bool loop) = 0; + /// @brief Play, replay or resume audio virtual void play() = 0; @@ -120,6 +133,14 @@ namespace audio { /// @brief Get speaker priority /// @return speaker priority value virtual int getPriority() const = 0; + + inline constexpr bool isPaused() const { + return getState() == State::paused; + } + + inline constexpr bool isStopped() const { + return getState() == State::stopped; + } }; class Backend { @@ -160,6 +181,26 @@ namespace audio { glm::vec3 lookAt, glm::vec3 up ); + + /// @brief Play 3D sound in the world + /// @param sound target sound + /// @param position sound world position + /// @param volume sound volume [0.0-1.0] + /// @param pitch sound pitch multiplier [0.0-...] + /// @param loop loop sound + /// @param priority sound priority + /// (PRIORITY_LOW, PRIORITY_NORMAL, PRIORITY_HIGH) + /// @return speaker id or 0 + extern speakerid_t play( + Sound* sound, + glm::vec3 position, + float volume, + float pitch, + bool loop, + int priority + ); + + extern Speaker* get(speakerid_t id); /// @brief Update audio streams and sound instanced /// @param delta time since the last update (seconds) diff --git a/src/engine.cpp b/src/engine.cpp index 6a4074ec..518fbd14 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -121,6 +121,8 @@ void Engine::mainloop() { assert(screen != nullptr); updateTimers(); updateHotkeys(); + + audio::update(delta); gui->act(delta); screen->update(delta);