From b809c2df84dd2226274d5d57dad904d1ff84ed80 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 23:29:09 +0300 Subject: [PATCH] audio test --- src/audio/ALAudio.cpp | 10 ++- src/audio/ALAudio.h | 4 + src/audio/NoAudio.h | 4 + src/audio/alutil.cpp | 165 --------------------------------------- src/audio/alutil.h | 21 +---- src/audio/audio.cpp | 24 +++++- src/audio/audio.h | 40 +++++++++- src/coders/wav.cpp | 118 ++++++++++++++++++++++++++++ src/coders/wav.h | 14 ++++ src/frontend/screens.cpp | 10 +++ 10 files changed, 215 insertions(+), 195 deletions(-) create mode 100644 src/coders/wav.cpp create mode 100644 src/coders/wav.h diff --git a/src/audio/ALAudio.cpp b/src/audio/ALAudio.cpp index bb57ebd5..ee179eef 100644 --- a/src/audio/ALAudio.cpp +++ b/src/audio/ALAudio.cpp @@ -24,6 +24,7 @@ Speaker* ALSound::newInstance(int priority) const { if (source == 0) { return nullptr; } + AL_CHECK(alSourcei(source, AL_BUFFER, buffer)); return new ALSpeaker(al, source, priority); } @@ -146,7 +147,7 @@ ALAudio::~ALAudio() { } AL_CHECK(alcMakeContextCurrent(context)); - AL_CHECK(alcDestroyContext(context)); + alcDestroyContext(context); if (!alcCloseDevice(device)) { std::cerr << "AL: device not closed!" << std::endl; } @@ -155,8 +156,10 @@ ALAudio::~ALAudio() { } Sound* ALAudio::createSound(std::shared_ptr pcm, bool keepPCM) { - // TODO: implement - return nullptr; + auto format = AL::to_al_format(pcm->channels, pcm->bitsPerSample); + uint buffer = getFreeBuffer(); + AL_CHECK(alBufferData(buffer, format, pcm->data.data(), pcm->data.size(), pcm->sampleRate)); + return new ALSound(this, buffer, pcm, keepPCM); } ALAudio* ALAudio::create() { @@ -187,7 +190,6 @@ uint ALAudio::getFreeSource(){ alGenSources(1, &id); if (!AL_GET_ERORR()) return 0; - allsources.push_back(id); return id; } diff --git a/src/audio/ALAudio.h b/src/audio/ALAudio.h index fca96659..8be5e5dd 100644 --- a/src/audio/ALAudio.h +++ b/src/audio/ALAudio.h @@ -111,6 +111,10 @@ namespace audio { ) override; void update(double delta) override; + + bool isDummy() const override { + return true; + } static ALAudio* create(); }; diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h index 1006bb83..4c9cee4f 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.h @@ -39,6 +39,10 @@ namespace audio { void update(double delta) override {} + bool isDummy() const override { + return true; + } + static NoAudio* create(); }; } diff --git a/src/audio/alutil.cpp b/src/audio/alutil.cpp index 42b2d38a..0dcfce3d 100644 --- a/src/audio/alutil.cpp +++ b/src/audio/alutil.cpp @@ -14,27 +14,6 @@ #include #endif -bool is_big_endian(void){ - uint32_t ui32_v = 0x01020304; - char bytes[sizeof(uint32_t)]; - std::memcpy(bytes, &ui32_v, sizeof(uint32_t)); - - return bytes[0] == 1; -} - -std::int32_t convert_to_int(char* buffer, std::size_t len){ - std::int32_t a = 0; - if (!is_big_endian()) { - std::memcpy(&a, buffer, len); - } - else { - for (std::size_t i = 0; i < len; ++i) { - reinterpret_cast(&a)[3 - i] = buffer[i]; - } - } - return a; -} - bool AL::check_errors(const std::string& filename, const std::uint_fast32_t line){ ALenum error = alGetError(); if(error != AL_NO_ERROR){ @@ -63,147 +42,3 @@ bool AL::check_errors(const std::string& filename, const std::uint_fast32_t line } return true; } - -bool load_wav_file_header(std::ifstream& file, - std::uint8_t& channels, - std::int32_t& sampleRate, - std::uint8_t& bitsPerSample, - ALsizei& size){ - char buffer[4]; - if(!file.is_open()) - return false; - - // the RIFF - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read RIFF" << std::endl; - return false; - } - if(std::strncmp(buffer, "RIFF", 4) != 0){ - std::cerr << "ERROR: file is not a valid WAVE file (header doesn't begin with RIFF)" << std::endl; - return false; - } - - // the size of the file - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read size of file" << std::endl; - return false; - } - - // the WAVE - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read WAVE" << std::endl; - return false; - } - if(std::strncmp(buffer, "WAVE", 4) != 0){ - std::cerr << "ERROR: file is not a valid WAVE file (header doesn't contain WAVE)" << std::endl; - return false; - } - - // "fmt/0" - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read fmt/0" << std::endl; - return false; - } - - // this is always 16, the size of the fmt data chunk - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read the 16" << std::endl; - return false; - } - - // PCM should be 1? - if(!file.read(buffer, 2)){ - std::cerr << "ERROR: could not read PCM" << std::endl; - return false; - } - - // the number of channels - if(!file.read(buffer, 2)){ - std::cerr << "ERROR: could not read number of channels" << std::endl; - return false; - } - channels = convert_to_int(buffer, 2); - - // sample rate - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read sample rate" << std::endl; - return false; - } - sampleRate = convert_to_int(buffer, 4); - - // (sampleRate * bitsPerSample * channels) / 8 - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read (sampleRate * bitsPerSample * channels) / 8" << std::endl; - return false; - } - - // ?? dafaq - if(!file.read(buffer, 2)){ - std::cerr << "ERROR: could not read dafaq" << std::endl; - return false; - } - - // bitsPerSample - if(!file.read(buffer, 2)){ - std::cerr << "ERROR: could not read bits per sample" << std::endl; - return false; - } - bitsPerSample = convert_to_int(buffer, 2); - - // data chunk header "data" - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read data chunk header" << std::endl; - return false; - } - if(std::strncmp(buffer, "data", 4) != 0){ - std::cerr << "ERROR: file is not a valid WAVE file (doesn't have 'data' tag)" << std::endl; - return false; - } - - // size of data - if(!file.read(buffer, 4)){ - std::cerr << "ERROR: could not read data size" << std::endl; - return false; - } - size = convert_to_int(buffer, 4); - - /* cannot be at the end of file */ - if(file.eof()){ - std::cerr << "ERROR: reached EOF on the file" << std::endl; - return false; - } - if(file.fail()){ - std::cerr << "ERROR: fail state set on the file" << std::endl; - return false; - } - return true; -} - -// after that user must free returned memory by himself! -char* load_wav( - const std::string& filename, - std::uint8_t& channels, - std::int32_t& sampleRate, - std::uint8_t& bitsPerSample, - ALsizei& size -){ - std::ifstream in(filename, std::ios::binary); - if(!in.is_open()){ - std::cerr << "ERROR: Could not open \"" << filename << "\"" << std::endl; - return nullptr; - } - if(!load_wav_file_header(in, channels, sampleRate, bitsPerSample, size)){ - std::cerr << "ERROR: Could not load wav header of \"" << filename << "\"" << std::endl; - return nullptr; - } - - std::unique_ptr data (new char[size]); - try { - in.read(data.get(), size); - return data.release(); - } - catch (const std::exception&) { - std::cerr << "ERROR: Could not load wav data of \"" << filename << "\"" << std::endl; - return nullptr; - } -} diff --git a/src/audio/alutil.h b/src/audio/alutil.h index ab60f081..93b4d38b 100644 --- a/src/audio/alutil.h +++ b/src/audio/alutil.h @@ -16,23 +16,6 @@ #define AL_CHECK(STATEMENT) STATEMENT; AL::check_errors(__FILE__, __LINE__) #define AL_GET_ERORR() AL::check_errors(__FILE__, __LINE__) -bool load_wav_file_header( - std::ifstream& file, - std::uint8_t& channels, - std::int32_t& sampleRate, - std::uint8_t& bitsPerSample, - ALsizei& size -); - -char* load_wav( - const std::string& filename, - std::uint8_t& channels, - std::int32_t& sampleRate, - std::uint8_t& bitsPerSample, - ALsizei& size -); - - namespace AL { bool check_errors(const std::string& filename, const std::uint_fast32_t line); @@ -69,10 +52,10 @@ namespace AL { return value; } - static inline ALenum to_al_format(short channels, short samples){ + static inline ALenum to_al_format(short channels, short bitsPerSample){ bool stereo = (channels > 1); - switch (samples) { + switch (bitsPerSample) { case 16: if (stereo) return AL_FORMAT_STEREO16; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 34f1891f..55018716 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -1,10 +1,13 @@ #include "audio.h" #include +#include #include "ALAudio.h" #include "NoAudio.h" +#include "../coders/wav.h" + namespace audio { static speakerid_t nextId = 1; static Backend* backend; @@ -23,6 +26,19 @@ void audio::initialize(bool enabled) { } } +PCM* audio::loadPCM(const fs::path& file, bool headerOnly) { + std::string ext = file.extension().u8string(); + if (ext == ".wav" || ext == ".WAV") { + return wav::load_pcm(file, headerOnly); + } // TODO: OGG support + 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 backend->createSound(pcm, keepPCM); +} + Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { return backend->createSound(pcm, keepPCM); } @@ -40,7 +56,7 @@ 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(); - speakers.erase(it); + it = speakers.erase(it); return; } it++; @@ -48,7 +64,7 @@ void remove_lower_priority_speaker(int priority) { for (auto it = speakers.begin(); it != speakers.end();) { if (it->second->getPriority() < priority) { it->second->stop(); - speakers.erase(it); + it = speakers.erase(it); return; } it++; @@ -72,7 +88,7 @@ speakerid_t audio::play( return 0; } speakerid_t id = nextId++; - speakers[id].reset(speaker); + speakers.emplace(id, speaker); speaker->setPosition(position); speaker->setVolume(volume); speaker->setPitch(pitch); @@ -94,7 +110,7 @@ void audio::update(double delta) { for (auto it = speakers.begin(); it != speakers.end();) { if (it->second->isStopped()) { - speakers.erase(it); + it = speakers.erase(it); } else { it++; } diff --git a/src/audio/audio.h b/src/audio/audio.h index 9ebea79a..a56dcbd1 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -3,9 +3,12 @@ #include #include +#include #include #include "../typedefs.h" +namespace fs = std::filesystem; + namespace audio { using speakerid_t = int64_t; /// @brief duration unit is second @@ -32,12 +35,23 @@ namespace audio { uint8_t bitsPerSample; uint sampleRate; + PCM( + std::vector data, + uint8_t channels, + uint8_t bitsPerSample, + uint sampleRate + ) : data(std::move(data)), + channels(channels), + bitsPerSample(bitsPerSample), + sampleRate(sampleRate) {} + constexpr inline size_t countSamples() const { return data.size() / channels / (bitsPerSample / 8); } constexpr inline duration_t getDuration() const { - return countSamples() / static_cast(sampleRate); + return static_cast(countSamples()) / + static_cast(sampleRate); } }; @@ -157,16 +171,33 @@ namespace audio { ) = 0; virtual void update(double delta) = 0; + + /// @brief Check if backend is an abstraction that does not internally + /// work with actual audio data or play anything + virtual bool isDummy() const = 0; }; /// @brief Initialize audio system or use no audio mode /// @param enabled try to initialize actual audio extern void initialize(bool enabled); + /// @brief Load audio file info and PCM data + /// @param file audio file + /// @param headerOnly read header only + /// @throws std::runtime_error if I/O error ocurred or format is unknown + /// @return PCM audio data + extern PCM* loadPCM(const fs::path& file, bool headerOnly); + + /// @brief Load sound from file + /// @param file audio file path + /// @param keepPCM store PCM data in sound to make it accessible with Sound::getPCM + /// @throws std::runtime_error if I/O error ocurred or format is unknown + /// @return new Sound instance + extern Sound* loadSound(const fs::path& file, bool keepPCM); + /// @brief Create new sound from PCM data /// @param pcm PCM data - /// @param keepPCM store PCM data in sound to make it accessible with - /// Sound::getPCM + /// @param keepPCM store PCM data in sound to make it accessible with Sound::getPCM /// @return new Sound instance extern Sound* createSound(std::shared_ptr pcm, bool keepPCM); @@ -200,6 +231,9 @@ namespace audio { int priority ); + /// @brief Get speaker by id + /// @param id speaker id + /// @return speaker or nullptr extern Speaker* get(speakerid_t id); /// @brief Update audio streams and sound instanced diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp new file mode 100644 index 00000000..a5dbca61 --- /dev/null +++ b/src/coders/wav.cpp @@ -0,0 +1,118 @@ +#include "wav.h" + +#include +#include +#include +#include + +#include "../audio/audio.h" + +bool is_big_endian() { + uint32_t ui32_v = 0x01020304; + char bytes[sizeof(uint32_t)]; + std::memcpy(bytes, &ui32_v, sizeof(uint32_t)); + return bytes[0] == 1; +} + +std::int32_t convert_to_int(char* buffer, std::size_t len){ + std::int32_t a = 0; + if (!is_big_endian()) { + std::memcpy(&a, buffer, len); + } + else { + for (std::size_t i = 0; i < len; ++i) { + reinterpret_cast(&a)[3 - i] = buffer[i]; + } + } + return a; +} + +audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { + std::ifstream in(file, std::ios::binary); + if(!in.is_open()){ + throw std::runtime_error("could not to open file '"+file.u8string()+"'"); + } + + char buffer[4]; + // the RIFF + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not to read RIFF"); + } + if(std::strncmp(buffer, "RIFF", 4) != 0){ + throw std::runtime_error("file is not a valid WAVE file (header doesn't begin with RIFF)"); + } + // the size of the file + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read size of file"); + } + // the WAVE + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not to read WAVE"); + } + if(std::strncmp(buffer, "WAVE", 4) != 0){ + throw std::runtime_error("file is not a valid WAVE file (header doesn't contain WAVE)"); + } + // "fmt/0" + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read fmt/0"); + } + // this is always 16, the size of the fmt data chunk + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read the 16"); + } + // PCM should be 1? + if(!in.read(buffer, 2)){ + throw std::runtime_error("could not read PCM"); + } + // the number of channels + if(!in.read(buffer, 2)){ + throw std::runtime_error("could not read number of channels"); + } + int channels = convert_to_int(buffer, 2); + // sample rate + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read sample rate"); + } + int sampleRate = convert_to_int(buffer, 4); + if (!in.read(buffer, 6)) { + throw std::runtime_error("could not to read WAV header"); + } + + // bitsPerSample + if(!in.read(buffer, 2)){ + throw std::runtime_error("could not read bits per sample"); + } + int bitsPerSample = convert_to_int(buffer, 2); + channels /= bitsPerSample/8; + + // data chunk header "data" + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read data chunk header"); + } + if(std::strncmp(buffer, "data", 4) != 0){ + throw std::runtime_error("file is not a valid WAVE file (doesn't have 'data' tag)"); + } + + // size of data + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read data size"); + } + size_t size = convert_to_int(buffer, 4); + + /* cannot be at the end of file */ + if(in.eof()){ + throw std::runtime_error("reached EOF on the file"); + } + if(in.fail()){ + throw std::runtime_error("fail state set on the file"); + } + + std::vector data; + if (!headerOnly) { + data.resize(size); + if (!in.read(data.data(), size)) { + throw std::runtime_error("could not load wav data of '"+file.u8string()+"'"); + } + } + return new audio::PCM(std::move(data), channels, bitsPerSample, sampleRate); +} diff --git a/src/coders/wav.h b/src/coders/wav.h new file mode 100644 index 00000000..209d5c25 --- /dev/null +++ b/src/coders/wav.h @@ -0,0 +1,14 @@ +#ifndef CODERS_WAV_H_ +#define CODERS_WAV_H_ + +#include + +namespace audio { + struct PCM; +} + +namespace wav { + extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly); +} + +#endif // CODERS_WAV_H_ diff --git a/src/frontend/screens.cpp b/src/frontend/screens.cpp index e54fe2ad..20e0c845 100644 --- a/src/frontend/screens.cpp +++ b/src/frontend/screens.cpp @@ -8,6 +8,7 @@ #include #include +#include "../audio/audio.h" #include "../window/Camera.h" #include "../window/Events.h" #include "../window/input.h" @@ -19,6 +20,7 @@ #include "../world/Level.h" #include "../world/World.h" #include "../objects/Player.h" +#include "../physics/Hitbox.h" #include "../logic/ChunksController.h" #include "../logic/LevelController.h" #include "../logic/scripting/scripting.h" @@ -148,6 +150,14 @@ void LevelScreen::update(float delta) { updateHotkeys(); } + auto camera = level->player->camera; + audio::setListener( + camera->position, + level->player->hitbox->velocity, + camera->position+camera->dir, + camera->up + ); + // TODO: subscribe for setting change EngineSettings& settings = engine->getSettings(); level->player->camera->setFov(glm::radians(settings.camera.fov));