VoxelEngine/src/audio/audio.cpp
Vyacheslav Ivanov 5dd7a15c09
fix: optimization: PVS-Studio warning V832
It's better to use '= default;' syntax instead of empty constructor and destructor body.
Using '= default;' can help the compiler generate more optimal code.

Reported by: PVS-Studio

Signed-off-by: Vyacheslav Ivanov <islavaivanov76@gmail.com>
2024-08-02 01:57:43 +03:00

435 lines
11 KiB
C++

#include "audio.hpp"
#include "NoAudio.hpp"
#include "AL/ALAudio.hpp"
#include "../coders/wav.hpp"
#include "../coders/ogg.hpp"
#include <iostream>
#include <stdexcept>
#include <utility>
namespace audio {
static speakerid_t nextId = 1;
static Backend* backend;
static std::unordered_map<speakerid_t, std::unique_ptr<Speaker>> speakers;
static std::unordered_map<speakerid_t, std::shared_ptr<Stream>> streams;
static std::vector<std::unique_ptr<Channel>> channels;
}
using namespace audio;
Channel::Channel(std::string name) : name(std::move(name)) {
}
float Channel::getVolume() const {
return volume;
}
void Channel::setVolume(float volume) {
this->volume = std::max(0.0f, std::min(volume, 1.0f));
}
const std::string& Channel::getName() const {
return name;
}
// see pause logic in audio::update
void Channel::pause() {
paused = true;
}
// see pause logic in audio::update
void Channel::resume() {
paused = false;
}
bool Channel::isPaused() const {
return paused;
}
size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) {
if (!isOpen()) {
return 0;
}
size_t size = 0;
do {
size_t bytes = 0;
do {
bytes = read(buffer, bufferSize);
if (bytes == PCMStream::ERROR) {
return size;
}
size += bytes;
bufferSize -= bytes;
buffer += bytes;
} while (bytes > 0 && bufferSize > 0);
if (bufferSize == 0) {
break;
}
if (loop) {
seek(0);
}
} while (loop);
return size;
}
/// @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*, size_t bufferSize) override {
if (closed) {
return 0;
}
if (!seekable) {
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 override {
return static_cast<duration_t>(totalSamples) /
static_cast<duration_t>(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().release();
}
if (backend == nullptr) {
std::cerr << "could not to initialize audio" << std::endl;
backend = NoAudio::create().release();
}
create_channel("master");
}
std::unique_ptr<PCM> audio::load_PCM(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");
}
std::unique_ptr<Sound> audio::load_sound(const fs::path& file, bool keepPCM) {
std::shared_ptr<PCM> pcm(load_PCM(file, !keepPCM && backend->isDummy()).release());
return create_sound(pcm, keepPCM);
}
std::unique_ptr<Sound> audio::create_sound(std::shared_ptr<PCM> pcm, bool keepPCM) {
return backend->createSound(std::move(pcm), keepPCM);
}
std::unique_ptr<PCMStream> audio::open_PCM_stream(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");
}
std::unique_ptr<Stream> audio::open_stream(const fs::path& file, bool keepSource) {
if (!keepSource && backend->isDummy()) {
auto header = load_PCM(file, true);
// using void source sized as audio instead of actual audio file
return open_stream(
std::make_shared<PCMVoidSource>(header->totalSamples, header->sampleRate, header->seekable),
keepSource
);
}
return open_stream(
std::shared_ptr<PCMStream>(open_PCM_stream(file)),
keepSource
);
}
std::unique_ptr<Stream> audio::open_stream(std::shared_ptr<PCMStream> stream, bool keepSource) {
return backend->openStream(std::move(stream), keepSource);
}
void audio::set_listener(
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,
bool relative,
float volume,
float pitch,
bool loop,
int priority,
int channel
) {
if (sound == nullptr) {
return 0;
}
if (!sound->variants.empty()) {
size_t index = rand() % (sound->variants.size() + 1);
if (index < sound->variants.size()) {
sound = sound->variants.at(index).get();
}
}
auto speaker_ptr = sound->newInstance(priority, channel);
if (speaker_ptr == nullptr) {
remove_lower_priority_speaker(priority);
speaker_ptr = sound->newInstance(priority, channel);
}
if (speaker_ptr == nullptr) {
return 0;
}
auto speaker = speaker_ptr.get();
speakerid_t id = nextId++;
speakers.try_emplace(id, std::move(speaker_ptr));
speaker->setPosition(position);
speaker->setVolume(volume);
speaker->setPitch(pitch);
speaker->setLoop(loop);
speaker->setRelative(relative);
speaker->play();
return id;
}
speakerid_t audio::play(
const std::shared_ptr<Stream>& stream,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop,
int channel
) {
auto speaker_ptr = stream->createSpeaker(loop, channel);
if (speaker_ptr == nullptr) {
remove_lower_priority_speaker(PRIORITY_HIGH);
speaker_ptr = stream->createSpeaker(loop, channel);
}
if (speaker_ptr == nullptr) {
return 0;
}
auto speaker = speaker_ptr.get();
speakerid_t id = nextId++;
streams.try_emplace(id, stream);
speakers.try_emplace(id, std::move(speaker_ptr));
stream->bindSpeaker(id);
speaker->setPosition(position);
speaker->setVolume(volume);
speaker->setPitch(pitch);
speaker->setLoop(false);
speaker->setRelative(relative);
speaker->play();
return id;
}
speakerid_t audio::play_stream(
const fs::path& file,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop,
int channel
) {
std::shared_ptr<Stream> stream (open_stream(file, false));
return play(stream, position, relative, volume, pitch, loop, channel);
}
Speaker* audio::get_speaker(speakerid_t id) {
auto found = speakers.find(id);
if (found == speakers.end()) {
return nullptr;
}
return found->second.get();
}
int audio::create_channel(const std::string& name) {
int index = get_channel_index(name);
if (index != -1) {
return index;
}
channels.emplace_back(std::make_unique<Channel>(name));
return channels.size()-1;
}
int audio::get_channel_index(const std::string& name) {
int index = 0;
for (auto& channel : channels) {
if (channel->getName() == name) {
return index;
}
index++;
}
return -1;
}
Channel* audio::get_channel(int index) {
if (index < 0 || index >= int(channels.size())) {
return nullptr;
}
return channels.at(index).get();
}
Channel* audio::get_channel(const std::string& name) {
return get_channel(get_channel_index(name));
}
std::shared_ptr<Stream> audio::get_associated_stream(speakerid_t id) {
auto found = streams.find(id);
if (found != streams.end()) {
return found->second;
}
return nullptr;
}
size_t audio::count_speakers() {
return speakers.size();
}
size_t audio::count_streams() {
return streams.size();
}
void audio::update(double delta) {
backend->update(delta);
for (auto& entry : streams) {
entry.second->update(delta);
}
for (auto it = speakers.begin(); it != speakers.end();) {
auto speaker = it->second.get();
int speakerChannel = speaker->getChannel();
auto channel = get_channel(speakerChannel);
if (channel != nullptr) {
speaker->update(channel);
}
if (speaker->isStopped()) {
streams.erase(it->first);
it = speakers.erase(it);
} else {
it++;
}
}
}
void audio::reset_channel(int index) {
auto channel = get_channel(index);
if (channel == nullptr) {
return;
}
for (auto& entry : speakers) {
if (entry.second->getChannel() == index) {
entry.second->stop();
}
}
for (auto& entry : streams) {
entry.second->update(0.0f);
}
for (auto it = speakers.begin(); it != speakers.end();) {
auto speaker = it->second.get();
int speakerChannel = speaker->getChannel();
if (speakerChannel == index) {
streams.erase(it->first);
it = speakers.erase(it);
} else {
it++;
}
}
if (channel->isPaused()) {
channel->resume();
}
}
void audio::close() {
speakers.clear();
delete backend;
backend = nullptr;
}