#include "audio.h" #include #include #include "NoAudio.h" #include "AL/ALAudio.h" #include "../coders/wav.h" #include "../coders/ogg.h" namespace audio { static speakerid_t nextId = 1; static Backend* backend; static std::unordered_map> speakers; static std::unordered_map> streams; } using namespace audio; /// @brief pcm source that does not initialize buffer class PCMVoidSource : public PCMStream { size_t totalSamples; size_t remain; uint sampleRate; bool seekable; bool closed = false; public: PCMVoidSource(size_t totalSamples, uint sampleRate, bool seekable) : totalSamples(totalSamples), remain(totalSamples), sampleRate(sampleRate), seekable(seekable) {} size_t read(char* buffer, size_t bufferSize, bool loop) override { if (closed) { return 0; } if (!seekable || loop) { return bufferSize; } size_t n = std::min(bufferSize, totalSamples); remain -= n; return n; } void close() override { closed = true; } bool isOpen() const override { return !closed; } size_t getTotalSamples() const override { return totalSamples; } duration_t getTotalDuration() const { return static_cast(totalSamples) / static_cast(sampleRate); } uint getChannels() const override { return 1; } uint getSampleRate() const override { return sampleRate; } uint getBitsPerSample() const override { return 8; } bool isSeekable() const override { return seekable; } void seek(size_t position) override { if (closed || !seekable) { return; } position %= totalSamples; remain = totalSamples - position; } }; void audio::initialize(bool enabled) { if (enabled) { backend = ALAudio::create(); } if (backend == nullptr) { std::cerr << "could not to initialize audio" << std::endl; backend = NoAudio::create(); } } PCM* audio::loadPCM(const fs::path& file, bool headerOnly) { if (!fs::exists(file)) { throw std::runtime_error("file not found '"+file.u8string()+"'"); } std::string ext = file.extension().u8string(); if (ext == ".wav" || ext == ".WAV") { return wav::load_pcm(file, headerOnly); } else if (ext == ".ogg" || ext == ".OGG") { return ogg::load_pcm(file, headerOnly); } throw std::runtime_error("unsupported audio format"); } Sound* audio::loadSound(const fs::path& file, bool keepPCM) { std::shared_ptr pcm(loadPCM(file, !keepPCM && backend->isDummy())); return createSound(pcm, keepPCM); } Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { return backend->createSound(pcm, keepPCM); } PCMStream* audio::openPCMStream(const fs::path& file) { std::string ext = file.extension().u8string(); if (ext == ".wav" || ext == ".WAV") { return wav::create_stream(file); } else if (ext == ".ogg" || ext == ".OGG") { return ogg::create_stream(file); } throw std::runtime_error("unsupported audio stream format"); } Stream* audio::openStream(const fs::path& file, bool keepSource) { if (!keepSource && backend->isDummy()) { auto header = loadPCM(file, true); // using void source sized as audio instead of actual audio file return openStream( std::make_shared(header->totalSamples, header->sampleRate, header->seekable), keepSource ); } return openStream( std::shared_ptr(openPCMStream(file)), keepSource ); } Stream* audio::openStream(std::shared_ptr stream, bool keepSource) { return backend->openStream(stream, keepSource); } void audio::setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 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()) { it->second->stop(); it = speakers.erase(it); return; } it++; } for (auto it = speakers.begin(); it != speakers.end();) { if (it->second->getPriority() < priority) { it->second->stop(); it = 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.emplace(id, speaker); speaker->setPosition(position); speaker->setVolume(volume); speaker->setPitch(pitch); speaker->setLoop(loop); speaker->play(); return id; } speakerid_t audio::play( std::shared_ptr stream, glm::vec3 position, float volume, float pitch, bool loop ) { Speaker* speaker = stream->createSpeaker(loop); if (speaker == nullptr) { remove_lower_priority_speaker(PRIORITY_HIGH); speaker = stream->createSpeaker(loop); } if (speaker == nullptr) { return 0; } speakerid_t id = nextId++; streams.emplace(id, stream); speakers.emplace(id, speaker); stream->bindSpeaker(id); speaker->setPosition(position); speaker->setVolume(volume); speaker->setPitch(pitch); speaker->setLoop(false); 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) { backend->update(delta); for (auto& entry : streams) { entry.second->update(delta); } for (auto it = speakers.begin(); it != speakers.end();) { if (it->second->isStopped()) { it = speakers.erase(it); } else { it++; } } } void audio::close() { speakers.clear(); delete backend; backend = nullptr; }