From 97539aa9f41218cd1f7c9dcf07e0123d32d98663 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 03:31:57 +0300 Subject: [PATCH 01/47] audio system basic interface --- src/audio/ALAudio.cpp | 197 ++++++++++++++++++++++++ src/audio/ALAudio.h | 79 ++++++++++ src/audio/Audio.cpp | 193 ----------------------- src/audio/Audio.h | 67 -------- src/audio/NoAudio.cpp | 18 +++ src/audio/NoAudio.h | 44 ++++++ src/audio/{audioutil.cpp => alutil.cpp} | 14 +- src/audio/alutil.h | 53 +++++++ src/audio/audio.cpp | 32 ++++ src/audio/audio.h | 94 +++++++++++ src/audio/audioutil.h | 49 ------ src/engine.cpp | 8 +- 12 files changed, 528 insertions(+), 320 deletions(-) create mode 100644 src/audio/ALAudio.cpp create mode 100644 src/audio/ALAudio.h delete mode 100644 src/audio/Audio.cpp delete mode 100644 src/audio/Audio.h create mode 100644 src/audio/NoAudio.cpp create mode 100644 src/audio/NoAudio.h rename src/audio/{audioutil.cpp => alutil.cpp} (96%) create mode 100644 src/audio/alutil.h create mode 100644 src/audio/audio.cpp create mode 100644 src/audio/audio.h delete mode 100644 src/audio/audioutil.h diff --git a/src/audio/ALAudio.cpp b/src/audio/ALAudio.cpp new file mode 100644 index 00000000..68853a76 --- /dev/null +++ b/src/audio/ALAudio.cpp @@ -0,0 +1,197 @@ +#include "ALAudio.h" +#include "alutil.h" +#include +#include + +using namespace audio; + +ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) +: device(device), context(context) +{ + ALCint size; + alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size); + std::vector attrs(size); + alcGetIntegerv(device, ALC_ALL_ATTRIBUTES, size, &attrs[0]); + for (size_t i = 0; i < attrs.size(); ++i){ + if (attrs[i] == ALC_MONO_SOURCES) { + std::cout << "AL: max mono sources: " << attrs[i+1] << std::endl; + maxSources = attrs[i+1]; + } + } + auto devices = getAvailableDevices(); + std::cout << "AL devices:" << std::endl; + for (auto& name : devices) { + std::cout << " " << name << std::endl; + } +} + +ALAudio::~ALAudio() { + for (ALSource* source : allsources) { + if (source->isPlaying()){ + alSourceStop(source->id); + alCheckErrorsMacro(); + } + alDeleteSources(1, &source->id); + alCheckErrorsMacro(); + } + + for (ALBuffer* buffer : allbuffers){ + alDeleteBuffers(1, &buffer->id); + alCheckErrorsMacro(); + } + + alcMakeContextCurrent(context); + alcDestroyContext(context); + if (!alcCloseDevice(device)) { + std::cerr << "AL: device not closed!" << std::endl; + } + device = nullptr; + context = nullptr; +} + +bool ALSource::setBuffer(ALBuffer* buffer) { + alSourcei(id, AL_BUFFER, buffer->id); + return alCheckErrorsMacro(); +} + +bool ALSource::play(){ + alSourcePlay(id); + return alCheckErrorsMacro(); +} + +bool ALSource::isPlaying() { + int state; + alGetSourcei(id, AL_SOURCE_STATE, &state); + return state == AL_PLAYING; +} + +bool ALSource::setPosition(glm::vec3 position) { + alSource3f(id, AL_POSITION, position.x, position.y, position.z); + return alCheckErrorsMacro(); +} + +bool ALSource::setVelocity(glm::vec3 velocity) { + alSource3f(id, AL_VELOCITY, velocity.x, velocity.y, velocity.z); + return alCheckErrorsMacro(); +} + +bool ALSource::setLoop(bool loop) { + alSourcei(id, AL_LOOPING, AL_TRUE ? loop : AL_FALSE); + return alCheckErrorsMacro(); +} + +bool ALSource::setGain(float gain) { + alSourcef(id, AL_GAIN, gain); + return alCheckErrorsMacro(); +} + + +bool ALSource::setPitch(float pitch) { + alSourcef(id, AL_PITCH, pitch); + return alCheckErrorsMacro(); +} + +bool ALBuffer::load(int format, const char* data, int size, int freq) { + alBufferData(id, format, data, size, freq); + return alCheckErrorsMacro(); +} + +Sound* ALAudio::createSound(std::shared_ptr pcm, bool keepPCM) { + // TODO: implement + return nullptr; +} + +ALAudio* ALAudio::create() { + ALCdevice* device = alcOpenDevice(nullptr); + if (device == nullptr) + return nullptr; + ALCcontext* context = alcCreateContext(device, nullptr); + if (!alcMakeContextCurrent(context)){ + alcCloseDevice(device); + return nullptr; + } + if (!alCheckErrorsMacro()) { + return nullptr; + } + std::cout << "AL: initialized" << std::endl; + return new ALAudio(device, context); +} + +ALSource* ALAudio::getFreeSource(){ + if (!freesources.empty()){ + ALSource* source = freesources.back(); + freesources.pop_back(); + return source; + } + if (allsources.size() == maxSources){ + std::cerr << "attempted to create new source, but limit is " << maxSources << std::endl; + return nullptr; + } + ALuint id; + alGenSources(1, &id); + if (!alCheckErrorsMacro()) + return nullptr; + + ALSource* source = new ALSource(id); + allsources.push_back(source); + return source; +} + +ALBuffer* ALAudio::getFreeBuffer(){ + if (!freebuffers.empty()){ + ALBuffer* buffer = freebuffers.back(); + freebuffers.pop_back(); + return buffer; + } + if (allbuffers.size() == maxBuffers){ + std::cerr << "attempted to create new ALbuffer, but limit is " << maxBuffers << std::endl; + return nullptr; + } + ALuint id; + alGenBuffers(1, &id); + if (!alCheckErrorsMacro()) { + return nullptr; + } + + ALBuffer* buffer = new ALBuffer(id); + allbuffers.push_back(buffer); + return buffer; +} + +void ALAudio::freeSource(ALSource* source){ + freesources.push_back(source); +} + +void ALAudio::freeBuffer(ALBuffer* buffer){ + freebuffers.push_back(buffer); +} + +std::vector ALAudio::getAvailableDevices() const { + std::vector devicesVec; + + const ALCchar* devices; + devices = alcGetString(device, ALC_DEVICE_SPECIFIER); + if (!alCheckErrorsMacro()) { + return devicesVec; + } + + const char* ptr = devices; + do { + devicesVec.push_back(std::string(ptr)); + ptr += devicesVec.back().size() + 1; + } + while (ptr[0]); + + return devicesVec; +} + +void ALAudio::setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up){ + ALfloat listenerOri[] = { at.x, at.y, at.z, up.x, up.y, up.z }; + + alListener3f(AL_POSITION, position.x, position.y, position.z); + alCheckErrorsMacro(); + alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z); + alCheckErrorsMacro(); + alListenerfv(AL_ORIENTATION, listenerOri); + alCheckErrorsMacro(); +} diff --git a/src/audio/ALAudio.h b/src/audio/ALAudio.h new file mode 100644 index 00000000..89f6816e --- /dev/null +++ b/src/audio/ALAudio.h @@ -0,0 +1,79 @@ +#ifndef SRC_AUDIO_AUDIO_H_ +#define SRC_AUDIO_AUDIO_H_ + +#include +#include +#include + +#ifdef __APPLE__ +#include +#include +#else +#include +#include +#endif + +#include "audio.h" +#include "../typedefs.h" + +namespace audio { + struct ALBuffer; + + struct ALSource { + uint id; + ALSource(uint id) : id(id) {} + + bool isPlaying(); + bool setPosition(glm::vec3 position); + bool setVelocity(glm::vec3 velocity); + bool setBuffer(ALBuffer* buffer); + bool setLoop(bool loop); + bool setGain(float gain); + bool setPitch(float pitch); + bool play(); + }; + + struct ALBuffer { + uint id; + ALBuffer(uint id) : id(id) {} + bool load(int format, const char* data, int size, int freq); + }; + + class ALAudio : public Backend { + ALCdevice* device; + ALCcontext* context; + + std::vector allsources; + std::vector freesources; + + std::vector allbuffers; + std::vector freebuffers; + + uint maxSources; + uint maxBuffers; + + ALAudio(ALCdevice* device, ALCcontext* context); + public: + ~ALAudio(); + + ALSource* getFreeSource(); + ALBuffer* getFreeBuffer(); + void freeSource(ALSource* source); + void freeBuffer(ALBuffer* buffer); + + std::vector getAvailableDevices() const; + + Sound* createSound(std::shared_ptr pcm, bool keepPCM) override; + + void setListener( + glm::vec3 position, + glm::vec3 velocity, + glm::vec3 lookAt, + glm::vec3 up + ) override; + + static ALAudio* create(); + }; +} + +#endif /* SRC_AUDIO_AUDIO_H_ */ diff --git a/src/audio/Audio.cpp b/src/audio/Audio.cpp deleted file mode 100644 index 1074d23c..00000000 --- a/src/audio/Audio.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "Audio.h" -#include "audioutil.h" -#include -#include - -#ifdef __APPLE__ -#include -#include -#else -#include -#include -#endif - -ALCdevice* Audio::device; -ALCcontext* Audio::context; -unsigned Audio::maxSources; -unsigned Audio::maxBuffers = 1024; -std::vector Audio::allsources; -std::vector Audio::freesources; -std::vector Audio::allbuffers; -std::vector Audio::freebuffers; - -bool ALSource::setBuffer(ALBuffer* buffer) { - alSourcei(id, AL_BUFFER, buffer->id); - return alCheckErrorsMacro(); -} - -bool ALSource::play(){ - alSourcePlay(id); - return alCheckErrorsMacro(); -} - -bool ALSource::isPlaying() { - int state; - alGetSourcei(id, AL_SOURCE_STATE, &state); - return state == AL_PLAYING; -} - -bool ALSource::setPosition(glm::vec3 position) { - alSource3f(id, AL_POSITION, position.x, position.y, position.z); - return alCheckErrorsMacro(); -} - -bool ALSource::setVelocity(glm::vec3 velocity) { - alSource3f(id, AL_VELOCITY, velocity.x, velocity.y, velocity.z); - return alCheckErrorsMacro(); -} - -bool ALSource::setLoop(bool loop) { - alSourcei(id, AL_LOOPING, AL_TRUE ? loop : AL_FALSE); - return alCheckErrorsMacro(); -} - -bool ALSource::setGain(float gain) { - alSourcef(id, AL_GAIN, gain); - return alCheckErrorsMacro(); -} - - -bool ALSource::setPitch(float pitch) { - alSourcef(id, AL_PITCH, pitch); - return alCheckErrorsMacro(); -} - -bool ALBuffer::load(int format, const char* data, int size, int freq) { - alBufferData(id, format, data, size, freq); - return alCheckErrorsMacro(); -} - - -bool Audio::initialize() { - device = alcOpenDevice(nullptr); - if (device == nullptr) - return false; - context = alcCreateContext(device, nullptr); - if (!alcMakeContextCurrent(context)){ - alcCloseDevice(device); - return false; - } - if (!alCheckErrorsMacro()) - return false; - - ALCint size; - alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size); - std::vector attrs(size); - alcGetIntegerv(device, ALC_ALL_ATTRIBUTES, size, &attrs[0]); - for(size_t i=0; iisPlaying()){ - alSourceStop(source->id); alCheckErrorsMacro(); - } - alDeleteSources(1, &source->id); alCheckErrorsMacro(); - } - - for (ALBuffer* buffer : allbuffers){ - alDeleteBuffers(1, &buffer->id); alCheckErrorsMacro(); - } - - alcMakeContextCurrent(context); - alcDestroyContext(context); - if (!alcCloseDevice(device)){ - std::cerr << "device not closed!" << std::endl; - } - device = nullptr; - context = nullptr; -} - -ALSource* Audio::getFreeSource(){ - if (!freesources.empty()){ - ALSource* source = freesources.back(); - freesources.pop_back(); - return source; - } - if (allsources.size() == maxSources){ - std::cerr << "attempted to create new source, but limit is " << maxSources << std::endl; - return nullptr; - } - ALuint id; - alGenSources(1, &id); - if (!alCheckErrorsMacro()) - return nullptr; - - ALSource* source = new ALSource(id); - allsources.push_back(source); - return source; -} - -ALBuffer* Audio::getFreeBuffer(){ - if (!freebuffers.empty()){ - ALBuffer* buffer = freebuffers.back(); - freebuffers.pop_back(); - return buffer; - } - if (allbuffers.size() == maxBuffers){ - std::cerr << "attempted to create new ALbuffer, but limit is " << maxBuffers << std::endl; - return nullptr; - } - ALuint id; - alGenBuffers(1, &id); - if (!alCheckErrorsMacro()) - return nullptr; - - ALBuffer* buffer = new ALBuffer(id); - allbuffers.push_back(buffer); - return buffer; -} - -void Audio::freeSource(ALSource* source){ - freesources.push_back(source); -} - -void Audio::freeBuffer(ALBuffer* buffer){ - freebuffers.push_back(buffer); -} - -bool Audio::get_available_devices(std::vector& devicesVec){ - const ALCchar* devices; - devices = alcGetString(device, ALC_DEVICE_SPECIFIER); - if (!alCheckErrorsMacro()) - return false; - - const char* ptr = devices; - - devicesVec.clear(); - - do { - devicesVec.push_back(std::string(ptr)); - ptr += devicesVec.back().size() + 1; - } - while(*(ptr + 1) != '\0'); - - return true; -} - -void Audio::setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up){ - ALfloat listenerOri[] = { at.x, at.y, at.z, up.x, up.y, up.z }; - - alListener3f(AL_POSITION, position.x, position.y, position.z); - alCheckErrorsMacro(); - alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z); - alCheckErrorsMacro(); - alListenerfv(AL_ORIENTATION, listenerOri); - alCheckErrorsMacro(); -} diff --git a/src/audio/Audio.h b/src/audio/Audio.h deleted file mode 100644 index 087122a6..00000000 --- a/src/audio/Audio.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef SRC_AUDIO_AUDIO_H_ -#define SRC_AUDIO_AUDIO_H_ - -#include -#include - -#ifdef __APPLE__ -#include -#include -#else -#include -#include -#endif - -#include - - -struct ALBuffer; - -struct ALSource { - ALuint id; - ALSource(ALuint id) : id(id) {} - - bool isPlaying(); - bool setPosition(glm::vec3 position); - bool setVelocity(glm::vec3 velocity); - bool setBuffer(ALBuffer* buffer); - bool setLoop(bool loop); - bool setGain(float gain); - bool setPitch(float pitch); - bool play(); -}; - -struct ALBuffer { - ALuint id; - ALBuffer(ALuint id) : id(id) {} - bool load(int format, const char* data, int size, int freq); -}; - -class Audio { - static ALCdevice* device; - static ALCcontext* context; - - static std::vector allsources; - static std::vector freesources; - - static std::vector allbuffers; - static std::vector freebuffers; - - static unsigned maxSources; - static unsigned maxBuffers; - -public: - static ALSource* getFreeSource(); - static ALBuffer* getFreeBuffer(); - static void freeSource(ALSource* source); - static void freeBuffer(ALBuffer* buffer); - - static bool initialize(); - static void finalize(); - static bool get_available_devices(std::vector& devicesVec); - - static void setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up); - -}; - -#endif /* SRC_AUDIO_AUDIO_H_ */ diff --git a/src/audio/NoAudio.cpp b/src/audio/NoAudio.cpp new file mode 100644 index 00000000..4a3a8e45 --- /dev/null +++ b/src/audio/NoAudio.cpp @@ -0,0 +1,18 @@ +#include "NoAudio.h" + +using namespace audio; + +NoSound::NoSound(std::shared_ptr pcm, bool keepPCM) { + duration = pcm->getDuration(); + if (keepPCM) { + this->pcm = pcm; + } +} + +Sound* NoAudio::createSound(std::shared_ptr pcm, bool keepPCM) { + return new NoSound(pcm, keepPCM); +} + +NoAudio* NoAudio::create() { + return new NoAudio(); +} diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h new file mode 100644 index 00000000..41bf356e --- /dev/null +++ b/src/audio/NoAudio.h @@ -0,0 +1,44 @@ +#ifndef AUDIO_NOAUDIO_H_ +#define AUDIO_NOAUDIO_H_ + +#include "audio.h" + +namespace audio { + class NoSound : public Sound { + std::shared_ptr pcm; + duration_t duration; + public: + NoSound(std::shared_ptr pcm, bool keepPCM); + ~NoSound() {} + + duration_t getDuration() const override { + return duration; + } + + std::shared_ptr getPCM() const override { + return pcm; + } + + speakerid_t newInstance(int priority) const override { + return 0; + } + }; + + class NoAudio : public Backend { + public: + ~NoAudio() {} + + Sound* createSound(std::shared_ptr pcm, bool keepPCM) override; + + void setListener( + glm::vec3 position, + glm::vec3 velocity, + glm::vec3 at, + glm::vec3 up + ) override {} + + static NoAudio* create(); + }; +} + +#endif // AUDIO_NOAUDIO_H_ diff --git a/src/audio/audioutil.cpp b/src/audio/alutil.cpp similarity index 96% rename from src/audio/audioutil.cpp rename to src/audio/alutil.cpp index 17e3d99c..b4827ff3 100644 --- a/src/audio/audioutil.cpp +++ b/src/audio/alutil.cpp @@ -1,4 +1,4 @@ -#include "audioutil.h" +#include "alutil.h" #include #include @@ -180,11 +180,13 @@ bool load_wav_file_header(std::ifstream& file, } // 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){ +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; diff --git a/src/audio/alutil.h b/src/audio/alutil.h new file mode 100644 index 00000000..2462a073 --- /dev/null +++ b/src/audio/alutil.h @@ -0,0 +1,53 @@ +#ifndef SRC_AUDIO_AUDIOUTIL_H_ +#define SRC_AUDIO_AUDIOUTIL_H_ + +#include +#include +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#define alCheckErrorsMacro() check_al_errors(__FILE__, __LINE__) + +bool check_al_errors(const std::string& filename, const std::uint_fast32_t 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 +); + +static inline ALenum to_al_format(short channels, short samples){ + bool stereo = (channels > 1); + + switch (samples) { + case 16: + if (stereo) + return AL_FORMAT_STEREO16; + else + return AL_FORMAT_MONO16; + case 8: + if (stereo) + return AL_FORMAT_STEREO8; + else + return AL_FORMAT_MONO8; + default: + return -1; + } +} + +#endif /* SRC_AUDIO_AUDIOUTIL_H_ */ diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp new file mode 100644 index 00000000..16dfaaa6 --- /dev/null +++ b/src/audio/audio.cpp @@ -0,0 +1,32 @@ +#include "audio.h" + +#include + +#include "ALAudio.h" +#include "NoAudio.h" + +audio::Backend* audio::backend = nullptr; + +void audio::initialize(bool enabled) { + if (enabled) { + audio::backend = ALAudio::create(); + } + if (audio::backend == nullptr) { + std::cerr << "could not to initialize audio" << std::endl; + audio::backend = NoAudio::create(); + } +} + +void audio::setListener( + glm::vec3 position, + glm::vec3 velocity, + glm::vec3 lookAt, + glm::vec3 up +) { + audio::backend->setListener(position, velocity, lookAt, up); +} + +void audio::close() { + delete audio::backend; + audio::backend = nullptr; +} diff --git a/src/audio/audio.h b/src/audio/audio.h new file mode 100644 index 00000000..ed23a06c --- /dev/null +++ b/src/audio/audio.h @@ -0,0 +1,94 @@ +#ifndef AUDIO_AUDIO_H_ +#define AUDIO_AUDIO_H_ + +#include +#include +#include +#include "../typedefs.h" + +namespace audio { + using speakerid_t = int64_t; + /// @brief duration unit is second + using duration_t = float; + + /// @brief Pulse-code modulation data + struct PCM { + /// @brief May contain 8 bit and 16 bit PCM data + std::vector data; + uint8_t channels; + uint8_t bitsPerSample; + uint sampleRate; + + constexpr inline size_t countSamples() const { + return data.size() / channels / (bitsPerSample / 8); + } + + constexpr inline duration_t getDuration() const { + return countSamples() / static_cast(sampleRate); + } + }; + + /// @brief Sound is an audio asset that supposed to support many + /// simultaneously playing instances with different sources. + /// So it's audio data is stored in memory. + class Sound { + public: + virtual ~Sound() {} + + /// @brief Get sound duration + /// @return duration in seconds (>= 0.0) + virtual duration_t getDuration() const = 0; + + /// @brief Get sound PCM data + /// @return PCM data or nullptr + virtual std::shared_ptr getPCM() const = 0; + + /// @brief Create new sound instance + /// @param priority instance priority. High priority instance can + /// take out speaker from low priority instance + /// @return new speaker id with sound bound or 0 + /// if all speakers are in use + virtual speakerid_t newInstance(int priority) const = 0; + }; + + class Backend { + public: + virtual ~Backend() {}; + + /// @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 + /// @return new Sound instance + virtual Sound* createSound(std::shared_ptr pcm, bool keepPCM) = 0; + + virtual void setListener( + glm::vec3 position, + glm::vec3 velocity, + glm::vec3 lookAt, + glm::vec3 up + ) = 0; + }; + + extern Backend* backend; + + /// @brief Initialize audio system or use no audio mode + /// @param enabled try to initialize actual audio + extern void initialize(bool enabled); + + /// @brief Configure 3D listener + /// @param position listener position + /// @param velocity listener velocity (used for Doppler effect) + /// @param lookAt point the listener look at + /// @param up camera up vector + extern void setListener( + glm::vec3 position, + glm::vec3 velocity, + glm::vec3 lookAt, + glm::vec3 up + ); + /// @brief Finalize audio system + extern void close(); +}; + +#endif // AUDIO_AUDIO_H_ diff --git a/src/audio/audioutil.h b/src/audio/audioutil.h deleted file mode 100644 index 8f0357cd..00000000 --- a/src/audio/audioutil.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef SRC_AUDIO_AUDIOUTIL_H_ -#define SRC_AUDIO_AUDIOUTIL_H_ - -#include -#include -#include - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#define alCheckErrorsMacro() check_al_errors(__FILE__, __LINE__) - -bool check_al_errors(const std::string& filename, const std::uint_fast32_t 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); - -static inline ALenum to_al_format(short channels, short samples){ - bool stereo = (channels > 1); - - switch (samples) { - case 16: - if (stereo) - return AL_FORMAT_STEREO16; - else - return AL_FORMAT_MONO16; - case 8: - if (stereo) - return AL_FORMAT_STEREO8; - else - return AL_FORMAT_MONO8; - default: - return -1; - } -} - -#endif /* SRC_AUDIO_AUDIOUTIL_H_ */ diff --git a/src/engine.cpp b/src/engine.cpp index d45f7816..6a4074ec 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -10,7 +10,7 @@ #include #define GLEW_STATIC -#include "audio/Audio.h" +#include "audio/audio.h" #include "assets/Assets.h" #include "assets/AssetsLoader.h" #include "world/WorldGenerators.h" @@ -56,6 +56,7 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) if (Window::initialize(settings.display)){ throw initialize_error("could not initialize window"); } + audio::initialize(true); auto resdir = paths->getResources(); scripting::initialize(this); @@ -66,7 +67,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) resPaths = std::make_unique(resdir, roots); assets = std::make_unique(); - AssetsLoader loader(assets.get(), resPaths.get()); AssetsLoader::addDefaults(loader, nullptr); @@ -79,8 +79,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) throw initialize_error("could not to load assets"); } } - - Audio::initialize(); gui = std::make_unique(); if (settings.ui.language == "auto") { settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources()); @@ -148,7 +146,7 @@ Engine::~Engine() { screen.reset(); content.reset(); - Audio::finalize(); + audio::close(); assets.reset(); scripting::close(); Window::terminate(); From 58661a94a6012b645787e047cb9eb544f06529ed Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 16:23:16 +0300 Subject: [PATCH 02/47] update --- src/audio/ALAudio.cpp | 219 +++++++++++++++++++++++++----------------- src/audio/ALAudio.h | 83 +++++++++++----- src/audio/NoAudio.h | 6 +- src/audio/alutil.cpp | 2 +- src/audio/alutil.h | 73 ++++++++++---- src/audio/audio.cpp | 8 ++ src/audio/audio.h | 96 ++++++++++++++++-- 7 files changed, 349 insertions(+), 138 deletions(-) diff --git a/src/audio/ALAudio.cpp b/src/audio/ALAudio.cpp index 68853a76..fe955202 100644 --- a/src/audio/ALAudio.cpp +++ b/src/audio/ALAudio.cpp @@ -5,6 +5,105 @@ using namespace audio; +ALSound::ALSound(ALAudio* al, uint buffer, std::shared_ptr pcm, bool keepPCM) +: al(al), buffer(buffer) +{ + duration = pcm->getDuration(); + if (keepPCM) { + this->pcm = pcm; + } +} + +ALSound::~ALSound() { + al->freeBuffer(buffer); + buffer = 0; +} + +Speaker* ALSound::newInstance(int priority) const { + uint source = al->getFreeSource(); + if (source == 0) { + return nullptr; + } + return new ALSpeaker(al, source, priority); +} + +ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority) : al(al), source(source), priority(priority) { +} + +ALSpeaker::~ALSpeaker() { + if (source) { + stop(); + } +} + +State ALSpeaker::getState() const { + int state = AL::getSourcei(source, AL_SOURCE_STATE, AL_STOPPED); + switch (state) { + case AL_PLAYING: return State::playing; + case AL_PAUSED: return State::paused; + default: return State::stopped; + } +} + +float ALSpeaker::getVolume() const { + return AL::getSourcef(source, AL_GAIN); +} + +void ALSpeaker::setVolume(float volume) { + AL_CHECK(alSourcef(source, AL_GAIN, volume)); +} + +float ALSpeaker::getPitch() const { + return AL::getSourcef(source, AL_PITCH); +} + +void ALSpeaker::setPitch(float pitch) { + AL_CHECK(alSourcef(source, AL_PITCH, pitch)); +} + +void ALSpeaker::play() { + AL_CHECK(alSourcePlay(source)); +} + +void ALSpeaker::pause() { + AL_CHECK(alSourcePause(source)); +} + +void ALSpeaker::stop() { + AL_CHECK(alSourceStop(source)); + al->freeSource(source); + source = 0; +} + +duration_t ALSpeaker::getTime() const { + return static_cast(AL::getSourcef(source, AL_SEC_OFFSET)); +} + +void ALSpeaker::setTime(duration_t time) { + AL_CHECK(alSourcef(source, AL_SEC_OFFSET, static_cast(time))); +} + +void ALSpeaker::setPosition(glm::vec3 pos) { + AL_CHECK(alSource3f(source, AL_POSITION, pos.x, pos.y, pos.z)); +} + +glm::vec3 ALSpeaker::getPosition() const { + return AL::getSource3f(source, AL_POSITION); +} + +void ALSpeaker::setVelocity(glm::vec3 vel) { + AL_CHECK(alSource3f(source, AL_VELOCITY, vel.x, vel.y, vel.z)); +} + +glm::vec3 ALSpeaker::getVelocity() const { + return AL::getSource3f(source, AL_VELOCITY); +} + +int ALSpeaker::getPriority() const { + return priority; +} + + ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) : device(device), context(context) { @@ -26,22 +125,20 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) } ALAudio::~ALAudio() { - for (ALSource* source : allsources) { - if (source->isPlaying()){ - alSourceStop(source->id); - alCheckErrorsMacro(); + for (uint source : allsources) { + int state = AL::getSourcei(source, AL_SOURCE_STATE); + if (state == AL_PLAYING || state == AL_PAUSED) { + AL_CHECK(alSourceStop(source)); } - alDeleteSources(1, &source->id); - alCheckErrorsMacro(); + AL_CHECK(alDeleteSources(1, &source)); } - for (ALBuffer* buffer : allbuffers){ - alDeleteBuffers(1, &buffer->id); - alCheckErrorsMacro(); + for (uint buffer : allbuffers){ + AL_CHECK(alDeleteBuffers(1, &buffer)); } - alcMakeContextCurrent(context); - alcDestroyContext(context); + AL_CHECK(alcMakeContextCurrent(context)); + AL_CHECK(alcDestroyContext(context)); if (!alcCloseDevice(device)) { std::cerr << "AL: device not closed!" << std::endl; } @@ -49,53 +146,6 @@ ALAudio::~ALAudio() { context = nullptr; } -bool ALSource::setBuffer(ALBuffer* buffer) { - alSourcei(id, AL_BUFFER, buffer->id); - return alCheckErrorsMacro(); -} - -bool ALSource::play(){ - alSourcePlay(id); - return alCheckErrorsMacro(); -} - -bool ALSource::isPlaying() { - int state; - alGetSourcei(id, AL_SOURCE_STATE, &state); - return state == AL_PLAYING; -} - -bool ALSource::setPosition(glm::vec3 position) { - alSource3f(id, AL_POSITION, position.x, position.y, position.z); - return alCheckErrorsMacro(); -} - -bool ALSource::setVelocity(glm::vec3 velocity) { - alSource3f(id, AL_VELOCITY, velocity.x, velocity.y, velocity.z); - return alCheckErrorsMacro(); -} - -bool ALSource::setLoop(bool loop) { - alSourcei(id, AL_LOOPING, AL_TRUE ? loop : AL_FALSE); - return alCheckErrorsMacro(); -} - -bool ALSource::setGain(float gain) { - alSourcef(id, AL_GAIN, gain); - return alCheckErrorsMacro(); -} - - -bool ALSource::setPitch(float pitch) { - alSourcef(id, AL_PITCH, pitch); - return alCheckErrorsMacro(); -} - -bool ALBuffer::load(int format, const char* data, int size, int freq) { - alBufferData(id, format, data, size, freq); - return alCheckErrorsMacro(); -} - Sound* ALAudio::createSound(std::shared_ptr pcm, bool keepPCM) { // TODO: implement return nullptr; @@ -110,59 +160,55 @@ ALAudio* ALAudio::create() { alcCloseDevice(device); return nullptr; } - if (!alCheckErrorsMacro()) { - return nullptr; - } + AL_CHECK(); std::cout << "AL: initialized" << std::endl; return new ALAudio(device, context); } -ALSource* ALAudio::getFreeSource(){ +uint ALAudio::getFreeSource(){ if (!freesources.empty()){ - ALSource* source = freesources.back(); + uint source = freesources.back(); freesources.pop_back(); return source; } if (allsources.size() == maxSources){ std::cerr << "attempted to create new source, but limit is " << maxSources << std::endl; - return nullptr; + return 0; } ALuint id; alGenSources(1, &id); - if (!alCheckErrorsMacro()) - return nullptr; + if (!AL_GET_ERORR()) + return 0; - ALSource* source = new ALSource(id); - allsources.push_back(source); - return source; + allsources.push_back(id); + return id; } -ALBuffer* ALAudio::getFreeBuffer(){ +uint ALAudio::getFreeBuffer(){ if (!freebuffers.empty()){ - ALBuffer* buffer = freebuffers.back(); + uint buffer = freebuffers.back(); freebuffers.pop_back(); return buffer; } if (allbuffers.size() == maxBuffers){ std::cerr << "attempted to create new ALbuffer, but limit is " << maxBuffers << std::endl; - return nullptr; + return 0; } ALuint id; alGenBuffers(1, &id); - if (!alCheckErrorsMacro()) { - return nullptr; + if (!AL_GET_ERORR()) { + return 0; } - ALBuffer* buffer = new ALBuffer(id); - allbuffers.push_back(buffer); - return buffer; + allbuffers.push_back(id); + return id; } -void ALAudio::freeSource(ALSource* source){ +void ALAudio::freeSource(uint source){ freesources.push_back(source); } -void ALAudio::freeBuffer(ALBuffer* buffer){ +void ALAudio::freeBuffer(uint buffer){ freebuffers.push_back(buffer); } @@ -171,7 +217,7 @@ std::vector ALAudio::getAvailableDevices() const { const ALCchar* devices; devices = alcGetString(device, ALC_DEVICE_SPECIFIER); - if (!alCheckErrorsMacro()) { + if (!AL_GET_ERORR()) { return devicesVec; } @@ -188,10 +234,11 @@ std::vector ALAudio::getAvailableDevices() const { void ALAudio::setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up){ ALfloat listenerOri[] = { at.x, at.y, at.z, up.x, up.y, up.z }; - alListener3f(AL_POSITION, position.x, position.y, position.z); - alCheckErrorsMacro(); - alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z); - alCheckErrorsMacro(); - alListenerfv(AL_ORIENTATION, listenerOri); - alCheckErrorsMacro(); + AL_CHECK(alListener3f(AL_POSITION, position.x, position.y, position.z)); + AL_CHECK(alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z)); + AL_CHECK(alListenerfv(AL_ORIENTATION, listenerOri)); +} + +void ALAudio::update(double delta) { + } diff --git a/src/audio/ALAudio.h b/src/audio/ALAudio.h index 89f6816e..164d6671 100644 --- a/src/audio/ALAudio.h +++ b/src/audio/ALAudio.h @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef __APPLE__ #include @@ -18,36 +19,70 @@ namespace audio { struct ALBuffer; + class ALAudio; - struct ALSource { - uint id; - ALSource(uint id) : id(id) {} + class ALSound : public Sound { + ALAudio* al; + uint buffer; + std::shared_ptr pcm; + duration_t duration; + public: + ALSound(ALAudio* al, uint buffer, std::shared_ptr pcm, bool keepPCM); + ~ALSound(); - bool isPlaying(); - bool setPosition(glm::vec3 position); - bool setVelocity(glm::vec3 velocity); - bool setBuffer(ALBuffer* buffer); - bool setLoop(bool loop); - bool setGain(float gain); - bool setPitch(float pitch); - bool play(); + duration_t getDuration() const override { + return duration; + } + + std::shared_ptr getPCM() const override { + return pcm; + } + + Speaker* newInstance(int priority) const override; }; - struct ALBuffer { - uint id; - ALBuffer(uint id) : id(id) {} - bool load(int format, const char* data, int size, int freq); + /// @brief AL source adapter + class ALSpeaker : public Speaker { + ALAudio* al; + uint source; + int priority; + public: + ALSpeaker(ALAudio* al, uint source, int priority); + ~ALSpeaker(); + + State getState() const override; + + float getVolume() const override; + void setVolume(float volume) override; + + float getPitch() const override; + void setPitch(float pitch) override; + + void play() override; + void pause() override; + void stop() override; + + duration_t getTime() const override; + void setTime(duration_t time) override; + + void setPosition(glm::vec3 pos) override; + glm::vec3 getPosition() const override; + + void setVelocity(glm::vec3 vel) override; + glm::vec3 getVelocity() const override; + + int getPriority() const override; }; class ALAudio : public Backend { ALCdevice* device; ALCcontext* context; - std::vector allsources; - std::vector freesources; + std::vector allsources; + std::vector freesources; - std::vector allbuffers; - std::vector freebuffers; + std::vector allbuffers; + std::vector freebuffers; uint maxSources; uint maxBuffers; @@ -56,10 +91,10 @@ namespace audio { public: ~ALAudio(); - ALSource* getFreeSource(); - ALBuffer* getFreeBuffer(); - void freeSource(ALSource* source); - void freeBuffer(ALBuffer* buffer); + uint getFreeSource(); + uint getFreeBuffer(); + void freeSource(uint source); + void freeBuffer(uint buffer); std::vector getAvailableDevices() const; @@ -72,6 +107,8 @@ namespace audio { glm::vec3 up ) override; + void update(double delta) override; + static ALAudio* create(); }; } diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h index 41bf356e..1006bb83 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.h @@ -19,8 +19,8 @@ namespace audio { return pcm; } - speakerid_t newInstance(int priority) const override { - return 0; + Speaker* newInstance(int priority) const override { + return nullptr; } }; @@ -37,6 +37,8 @@ namespace audio { glm::vec3 up ) override {} + void update(double delta) override {} + static NoAudio* create(); }; } diff --git a/src/audio/alutil.cpp b/src/audio/alutil.cpp index b4827ff3..42b2d38a 100644 --- a/src/audio/alutil.cpp +++ b/src/audio/alutil.cpp @@ -35,7 +35,7 @@ std::int32_t convert_to_int(char* buffer, std::size_t len){ return a; } -bool check_al_errors(const std::string& filename, const std::uint_fast32_t line){ +bool AL::check_errors(const std::string& filename, const std::uint_fast32_t line){ ALenum error = alGetError(); if(error != AL_NO_ERROR){ std::cerr << "OpenAL ERROR (" << filename << ": " << line << ")\n" ; diff --git a/src/audio/alutil.h b/src/audio/alutil.h index 2462a073..ab60f081 100644 --- a/src/audio/alutil.h +++ b/src/audio/alutil.h @@ -11,9 +11,10 @@ #include #endif -#define alCheckErrorsMacro() check_al_errors(__FILE__, __LINE__) +#include -bool check_al_errors(const std::string& filename, const std::uint_fast32_t line); +#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, @@ -31,22 +32,60 @@ char* load_wav( ALsizei& size ); -static inline ALenum to_al_format(short channels, short samples){ - bool stereo = (channels > 1); - switch (samples) { - case 16: - if (stereo) - return AL_FORMAT_STEREO16; - else - return AL_FORMAT_MONO16; - case 8: - if (stereo) - return AL_FORMAT_STEREO8; - else - return AL_FORMAT_MONO8; - default: - return -1; +namespace AL { + bool check_errors(const std::string& filename, const std::uint_fast32_t line); + + /// @brief alGetSourcef wrapper + /// @param source target source + /// @param field enum value + /// @param def default value will be returned in case of error + /// @return field value or default + inline float getSourcef(uint source, ALenum field, float def=0.0f) { + float value = def; + AL_CHECK(alGetSourcef(source, field, &value)); + return value; + } + + /// @brief alGetSource3f wrapper + /// @param source target source + /// @param field enum value + /// @param def default value will be returned in case of error + /// @return field value or default + inline glm::vec3 getSource3f(uint source, ALenum field, glm::vec3 def={}) { + glm::vec3 value = def; + AL_CHECK(alGetSource3f(source, field, &value.x, &value.y, &value.z)); + return value; + } + + /// @brief alGetSourcei wrapper + /// @param source target source + /// @param field enum value + /// @param def default value will be returned in case of error + /// @return field value or default + inline float getSourcei(uint source, ALenum field, int def=0) { + int value = def; + AL_CHECK(alGetSourcei(source, field, &value)); + return value; + } + + static inline ALenum to_al_format(short channels, short samples){ + bool stereo = (channels > 1); + + switch (samples) { + case 16: + if (stereo) + return AL_FORMAT_STEREO16; + else + return AL_FORMAT_MONO16; + case 8: + if (stereo) + return AL_FORMAT_STEREO8; + else + return AL_FORMAT_MONO8; + default: + return -1; + } } } diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 16dfaaa6..aa8d1db6 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -5,6 +5,10 @@ #include "ALAudio.h" #include "NoAudio.h" +namespace audio { + extern Backend* backend; +} + audio::Backend* audio::backend = nullptr; void audio::initialize(bool enabled) { @@ -26,6 +30,10 @@ void audio::setListener( audio::backend->setListener(position, velocity, lookAt, up); } +void audio::update(double delta) { + audio::backend->update(delta); +} + void audio::close() { delete audio::backend; audio::backend = nullptr; diff --git a/src/audio/audio.h b/src/audio/audio.h index ed23a06c..7b16eec1 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -11,6 +11,14 @@ namespace audio { /// @brief duration unit is second using duration_t = float; + class Speaker; + + enum class State { + playing, + paused, + stopped + }; + /// @brief Pulse-code modulation data struct PCM { /// @brief May contain 8 bit and 16 bit PCM data @@ -46,20 +54,78 @@ namespace audio { /// @brief Create new sound instance /// @param priority instance priority. High priority instance can /// take out speaker from low priority instance - /// @return new speaker id with sound bound or 0 + /// @return new speaker with sound bound or nullptr /// if all speakers are in use - virtual speakerid_t newInstance(int priority) const = 0; + virtual Speaker* newInstance(int priority) const = 0; + }; + + /// @brief Audio source controller interface + class Speaker { + public: + virtual ~Speaker() {} + + /// @brief Get current speaker state + /// @return speaker state + virtual State getState() const = 0; + + /// @brief Get speaker audio gain + /// @return speaker audio gain value + virtual float getVolume() const = 0; + + /// @brief Set speaker audio gain (must be positive) + /// @param volume new gain value + virtual void setVolume(float volume) = 0; + + /// @brief Get speaker pitch multiplier + /// @return pitch multiplier + virtual float getPitch() const = 0; + + /// @brief Set speaker pitch multiplier + /// @param pitch new pitch multiplier (must be positive) + virtual void setPitch(float pitch) = 0; + + /// @brief Play, replay or resume audio + virtual void play() = 0; + + /// @brief Pause playing audio and keep speaker alive + virtual void pause() = 0; + + /// @brief Stop and destroy speaker + virtual void stop() = 0; + + /// @brief Get current time position of playing audio + /// @return time position in seconds + virtual duration_t getTime() const = 0; + + /// @brief Set playing audio time position + /// @param time time position in seconds + virtual void setTime(duration_t time) = 0; + + /// @brief Set speaker 3D position in the world + /// @param pos new position + virtual void setPosition(glm::vec3 pos) = 0; + + /// @brief Get speaker 3D position in the world + /// @return position + virtual glm::vec3 getPosition() const = 0; + + /// @brief Set speaker movement velocity used for Doppler effect + /// @param vel velocity vector + virtual void setVelocity(glm::vec3 vel) = 0; + + /// @brief Get speaker movement velocity used for Doppler effect + /// @return velocity vector + virtual glm::vec3 getVelocity() const = 0; + + /// @brief Get speaker priority + /// @return speaker priority value + virtual int getPriority() const = 0; }; class Backend { public: virtual ~Backend() {}; - /// @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 - /// @return new Sound instance virtual Sound* createSound(std::shared_ptr pcm, bool keepPCM) = 0; virtual void setListener( @@ -68,14 +134,21 @@ namespace audio { glm::vec3 lookAt, glm::vec3 up ) = 0; - }; - extern Backend* backend; + virtual void update(double delta) = 0; + }; /// @brief Initialize audio system or use no audio mode /// @param enabled try to initialize actual audio extern void initialize(bool enabled); + /// @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 + /// @return new Sound instance + extern Sound* createSound(std::shared_ptr pcm, bool keepPCM); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) @@ -87,6 +160,11 @@ namespace audio { glm::vec3 lookAt, glm::vec3 up ); + + /// @brief Update audio streams and sound instanced + /// @param delta time since the last update (seconds) + extern void update(double delta); + /// @brief Finalize audio system extern void close(); }; From acd5d11378b80c19de37b48f5881e65816848b65 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 18:58:41 +0300 Subject: [PATCH 03/47] audio::play --- src/audio/ALAudio.cpp | 9 ++++- src/audio/ALAudio.h | 3 ++ src/audio/audio.cpp | 83 ++++++++++++++++++++++++++++++++++++++----- src/audio/audio.h | 41 +++++++++++++++++++++ src/engine.cpp | 2 ++ 5 files changed, 128 insertions(+), 10 deletions(-) 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); From 4a233d72ed400e47cb23d01914b8217414aab8b9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 19:19:53 +0300 Subject: [PATCH 04/47] fix --- src/audio/audio.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index cea25063..34f1891f 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -39,6 +39,7 @@ void audio::setListener( 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); return; } @@ -46,6 +47,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); return; } From b809c2df84dd2226274d5d57dad904d1ff84ed80 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 23:29:09 +0300 Subject: [PATCH 05/47] 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)); From 59fe8af8ca6d9f0ff3d66b753dab2dca8b75d829 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 23:41:33 +0300 Subject: [PATCH 06/47] fix --- src/coders/wav.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index a5dbca61..0e95272c 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -27,6 +27,8 @@ std::int32_t convert_to_int(char* buffer, std::size_t len){ return a; } +#include + audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { std::ifstream in(file, std::ios::binary); if(!in.is_open()){ @@ -83,7 +85,7 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { throw std::runtime_error("could not read bits per sample"); } int bitsPerSample = convert_to_int(buffer, 2); - channels /= bitsPerSample/8; + std::cout << "CHANN " << channels << std::endl; // data chunk header "data" if(!in.read(buffer, 4)){ @@ -114,5 +116,6 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { throw std::runtime_error("could not load wav data of '"+file.u8string()+"'"); } } + std::cout << channels << " " << bitsPerSample << " " << sampleRate << std::endl; return new audio::PCM(std::move(data), channels, bitsPerSample, sampleRate); } From 8ca10c1ddc1f44d9534ca5cd5f6625c3884b0ead Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 23:42:19 +0300 Subject: [PATCH 07/47] another fix --- src/coders/wav.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index 0e95272c..bb104f0b 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -116,6 +116,5 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { throw std::runtime_error("could not load wav data of '"+file.u8string()+"'"); } } - std::cout << channels << " " << bitsPerSample << " " << sampleRate << std::endl; return new audio::PCM(std::move(data), channels, bitsPerSample, sampleRate); } From 96ad7664c465976fed609a8ad0152878a2889dcd Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 23:54:28 +0300 Subject: [PATCH 08/47] fix --- src/audio/audio.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio/audio.h b/src/audio/audio.h index a56dcbd1..8ad9828a 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -45,11 +45,11 @@ namespace audio { bitsPerSample(bitsPerSample), sampleRate(sampleRate) {} - constexpr inline size_t countSamples() const { + inline size_t countSamples() const { return data.size() / channels / (bitsPerSample / 8); } - constexpr inline duration_t getDuration() const { + inline duration_t getDuration() const { return static_cast(countSamples()) / static_cast(sampleRate); } @@ -148,11 +148,11 @@ namespace audio { /// @return speaker priority value virtual int getPriority() const = 0; - inline constexpr bool isPaused() const { + inline bool isPaused() const { return getState() == State::paused; } - inline constexpr bool isStopped() const { + inline bool isStopped() const { return getState() == State::stopped; } }; From 33b39dfece8e80ec50d5ea25f05c56327a7d1a3d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 12:37:53 +0300 Subject: [PATCH 09/47] PCMStream, msvc build fix, sound is an asset now --- src/assets/Assets.cpp | 25 ++++++++++++++++++++----- src/assets/Assets.h | 12 +++++++----- src/assets/AssetsLoader.cpp | 2 +- src/assets/AssetsLoader.h | 33 +++++++++++++++++++++++++-------- src/assets/assetload_funcs.cpp | 32 ++++++++++++++++++++++++++------ src/assets/assetload_funcs.h | 20 +++++++++++++++----- src/audio/alutil.h | 1 + src/audio/audio.h | 14 ++++++++++++++ 8 files changed, 109 insertions(+), 30 deletions(-) diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index 5183cca2..f05cda5f 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -1,5 +1,6 @@ #include "Assets.h" +#include "../audio/audio.h" #include "../graphics/Texture.h" #include "../graphics/Shader.h" #include "../graphics/Atlas.h" @@ -18,7 +19,7 @@ Texture* Assets::getTexture(std::string name) const { } void Assets::store(Texture* texture, std::string name){ - textures[name].reset(texture); + textures.emplace(name, texture); } @@ -30,7 +31,7 @@ Shader* Assets::getShader(std::string name) const{ } void Assets::store(Shader* shader, std::string name){ - shaders[name].reset(shader); + shaders.emplace(name, shader); } @@ -42,7 +43,7 @@ Font* Assets::getFont(std::string name) const { } void Assets::store(Font* font, std::string name){ - fonts[name].reset(font); + fonts.emplace(name, font); } Atlas* Assets::getAtlas(std::string name) const { @@ -53,7 +54,18 @@ Atlas* Assets::getAtlas(std::string name) const { } void Assets::store(Atlas* atlas, std::string name){ - atlases[name].reset(atlas); + atlases.emplace(name, atlas); +} + +audio::Sound* Assets::getSound(std::string name) const { + auto found = sounds.find(name); + if (found == sounds.end()) + return nullptr; + return found->second.get(); +} + +void Assets::store(audio::Sound* sound, std::string name) { + sounds.emplace(name, sound); } const std::vector& Assets::getAnimations() { @@ -72,7 +84,7 @@ UiDocument* Assets::getLayout(std::string name) const { } void Assets::store(UiDocument* layout, std::string name) { - layouts[name].reset(layout); + layouts.emplace(name, layout); } void Assets::extend(const Assets& assets) { @@ -91,6 +103,9 @@ void Assets::extend(const Assets& assets) { for (auto entry : assets.layouts) { layouts[entry.first] = entry.second; } + for (auto entry : assets.sounds) { + sounds[entry.first] = entry.second; + } animations.clear(); for (auto entry : assets.animations) { animations.emplace_back(entry); diff --git a/src/assets/Assets.h b/src/assets/Assets.h index 87e94380..f085ea7e 100644 --- a/src/assets/Assets.h +++ b/src/assets/Assets.h @@ -14,11 +14,9 @@ class Font; class Atlas; class UiDocument; -struct LayoutCfg { - int env; - - LayoutCfg(int env) : env(env) {} -}; +namespace audio { + class Sound; +} class Assets { std::unordered_map> textures; @@ -26,6 +24,7 @@ class Assets { std::unordered_map> fonts; std::unordered_map> atlases; std::unordered_map> layouts; + std::unordered_map> sounds; std::vector animations; public: ~Assets(); @@ -41,6 +40,9 @@ public: Atlas* getAtlas(std::string name) const; void store(Atlas* atlas, std::string name); + audio::Sound* getSound(std::string name) const; + void store(audio::Sound* sound, std::string name); + const std::vector& getAnimations(); void store(const TextureAnimation& animation); diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 2298904a..0fc979b6 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -24,7 +24,7 @@ void AssetsLoader::addLoader(int tag, aloader_func func) { loaders[tag] = func; } -void AssetsLoader::add(int tag, const std::string filename, const std::string alias, std::shared_ptr settings) { +void AssetsLoader::add(int tag, const std::string filename, const std::string alias, std::shared_ptr settings) { entries.push(aloader_entry{ tag, filename, alias, settings}); } diff --git a/src/assets/AssetsLoader.h b/src/assets/AssetsLoader.h index de401846..17826fa8 100644 --- a/src/assets/AssetsLoader.h +++ b/src/assets/AssetsLoader.h @@ -7,24 +7,41 @@ #include #include -const short ASSET_TEXTURE = 1; -const short ASSET_SHADER = 2; -const short ASSET_FONT = 3; -const short ASSET_ATLAS = 4; -const short ASSET_LAYOUT = 5; +inline constexpr short ASSET_TEXTURE = 1; +inline constexpr short ASSET_SHADER = 2; +inline constexpr short ASSET_FONT = 3; +inline constexpr short ASSET_ATLAS = 4; +inline constexpr short ASSET_LAYOUT = 5; +inline constexpr short ASSET_SOUND = 6; class ResPaths; class Assets; class AssetsLoader; class Content; -using aloader_func = std::function)>; +struct AssetCfg { + virtual ~AssetCfg() {} +}; + +struct LayoutCfg : AssetCfg { + int env; + + LayoutCfg(int env) : env(env) {} +}; + +struct SoundCfg : AssetCfg { + bool keepPCM; + + SoundCfg(bool keepPCM) : keepPCM(keepPCM) {} +}; + +using aloader_func = std::function)>; struct aloader_entry { int tag; const std::string filename; const std::string alias; - std::shared_ptr config; + std::shared_ptr config; }; class AssetsLoader { @@ -39,7 +56,7 @@ public: int tag, const std::string filename, const std::string alias, - std::shared_ptr settings=nullptr + std::shared_ptr settings=nullptr ); diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 5a7c6809..7c351cf3 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -4,6 +4,7 @@ #include #include "Assets.h" #include "AssetsLoader.h" +#include "../audio/audio.h" #include "../files/files.h" #include "../files/engine_paths.h" #include "../coders/png.h" @@ -25,7 +26,7 @@ bool assetload::texture( const ResPaths* paths, const std::string filename, const std::string name, - std::shared_ptr + std::shared_ptr ) { std::unique_ptr texture( png::load_texture(paths->find(filename).u8string()) @@ -44,7 +45,7 @@ bool assetload::shader( const ResPaths* paths, const std::string filename, const std::string name, - std::shared_ptr + std::shared_ptr ) { fs::path vertexFile = paths->find(filename+".glslv"); fs::path fragmentFile = paths->find(filename+".glslf"); @@ -92,7 +93,7 @@ bool assetload::atlas( const ResPaths* paths, const std::string directory, const std::string name, - std::shared_ptr + std::shared_ptr ) { AtlasBuilder builder; for (const auto& file : paths->listdir(directory)) { @@ -112,7 +113,7 @@ bool assetload::font( const ResPaths* paths, const std::string filename, const std::string name, - std::shared_ptr + std::shared_ptr ) { std::vector> pages; for (size_t i = 0; i <= 4; i++) { @@ -138,10 +139,10 @@ bool assetload::layout( const ResPaths* paths, const std::string file, const std::string name, - std::shared_ptr config + std::shared_ptr config ) { try { - LayoutCfg* cfg = reinterpret_cast(config.get()); + auto cfg = dynamic_cast(config.get()); auto document = UiDocument::read(loader, cfg->env, name, file); assets->store(document.release(), name); return true; @@ -152,6 +153,25 @@ bool assetload::layout( } } +bool assetload::sound( + AssetsLoader& loader, + Assets* assets, + const ResPaths* paths, + const std::string file, + const std::string name, + std::shared_ptr config +) { + auto cfg = dynamic_cast(config.get()); + auto sound = audio::loadSound(paths->find(file), cfg->keepPCM); + if (sound == nullptr) { + std::cerr << "failed to load sound '" << name << "' from '"; + std::cerr << file << "'" << std::endl; + return false; + } + assets->store(sound, name); + return true; +} + bool assetload::animation(Assets* assets, const ResPaths* paths, const std::string directory, diff --git a/src/assets/assetload_funcs.h b/src/assets/assetload_funcs.h index a9ef5d26..893ba2a5 100644 --- a/src/assets/assetload_funcs.h +++ b/src/assets/assetload_funcs.h @@ -8,6 +8,7 @@ class ResPaths; class Assets; class AssetsLoader; class Atlas; +struct AssetCfg; namespace assetload { bool texture( @@ -16,7 +17,7 @@ namespace assetload { const ResPaths* paths, const std::string filename, const std::string name, - std::shared_ptr settings + std::shared_ptr settings ); bool shader( AssetsLoader&, @@ -24,7 +25,7 @@ namespace assetload { const ResPaths* paths, const std::string filename, const std::string name, - std::shared_ptr settings + std::shared_ptr settings ); bool atlas( AssetsLoader&, @@ -32,7 +33,7 @@ namespace assetload { const ResPaths* paths, const std::string directory, const std::string name, - std::shared_ptr settings + std::shared_ptr settings ); bool font( AssetsLoader&, @@ -40,7 +41,7 @@ namespace assetload { const ResPaths* paths, const std::string filename, const std::string name, - std::shared_ptr settings + std::shared_ptr settings ); bool layout( AssetsLoader&, @@ -48,7 +49,16 @@ namespace assetload { const ResPaths* paths, const std::string file, const std::string name, - std::shared_ptr settings + std::shared_ptr settings + ); + + bool sound( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string file, + const std::string name, + std::shared_ptr settings ); bool animation( diff --git a/src/audio/alutil.h b/src/audio/alutil.h index 93b4d38b..09f068b9 100644 --- a/src/audio/alutil.h +++ b/src/audio/alutil.h @@ -12,6 +12,7 @@ #endif #include +#include "../typedefs.h" #define AL_CHECK(STATEMENT) STATEMENT; AL::check_errors(__FILE__, __LINE__) #define AL_GET_ERORR() AL::check_errors(__FILE__, __LINE__) diff --git a/src/audio/audio.h b/src/audio/audio.h index 8ad9828a..1076caf8 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -55,6 +55,20 @@ namespace audio { } }; + /// @brief PCM data streaming interface + class PCMStream { + public: + virtual ~PCMStream() {}; + virtual size_t read(char* buffer, size_t bufferSize, bool loop)=0; + virtual void close()=0; + + virtual size_t getTotalSamples() const=0; + virtual duration_t getTotalDuration() const=0; + virtual uint getChannels() const=0; + virtual uint getSampleRate() const=0; + virtual uint getBitsPerSample() const=0; + }; + /// @brief Sound is an audio asset that supposed to support many /// simultaneously playing instances with different sources. /// So it's audio data is stored in memory. From e3c2a1da5250960950dc667d67b0672672f4e694 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 12:43:57 +0300 Subject: [PATCH 10/47] workflows: added libvorbis-dev package --- .github/workflows/appimage.yml | 2 +- .github/workflows/cmake.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index f799077e..398c3b5c 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -23,7 +23,7 @@ jobs: - name: install dependencies run: | sudo apt-get update - sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev cmake squashfs-tools + sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev cmake squashfs-tools sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a sudo ln -s /usr/include/luajit-2.1 /usr/include/lua - name: configure diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index bd01c399..075c4b74 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -35,7 +35,7 @@ jobs: # make && make install INSTALL_INC=/usr/include/lua run: | sudo apt-get update - sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev + sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a sudo ln -s /usr/include/luajit-2.1 /usr/include/lua From 145ebf8468dc6e0d1b19a476fd3bea6b5921094f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 13:22:33 +0300 Subject: [PATCH 11/47] ogg decoder --- CMakeLists.txt | 3 ++- Dockerfile | 1 + src/audio/audio.cpp | 5 +++- src/coders/ogg.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++ src/coders/ogg.h | 14 ++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/coders/ogg.cpp create mode 100644 src/coders/ogg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b772fb72..db1dc456 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ if (WIN32) find_package(glfw3 REQUIRED) find_package(spng REQUIRED) find_package(glm REQUIRED) + find_package(libogg REQUIRED) set(PNGLIB spng::spng) else() find_package(Lua REQUIRED) @@ -118,7 +119,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() include_directories(${LUA_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS}) +target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB vorbis vorbisfile ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/res DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/Dockerfile b/Dockerfile index 3785e02c..ad2fce9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ libpng-dev \ libopenal-dev \ libluajit-5.1-dev \ + libvorbis-dev \ ca-certificates \ && rm -rf /var/lib/apt/lists/* diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 55018716..bf97ff6f 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -7,6 +7,7 @@ #include "NoAudio.h" #include "../coders/wav.h" +#include "../coders/ogg.h" namespace audio { static speakerid_t nextId = 1; @@ -30,7 +31,9 @@ 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 + } else if (ext == ".ogg" || ext == ".OGG") { + return ogg::load_pcm(file, headerOnly); + } throw std::runtime_error("unsupported audio format"); } diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp new file mode 100644 index 00000000..641d15a7 --- /dev/null +++ b/src/coders/ogg.cpp @@ -0,0 +1,56 @@ +#include "ogg.h" + +#include +#include +#include + +#include "../audio/audio.h" +#include "../typedefs.h" + +static inline const char* vorbis_error_message(int code) { + switch (code) { + case 0: return "no error"; + case OV_EREAD: return "a read from media returned an error"; + case OV_ENOTVORBIS: return "bitstream does not contain any Vorbis data"; + case OV_EVERSION: return "vorbis version mismatch"; + case OV_EBADHEADER: return "invalid Vorbis bitstream header"; + case OV_EFAULT: return "internal logic fault"; + default: + return "unknown"; + } +} + +audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { + OggVorbis_File vf; + int code; + if ((code = ov_fopen(file.u8string().c_str(), &vf))) { + throw std::runtime_error(vorbis_error_message(code)); + } + + std::vector data; + + vorbis_info* info = ov_info(&vf, -1); + uint channels = info->channels; + uint sampleRate = info->rate; + + if (!headerOnly) { + const int bufferSize = 4096; + int section = 0; + char buffer[bufferSize]; + + bool eof = false; + while (!eof) { + long ret = ov_read(&vf, buffer, bufferSize, 0, 2, true, §ion); + if (ret == 0) { + eof = true; + } else if (ret < 0) { + std::cerr << "ogg::load_pcm: " << vorbis_error_message(ret) << std::endl; + } else { + data.insert(data.end(), std::begin(buffer), std::end(buffer)); + } + } + } + + ov_clear(&vf); + return new audio::PCM(data, channels, 16, sampleRate); +} diff --git a/src/coders/ogg.h b/src/coders/ogg.h new file mode 100644 index 00000000..c1203be2 --- /dev/null +++ b/src/coders/ogg.h @@ -0,0 +1,14 @@ +#ifndef CODERS_OGG_H_ +#define CODERS_OGG_H_ + +#include + +namespace audio { + struct PCM; +} + +namespace ogg { + extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly); +} + +#endif // CODERS_OGG_H_ From 6ba638fad6ab00798a79cc4ef83ae3c2df35cc6d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 13:34:59 +0300 Subject: [PATCH 12/47] CMakeLists.txt & vcpkg.json update --- CMakeLists.txt | 6 ++++-- vcpkg.json | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db1dc456..82a81fec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,11 +89,12 @@ if (WIN32) find_package(glfw3 REQUIRED) find_package(spng REQUIRED) find_package(glm REQUIRED) - find_package(libogg REQUIRED) + find_package(vorbis REQUIRED) set(PNGLIB spng::spng) else() find_package(Lua REQUIRED) set(PNGLIB spng) + set(VORBISLIB vorbis vorbisfile) # not tested add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw) endif() else() @@ -101,6 +102,7 @@ else() find_package(Lua REQUIRED) find_package(PNG REQUIRED) set(PNGLIB PNG::PNG) + set(VORBISLIB vorbis vorbisfile) endif() if (APPLE) @@ -119,7 +121,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() include_directories(${LUA_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB vorbis vorbisfile ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS}) +target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB ${VORBISLIB} ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/res DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/vcpkg.json b/vcpkg.json index 626e06e6..8a5f6049 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -9,6 +9,7 @@ "glm", "libspng", "zlib", - "luajit" + "luajit", + "libvorbis" ] } From 2440d852d343aee7f8256ee9b7cc9d55309dbd32 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 13:40:04 +0300 Subject: [PATCH 13/47] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f8c94187..bf2a6e18 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ cmake --build . #### Debian-based distro: ```sh -sudo apt install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev +sudo apt install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev ``` CMake missing LUA_INCLUDE_DIR and LUA_LIBRARIES fix: @@ -44,7 +44,7 @@ sudo ln -s /usr/include/luajit-2.1 /usr/include/lua #### RHEL-based distro: ```sh -sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel openal-devel +sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel libvorbis-devel openal-devel ``` \+ install LuaJIT @@ -52,12 +52,12 @@ sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel openal-devel #### Arch-based distro: If you use X11 ```sh -sudo pacman -S glfw-x11 glew glm libpng openal +sudo pacman -S glfw-x11 glew glm libpng libvorbis openal ``` If you use Wayland ```sh -sudo pacman -S glfw-wayland glew glm libpng openal +sudo pacman -S glfw-wayland glew glm libpng libvorbis openal ``` \+ install LuaJIT @@ -72,7 +72,7 @@ make && sudo make install INSTALL_INC=/usr/include/lua #### macOS: ``` -brew install glfw3 glew glm libpng lua luajit openal-soft +brew install glfw3 glew glm libpng libvorbis lua luajit openal-soft ``` If homebrew for some reason could not install the necessary packages: ```lua luajit openal-soft```, then download, install and compile them manually (Lua, LuaJIT and OpenAL). From d1b145e7a46fc1877b0da7a5e955c5a83b8488a4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 14:58:13 +0300 Subject: [PATCH 14/47] audio fixes --- src/audio/ALAudio.cpp | 10 +++------- src/audio/ALAudio.h | 1 - src/audio/alutil.h | 2 +- src/coders/ogg.cpp | 3 +-- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/audio/ALAudio.cpp b/src/audio/ALAudio.cpp index ee179eef..22e418e7 100644 --- a/src/audio/ALAudio.cpp +++ b/src/audio/ALAudio.cpp @@ -188,7 +188,7 @@ uint ALAudio::getFreeSource(){ } ALuint id; alGenSources(1, &id); - if (!AL_GET_ERORR()) + if (!AL_GET_ERROR()) return 0; allsources.push_back(id); return id; @@ -200,13 +200,9 @@ uint ALAudio::getFreeBuffer(){ freebuffers.pop_back(); return buffer; } - if (allbuffers.size() == maxBuffers){ - std::cerr << "attempted to create new ALbuffer, but limit is " << maxBuffers << std::endl; - return 0; - } ALuint id; alGenBuffers(1, &id); - if (!AL_GET_ERORR()) { + if (!AL_GET_ERROR()) { return 0; } @@ -227,7 +223,7 @@ std::vector ALAudio::getAvailableDevices() const { const ALCchar* devices; devices = alcGetString(device, ALC_DEVICE_SPECIFIER); - if (!AL_GET_ERORR()) { + if (!AL_GET_ERROR()) { return devicesVec; } diff --git a/src/audio/ALAudio.h b/src/audio/ALAudio.h index 8be5e5dd..31024c2c 100644 --- a/src/audio/ALAudio.h +++ b/src/audio/ALAudio.h @@ -88,7 +88,6 @@ namespace audio { std::vector freebuffers; uint maxSources; - uint maxBuffers; ALAudio(ALCdevice* device, ALCcontext* context); public: diff --git a/src/audio/alutil.h b/src/audio/alutil.h index 09f068b9..ed7e37be 100644 --- a/src/audio/alutil.h +++ b/src/audio/alutil.h @@ -15,7 +15,7 @@ #include "../typedefs.h" #define AL_CHECK(STATEMENT) STATEMENT; AL::check_errors(__FILE__, __LINE__) -#define AL_GET_ERORR() AL::check_errors(__FILE__, __LINE__) +#define AL_GET_ERROR() AL::check_errors(__FILE__, __LINE__) namespace AL { bool check_errors(const std::string& filename, const std::uint_fast32_t line); diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index 641d15a7..ff1a0bcb 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -26,7 +26,6 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { if ((code = ov_fopen(file.u8string().c_str(), &vf))) { throw std::runtime_error(vorbis_error_message(code)); } - std::vector data; vorbis_info* info = ov_info(&vf, -1); @@ -52,5 +51,5 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { } ov_clear(&vf); - return new audio::PCM(data, channels, 16, sampleRate); + return new audio::PCM(std::move(data), channels, 16, sampleRate); } From 83d699085294ea9566c778d94a141a84fb469002 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 15:01:19 +0300 Subject: [PATCH 15/47] ogg decoding fix --- src/coders/ogg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index ff1a0bcb..6afc129c 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -45,7 +45,7 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { } else if (ret < 0) { std::cerr << "ogg::load_pcm: " << vorbis_error_message(ret) << std::endl; } else { - data.insert(data.end(), std::begin(buffer), std::end(buffer)); + data.insert(data.end(), std::begin(buffer), std::begin(buffer)+ret); } } } From f3d4ac8a5877a208b9a1060edd1bf1679f0d15ef Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 16:31:51 +0300 Subject: [PATCH 16/47] small fix --- src/audio/ALAudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/ALAudio.h b/src/audio/ALAudio.h index 31024c2c..56b2783e 100644 --- a/src/audio/ALAudio.h +++ b/src/audio/ALAudio.h @@ -112,7 +112,7 @@ namespace audio { void update(double delta) override; bool isDummy() const override { - return true; + return false; } static ALAudio* create(); From ebbee4f0f2c6746e4c5100502355403501819e7f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 16:42:59 +0300 Subject: [PATCH 17/47] minor refactor --- src/audio/{ => AL}/ALAudio.cpp | 0 src/audio/{ => AL}/ALAudio.h | 4 ++-- src/audio/{ => AL}/alutil.cpp | 0 src/audio/{ => AL}/alutil.h | 2 +- src/audio/audio.cpp | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/audio/{ => AL}/ALAudio.cpp (100%) rename src/audio/{ => AL}/ALAudio.h (98%) rename src/audio/{ => AL}/alutil.cpp (100%) rename src/audio/{ => AL}/alutil.h (98%) diff --git a/src/audio/ALAudio.cpp b/src/audio/AL/ALAudio.cpp similarity index 100% rename from src/audio/ALAudio.cpp rename to src/audio/AL/ALAudio.cpp diff --git a/src/audio/ALAudio.h b/src/audio/AL/ALAudio.h similarity index 98% rename from src/audio/ALAudio.h rename to src/audio/AL/ALAudio.h index 56b2783e..7389b76e 100644 --- a/src/audio/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -14,8 +14,8 @@ #include #endif -#include "audio.h" -#include "../typedefs.h" +#include "../audio.h" +#include "../../typedefs.h" namespace audio { struct ALBuffer; diff --git a/src/audio/alutil.cpp b/src/audio/AL/alutil.cpp similarity index 100% rename from src/audio/alutil.cpp rename to src/audio/AL/alutil.cpp diff --git a/src/audio/alutil.h b/src/audio/AL/alutil.h similarity index 98% rename from src/audio/alutil.h rename to src/audio/AL/alutil.h index ed7e37be..f07dbc46 100644 --- a/src/audio/alutil.h +++ b/src/audio/AL/alutil.h @@ -12,7 +12,7 @@ #endif #include -#include "../typedefs.h" +#include "../../typedefs.h" #define AL_CHECK(STATEMENT) STATEMENT; AL::check_errors(__FILE__, __LINE__) #define AL_GET_ERROR() AL::check_errors(__FILE__, __LINE__) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index bf97ff6f..1b927b0b 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -3,8 +3,8 @@ #include #include -#include "ALAudio.h" #include "NoAudio.h" +#include "AL/ALAudio.h" #include "../coders/wav.h" #include "../coders/ogg.h" From 70657d9e7bc8e8e138afc42c38152ea85a3f2487 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 20:42:42 +0300 Subject: [PATCH 18/47] audio::Stream interface --- src/audio/audio.h | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/audio/audio.h b/src/audio/audio.h index 1076caf8..cbeca812 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -69,6 +69,29 @@ namespace audio { virtual uint getBitsPerSample() const=0; }; + /// @brief Audio streaming interface + class Stream { + public: + virtual ~Stream() {}; + + /// @brief Create new speaker bound to the Stream + /// and having high priority + /// @return speaker id or 0 + virtual speakerid_t createSpeaker() = 0; + + /// @brief Get id of the bound speaker + /// @return speaker id or 0 if no speaker bound + virtual speakerid_t getSpeaker() const = 0; + + /// @brief Update stream state (preload samples if needed) + /// @param delta time elapsed since the last update + virtual void update(double delta) = 0; + + /// @brief Set playhead to the selected time + /// @param time selected time + virtual void setTime(duration_t time) = 0; + }; + /// @brief Sound is an audio asset that supposed to support many /// simultaneously playing instances with different sources. /// So it's audio data is stored in memory. @@ -251,7 +274,7 @@ namespace audio { extern Speaker* get(speakerid_t id); /// @brief Update audio streams and sound instanced - /// @param delta time since the last update (seconds) + /// @param delta time elapsed since the last update (seconds) extern void update(double delta); /// @brief Finalize audio system From 4d15ebb7de60eaa1fb9201522325779e6553a37a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 23:14:28 +0300 Subject: [PATCH 19/47] audio::PCM.totalSamples --- src/audio/audio.h | 13 ++++++++----- src/coders/ogg.cpp | 3 ++- src/coders/ogg.h | 1 + src/coders/wav.cpp | 6 ++---- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/audio/audio.h b/src/audio/audio.h index cbeca812..48857e3b 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -31,26 +31,29 @@ namespace audio { struct PCM { /// @brief May contain 8 bit and 16 bit PCM data std::vector data; + size_t totalSamples; uint8_t channels; uint8_t bitsPerSample; uint sampleRate; - PCM( + PCM( std::vector data, + size_t totalSamples, uint8_t channels, uint8_t bitsPerSample, uint sampleRate - ) : data(std::move(data)), + ) : data(std::move(data)), + totalSamples(totalSamples), channels(channels), bitsPerSample(bitsPerSample), sampleRate(sampleRate) {} - inline size_t countSamples() const { - return data.size() / channels / (bitsPerSample / 8); + inline size_t countSamplesMono() const { + return totalSamples / channels; } inline duration_t getDuration() const { - return static_cast(countSamples()) / + return static_cast(countSamplesMono()) / static_cast(sampleRate); } }; diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index 6afc129c..d25a9bec 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -31,6 +31,7 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { vorbis_info* info = ov_info(&vf, -1); uint channels = info->channels; uint sampleRate = info->rate; + size_t totalSamples = ov_pcm_total(&vf, -1); if (!headerOnly) { const int bufferSize = 4096; @@ -51,5 +52,5 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { } ov_clear(&vf); - return new audio::PCM(std::move(data), channels, 16, sampleRate); + return new audio::PCM(std::move(data), totalSamples, channels, 16, sampleRate); } diff --git a/src/coders/ogg.h b/src/coders/ogg.h index c1203be2..dc9cfe33 100644 --- a/src/coders/ogg.h +++ b/src/coders/ogg.h @@ -5,6 +5,7 @@ namespace audio { struct PCM; + class Stream; } namespace ogg { diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index bb104f0b..f08804d9 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -27,8 +27,6 @@ std::int32_t convert_to_int(char* buffer, std::size_t len){ return a; } -#include - audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { std::ifstream in(file, std::ios::binary); if(!in.is_open()){ @@ -85,7 +83,6 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { throw std::runtime_error("could not read bits per sample"); } int bitsPerSample = convert_to_int(buffer, 2); - std::cout << "CHANN " << channels << std::endl; // data chunk header "data" if(!in.read(buffer, 4)){ @@ -110,11 +107,12 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { } std::vector data; + size_t totalSamples = size / (bitsPerSample / 8); 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); + return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate); } From ee5ebaf8db894bd29e9a83ecb3fa634a8194d41e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 00:40:03 +0300 Subject: [PATCH 20/47] audio streaming interfaces update --- src/audio/audio.cpp | 9 ++++ src/audio/audio.h | 38 +++++++++++++++- src/coders/ogg.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++-- src/coders/ogg.h | 3 +- 4 files changed, 148 insertions(+), 6 deletions(-) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 1b927b0b..aef28d5f 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -46,6 +46,14 @@ 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 == ".ogg" || ext == ".OGG") { + return ogg::create_stream(file); + } + throw std::runtime_error("unsupported audio stream format"); +} + void audio::setListener( glm::vec3 position, glm::vec3 velocity, @@ -121,6 +129,7 @@ void audio::update(double delta) { } void audio::close() { + speakers.clear(); delete backend; backend = nullptr; } diff --git a/src/audio/audio.h b/src/audio/audio.h index 48857e3b..93ff6825 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -12,7 +12,7 @@ namespace fs = std::filesystem; namespace audio { using speakerid_t = int64_t; /// @brief duration unit is second - using duration_t = float; + using duration_t = double; constexpr inline int PRIORITY_LOW = 0; constexpr inline int PRIORITY_NORMAL = 5; @@ -58,18 +58,46 @@ namespace audio { } }; - /// @brief PCM data streaming interface + /// @brief audio::PCMStream is a data source for audio::Stream class PCMStream { public: virtual ~PCMStream() {}; + + /// @brief Read samples data to buffer + /// @param buffer destination buffer + /// @param bufferSize destination buffer size + /// @param loop loop stream (seek to start when end reached) + /// @return size of data received + /// (always equals bufferSize if seekable and looped) virtual size_t read(char* buffer, size_t bufferSize, bool loop)=0; + + /// @brief Close stream virtual void close()=0; + /// @brief Get total samples number if seekable or 0 virtual size_t getTotalSamples() const=0; + + /// @brief Get total audio track duration if seekable or 0.0 virtual duration_t getTotalDuration() const=0; + + /// @brief Get number of audio channels + /// @return 1 if mono, 2 if stereo virtual uint getChannels() const=0; + + /// @brief Get audio sampling frequency + /// @return number of mono samples per second virtual uint getSampleRate() const=0; + + /// @brief Get number of bits per mono sample + /// @return 8 or 16 virtual uint getBitsPerSample() const=0; + + /// @brief Check if the stream does support seek feature + virtual bool isSeekable() const=0; + + /// @brief Move playhead to the selected sample number + /// @param position selected sample number + virtual void seek(size_t position) = 0; }; /// @brief Audio streaming interface @@ -241,6 +269,12 @@ namespace audio { /// @return new Sound instance extern Sound* createSound(std::shared_ptr pcm, bool keepPCM); + /// @brief Open new PCM stream from file + /// @param file audio file path + /// @throws std::runtime_error if I/O error ocurred or format is unknown + /// @return new PCMStream instance + extern PCMStream* openPCMStream(const fs::path& file); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index d25a9bec..6e94cdd1 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -7,6 +7,8 @@ #include "../audio/audio.h" #include "../typedefs.h" +using namespace audio; + static inline const char* vorbis_error_message(int code) { switch (code) { case 0: return "no error"; @@ -31,7 +33,7 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { vorbis_info* info = ov_info(&vf, -1); uint channels = info->channels; uint sampleRate = info->rate; - size_t totalSamples = ov_pcm_total(&vf, -1); + size_t totalSamples = ov_seekable(&vf) ? ov_pcm_total(&vf, -1) : 0; if (!headerOnly) { const int bufferSize = 4096; @@ -49,8 +51,104 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { data.insert(data.end(), std::begin(buffer), std::begin(buffer)+ret); } } + totalSamples = data.size(); } - ov_clear(&vf); - return new audio::PCM(std::move(data), totalSamples, channels, 16, sampleRate); + return new PCM(std::move(data), totalSamples, channels, 16, sampleRate); +} + +class OggStream : public PCMStream { + OggVorbis_File vf; + bool closed = false; + uint channels; + uint sampleRate; + size_t totalSamples = 0; + bool seekable; +public: + OggStream(OggVorbis_File vf) : vf(std::move(vf)) { + vorbis_info* info = ov_info(&vf, -1); + channels = info->channels; + sampleRate = info->rate; + seekable = ov_seekable(&vf); + if (seekable) { + totalSamples = ov_pcm_total(&vf, -1); + } + } + + ~OggStream() { + if (!closed) { + close(); + } + } + + size_t read(char* buffer, size_t bufferSize, bool loop) { + int bitstream; + long bytes = 0; + size_t size = 0; + do { + do { + bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream); + if (bytes < 0) { + std::cerr << vorbis_error_message(bytes) << std::endl; + continue; + } + size += bytes; + bufferSize -= bytes; + buffer += bytes; + } while (bytes > 0); + + if (loop) { + seek(0); + } + if (bufferSize == 0) { + return size; + } + } while (loop); + return size; + } + + void close() { + ov_clear(&vf); + closed = true; + } + + size_t getTotalSamples() const { + return totalSamples; + } + + duration_t getTotalDuration() const { + return static_cast(totalSamples) / + static_cast(sampleRate); + } + + uint getChannels() const { + return channels; + } + + uint getSampleRate() const { + return sampleRate; + } + + uint getBitsPerSample() const { + return 16; + } + + bool isSeekable() const { + return seekable; + } + + void seek(size_t position) { + if (seekable) { + ov_raw_seek(&vf, position); + } + } +}; + +PCMStream* ogg::create_stream(const std::filesystem::path& file) { + OggVorbis_File vf; + int code; + if ((code = ov_fopen(file.u8string().c_str(), &vf))) { + throw std::runtime_error(vorbis_error_message(code)); + } + return new OggStream(std::move(vf)); } diff --git a/src/coders/ogg.h b/src/coders/ogg.h index dc9cfe33..828f2be5 100644 --- a/src/coders/ogg.h +++ b/src/coders/ogg.h @@ -5,11 +5,12 @@ namespace audio { struct PCM; - class Stream; + class PCMStream; } namespace ogg { extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly); + extern audio::PCMStream* create_stream(const std::filesystem::path& file); } #endif // CODERS_OGG_H_ From 1bf0c7921dae7f24c4da08677d2e289a6cad68f5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 01:27:03 +0300 Subject: [PATCH 21/47] AppImage recipe update --- dev/AppImageBuilder.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index 567dd872..dfe7b80f 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -29,6 +29,8 @@ AppDir: - libopengl0 - libasound2 - libglx0 + - libogg0 + - libvorbis0 exclude: - hicolor-icon-theme - sound-theme-freedesktop From d0143004e2b3e5d8e9940f31f4ff063205e626e5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 01:31:13 +0300 Subject: [PATCH 22/47] AppImage recipe update --- dev/AppImageBuilder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index dfe7b80f..6e6c3ac5 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -29,8 +29,8 @@ AppDir: - libopengl0 - libasound2 - libglx0 - - libogg0 - - libvorbis0 + - libogg + - libvorbis exclude: - hicolor-icon-theme - sound-theme-freedesktop From 961a37fe0aaf774b9a6bf6bfa1546025d4027b3f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 01:36:40 +0300 Subject: [PATCH 23/47] AppImage recipe fix --- dev/AppImageBuilder.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index 6e6c3ac5..4025fa72 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -29,8 +29,9 @@ AppDir: - libopengl0 - libasound2 - libglx0 - - libogg - - libvorbis + - libogg0 + - libvorbis0a + - libvorbisfile3 exclude: - hicolor-icon-theme - sound-theme-freedesktop From 8bd43098d59c7c5336b572fd835de0b25cbe2c13 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 13:21:58 +0300 Subject: [PATCH 24/47] ALStream WIP --- src/audio/AL/ALAudio.cpp | 48 ++++++++++++++++++++ src/audio/AL/ALAudio.h | 19 ++++++++ src/audio/NoAudio.cpp | 4 ++ src/audio/NoAudio.h | 34 +++++++++++++++ src/audio/audio.cpp | 94 +++++++++++++++++++++++++++++++++++++++- src/audio/audio.h | 36 ++++++++++++--- src/coders/ogg.cpp | 20 ++++++--- src/coders/wav.cpp | 2 +- 8 files changed, 245 insertions(+), 12 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 22e418e7..92eb5441 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -28,6 +28,50 @@ Speaker* ALSound::newInstance(int priority) const { return new ALSpeaker(al, source, priority); } +ALStream::ALStream(ALAudio* al, std::shared_ptr source, bool keepSource) +: al(al), source(source), keepSource(keepSource) { +} + +ALStream::~ALStream() { + bindSpeaker(0); + source = nullptr; +} + +std::shared_ptr ALStream::getSource() const { + if (keepSource) { + return source; + } else { + return nullptr; + } +} + +Speaker* ALStream::createSpeaker() { + uint source = al->getFreeSource(); + // TODO: prepare source and enqueue buffers + return new ALSpeaker(al, source, PRIORITY_HIGH); +} + + +void ALStream::bindSpeaker(speakerid_t speaker) { + auto sp = audio::get(this->speaker); + if (sp) { + sp->stop(); + } + this->speaker = speaker; +} + +speakerid_t ALStream::getSpeaker() const { + return speaker; +} + +void ALStream::update(double delta) { + // TODO: implement +} + +void ALStream::setTime(duration_t time) { + // TODO: implement +} + ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority) : al(al), source(source), priority(priority) { } @@ -162,6 +206,10 @@ Sound* ALAudio::createSound(std::shared_ptr pcm, bool keepPCM) { return new ALSound(this, buffer, pcm, keepPCM); } +Stream* ALAudio::openStream(std::shared_ptr stream, bool keepSource) { + return new ALStream(this, stream, keepSource); +} + ALAudio* ALAudio::create() { ALCdevice* device = alcOpenDevice(nullptr); if (device == nullptr) diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.h index 7389b76e..b37a602d 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -20,6 +20,7 @@ namespace audio { struct ALBuffer; class ALAudio; + class PCMStream; class ALSound : public Sound { ALAudio* al; @@ -41,6 +42,23 @@ namespace audio { Speaker* newInstance(int priority) const override; }; + class ALStream : public Stream { + ALAudio* al; + std::shared_ptr source; + speakerid_t speaker = 0; + bool keepSource; + public: + ALStream(ALAudio* al, std::shared_ptr source, bool keepSource); + ~ALStream(); + + std::shared_ptr getSource() const override; + void bindSpeaker(speakerid_t speaker) override; + Speaker* createSpeaker() override; + speakerid_t getSpeaker() const override; + void update(double delta) override; + void setTime(duration_t time) override; + }; + /// @brief AL source adapter class ALSpeaker : public Speaker { ALAudio* al; @@ -101,6 +119,7 @@ namespace audio { std::vector getAvailableDevices() const; Sound* createSound(std::shared_ptr pcm, bool keepPCM) override; + Stream* openStream(std::shared_ptr stream, bool keepSource) override; void setListener( glm::vec3 position, diff --git a/src/audio/NoAudio.cpp b/src/audio/NoAudio.cpp index 4a3a8e45..b6193fd0 100644 --- a/src/audio/NoAudio.cpp +++ b/src/audio/NoAudio.cpp @@ -13,6 +13,10 @@ Sound* NoAudio::createSound(std::shared_ptr pcm, bool keepPCM) { return new NoSound(pcm, keepPCM); } +Stream* NoAudio::openStream(std::shared_ptr stream, bool keepSource) { + return new NoStream(stream, keepSource); +} + NoAudio* NoAudio::create() { return new NoAudio(); } diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h index 4c9cee4f..e2a61881 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.h @@ -24,11 +24,45 @@ namespace audio { } }; + class NoStream : public Stream { + std::shared_ptr source; + duration_t duration; + public: + NoStream(std::shared_ptr source, bool keepSource) { + duration = source->getTotalDuration(); + if (keepSource) { + this->source = source; + } + } + + std::shared_ptr getSource() const { + return source; + } + + void bindSpeaker(speakerid_t speaker) { + } + + Speaker* createSpeaker() { + return nullptr; + } + + speakerid_t getSpeaker() const { + return 0; + } + + void update(double delta) { + } + + void setTime(duration_t time) { + } + }; + class NoAudio : public Backend { public: ~NoAudio() {} Sound* createSound(std::shared_ptr pcm, bool keepPCM) override; + Stream* openStream(std::shared_ptr stream, bool keepSource) override; void setListener( glm::vec3 position, diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index aef28d5f..37b06b7d 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -17,6 +17,75 @@ namespace audio { 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(); @@ -28,6 +97,9 @@ void audio::initialize(bool enabled) { } 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); @@ -39,7 +111,7 @@ PCM* audio::loadPCM(const fs::path& file, bool headerOnly) { Sound* audio::loadSound(const fs::path& file, bool keepPCM) { std::shared_ptr pcm(loadPCM(file, !keepPCM && backend->isDummy())); - return backend->createSound(pcm, keepPCM); + return createSound(pcm, keepPCM); } Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { @@ -54,6 +126,26 @@ PCMStream* audio::openPCMStream(const fs::path& 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, diff --git a/src/audio/audio.h b/src/audio/audio.h index 93ff6825..ac6cf4b9 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -35,18 +35,21 @@ namespace audio { uint8_t channels; uint8_t bitsPerSample; uint sampleRate; + bool seekable; PCM( std::vector data, size_t totalSamples, uint8_t channels, uint8_t bitsPerSample, - uint sampleRate + uint sampleRate, + bool seekable ) : data(std::move(data)), totalSamples(totalSamples), channels(channels), bitsPerSample(bitsPerSample), - sampleRate(sampleRate) {} + sampleRate(sampleRate), + seekable(seekable) {} inline size_t countSamplesMono() const { return totalSamples / channels; @@ -74,6 +77,9 @@ namespace audio { /// @brief Close stream virtual void close()=0; + /// @brief Check if stream is open + virtual bool isOpen() const=0; + /// @brief Get total samples number if seekable or 0 virtual size_t getTotalSamples() const=0; @@ -105,10 +111,19 @@ namespace audio { public: virtual ~Stream() {}; + /// @brief Get pcm data source + /// @return PCM stream or nullptr if audio::openStream + /// keepSource argument is set to false + virtual std::shared_ptr getSource() const = 0; + /// @brief Create new speaker bound to the Stream /// and having high priority /// @return speaker id or 0 - virtual speakerid_t createSpeaker() = 0; + virtual Speaker* createSpeaker() = 0; + + /// @brief Unbind previous speaker and bind new speaker to the stream + /// @param speaker speaker id or 0 if all you need is unbind speaker + virtual void bindSpeaker(speakerid_t speaker) = 0; /// @brief Get id of the bound speaker /// @return speaker id or 0 if no speaker bound @@ -230,14 +245,13 @@ namespace audio { virtual ~Backend() {}; virtual Sound* createSound(std::shared_ptr pcm, bool keepPCM) = 0; - + virtual Stream* openStream(std::shared_ptr stream, bool keepSource) = 0; virtual void setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up ) = 0; - virtual void update(double delta) = 0; /// @brief Check if backend is an abstraction that does not internally @@ -275,6 +289,18 @@ namespace audio { /// @return new PCMStream instance extern PCMStream* openPCMStream(const fs::path& file); + /// @brief Open new audio stream from file + /// @param file audio file path + /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource + /// @return new Stream instance + extern Stream* openStream(const fs::path& file, bool keepSource); + + /// @brief Open new audio stream from source + /// @param stream PCM data source + /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource + /// @return new Stream instance + extern Stream* openStream(std::shared_ptr stream, bool keepSource); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index 6e94cdd1..dfe60177 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -33,7 +33,8 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { vorbis_info* info = ov_info(&vf, -1); uint channels = info->channels; uint sampleRate = info->rate; - size_t totalSamples = ov_seekable(&vf) ? ov_pcm_total(&vf, -1) : 0; + bool seekable = ov_seekable(&vf); + size_t totalSamples = seekable ? ov_pcm_total(&vf, -1) : 0; if (!headerOnly) { const int bufferSize = 4096; @@ -54,7 +55,7 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { totalSamples = data.size(); } ov_clear(&vf); - return new PCM(std::move(data), totalSamples, channels, 16, sampleRate); + return new PCM(std::move(data), totalSamples, channels, 16, sampleRate, seekable); } class OggStream : public PCMStream { @@ -82,6 +83,9 @@ public: } size_t read(char* buffer, size_t bufferSize, bool loop) { + if (closed) { + return 0; + } int bitstream; long bytes = 0; size_t size = 0; @@ -108,8 +112,14 @@ public: } void close() { - ov_clear(&vf); - closed = true; + if (!closed) { + ov_clear(&vf); + closed = true; + } + } + + bool isOpen() const { + return !closed; } size_t getTotalSamples() const { @@ -138,7 +148,7 @@ public: } void seek(size_t position) { - if (seekable) { + if (!closed && seekable) { ov_raw_seek(&vf, position); } } diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index f08804d9..578ddb21 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -114,5 +114,5 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { throw std::runtime_error("could not load wav data of '"+file.u8string()+"'"); } } - return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate); + return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate, true); } From cc9a3e3ec865f6930c6c9c07d496736fc0554480 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Mar 2024 02:37:32 +0300 Subject: [PATCH 25/47] AL streaming test --- src/audio/AL/ALAudio.cpp | 53 +++++++++++++++++++++++++++++++++++++--- src/audio/AL/ALAudio.h | 17 ++++++++++--- src/audio/NoAudio.h | 12 ++++----- src/audio/audio.cpp | 33 +++++++++++++++++++++++++ src/audio/audio.h | 18 +++++++++++++- src/coders/ogg.cpp | 11 ++++++--- 6 files changed, 127 insertions(+), 17 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 92eb5441..1a12034a 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -45,9 +45,28 @@ std::shared_ptr ALStream::getSource() const { } } -Speaker* ALStream::createSpeaker() { +bool ALStream::preloadBuffer(uint buffer, bool loop) { + size_t read = source->read(this->buffer, BUFFER_SIZE, loop); + if (!read) + return false; + ALenum format = AL::to_al_format(source->getChannels(), source->getBitsPerSample()); + AL_CHECK(alBufferData(buffer, format, this->buffer, read, source->getSampleRate())); + return true; +} + +Speaker* ALStream::createSpeaker(bool loop) { + this->loop = loop; uint source = al->getFreeSource(); - // TODO: prepare source and enqueue buffers + if (source == 0) { + return nullptr; + } + for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) { + uint buffer = al->getFreeBuffer(); + if (!preloadBuffer(buffer, loop)) { + break; + } + AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); + } return new ALSpeaker(al, source, PRIORITY_HIGH); } @@ -65,14 +84,40 @@ speakerid_t ALStream::getSpeaker() const { } void ALStream::update(double delta) { - // TODO: implement + if (this->speaker == 0) { + return; + } + Speaker* speaker = audio::get(this->speaker); + if (speaker == nullptr) { + speaker = 0; + return; + } + ALSpeaker* alspeaker = dynamic_cast(speaker); + uint source = alspeaker->source; + uint processed = AL::getSourcei(source, AL_BUFFERS_PROCESSED); + + while (processed--) { + uint buffer; + AL_CHECK(alSourceUnqueueBuffers(source, 1, &buffer)); + unusedBuffers.push(buffer); + std::cout << "unqueue " << buffer << std::endl; + } + + if (!unusedBuffers.empty()) { + uint buffer = unusedBuffers.front(); + if (preloadBuffer(buffer, loop)) { + unusedBuffers.pop(); + std::cout << "queue " << buffer << std::endl; + AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); + } + } } void ALStream::setTime(duration_t time) { // TODO: implement } -ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority) : al(al), source(source), priority(priority) { +ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority) : al(al), priority(priority), source(source) { } ALSpeaker::~ALSpeaker() { diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.h index b37a602d..ca973f48 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -1,6 +1,7 @@ #ifndef SRC_AUDIO_AUDIO_H_ #define SRC_AUDIO_AUDIO_H_ +#include #include #include #include @@ -43,28 +44,38 @@ namespace audio { }; class ALStream : public Stream { + static inline constexpr size_t BUFFER_SIZE = 44100; + ALAudio* al; std::shared_ptr source; + std::queue unusedBuffers; speakerid_t speaker = 0; bool keepSource; + char buffer[BUFFER_SIZE]; + bool loop = false; + + bool preloadBuffer(uint buffer, bool loop); public: ALStream(ALAudio* al, std::shared_ptr source, bool keepSource); ~ALStream(); std::shared_ptr getSource() const override; void bindSpeaker(speakerid_t speaker) override; - Speaker* createSpeaker() override; + Speaker* createSpeaker(bool loop) override; speakerid_t getSpeaker() const override; void update(double delta) override; - void setTime(duration_t time) override; + void setTime(duration_t time) override; + + static inline constexpr uint STREAM_BUFFERS = 3; }; /// @brief AL source adapter class ALSpeaker : public Speaker { ALAudio* al; - uint source; int priority; public: + uint source; + ALSpeaker(ALAudio* al, uint source, int priority); ~ALSpeaker(); diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h index e2a61881..8d91ebd3 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.h @@ -35,25 +35,25 @@ namespace audio { } } - std::shared_ptr getSource() const { + std::shared_ptr getSource() const override { return source; } - void bindSpeaker(speakerid_t speaker) { + void bindSpeaker(speakerid_t speaker) override { } - Speaker* createSpeaker() { + Speaker* createSpeaker(bool loop) override{ return nullptr; } - speakerid_t getSpeaker() const { + speakerid_t getSpeaker() const override { return 0; } - void update(double delta) { + void update(double delta) override { } - void setTime(duration_t time) { + void setTime(duration_t time) override { } }; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 37b06b7d..b06043b2 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -13,6 +13,7 @@ namespace audio { static speakerid_t nextId = 1; static Backend* backend; static std::unordered_map> speakers; + static std::unordered_map> streams; } using namespace audio; @@ -200,6 +201,34 @@ speakerid_t audio::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()) { @@ -211,6 +240,10 @@ Speaker* audio::get(speakerid_t id) { 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); diff --git a/src/audio/audio.h b/src/audio/audio.h index ac6cf4b9..9e924946 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -118,8 +118,9 @@ namespace audio { /// @brief Create new speaker bound to the Stream /// and having high priority + /// @param loop is stream looped (required for correct buffers preload) /// @return speaker id or 0 - virtual Speaker* createSpeaker() = 0; + virtual Speaker* createSpeaker(bool loop) = 0; /// @brief Unbind previous speaker and bind new speaker to the stream /// @param speaker speaker id or 0 if all you need is unbind speaker @@ -331,6 +332,21 @@ namespace audio { int priority ); + /// @brief Play stream in the world + /// @param stream target stream + /// @param position stream world position + /// @param volume stream volume [0.0-1.0] + /// @param pitch stream pitch multiplier [0.0-...] + /// @param loop loop stream + /// @return speaker id or 0 + extern speakerid_t play( + std::shared_ptr stream, + glm::vec3 position, + float volume, + float pitch, + bool loop + ); + /// @brief Get speaker by id /// @param id speaker id /// @return speaker or nullptr diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index dfe60177..a420a93c 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -17,6 +17,7 @@ static inline const char* vorbis_error_message(int code) { case OV_EVERSION: return "vorbis version mismatch"; case OV_EBADHEADER: return "invalid Vorbis bitstream header"; case OV_EFAULT: return "internal logic fault"; + case OV_EINVAL: return "header couldn't be read or are corrupt"; default: return "unknown"; } @@ -86,20 +87,24 @@ public: if (closed) { return 0; } - int bitstream; + int bitstream = 0; long bytes = 0; size_t size = 0; do { do { bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream); if (bytes < 0) { - std::cerr << vorbis_error_message(bytes) << std::endl; + std::cerr << "ogg::load_pcm: " << vorbis_error_message(bytes) << " " << bytes << std::endl; continue; } size += bytes; bufferSize -= bytes; buffer += bytes; - } while (bytes > 0); + } while (bytes > 0 && bufferSize > 0); + + if (bufferSize == 0) { + break; + } if (loop) { seek(0); From 7c51e39dfe25f736a1e365e5592763fe5d1ad38a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Mar 2024 14:08:13 +0300 Subject: [PATCH 26/47] wav files streaming --- src/audio/audio.cpp | 4 +- src/coders/wav.cpp | 122 ++++++++++++++++++++++++++++++++++++++++++-- src/coders/wav.h | 2 + 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index b06043b2..5dc3cd58 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -121,7 +121,9 @@ Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { PCMStream* audio::openPCMStream(const fs::path& file) { std::string ext = file.extension().u8string(); - if (ext == ".ogg" || ext == ".OGG") { + 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"); diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index 578ddb21..ad3c1599 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "../audio/audio.h" @@ -27,7 +28,108 @@ std::int32_t convert_to_int(char* buffer, std::size_t len){ return a; } -audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { +/// @brief Seekable WAV-file PCM stream +class WavStream : public audio::PCMStream { + std::ifstream in; + uint channels; + uint bytesPerSample; + uint sampleRate; + size_t totalSize; + size_t totalSamples; + size_t initialPosition; +public: + WavStream( + std::ifstream in, + uint channels, + uint bitsPerSample, + uint sampleRate, + size_t size + ) : in(std::move(in)), + channels(channels), + bytesPerSample(bitsPerSample/8), + sampleRate(sampleRate), + totalSize(size) + { + totalSamples = totalSize / channels / bytesPerSample; + initialPosition = in.tellg(); + } + + size_t read(char* buffer, size_t bufferSize, bool loop) override { + if (!isOpen()) { + return 0; + } + long bytes = 0; + size_t size = 0; + do { + do { + in.read(buffer, bufferSize); + if (in.failbit) { + std::cerr << "Wav::load_pcm: I/O error ocurred" << std::endl; + continue; + } + bytes = in.gcount(); + size += bytes; + bufferSize -= bytes; + buffer += bytes; + } while (bytes > 0 && bufferSize > 0); + + if (bufferSize == 0) { + break; + } + + if (loop) { + seek(0); + } + if (bufferSize == 0) { + return size; + } + } while (loop); + return size; + } + + void close() override { + if (!isOpen()) + return; + in.close(); + } + + bool isOpen() const override { + return in.is_open(); + } + + size_t getTotalSamples() const override { + return totalSamples; + } + + audio::duration_t getTotalDuration() const override { + return totalSamples / static_cast(sampleRate); + } + + uint getChannels() const override { + return channels; + } + + uint getSampleRate() const override { + return sampleRate; + } + + uint getBitsPerSample() const override { + return bytesPerSample * 8; + } + + bool isSeekable() const override { + return true; + } + + void seek(size_t position) override { + if (!isOpen()) + return; + position %= totalSamples; + in.seekg(initialPosition + position * channels * bytesPerSample); + } +}; + +audio::PCMStream* wav::create_stream(const std::filesystem::path& file) { std::ifstream in(file, std::ios::binary); if(!in.is_open()){ throw std::runtime_error("could not to open file '"+file.u8string()+"'"); @@ -105,14 +207,24 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { if(in.fail()){ throw std::runtime_error("fail state set on the file"); } + return new WavStream(std::move(in), channels, bitsPerSample, sampleRate, size); +} + +audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { + std::unique_ptr stream(wav::create_stream(file)); + + size_t totalSamples = stream->getTotalSamples(); + uint channels = stream->getChannels(); + uint bitsPerSample = stream->getBitsPerSample(); + uint sampleRate = stream->getSampleRate(); std::vector data; - size_t totalSamples = size / (bitsPerSample / 8); if (!headerOnly) { + size_t size = stream->getTotalSamples() * + (stream->getBitsPerSample()/8) * + stream->getChannels(); data.resize(size); - if (!in.read(data.data(), size)) { - throw std::runtime_error("could not load wav data of '"+file.u8string()+"'"); - } + stream->read(data.data(), size, false); } return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate, true); } diff --git a/src/coders/wav.h b/src/coders/wav.h index 209d5c25..5a8a2095 100644 --- a/src/coders/wav.h +++ b/src/coders/wav.h @@ -5,10 +5,12 @@ namespace audio { struct PCM; + class PCMStream; } namespace wav { extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly); + extern audio::PCMStream* create_stream(const std::filesystem::path& file); } #endif // CODERS_WAV_H_ From 2fab1593c83f49ef735116ea9c3e5caf8e885e11 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Mar 2024 14:27:04 +0300 Subject: [PATCH 27/47] PCMStream interface update --- src/audio/AL/ALAudio.cpp | 2 +- src/audio/audio.cpp | 32 ++++++++++++++++++++++++++++++-- src/audio/audio.h | 6 ++++-- src/coders/ogg.cpp | 34 +++++++--------------------------- src/coders/wav.cpp | 37 ++++++++----------------------------- 5 files changed, 50 insertions(+), 61 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 1a12034a..464dd12a 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -46,7 +46,7 @@ std::shared_ptr ALStream::getSource() const { } bool ALStream::preloadBuffer(uint buffer, bool loop) { - size_t read = source->read(this->buffer, BUFFER_SIZE, loop); + size_t read = source->readFully(this->buffer, BUFFER_SIZE, loop); if (!read) return false; ALenum format = AL::to_al_format(source->getChannels(), source->getBitsPerSample()); diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 5dc3cd58..dc12f93c 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -18,6 +18,34 @@ namespace audio { using namespace audio; +size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) { + if (!isOpen()) { + return 0; + } + long bytes = 0; + size_t size = 0; + do { + do { + bytes = read(buffer, bufferSize); + size += bytes; + bufferSize -= bytes; + buffer += bytes; + } while (bytes > 0 && bufferSize > 0); + + if (bufferSize == 0) { + break; + } + + if (loop) { + seek(0); + } + if (bufferSize == 0) { + return size; + } + } while (loop); + return size; +} + /// @brief pcm source that does not initialize buffer class PCMVoidSource : public PCMStream { size_t totalSamples; @@ -33,11 +61,11 @@ public: seekable(seekable) {} - size_t read(char* buffer, size_t bufferSize, bool loop) override { + size_t read(char* buffer, size_t bufferSize) override { if (closed) { return 0; } - if (!seekable || loop) { + if (!seekable) { return bufferSize; } size_t n = std::min(bufferSize, totalSamples); diff --git a/src/audio/audio.h b/src/audio/audio.h index 9e924946..7b14c652 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -72,8 +72,10 @@ namespace audio { /// @param loop loop stream (seek to start when end reached) /// @return size of data received /// (always equals bufferSize if seekable and looped) - virtual size_t read(char* buffer, size_t bufferSize, bool loop)=0; - + virtual size_t readFully(char* buffer, size_t bufferSize, bool loop); + + virtual size_t read(char* buffer, size_t bufferSize) = 0; + /// @brief Close stream virtual void close()=0; diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index a420a93c..90ac0ef1 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -83,37 +83,17 @@ public: } } - size_t read(char* buffer, size_t bufferSize, bool loop) { + size_t read(char* buffer, size_t bufferSize) override { if (closed) { return 0; } int bitstream = 0; - long bytes = 0; - size_t size = 0; - do { - do { - bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream); - if (bytes < 0) { - std::cerr << "ogg::load_pcm: " << vorbis_error_message(bytes) << " " << bytes << std::endl; - continue; - } - size += bytes; - bufferSize -= bytes; - buffer += bytes; - } while (bytes > 0 && bufferSize > 0); - - if (bufferSize == 0) { - break; - } - - if (loop) { - seek(0); - } - if (bufferSize == 0) { - return size; - } - } while (loop); - return size; + long bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream); + if (bytes < 0) { + std::cerr << "ogg::load_pcm: " << vorbis_error_message(bytes) << " " << bytes << std::endl; + return 0; + } + return bytes; } void close() { diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index ad3c1599..c483b8b2 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -54,37 +54,16 @@ public: initialPosition = in.tellg(); } - size_t read(char* buffer, size_t bufferSize, bool loop) override { + size_t read(char* buffer, size_t bufferSize) override { if (!isOpen()) { return 0; } - long bytes = 0; - size_t size = 0; - do { - do { - in.read(buffer, bufferSize); - if (in.failbit) { - std::cerr << "Wav::load_pcm: I/O error ocurred" << std::endl; - continue; - } - bytes = in.gcount(); - size += bytes; - bufferSize -= bytes; - buffer += bytes; - } while (bytes > 0 && bufferSize > 0); - - if (bufferSize == 0) { - break; - } - - if (loop) { - seek(0); - } - if (bufferSize == 0) { - return size; - } - } while (loop); - return size; + in.read(buffer, bufferSize); + if (in.failbit) { + std::cerr << "Wav::load_pcm: I/O error ocurred" << std::endl; + return 0; + } + return in.gcount(); } void close() override { @@ -224,7 +203,7 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { (stream->getBitsPerSample()/8) * stream->getChannels(); data.resize(size); - stream->read(data.data(), size, false); + stream->readFully(data.data(), size, false); } return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate, true); } From 603546f642cabc81e7b554658871890cbd0017d0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Mar 2024 14:47:12 +0300 Subject: [PATCH 28/47] audio::playStream + speaker relative property --- src/audio/AL/ALAudio.cpp | 8 ++++++++ src/audio/AL/ALAudio.h | 3 +++ src/audio/audio.cpp | 16 ++++++++++++++++ src/audio/audio.h | 38 +++++++++++++++++++++++++++++++++++++- src/coders/ogg.cpp | 2 +- 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 464dd12a..c1798537 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -197,6 +197,14 @@ glm::vec3 ALSpeaker::getVelocity() const { return AL::getSource3f(source, AL_VELOCITY); } +void ALSpeaker::setRelative(bool relative) { + AL_CHECK(alSourcei(source, AL_SOURCE_RELATIVE, relative ? AL_TRUE : AL_FALSE)); +} + +bool ALSpeaker::isRelative() const { + return AL::getSourcei(source, AL_SOURCE_RELATIVE) == AL_TRUE; +} + int ALSpeaker::getPriority() const { return priority; } diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.h index ca973f48..f8d02c2c 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -103,6 +103,9 @@ namespace audio { void setVelocity(glm::vec3 vel) override; glm::vec3 getVelocity() const override; + void setRelative(bool relative) override; + bool isRelative() const override; + int getPriority() const override; }; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index dc12f93c..4cf0fdb5 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -208,6 +208,7 @@ void remove_lower_priority_speaker(int priority) { speakerid_t audio::play( Sound* sound, glm::vec3 position, + bool relative, float volume, float pitch, bool loop, @@ -227,6 +228,7 @@ speakerid_t audio::play( speaker->setVolume(volume); speaker->setPitch(pitch); speaker->setLoop(loop); + speaker->setRelative(relative); speaker->play(); return id; } @@ -234,6 +236,7 @@ speakerid_t audio::play( speakerid_t audio::play( std::shared_ptr stream, glm::vec3 position, + bool relative, float volume, float pitch, bool loop @@ -255,10 +258,23 @@ speakerid_t audio::play( speaker->setVolume(volume); speaker->setPitch(pitch); speaker->setLoop(false); + speaker->setRelative(relative); speaker->play(); return id; } +speakerid_t audio::playStream( + const fs::path& file, + glm::vec3 position, + bool relative, + float volume, + float pitch, + bool loop +) { + std::shared_ptr stream (openStream(file, false)); + return play(stream, position, relative, volume, pitch, loop); +} + Speaker* audio::get(speakerid_t id) { auto found = speakers.find(id); if (found == speakers.end()) { diff --git a/src/audio/audio.h b/src/audio/audio.h index 7b14c652..11fc49bc 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -234,10 +234,24 @@ namespace audio { /// @return speaker priority value virtual int getPriority() const = 0; + /// @brief Determines if the position is relative to the listener + /// @param relative true - relative to the listener (default: false) + virtual void setRelative(bool relative) = 0; + + /// @brief Determines if the position is relative to the listener + virtual bool isRelative() const = 0; + + /// @brief Check if speaker is playing + inline bool isPlaying() const { + return getState() == State::playing; + } + + /// @brief Check if speaker is paused inline bool isPaused() const { return getState() == State::paused; } + /// @brief Check if speaker is stopped inline bool isStopped() const { return getState() == State::stopped; } @@ -319,6 +333,7 @@ namespace audio { /// @brief Play 3D sound in the world /// @param sound target sound /// @param position sound world position + /// @param relative position speaker relative to listener /// @param volume sound volume [0.0-1.0] /// @param pitch sound pitch multiplier [0.0-...] /// @param loop loop sound @@ -328,15 +343,17 @@ namespace audio { extern speakerid_t play( Sound* sound, glm::vec3 position, + bool relative, float volume, float pitch, bool loop, int priority ); - /// @brief Play stream in the world + /// @brief Play stream /// @param stream target stream /// @param position stream world position + /// @param relative position speaker relative to listener /// @param volume stream volume [0.0-1.0] /// @param pitch stream pitch multiplier [0.0-...] /// @param loop loop stream @@ -344,6 +361,25 @@ namespace audio { extern speakerid_t play( std::shared_ptr stream, glm::vec3 position, + bool relative, + float volume, + float pitch, + bool loop + ); + + /// @brief Play stream from file + /// @param file audio file path + /// @param position stream world position + /// @param relative position speaker relative to listener + /// @param volume stream volume [0.0-1.0] + /// @param pitch stream pitch multiplier [0.0-...] + /// @param loop loop stream + /// @return speaker id or 0 + /// @return + extern speakerid_t playStream( + const fs::path& file, + glm::vec3 position, + bool relative, float volume, float pitch, bool loop diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index 90ac0ef1..a7e8568d 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -17,7 +17,7 @@ static inline const char* vorbis_error_message(int code) { case OV_EVERSION: return "vorbis version mismatch"; case OV_EBADHEADER: return "invalid Vorbis bitstream header"; case OV_EFAULT: return "internal logic fault"; - case OV_EINVAL: return "header couldn't be read or are corrupt"; + case OV_EINVAL: return "invalid read operation"; default: return "unknown"; } From 011bdd83974102fd3dce24cf569d92ea264c10db Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Mar 2024 19:03:27 +0300 Subject: [PATCH 29/47] wav streaming fix --- src/audio/audio.cpp | 3 +++ src/coders/wav.cpp | 43 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 4cf0fdb5..bf10547d 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -27,6 +27,9 @@ size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) { do { do { bytes = read(buffer, bufferSize); + if (bytes < 0) { + return size; + } size += bytes; bufferSize -= bytes; buffer += bytes; diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index c483b8b2..2ab78a7b 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -43,15 +43,16 @@ public: uint channels, uint bitsPerSample, uint sampleRate, - size_t size - ) : in(std::move(in)), + size_t size, + size_t initialPosition + ) : in(std::move(in)), channels(channels), bytesPerSample(bitsPerSample/8), sampleRate(sampleRate), totalSize(size) { totalSamples = totalSize / channels / bytesPerSample; - initialPosition = in.tellg(); + this->initialPosition = initialPosition; } size_t read(char* buffer, size_t bufferSize) override { @@ -59,10 +60,13 @@ public: return 0; } in.read(buffer, bufferSize); - if (in.failbit) { - std::cerr << "Wav::load_pcm: I/O error ocurred" << std::endl; + if (in.eof()) { return 0; } + if (in.fail()) { + std::cerr << "Wav::load_pcm: I/O error ocurred" << std::endl; + return -1; + } return in.gcount(); } @@ -104,7 +108,8 @@ public: if (!isOpen()) return; position %= totalSamples; - in.seekg(initialPosition + position * channels * bytesPerSample); + in.clear(); + in.seekg(initialPosition + position * channels * bytesPerSample, std::ios_base::beg); } }; @@ -114,7 +119,7 @@ audio::PCMStream* wav::create_stream(const std::filesystem::path& file) { throw std::runtime_error("could not to open file '"+file.u8string()+"'"); } - char buffer[4]; + char buffer[6]; // the RIFF if(!in.read(buffer, 4)){ throw std::runtime_error("could not to read RIFF"); @@ -164,12 +169,34 @@ audio::PCMStream* wav::create_stream(const std::filesystem::path& file) { throw std::runtime_error("could not read bits per sample"); } int bitsPerSample = convert_to_int(buffer, 2); + if (bitsPerSample >= 24) { + throw std::runtime_error(std::to_string(bitsPerSample)+" bit depth is not supported by OpenAL"); + } // data chunk header "data" if(!in.read(buffer, 4)){ throw std::runtime_error("could not read data chunk header"); } + + size_t initialOffset = 44; + // skip garbage in WAV + if (std::strncmp(buffer, "LIST", 4) == 0) { + // chunk size + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read comment chunk size"); + } + int chunkSize = convert_to_int(buffer, 4); + in.seekg(chunkSize, std::ios_base::cur); + + initialOffset += chunkSize + 4; + + if(!in.read(buffer, 4)){ + throw std::runtime_error("could not read data chunk header"); + } + } + if(std::strncmp(buffer, "data", 4) != 0){ + std::cerr << buffer << std::endl; throw std::runtime_error("file is not a valid WAVE file (doesn't have 'data' tag)"); } @@ -186,7 +213,7 @@ audio::PCMStream* wav::create_stream(const std::filesystem::path& file) { if(in.fail()){ throw std::runtime_error("fail state set on the file"); } - return new WavStream(std::move(in), channels, bitsPerSample, sampleRate, size); + return new WavStream(std::move(in), channels, bitsPerSample, sampleRate, size, initialOffset); } audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { From cd657a12341b4c3cfd24362460023ef527ca4e19 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Mar 2024 14:49:47 +0300 Subject: [PATCH 30/47] stream now restores speaker after long freezes --- src/audio/AL/ALAudio.cpp | 25 ++++++++++++++++++++++--- src/audio/AL/ALAudio.h | 2 ++ src/audio/audio.cpp | 3 ++- src/audio/audio.h | 3 +++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index c1798537..01d750a3 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -103,14 +103,25 @@ void ALStream::update(double delta) { std::cout << "unqueue " << buffer << std::endl; } + uint preloaded = 0; if (!unusedBuffers.empty()) { uint buffer = unusedBuffers.front(); if (preloadBuffer(buffer, loop)) { + preloaded++; unusedBuffers.pop(); std::cout << "queue " << buffer << std::endl; AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); } } + if (speaker->isStopped() && !speaker->isStoppedManually()) { + std::cout << "preloaded " << preloaded << std::endl; + if (preloaded) { + speaker->play(); + std::cout << "speaker restored" << std::endl; + } else { + speaker->stop(); + } + } } void ALStream::setTime(duration_t time) { @@ -160,6 +171,7 @@ void ALSpeaker::setLoop(bool loop) { } void ALSpeaker::play() { + stoppedManually = false; AL_CHECK(alSourcePlay(source)); } @@ -168,9 +180,16 @@ void ALSpeaker::pause() { } void ALSpeaker::stop() { - AL_CHECK(alSourceStop(source)); - al->freeSource(source); - source = 0; + stoppedManually = true; + if (source) { + AL_CHECK(alSourceStop(source)); + al->freeSource(source); + source = 0; + } +} + +bool ALSpeaker::isStoppedManually() const { + return stoppedManually; } duration_t ALSpeaker::getTime() const { diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.h index f8d02c2c..e2afd03b 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -73,6 +73,7 @@ namespace audio { class ALSpeaker : public Speaker { ALAudio* al; int priority; + bool stoppedManually = false; public: uint source; @@ -93,6 +94,7 @@ namespace audio { void play() override; void pause() override; void stop() override; + bool isStoppedManually() const override; duration_t getTime() const override; void setTime(duration_t time) override; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index bf10547d..738ff3f5 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -294,7 +294,8 @@ void audio::update(double delta) { } for (auto it = speakers.begin(); it != speakers.end();) { - if (it->second->isStopped()) { + if (it->second->isStoppedManually()) { + streams.erase(it->first); it = speakers.erase(it); } else { it++; diff --git a/src/audio/audio.h b/src/audio/audio.h index 11fc49bc..1bcb3512 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -205,6 +205,9 @@ namespace audio { /// @brief Stop and destroy speaker virtual void stop() = 0; + + /// @brief Check if the speaker has stopped by calling stop() + virtual bool isStoppedManually() const = 0; /// @brief Get current time position of playing audio /// @return time position in seconds From 43b21c664a34ae53993a9f3e863ffefd65859777 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Mar 2024 15:01:09 +0300 Subject: [PATCH 31/47] fixed clang warnings --- src/audio/AL/ALAudio.cpp | 4 ---- src/audio/audio.cpp | 2 +- src/coders/ogg.cpp | 18 +++++++++--------- src/frontend/screens.cpp | 4 ++-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 01d750a3..8cbf01e4 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -100,7 +100,6 @@ void ALStream::update(double delta) { uint buffer; AL_CHECK(alSourceUnqueueBuffers(source, 1, &buffer)); unusedBuffers.push(buffer); - std::cout << "unqueue " << buffer << std::endl; } uint preloaded = 0; @@ -109,15 +108,12 @@ void ALStream::update(double delta) { if (preloadBuffer(buffer, loop)) { preloaded++; unusedBuffers.pop(); - std::cout << "queue " << buffer << std::endl; AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); } } if (speaker->isStopped() && !speaker->isStoppedManually()) { - std::cout << "preloaded " << preloaded << std::endl; if (preloaded) { speaker->play(); - std::cout << "speaker restored" << std::endl; } else { speaker->stop(); } diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 738ff3f5..ecb5db2d 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -88,7 +88,7 @@ public: return totalSamples; } - duration_t getTotalDuration() const { + duration_t getTotalDuration() const override { return static_cast(totalSamples) / static_cast(sampleRate); } diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index a7e8568d..2be7fe95 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -96,43 +96,43 @@ public: return bytes; } - void close() { + void close() override { if (!closed) { ov_clear(&vf); closed = true; } } - bool isOpen() const { + bool isOpen() const override { return !closed; } - size_t getTotalSamples() const { + size_t getTotalSamples() const override { return totalSamples; } - duration_t getTotalDuration() const { + duration_t getTotalDuration() const override { return static_cast(totalSamples) / static_cast(sampleRate); } - uint getChannels() const { + uint getChannels() const override { return channels; } - uint getSampleRate() const { + uint getSampleRate() const override { return sampleRate; } - uint getBitsPerSample() const { + uint getBitsPerSample() const override { return 16; } - bool isSeekable() const { + bool isSeekable() const override { return seekable; } - void seek(size_t position) { + void seek(size_t position) override { if (!closed && seekable) { ov_raw_seek(&vf, position); } diff --git a/src/frontend/screens.cpp b/src/frontend/screens.cpp index 20e0c845..12f2dea1 100644 --- a/src/frontend/screens.cpp +++ b/src/frontend/screens.cpp @@ -150,10 +150,10 @@ void LevelScreen::update(float delta) { updateHotkeys(); } - auto camera = level->player->camera; + auto camera = this->level->player->camera; audio::setListener( camera->position, - level->player->hitbox->velocity, + this->level->player->hitbox->velocity, camera->position+camera->dir, camera->up ); From 3ce46496f19127038b1bb6bc07b5bb38a21edb0b Mon Sep 17 00:00:00 2001 From: Onran <100285264+Onran0@users.noreply.github.com> Date: Tue, 27 Feb 2024 02:59:43 +0900 Subject: [PATCH 32/47] conversion to/from bytes and data buffer modules --- res/modules/bit_converter.lua | 270 ++++++++++++++++++++++++++++++++++ res/modules/data_buffer.lua | 148 +++++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 res/modules/bit_converter.lua create mode 100644 res/modules/data_buffer.lua diff --git a/res/modules/bit_converter.lua b/res/modules/bit_converter.lua new file mode 100644 index 00000000..e6a49972 --- /dev/null +++ b/res/modules/bit_converter.lua @@ -0,0 +1,270 @@ +local bit_converter = { } + +local MAX_UINT16 = 65535 +local MIN_UINT16 = 0 +local MAX_UINT32 = 4294967295 +local MIN_UINT32 = 0 +local MAX_UINT64 = 18446744073709551615 +local MIN_UINT64 = 0 + +local MAX_INT16 = 32767 +local MIN_INT16 = -32768 +local MAX_INT32 = 2147483647 +local MIN_INT32 = -2147483648 +local MAX_INT64 = 9223372036854775807 +local MIN_INT64 = -9223372036854775808 + +local function intToByte(num) + return bit.band(num, 0xFF) +end + +local function reverse(tab) + for i = 1, math.floor(#tab, 2), 1 do + tab[i], tab[#tab-i+1] = tab[#tab-i+1], tab[i] + end + return tab +end + +function bit_converter.string_to_bytes(str) + local bytes = { } + + local len = string.len(str) + + local lenBytes = bit_converter.int32_to_bytes(len) + + for i = 1, #lenBytes do + bytes[i] = lenBytes[i] + end + + for i = 1, len do + bytes[#bytes + 1] = string.byte(string.sub(str, i, i)) + end + + return bytes +end + +function bit_converter.bool_to_byte(bool) + return bool and 1 or 0 +end + +-- Credits to Iryont + +local function floatOrDoubleToBytes(val, opt) + local sign = 0 + + if val < 0 then + sign = 1 + val = -val + end + + local mantissa, exponent = math.frexp(val) + if val == 0 then + mantissa = 0 + exponent = 0 + else + mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24) + exponent = exponent + ((opt == 'd') and 1022 or 126) + end + + local bytes = {} + if opt == 'd' then + val = mantissa + for i = 1, 6 do + table.insert(bytes, math.floor(val) % (2 ^ 8)) + val = math.floor(val / (2 ^ 8)) + end + else + table.insert(bytes, math.floor(mantissa) % (2 ^ 8)) + val = math.floor(mantissa / (2 ^ 8)) + table.insert(bytes, math.floor(val) % (2 ^ 8)) + val = math.floor(val / (2 ^ 8)) + end + + table.insert(bytes, math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8)) + val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8)) + table.insert(bytes, math.floor(sign * 128 + val) % (2 ^ 8)) + val = math.floor((sign * 128 + val) / (2 ^ 8)) + + if not endianness then + reverse(bytes) + end + return bytes +end + +local function bytesToFloatOrDouble(bytes, opt) + local n = (opt == 'd') and 8 or 4 + + if not endianness then + reverse(bytes) + end + + local sign = 1 + local mantissa = bytes[n - 1] % ((opt == 'd') and 16 or 128) + for i = n - 2, 1, -1 do + mantissa = mantissa * (2 ^ 8) + bytes[i] + end + + if bytes[n] > 127 then + sign = -1 + end + + local exponent = (bytes[n] % 128) * ((opt == 'd') and 16 or 2) + math.floor(bytes[n - 1] / ((opt == 'd') and 16 or 128)) + if exponent == 0 then + return 0.0 + else + mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign + return math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127)) + end +end + +-- + +function bit_converter.single_to_bytes(float) + return floatOrDoubleToBytes(float, 'f') +end + +function bit_converter.double_to_bytes(double) + return floatOrDoubleToBytes(double, 'd') +end + +function bit_converter.int64_to_bytes(int) + if int > MAX_INT64 or int < MIN_INT64 then + error("invalid int64") + end + + return { + intToByte(bit.rshift(int, 56)), + intToByte(bit.rshift(int, 48)), + intToByte(bit.rshift(int, 40)), + intToByte(bit.rshift(int, 32)), + intToByte(bit.rshift(int, 24)), + intToByte(bit.rshift(int, 16)), + intToByte(bit.rshift(int, 8)), + intToByte(int) + } +end + +function bit_converter.uint32_to_bytes(int) + if int > MAX_UINT32 or int < MIN_UINT32 then + error("invalid uint32") + end + + return { + intToByte(bit.rshift(int, 24)), + intToByte(bit.rshift(int, 16)), + intToByte(bit.rshift(int, 8)), + intToByte(int) + } +end + +function bit_converter.uint16_to_bytes(int) + if int > MAX_UINT16 or int < MIN_UINT16 then + error("invalid uint16") + end + + return { + intToByte(bit.rshift(int, 8)), + intToByte(int) + } +end + +function bit_converter.int32_to_bytes(int) + if int > MAX_INT32 or int < MIN_INT32 then + error("invalid int32") + end + + return bit_converter.uint32_to_bytes(int + MAX_INT32) +end + +function bit_converter.int16_to_bytes(int) + if int > MAX_INT16 or int < MIN_INT16 then + error("invalid int16") + end + + return bit_converter.uint16_to_bytes(int + MAX_INT16) +end + +function bit_converter.bytes_to_single(bytes) + return bytesToFloatOrDouble(bytes, 'f') +end + +function bit_converter.bytes_to_double(bytes) + return bytesToFloatOrDouble(bytes, 'd') +end + +function bit_converter.bytes_to_string(bytes) + local len = bit_converter.bytes_to_int32({ bytes[1], bytes[2], bytes[3], bytes[4]}) + + local str = "" + + for i = 1, len do + str = str..string.char(bytes[i]) + end + + return str +end + +function bit_converter.byte_to_bool(byte) + return byte ~= 0 +end + +function bit_converter.bytes_to_float(bytes) + if #bytes < 8 then + error("eof") + end + error("unsupported operation") +end + +function bit_converter.bytes_to_int64(bytes) + if #bytes < 8 then + error("eof") + end + return + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.lshift(bytes[1], 56), + bit.lshift(bytes[2], 48)), + bit.lshift(bytes[3], 40)), + bit.lshift(bytes[4], 32)), + bit.lshift(bytes[5], 24)), + bit.lshift(bit.band(bytes[6], 0xFF), 16)), + bit.lshift(bit.band(bytes[7], 0xFF), 8)),bit.band(bytes[8], 0xFF)) +end + +function bit_converter.bytes_to_uint32(bytes) + if #bytes < 4 then + error("eof") + end + return + bit.bor( + bit.bor( + bit.bor( + bit.lshift(bytes[1], 24), + bit.lshift(bytes[2], 16)), + bit.lshift(bytes[3], 8)),bytes[4]) +end + +function bit_converter.bytes_to_uint16(bytes) + if #bytes < 2 then + error("eof") + end + return + bit.bor( + bit.lshift(bytes[1], 8), + bytes[2], 0) +end + +function bit_converter.bytes_to_int32(bytes) + return bit_converter.bytes_to_uint32(bytes) - MAX_INT32 +end + +function bit_converter.bytes_to_int16(bytes) + return bit_converter.bytes_to_uint16(bytes) - MAX_INT16 +end + +return bit_converter \ No newline at end of file diff --git a/res/modules/data_buffer.lua b/res/modules/data_buffer.lua new file mode 100644 index 00000000..93576a12 --- /dev/null +++ b/res/modules/data_buffer.lua @@ -0,0 +1,148 @@ +local bit_converter = require "core:bit_converter" + +-- Data buffer + +local data_buffer = { } + +function data_buffer.__call(bytes) + return setmetatable({ pos = 1, bytes = bytes or { } }, { __index = data_buffer }) +end + +-- Push functions + +function data_buffer:put_byte(byte) + if byte < 0 or byte > 255 then + error("invalid byte") + end + + self.bytes[self.pos] = byte + + self.pos = self.pos + 1 +end + +function data_buffer:put_bytes(bytes) + for i = 1, #bytes do + self:put_byte(bytes[i]) + end +end + +function data_buffer:put_single(single) + self:put_bytes(bit_converter.single_to_bytes(single)) +end + +function data_buffer:put_double(double) + self:put_bytes(bit_converter.double_to_bytes(double)) +end + +function data_buffer:put_string(str) + self:put_bytes(bit_converter.string_to_bytes(str)) +end + +function data_buffer:put_bool(bool) + self:put_byte(bit_converter.bool_to_byte(bool)) +end + +function data_buffer:put_uint16(uint16) + self:put_bytes(bit_converter.uint16_to_bytes(uint16)) +end + +function data_buffer:put_uint32(uint32) + self:put_bytes(bit_converter.uint32_to_bytes(uint32)) +end + +function data_buffer:put_int16(int16) + self:put_bytes(bit_converter.int16_to_bytes(int16)) +end + +function data_buffer:put_int32(int32) + self:put_bytes(bit_converter.int32_to_bytes(int32)) +end + +function data_buffer:put_int64(int64) + self:put_bytes(bit_converter.int64_to_bytes(int64)) +end + +-- Get functions + +function data_buffer:get_byte() + local byte = self.bytes[self.pos] + self.pos = self.pos + 1 + return byte +end + +function data_buffer:get_single() + return bit_converter.bytes_to_single(self:get_bytes(4)) +end + +function data_buffer:get_double() + return bit_converter.bytes_to_double(self:get_bytes(8)) +end + +function data_buffer:get_string() + local len = self:get_bytes(4) + local str = self:get_bytes(bit_converter.bytes_to_int32(len)) + local bytes = { } + + for i = 1, #len do + bytes[i] = len[i] + end + + for i = 1, #str do + bytes[#bytes + 1] = str[i] + end + + return bit_converter.bytes_to_string(bytes) +end + +function data_buffer:get_bool() + return bit_converter.byte_to_bool(self:get_byte()) +end + +function data_buffer:get_int16() + return bit_converter.bytes_to_int16(self:get_bytes(2)) +end + +function data_buffer:get_int32() + return bit_converter.bytes_to_int32(self:get_bytes(4)) +end + +function data_buffer:get_int64() + return bit_converter.bytes_to_int64(self:get_bytes(8)) +end + +function data_buffer:size() + return #self.bytes +end + +function data_buffer:get_bytes(len) + if len == nil then + return self.bytes + else + local bytes = { } + + for i = 1, n do + bytes[i] = self:get_byte() + end + + return bytes + end +end + +function data_buffer:set_position(pos) + self.pos = pos +end + +function data_buffer:set_bytes(bytes) + for i = 1, #bytes do + local byte = bytes[i] + if byte < 0 or byte > 255 then + error("invalid byte") + end + end + + self.bytes = bytes +end + +setmetatable(data_buffer, data_buffer) + +return data_buffer \ No newline at end of file From 1e6babd2db648d8258e007e8b151496a95aa9ec0 Mon Sep 17 00:00:00 2001 From: Onran <100285264+Onran0@users.noreply.github.com> Date: Tue, 27 Feb 2024 03:54:04 +0900 Subject: [PATCH 33/47] file.write_bytes fix --- src/logic/scripting/lua/api_lua.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/api_lua.cpp b/src/logic/scripting/lua/api_lua.cpp index aaa490be..a7c02707 100644 --- a/src/logic/scripting/lua/api_lua.cpp +++ b/src/logic/scripting/lua/api_lua.cpp @@ -140,6 +140,10 @@ int l_file_write_bytes(lua_State* L) { int i = 1; while(lua_next(L, bytesIndex) != 0) { + if(i == len) { + break; + } + if(lua_isnumber(L, -1)) { const int byte = lua_tointeger(L, -1); @@ -153,7 +157,7 @@ int l_file_write_bytes(lua_State* L) { i++; } else { - return luaL_error(L, "number expected at index '%i'", i); + return luaL_error(L, "number expected at index '%i' (got %s)", i, lua_typename(L, lua_type(L, -1))); } } From 33cfc3c20808cb3fb3a3e88729b4258be56402fc Mon Sep 17 00:00:00 2001 From: Onran <100285264+Onran0@users.noreply.github.com> Date: Tue, 27 Feb 2024 03:54:09 +0900 Subject: [PATCH 34/47] Add files via upload From 72e3f49d4218ce7fd2dc02f569802a1e9951cfb6 Mon Sep 17 00:00:00 2001 From: Onran <100285264+Onran0@users.noreply.github.com> Date: Tue, 27 Feb 2024 03:54:14 +0900 Subject: [PATCH 35/47] file.write_bytes fix From 9c66d876995ed8b994888e99b9fdab365d62f299 Mon Sep 17 00:00:00 2001 From: Onran <100285264+Onran0@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:44:22 +0900 Subject: [PATCH 36/47] conversion to/from bytes and data buffer modules --- res/modules/bit_converter.lua | 82 +++++++++++++++--------------- res/modules/data_buffer.lua | 96 +++++++++++++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 46 deletions(-) diff --git a/res/modules/bit_converter.lua b/res/modules/bit_converter.lua index e6a49972..38e69aa7 100644 --- a/res/modules/bit_converter.lua +++ b/res/modules/bit_converter.lua @@ -4,8 +4,6 @@ local MAX_UINT16 = 65535 local MIN_UINT16 = 0 local MAX_UINT32 = 4294967295 local MIN_UINT32 = 0 -local MAX_UINT64 = 18446744073709551615 -local MIN_UINT64 = 0 local MAX_INT16 = 32767 local MIN_INT16 = -32768 @@ -30,7 +28,7 @@ function bit_converter.string_to_bytes(str) local len = string.len(str) - local lenBytes = bit_converter.int32_to_bytes(len) + local lenBytes = bit_converter.uint16_to_bytes(len) for i = 1, #lenBytes do bytes[i] = lenBytes[i] @@ -127,23 +125,6 @@ function bit_converter.double_to_bytes(double) return floatOrDoubleToBytes(double, 'd') end -function bit_converter.int64_to_bytes(int) - if int > MAX_INT64 or int < MIN_INT64 then - error("invalid int64") - end - - return { - intToByte(bit.rshift(int, 56)), - intToByte(bit.rshift(int, 48)), - intToByte(bit.rshift(int, 40)), - intToByte(bit.rshift(int, 32)), - intToByte(bit.rshift(int, 24)), - intToByte(bit.rshift(int, 16)), - intToByte(bit.rshift(int, 8)), - intToByte(int) - } -end - function bit_converter.uint32_to_bytes(int) if int > MAX_UINT32 or int < MIN_UINT32 then error("invalid uint32") @@ -168,6 +149,23 @@ function bit_converter.uint16_to_bytes(int) } end +function bit_converter.int64_to_bytes(int) + if int > MAX_INT64 or int < MIN_INT64 then + error("invalid int64") + end + + return { + intToByte(bit.rshift(int, 56)), + intToByte(bit.rshift(int, 48)), + intToByte(bit.rshift(int, 40)), + intToByte(bit.rshift(int, 32)), + intToByte(bit.rshift(int, 24)), + intToByte(bit.rshift(int, 16)), + intToByte(bit.rshift(int, 8)), + intToByte(int) + } +end + function bit_converter.int32_to_bytes(int) if int > MAX_INT32 or int < MIN_INT32 then error("invalid int32") @@ -193,7 +191,7 @@ function bit_converter.bytes_to_double(bytes) end function bit_converter.bytes_to_string(bytes) - local len = bit_converter.bytes_to_int32({ bytes[1], bytes[2], bytes[3], bytes[4]}) + local len = bit_converter.bytes_to_uint16({ bytes[1], bytes[2] }) local str = "" @@ -215,27 +213,6 @@ function bit_converter.bytes_to_float(bytes) error("unsupported operation") end -function bit_converter.bytes_to_int64(bytes) - if #bytes < 8 then - error("eof") - end - return - bit.bor( - bit.bor( - bit.bor( - bit.bor( - bit.bor( - bit.bor( - bit.bor( - bit.lshift(bytes[1], 56), - bit.lshift(bytes[2], 48)), - bit.lshift(bytes[3], 40)), - bit.lshift(bytes[4], 32)), - bit.lshift(bytes[5], 24)), - bit.lshift(bit.band(bytes[6], 0xFF), 16)), - bit.lshift(bit.band(bytes[7], 0xFF), 8)),bit.band(bytes[8], 0xFF)) -end - function bit_converter.bytes_to_uint32(bytes) if #bytes < 4 then error("eof") @@ -259,6 +236,27 @@ function bit_converter.bytes_to_uint16(bytes) bytes[2], 0) end +function bit_converter.bytes_to_int64(bytes) + if #bytes < 8 then + error("eof") + end + return + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.bor( + bit.lshift(bytes[1], 56), + bit.lshift(bytes[2], 48)), + bit.lshift(bytes[3], 40)), + bit.lshift(bytes[4], 32)), + bit.lshift(bytes[5], 24)), + bit.lshift(bit.band(bytes[6], 0xFF), 16)), + bit.lshift(bit.band(bytes[7], 0xFF), 8)),bit.band(bytes[8], 0xFF)) +end + function bit_converter.bytes_to_int32(bytes) return bit_converter.bytes_to_uint32(bytes) - MAX_INT32 end diff --git a/res/modules/data_buffer.lua b/res/modules/data_buffer.lua index 93576a12..002c8ea1 100644 --- a/res/modules/data_buffer.lua +++ b/res/modules/data_buffer.lua @@ -1,5 +1,25 @@ local bit_converter = require "core:bit_converter" +local MAX_UINT16 = 65535 +local MIN_UINT16 = 0 +local MAX_UINT32 = 4294967295 +local MIN_UINT32 = 0 + +local MAX_INT16 = 32767 +local MIN_INT16 = -32768 +local MAX_INT32 = 2147483647 +local MIN_INT32 = -2147483648 +local MAX_INT64 = 9223372036854775807 +local MIN_INT64 = -9223372036854775808 + +local TYPE_ZERO = 0 +local TYPE_UINT16 = 1 +local TYPE_UINT32 = 2 +local TYPE_INT16 = 3 +local TYPE_INT32 = 4 +local TYPE_INT64 = 5 +local TYPE_DOUBLE = 6 + -- Data buffer local data_buffer = { } @@ -62,6 +82,44 @@ function data_buffer:put_int64(int64) self:put_bytes(bit_converter.int64_to_bytes(int64)) end +function data_buffer:put_number(num) + local bytes + local type + + if math.floor(num) ~= num then + type = TYPE_DOUBLE + bytes = bit_converter.double_to_bytes(num) + elseif num == 0 then + type = TYPE_ZERO + bytes = { } + elseif num > 0 then + if num <= MAX_UINT16 then + type = TYPE_UINT16 + bytes = bit_converter.uint16_to_bytes(num) + elseif num <= MAX_UINT32 then + type = TYPE_UINT32 + bytes = bit_converter.uint32_to_bytes(num) + elseif num <= MAX_INT64 then + type = TYPE_INT64 + bytes = bit_converter.int64_to_bytes(num) + end + elseif num < 0 then + if num >= MIN_INT16 then + type = TYPE_INT16 + bytes = bit_converter.int16_to_bytes(num) + elseif num >= MIN_INT32 then + type = TYPE_INT32 + bytes = bit_converter.int32_to_bytes(num) + elseif num >= MIN_INT64 then + type = TYPE_INT64 + bytes = bit_converter.int64_to_bytes(num) + end + end + + self:put_byte(type) + self:put_bytes(bytes) +end + -- Get functions function data_buffer:get_byte() @@ -70,6 +128,28 @@ function data_buffer:get_byte() return byte end +function data_buffer:get_number() + local type = self:get_byte() + + if type == TYPE_ZERO then + return 0 + elseif type == TYPE_UINT16 then + return self:get_uint16() + elseif type == TYPE_UINT32 then + return self:get_uint32() + elseif type == TYPE_INT16 then + return self:get_int16() + elseif type == TYPE_INT32 then + return self:get_int32() + elseif type == TYPE_INT64 then + return self:get_int64() + elseif type == TYPE_DOUBLE then + return self:get_double() + else + error("unknown lua number type: "..type) + end +end + function data_buffer:get_single() return bit_converter.bytes_to_single(self:get_bytes(4)) end @@ -79,8 +159,8 @@ function data_buffer:get_double() end function data_buffer:get_string() - local len = self:get_bytes(4) - local str = self:get_bytes(bit_converter.bytes_to_int32(len)) + local len = self:get_bytes(2) + local str = self:get_bytes(bit_converter.bytes_to_uint16(len)) local bytes = { } for i = 1, #len do @@ -98,6 +178,14 @@ function data_buffer:get_bool() return bit_converter.byte_to_bool(self:get_byte()) end +function data_buffer:get_uint16() + return bit_converter.bytes_to_uint16(self:get_bytes(2)) +end + +function data_buffer:get_uint32() + return bit_converter.bytes_to_uint32(self:get_bytes(4)) +end + function data_buffer:get_int16() return bit_converter.bytes_to_int16(self:get_bytes(2)) end @@ -114,8 +202,8 @@ function data_buffer:size() return #self.bytes end -function data_buffer:get_bytes(len) - if len == nil then +function data_buffer:get_bytes(n) + if n == nil then return self.bytes else local bytes = { } From ed1b660dcc82ce291fc4a860ce3ca0ea0f02bbb7 Mon Sep 17 00:00:00 2001 From: Onran <100285264+Onran0@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:45:38 +0900 Subject: [PATCH 37/47] file.write_bytes fix --- src/logic/scripting/lua/api_lua.cpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/logic/scripting/lua/api_lua.cpp b/src/logic/scripting/lua/api_lua.cpp index a7c02707..df1b18ef 100644 --- a/src/logic/scripting/lua/api_lua.cpp +++ b/src/logic/scripting/lua/api_lua.cpp @@ -140,25 +140,21 @@ int l_file_write_bytes(lua_State* L) { int i = 1; while(lua_next(L, bytesIndex) != 0) { - if(i == len) { + if(i >= len) { break; } - if(lua_isnumber(L, -1)) { - const int byte = lua_tointeger(L, -1); + const int byte = lua_tointeger(L, -1); - if(byte < 0 || byte > 255) { - return luaL_error(L, "byte '%i' at index '%i' is less than 0 or greater than 255", byte, i); - } - - bytes.push_back(byte); - - lua_pop(L, 1); - - i++; - } else { - return luaL_error(L, "number expected at index '%i' (got %s)", i, lua_typename(L, lua_type(L, -1))); + if(byte < 0 || byte > 255) { + return luaL_error(L, "byte '%i' at index '%i' is less than 0 or greater than 255", byte, i); } + + bytes.push_back(byte); + + lua_pop(L, 1); + + i++; } lua_pushboolean(L, files::write_bytes(path, &bytes[0], len)); From 892aaddd2c051a0497d36631aff5f91a1ac9d0cc Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 27 Feb 2024 23:49:30 +0300 Subject: [PATCH 38/47] fix --- src/frontend/gui/controls.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/gui/controls.cpp b/src/frontend/gui/controls.cpp index cc058f19..85cb7568 100644 --- a/src/frontend/gui/controls.cpp +++ b/src/frontend/gui/controls.cpp @@ -569,6 +569,7 @@ void TextBox::focus(GUI* gui) { if (onEditStart){ setCaret(input.size()); onEditStart(); + resetSelection(); } } From f64802022e28184508e89c313600738dd78f59ee Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 28 Feb 2024 16:39:41 +0300 Subject: [PATCH 39/47] removed unused file --- res/fonts/font.png | Bin 2027 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 res/fonts/font.png diff --git a/res/fonts/font.png b/res/fonts/font.png deleted file mode 100644 index 3278f04cf6ece29ea3c3b9f7ed7d042702092dc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2027 zcmVxr^0dAyo)a6&VPm%%iUV-rFdvpNU_C=mu{Y!%* zD^l3!tayrwc60_-KeP3XE;Av7RLxKp{ig$fS>tIPU?jlV&k_mXey)@-JETyg^P}G* zI!d(vKa=(+cgIIf)bdVXD~T=@3M*hxOblR#poxf4hA67w0gf5m)c{6+gD`EK06yy! zFt|VS$eL^lqJ>dHUo8PL0Ehs@MTA_{WA%O|=`vjZ0DxE`q62U@aCZQ^s|QLVy-vYX zC}|99=VH>bJ94A;7b%<5`3DjiSu4R|$0L7NeIIGt(Ztja07SKPL=R#25)|({nY(*F zBFr#WS#`CKQG#C$fUE>S2Oky1ECARE;QpQ-N4Ers5=AIcRSkd+5};iAtk&p^TNk1u zxOIR>17ODzz*+&!5}>jos`~c?-D6Vs0a7VptXC__OH zau74I(kHNkBw*VAj}ql#)S_BGawL72~$U zJ2RP+PPX%%JVC}rXI#Qu9zPkU(j8WYBfroIVb|fr?q>_t0T!}*6&dU5ZUVsl8L`VV zGf+JMigSA`)2Om?7=WT)(P02wZK!lS4__fdDsT<~Ov%$__1>8GsHFz|k%T03}PnX#iLaAkCBk zrl5*LXa@jC1dkm6h{P9A)$eftxTA?>cIb!OuXipXk-xKX3|Ssx(xkAGpJoCT5@bhy zuCsye@b_&FhMWw5PW5l2-MhrdodU+XT_ZY#f{DDVzfuAy975Rk%Mq>IJ#<`&5+$-! z#LCKH1C>ejW$l{pq2 z#T^H)!>wGM?+sLxPe~|M0EMc%c@+b^qkb;3h;CmOw{X|{4!dn`08mQqE+(w%ef90; z)e<0Nr6_Oi>_+8g+vhOTssWIyOSc)Uc;BIY?E&!g{p{JVH+GUD9zVkaFm`m5CYFg< zpzl>glw|^BH(5^P8FqLSId!1hamr}1F28iAUwd~YG_%Gd676RoLoC{VO4f1L_pHzTDsTF=8TQKgKNmF|ID$sNU-ma9)MK9 z8D~)WJsV?abylAh#R`DZlc3{nU!;NPrB6{{M3i60df=(;I5ujqyB+pv-o9OuM->Xop z2&cfjUzM$r5|Or{l7h{jIv$=?!mh5CyAziKfQA6*E&(!T7BT?Hy@;6s`rag0{#zwL zboQSh0W!zX7zw~kgvUt$wO>9v1KkopQ9q{sFb{CL4#3VJHh;H{LQA-(TLA-ri*nzh z0}KEH0B#g_mH^djbanj(00V#lz^egJy`AbtuA+QqV9+&~{e8In0l)wty<3$% zAQ2q^=9Eyje8kEa32@L190{-}|7%WBJDZgN-;HS}kKzW5Obg#(hE(nbqV*d93;+fI z1AvDBVCVirxAHTmpCV zT0dAB?QT!1Z1=AAbDR3CW(S)`#-82THaG834uT93!gOL*L?g;#MiKh?|6L$haTIs$ zDs}UUYMiWuVEBSa2O{8h13)2V;U%oA1Yo%K&g(G(fEIfIJ9Ge-L&%EjPyrxgB}4&$ z`XU^u1vB2V(KWQ9Z98{L<@r0kc$5I>o{rn5?PT*lrQ Date: Thu, 29 Feb 2024 13:24:43 +0300 Subject: [PATCH 40/47] warning fix --- src/logic/scripting/lua/api_lua.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/api_lua.cpp b/src/logic/scripting/lua/api_lua.cpp index df1b18ef..258e9612 100644 --- a/src/logic/scripting/lua/api_lua.cpp +++ b/src/logic/scripting/lua/api_lua.cpp @@ -137,7 +137,7 @@ int l_file_write_bytes(lua_State* L) { bytesIndex--; - int i = 1; + size_t i = 1; while(lua_next(L, bytesIndex) != 0) { if(i >= len) { From 6f62ddea4ba14989ec7a1d1e111f5b46a46b0ef5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 23:47:07 +0300 Subject: [PATCH 41/47] contentpack remove feature WIP --- res/shaders/lib/commons.glsl | 3 +- res/texts/en_US.txt | 1 + res/texts/ru_RU.txt | 2 + res/textures/gui/cross.png | Bin 0 -> 6275 bytes src/assets/AssetsLoader.cpp | 4 +- src/content/Content.cpp | 14 +- src/content/Content.h | 10 +- src/content/ContentLoader.cpp | 5 + src/content/ContentPack.cpp | 2 +- src/content/ContentPack.h | 20 +- src/files/WorldFiles.cpp | 484 +++++++++++++++++---------------- src/files/WorldFiles.h | 139 +++++----- src/frontend/WorldRenderer.cpp | 1 + src/frontend/menu.cpp | 69 +++-- src/frontend/menu.h | 2 + src/frontend/screens.cpp | 25 +- src/window/Events.cpp | 141 +++++----- src/window/Events.h | 52 ++-- 18 files changed, 541 insertions(+), 433 deletions(-) create mode 100644 res/textures/gui/cross.png diff --git a/res/shaders/lib/commons.glsl b/res/shaders/lib/commons.glsl index 291c4cd8..f2f1b3bc 100644 --- a/res/shaders/lib/commons.glsl +++ b/res/shaders/lib/commons.glsl @@ -1,4 +1,5 @@ -// Example of a GLSL library +#define PI 3.1415926535897932384626433832795 +#define PI2 (PI*2) vec4 decompress_light(float compressed_light) { vec4 result; diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 26ade5db..99b97604 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -1,6 +1,7 @@ # Menu menu.missing-content=Missing Content! world.convert-request=Content indices have changed! Convert world files? +pack.remove-confirm=Do you want to erase all pack content from the world forever? error.pack-not-found=Could not to find pack error.dependency-not-found=Dependency pack is not found world.delete-confirm=Do you want to delete world forever? diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index caa5f607..7c03f37b 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -10,6 +10,8 @@ Add=Добавить error.pack-not-found=Не удалось найти пакет error.dependency-not-found=Используемая зависимость не найдена +pack.remove-confirm=Удалить весь поставляемый паком контент из мира (безвозвратно)? + # Меню menu.New World=Новый Мир menu.Quit=Выход diff --git a/res/textures/gui/cross.png b/res/textures/gui/cross.png new file mode 100644 index 0000000000000000000000000000000000000000..2656b8bc7bc270faf119f39469090114fde92617 GIT binary patch literal 6275 zcmeHLX;>528VI*+?cNYQ3(gplH=v zEh6rfRzayt6>MDq_ocRKi-;oCUiG?GTP=v(GXW7=uf0B9e#!F$a?W|b_uIcSOipZc zL{~R&HwJ^zRT&u;2mW@aKQ7MTx9@{yjKO$g+vJ2KDh{(TO=hD;t0S4zBooOb?OF|k zVZTtkJY&fZgFV8VGkR@*v;X}uZ#l>FgLWU^=ld8|UWhJ;j;ijS?6TJHsv*C*vF3U= zHdacEoi}un(Ns+=B|O$8mq-hBx9lasCj`5fe*1J7Gwxy8lLh}ecloOB&XCMQM{2!t z$*FMdzR0YdzP=vC?%GF@+@j5EcX-DokGnUK$*DnxR+-APwc{GfZue&kf1W!BnR*B0fA~6vy%*+gF)GHr&5f@!^w`4`y7jG$KG9xk(;kYw?%cfQi_^Hdlo9~E2 zO}?fR9=cHRoSd{l*8$qcHgP02fZVec(=>Y@)CtnoF8hg@%Ot&^mcA;SZ^Yzv2lJv! zg4cFtUfd-$`|VGivR5*?bExoO-_irRPpT@f+D{f2Mo2;ed-^n7OEML0cAD*y5hSW` z>vU+lruxuSuHlT*-uUgL5{{XE@si>vsjrGCpaLFLt_v4xg=i~KXbe%GEKQ`1W~eq@^%<$m7xwuFDF=qU4CuZrld z@$&DZOq>BF=o@gUNBvsQkYOXzG_2IXvuCWD0N3bjPx}o?!Le0dE*Czo;$Nz6^0OVz z+tM^|rS59c`bKB*&Fj%enyzMr6#ueS1rMJU8$3r=6rTBH4fpU6_|c*Z*9tGKGM$WF zsqRtwhOZN07@IBv2sBB;E+ zc0F0vbGm!bhJPP;|4wbOf7ya-ikRgybGBceTXjmm{NpK88)l;GH_hME_}S=&{W;}P zVp)cwcjLIz=-x|@O%tylbrqi&)37jj(;W8W2VZ|XcUxBC!#+t7u5#av5Audi{qU5~ zY3+B{9;S3&^M%_3&#l+LWKFx{^W~@&tItvm6Yi|CJzN=c`poc$H!JM?I}cZN%koKW z96PwM--YqMyeFmE)7IUXmRiyy{llag6ocWEt(D7Tm2&yB-US_7FmOS&F?5ZL2dc8enhnu3P`^4--pD1?I%z&=S-1+P4QcU;o(wlYlTQ<+y%yjwJ zbWxCKP+4S7^9XlHEfw84Ts~z{xovtZ2R(Vyy9=*Y5TCPq#nF=78KYgi!n>pMj-Eh% z>_5o$^nGhL-|=_XNsl*PT)lqu$z+dvIhET_aP{?F>pxZ1)a{Mwt5GET4;|1Cy)mm1M|J__s5>x71-4^vX-_jQ_Dv%kuk z8vo1m<31DWj((u3UiO5S{eC}t@OtC1PEB8gn2M`YGkiC73&}4Veu>d#?*8-{pMNL2 zRQqjp&AzmhrY6SiIP>=ln|C!b7@+^bFilcLNpPc{jS)sQ$+qiFVAwJk(qOv@!!t;V zsV3931{v#a`92m?OUPKG`6{l;Bq!6gk(142{N(5ad~yaZCRo8k-K2I2AkdQ(#x&w;5)s|VKE&LDnrIfQpGamMl;Fev3YDRq_AtV5Y|vPrqoPmBynNktrTD- zW2IA+Ny6dSY&NzHWgE?D99S$CbGQfxK_GyDERzfrW`_(GKboS2BaF1*X03_R8VyXE z6H^`%BD1xH|3m)(X05}MjD}uNPgoruq<-w^+)h2DQ zv_=uclVitB9GK1J==B{WER-VaxxN=lSQ5aU#)%^>MynYo6$F-( zddp$Aj?i#`Q4Y=RWg?WS*ftqDjA>fE$ss|b+an3Q4QH~Nbq)-Hb4VSj2ZmSxGu#1B zX*JIi)WIM6oiP!J@BLp(JuhD2gi z2=Vx85+Owz0fOS~C}PZ7&WVRN7F#Og3gV>Ih!tSGI)YJYx^pw*M{6h;RP7IMW1m&X%{xk7;e z2-MhK1IH|!RphO%L1GBgd;0LU|FEk~Z210B^jsG}{mbdoMD zF2bV+Ak0hP!V(@&f{FvVsD#U9wPd`t0{$KsNwF9ep%~~-l!rk)u|N%panQJ+Z%I@n z5Fr{q+R^Ag>mu+^DB=EtE)uX%EhY#U!g#O-3?oDcVPM2TLWCqR0@jF7VN08~)yQAq zq6p;gpUWZ`w>XT#C?q6tFtTw3hty&)yitq~i(s`_C?Yj~K8qdwk#gvp|5+bO>AO*- zlC4AZ=IfhB;Fp@A}u$XCV|@}or@qgn_>sz#buoqSe^#`XkTvy@?>38Lf0 zi;urOo#_!a{+k)A<90N6+R-!EzjCPuD{KxdoN###ab;1;u8NOtZ|nW#C*AH>pWvd> zyiO-@j(@1@SMRDg!vn-#A<`>6s?)YRX}wQ*+wl$eDwtyvFY=$N7z+Ewuj88YzAg(Z w&N((eXgetMainRoot()/fs::path("layouts"), loader); - for (auto& pack : content->getPacks()) { + for (auto& entry : content->getPacks()) { + auto pack = entry.second.get(); auto& info = pack->getInfo(); fs::path folder = info.folder / fs::path("layouts"); addLayouts(pack->getEnvironment()->getId(), info.id, folder, loader); diff --git a/src/content/Content.cpp b/src/content/Content.cpp index cdbf2a1d..99db7f5c 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -26,7 +26,7 @@ void ContentBuilder::add(ItemDef* def) { } void ContentBuilder::add(ContentPackRuntime* pack) { - packs.push_back(std::unique_ptr(pack)); + packs.emplace(pack->getId(), pack); } Block& ContentBuilder::createBlock(std::string id) { @@ -136,7 +136,7 @@ Content::Content(ContentIndices* indices, std::unique_ptr drawGroups, std::unordered_map blockDefs, std::unordered_map itemDefs, - std::vector> packs) + std::unordered_map> packs) : blockDefs(blockDefs), itemDefs(itemDefs), indices(indices), @@ -179,6 +179,14 @@ ItemDef& Content::requireItem(std::string id) const { return *found->second; } -const std::vector>& Content::getPacks() const { +const ContentPackRuntime* Content::getPackRuntime(std::string id) const { + auto found = packs.find(id); + if (found == packs.end()) { + return nullptr; + } + return found->second.get(); +} + +const std::unordered_map>& Content::getPacks() const { return packs; } diff --git a/src/content/Content.h b/src/content/Content.h index fa0e9c53..32cba96f 100644 --- a/src/content/Content.h +++ b/src/content/Content.h @@ -48,7 +48,7 @@ class ContentBuilder { std::unordered_map itemDefs; std::vector itemIds; - std::vector> packs; + std::unordered_map> packs; public: ~ContentBuilder(); @@ -108,7 +108,7 @@ class Content { std::unordered_map blockDefs; std::unordered_map itemDefs; std::unique_ptr indices; - std::vector> packs; + std::unordered_map> packs; public: std::unique_ptr const drawGroups; @@ -116,7 +116,7 @@ public: std::unique_ptr drawGroups, std::unordered_map blockDefs, std::unordered_map itemDefs, - std::vector> packs); + std::unordered_map> packs); ~Content(); inline ContentIndices* getIndices() const { @@ -129,7 +129,9 @@ public: ItemDef* findItem(std::string id) const; ItemDef& requireItem(std::string id) const; - const std::vector>& getPacks() const; + const ContentPackRuntime* getPackRuntime(std::string id) const; + + const std::unordered_map>& getPacks() const; }; #endif // CONTENT_CONTENT_H_ \ No newline at end of file diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index a06ed489..7efced5e 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -294,6 +294,7 @@ void ContentLoader::load(ContentBuilder& builder) { auto runtime = new ContentPackRuntime(*pack, scripting::create_pack_environment(*pack)); builder.add(runtime); env = runtime->getEnvironment()->getId(); + ContentPackStats& stats = runtime->getStatsWriteable(); fixPackIndices(); @@ -306,6 +307,7 @@ void ContentLoader::load(ContentBuilder& builder) { if (!fs::is_regular_file(pack->getContentFile())) return; + auto root = files::read_json(pack->getContentFile()); auto blocksarr = root->list("blocks"); if (blocksarr) { @@ -314,6 +316,7 @@ void ContentLoader::load(ContentBuilder& builder) { std::string full = pack->id+":"+name; auto& def = builder.createBlock(full); loadBlock(def, full, name); + stats.totalBlocks++; if (!def.hidden) { auto& item = builder.createItem(full+BLOCK_ITEM_SUFFIX); item.generated = true; @@ -324,6 +327,7 @@ void ContentLoader::load(ContentBuilder& builder) { for (uint j = 0; j < 4; j++) { item.emission[j] = def.emission[j]; } + stats.totalItems++; } } } @@ -334,6 +338,7 @@ void ContentLoader::load(ContentBuilder& builder) { std::string name = itemsarr->str(i); std::string full = pack->id+":"+name; loadItem(builder.createItem(full), full, name); + stats.totalItems++; } } } diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 4266f867..5ad8d9ba 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -167,7 +167,7 @@ void ContentPack::readPacks(const EnginePaths* paths, } ContentPackRuntime::ContentPackRuntime( - ContentPack info, + ContentPack info, std::unique_ptr env ) : info(info), env(std::move(env)) { diff --git a/src/content/ContentPack.h b/src/content/ContentPack.h index 952dc65b..c6ce7c86 100644 --- a/src/content/ContentPack.h +++ b/src/content/ContentPack.h @@ -77,15 +77,33 @@ struct ContentPack { ); }; +struct ContentPackStats { + size_t totalBlocks; + size_t totalItems; + + inline bool hasSavingContent() const { + return totalBlocks + totalItems > 0; + } +}; + class ContentPackRuntime { ContentPack info; + ContentPackStats stats {}; std::unique_ptr env; public: ContentPackRuntime( - ContentPack info, + ContentPack info, std::unique_ptr env ); + inline const ContentPackStats& getStats() const { + return stats; + } + + inline ContentPackStats& getStatsWriteable() { + return stats; + } + inline const std::string& getId() { return info.id; } diff --git a/src/files/WorldFiles.cpp b/src/files/WorldFiles.cpp index 24c3dfe4..11729085 100644 --- a/src/files/WorldFiles.cpp +++ b/src/files/WorldFiles.cpp @@ -49,91 +49,91 @@ regfile::regfile(fs::path filename) : file(filename) { } WorldRegion::WorldRegion() { - chunksData = new ubyte*[REGION_CHUNKS_COUNT]{}; - sizes = new uint32_t[REGION_CHUNKS_COUNT]{}; + chunksData = new ubyte*[REGION_CHUNKS_COUNT]{}; + sizes = new uint32_t[REGION_CHUNKS_COUNT]{}; } WorldRegion::~WorldRegion() { - for (uint i = 0; i < REGION_CHUNKS_COUNT; i++) { - delete[] chunksData[i]; - } - delete[] sizes; - delete[] chunksData; + for (uint i = 0; i < REGION_CHUNKS_COUNT; i++) { + delete[] chunksData[i]; + } + delete[] sizes; + delete[] chunksData; } void WorldRegion::setUnsaved(bool unsaved) { - this->unsaved = unsaved; + this->unsaved = unsaved; } bool WorldRegion::isUnsaved() const { - return unsaved; + return unsaved; } ubyte** WorldRegion::getChunks() const { - return chunksData; + return chunksData; } uint32_t* WorldRegion::getSizes() const { - return sizes; + return sizes; } void WorldRegion::put(uint x, uint z, ubyte* data, uint32_t size) { - size_t chunk_index = z * REGION_SIZE + x; - delete[] chunksData[chunk_index]; - chunksData[chunk_index] = data; - sizes[chunk_index] = size; + size_t chunk_index = z * REGION_SIZE + x; + delete[] chunksData[chunk_index]; + chunksData[chunk_index] = data; + sizes[chunk_index] = size; } ubyte* WorldRegion::getChunkData(uint x, uint z) { - return chunksData[z * REGION_SIZE + x]; + return chunksData[z * REGION_SIZE + x]; } uint WorldRegion::getChunkDataSize(uint x, uint z) { - return sizes[z * REGION_SIZE + x]; + return sizes[z * REGION_SIZE + x]; } const char* WorldFiles::WORLD_FILE = "world.json"; WorldFiles::WorldFiles(fs::path directory, const DebugSettings& settings) - : directory(directory), - generatorTestMode(settings.generatorTestMode), - doWriteLights(settings.doWriteLights) { - compressionBuffer.reset(new ubyte[CHUNK_DATA_LEN * 2]); + : directory(directory), + generatorTestMode(settings.generatorTestMode), + doWriteLights(settings.doWriteLights) { + compressionBuffer.reset(new ubyte[CHUNK_DATA_LEN * 2]); } WorldFiles::~WorldFiles(){ } WorldRegion* WorldFiles::getRegion(regionsmap& regions, int x, int z) { - auto found = regions.find(glm::ivec2(x, z)); - if (found == regions.end()) - return nullptr; - return found->second.get(); + auto found = regions.find(glm::ivec2(x, z)); + if (found == regions.end()) + return nullptr; + return found->second.get(); } WorldRegion* WorldFiles::getOrCreateRegion(regionsmap& regions, int x, int z) { - WorldRegion* region = getRegion(regions, x, z); - if (region == nullptr) { - region = new WorldRegion(); - regions[glm::ivec2(x, z)].reset(region); - } - return region; + WorldRegion* region = getRegion(regions, x, z); + if (region == nullptr) { + region = new WorldRegion(); + regions[glm::ivec2(x, z)].reset(region); + } + return region; } ubyte* WorldFiles::compress(const ubyte* src, size_t srclen, size_t& len) { ubyte* buffer = this->compressionBuffer.get(); - len = extrle::encode(src, srclen, buffer); - ubyte* data = new ubyte[len]; - for (size_t i = 0; i < len; i++) { - data[i] = buffer[i]; - } - return data; + len = extrle::encode(src, srclen, buffer); + ubyte* data = new ubyte[len]; + for (size_t i = 0; i < len; i++) { + data[i] = buffer[i]; + } + return data; } ubyte* WorldFiles::decompress(const ubyte* src, size_t srclen, size_t dstlen) { - ubyte* decompressed = new ubyte[dstlen]; - extrle::decode(src, srclen, decompressed); - return decompressed; + ubyte* decompressed = new ubyte[dstlen]; + extrle::decode(src, srclen, decompressed); + return decompressed; } int WorldFiles::getVoxelRegionVersion(int x, int z) { @@ -168,49 +168,49 @@ int WorldFiles::getVoxelRegionsVersion() { */ void WorldFiles::put(int x, int z, const ubyte* voxelData) { int regionX = floordiv(x, REGION_SIZE); - int regionZ = floordiv(z, REGION_SIZE); - int localX = x - (regionX * REGION_SIZE); - int localZ = z - (regionZ * REGION_SIZE); + int regionZ = floordiv(z, REGION_SIZE); + int localX = x - (regionX * REGION_SIZE); + int localZ = z - (regionZ * REGION_SIZE); - /* Writing Voxels */ { - WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); - region->setUnsaved(true); - size_t compressedSize; - ubyte* data = compress(voxelData, CHUNK_DATA_LEN, compressedSize); - region->put(localX, localZ, data, compressedSize); - } + /* Writing Voxels */ { + WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); + region->setUnsaved(true); + size_t compressedSize; + ubyte* data = compress(voxelData, CHUNK_DATA_LEN, compressedSize); + region->put(localX, localZ, data, compressedSize); + } } /* * Store chunk (voxels and lights) in region (existing or new) */ void WorldFiles::put(Chunk* chunk){ - assert(chunk != nullptr); + assert(chunk != nullptr); - int regionX = floordiv(chunk->x, REGION_SIZE); - int regionZ = floordiv(chunk->z, REGION_SIZE); - int localX = chunk->x - (regionX * REGION_SIZE); - int localZ = chunk->z - (regionZ * REGION_SIZE); + int regionX = floordiv(chunk->x, REGION_SIZE); + int regionZ = floordiv(chunk->z, REGION_SIZE); + int localX = chunk->x - (regionX * REGION_SIZE); + int localZ = chunk->z - (regionZ * REGION_SIZE); - /* Writing voxels */ { + /* Writing voxels */ { size_t compressedSize; std::unique_ptr chunk_data (chunk->encode()); - ubyte* data = compress(chunk_data.get(), CHUNK_DATA_LEN, compressedSize); + ubyte* data = compress(chunk_data.get(), CHUNK_DATA_LEN, compressedSize); - WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); - region->setUnsaved(true); - region->put(localX, localZ, data, compressedSize); - } + WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); + region->setUnsaved(true); + region->put(localX, localZ, data, compressedSize); + } /* Writing lights cache */ - if (doWriteLights && chunk->isLighted()) { + if (doWriteLights && chunk->isLighted()) { size_t compressedSize; std::unique_ptr light_data (chunk->lightmap.encode()); - ubyte* data = compress(light_data.get(), LIGHTMAP_DATA_LEN, compressedSize); + ubyte* data = compress(light_data.get(), LIGHTMAP_DATA_LEN, compressedSize); - WorldRegion* region = getOrCreateRegion(lights, regionX, regionZ); - region->setUnsaved(true); - region->put(localX, localZ, data, compressedSize); - } + WorldRegion* region = getOrCreateRegion(lights, regionX, regionZ); + region->setUnsaved(true); + region->put(localX, localZ, data, compressedSize); + } /* Writing block inventories */ if (!chunk->inventories.empty()){ auto& inventories = chunk->inventories; @@ -237,19 +237,19 @@ void WorldFiles::put(Chunk* chunk){ } fs::path WorldFiles::getRegionsFolder() const { - return directory/fs::path("regions"); + return directory/fs::path("regions"); } fs::path WorldFiles::getLightsFolder() const { - return directory/fs::path("lights"); + return directory/fs::path("lights"); } fs::path WorldFiles::getInventoriesFolder() const { - return directory/fs::path("inventories"); + return directory/fs::path("inventories"); } fs::path WorldFiles::getRegionFilename(int x, int z) const { - return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin"); + return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin"); } /* @@ -275,78 +275,78 @@ bool WorldFiles::parseRegionFilename(const std::string& name, int& x, int& z) { } fs::path WorldFiles::getPlayerFile() const { - return directory/fs::path("player.json"); + return directory/fs::path("player.json"); } fs::path WorldFiles::getWorldFile() const { - return directory/fs::path(WORLD_FILE); + return directory/fs::path(WORLD_FILE); } fs::path WorldFiles::getIndicesFile() const { - return directory/fs::path("indices.json"); + return directory/fs::path("indices.json"); } fs::path WorldFiles::getPacksFile() const { - return directory/fs::path("packs.list"); + return directory/fs::path("packs.list"); } ubyte* WorldFiles::getChunk(int x, int z){ - return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS, true); + return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS, true); } /* Get cached lights for chunk at x,z * @return lights data or nullptr */ light_t* WorldFiles::getLights(int x, int z) { - std::unique_ptr data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS, true)); - if (data == nullptr) - return nullptr; - return Lightmap::decode(data.get()); + std::unique_ptr data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS, true)); + if (data == nullptr) + return nullptr; + return Lightmap::decode(data.get()); } chunk_inventories_map WorldFiles::fetchInventories(int x, int z) { - chunk_inventories_map inventories; - const ubyte* data = getData(storages, getInventoriesFolder(), x, z, REGION_LAYER_INVENTORIES, false); - if (data == nullptr) - return inventories; - ByteReader reader(data, BUFFER_SIZE_UNKNOWN); - int count = reader.getInt32(); - for (int i = 0; i < count; i++) { - uint index = reader.getInt32(); - uint size = reader.getInt32(); - auto map = json::from_binary(reader.pointer(), size); - reader.skip(size); - auto inv = std::make_shared(0, 0); - inv->deserialize(map.get()); - inventories[index] = inv; - } - return inventories; + chunk_inventories_map inventories; + const ubyte* data = getData(storages, getInventoriesFolder(), x, z, REGION_LAYER_INVENTORIES, false); + if (data == nullptr) + return inventories; + ByteReader reader(data, BUFFER_SIZE_UNKNOWN); + int count = reader.getInt32(); + for (int i = 0; i < count; i++) { + uint index = reader.getInt32(); + uint size = reader.getInt32(); + auto map = json::from_binary(reader.pointer(), size); + reader.skip(size); + auto inv = std::make_shared(0, 0); + inv->deserialize(map.get()); + inventories[index] = inv; + } + return inventories; } ubyte* WorldFiles::getData(regionsmap& regions, const fs::path& folder, int x, int z, int layer, bool compression) { - int regionX = floordiv(x, REGION_SIZE); - int regionZ = floordiv(z, REGION_SIZE); + int regionX = floordiv(x, REGION_SIZE); + int regionZ = floordiv(z, REGION_SIZE); - int localX = x - (regionX * REGION_SIZE); - int localZ = z - (regionZ * REGION_SIZE); + int localX = x - (regionX * REGION_SIZE); + int localZ = z - (regionZ * REGION_SIZE); - WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); - ubyte* data = region->getChunkData(localX, localZ); - if (data == nullptr) { - uint32_t size; - data = readChunkData(x, z, size, folder, layer); - if (data != nullptr) { - region->put(localX, localZ, data, size); - } - } - if (data != nullptr) { + WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); + ubyte* data = region->getChunkData(localX, localZ); + if (data == nullptr) { + uint32_t size; + data = readChunkData(x, z, size, folder, layer); + if (data != nullptr) { + region->put(localX, localZ, data, size); + } + } + if (data != nullptr) { size_t size = region->getChunkDataSize(localX, localZ); - if (compression) { - return decompress(data, size, CHUNK_DATA_LEN); - } - return data; - } - return nullptr; + if (compression) { + return decompress(data, size, CHUNK_DATA_LEN); + } + return data; + } + return nullptr; } @@ -360,7 +360,7 @@ regfile* WorldFiles::getRegFile(glm::ivec3 coord, const fs::path& folder) { auto iter = std::next(openRegFiles.begin(), rand() % openRegFiles.size()); openRegFiles.erase(iter); } - fs::path filename = folder / getRegionFilename(coord[0], coord[1]); + fs::path filename = folder / getRegionFilename(coord[0], coord[1]); if (!fs::is_regular_file(filename)) { return nullptr; } @@ -373,14 +373,14 @@ ubyte* WorldFiles::readChunkData(int x, uint32_t& length, fs::path folder, int layer){ - if (generatorTestMode) - return nullptr; - - int regionX = floordiv(x, REGION_SIZE); - int regionZ = floordiv(z, REGION_SIZE); - int localX = x - (regionX * REGION_SIZE); - int localZ = z - (regionZ * REGION_SIZE); - int chunkIndex = localZ * REGION_SIZE + localX; + if (generatorTestMode) + return nullptr; + + int regionX = floordiv(x, REGION_SIZE); + int regionZ = floordiv(z, REGION_SIZE); + int localX = x - (regionX * REGION_SIZE); + int localZ = z - (regionZ * REGION_SIZE); + int chunkIndex = localZ * REGION_SIZE + localX; glm::ivec3 coord(regionX, regionZ, layer); regfile* rfile = WorldFiles::getRegFile(coord, folder); @@ -389,24 +389,24 @@ ubyte* WorldFiles::readChunkData(int x, } files::rafile& file = rfile->file; - size_t file_size = file.length(); - size_t table_offset = file_size - REGION_CHUNKS_COUNT * 4; + size_t file_size = file.length(); + size_t table_offset = file_size - REGION_CHUNKS_COUNT * 4; - uint32_t offset; - file.seekg(table_offset + chunkIndex * 4); - file.read((char*)(&offset), 4); - offset = dataio::read_int32_big((const ubyte*)(&offset), 0); + uint32_t offset; + file.seekg(table_offset + chunkIndex * 4); + file.read((char*)(&offset), 4); + offset = dataio::read_int32_big((const ubyte*)(&offset), 0); - if (offset == 0){ - return nullptr; - } + if (offset == 0){ + return nullptr; + } - file.seekg(offset); - file.read((char*)(&offset), 4); - length = dataio::read_int32_big((const ubyte*)(&offset), 0); - ubyte* data = new ubyte[length]{}; - file.read((char*)data, length); - return data; + file.seekg(offset); + file.read((char*)(&offset), 4); + length = dataio::read_int32_big((const ubyte*)(&offset), 0); + ubyte* data = new ubyte[length]{}; + file.read((char*)data, length); + return data; } /* Read missing chunks data (null pointers) from region file @@ -415,7 +415,7 @@ ubyte* WorldFiles::readChunkData(int x, */ void WorldFiles::fetchChunks(WorldRegion* region, int x, int z, fs::path folder, int layer) { ubyte** chunks = region->getChunks(); - uint32_t* sizes = region->getSizes(); + uint32_t* sizes = region->getSizes(); for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { int chunk_x = (i % REGION_SIZE) + x * REGION_SIZE; @@ -441,70 +441,70 @@ void WorldFiles::writeRegion(int x, int z, WorldRegion* entry, fs::path folder, openRegFiles.erase(regcoord); } - char header[REGION_HEADER_SIZE] = REGION_FORMAT_MAGIC; - header[8] = REGION_FORMAT_VERSION; - header[9] = 0; // flags - std::ofstream file(filename, std::ios::out | std::ios::binary); - file.write(header, REGION_HEADER_SIZE); + char header[REGION_HEADER_SIZE] = REGION_FORMAT_MAGIC; + header[8] = REGION_FORMAT_VERSION; + header[9] = 0; // flags + std::ofstream file(filename, std::ios::out | std::ios::binary); + file.write(header, REGION_HEADER_SIZE); - size_t offset = REGION_HEADER_SIZE; - char intbuf[4]{}; - uint offsets[REGION_CHUNKS_COUNT]{}; - - ubyte** region = entry->getChunks(); - uint32_t* sizes = entry->getSizes(); + size_t offset = REGION_HEADER_SIZE; + char intbuf[4]{}; + uint offsets[REGION_CHUNKS_COUNT]{}; - for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { - ubyte* chunk = region[i]; - if (chunk == nullptr){ - offsets[i] = 0; - } else { - offsets[i] = offset; + ubyte** region = entry->getChunks(); + uint32_t* sizes = entry->getSizes(); + + for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { + ubyte* chunk = region[i]; + if (chunk == nullptr){ + offsets[i] = 0; + } else { + offsets[i] = offset; - size_t compressedSize = sizes[i]; - dataio::write_int32_big(compressedSize, (ubyte*)intbuf, 0); - offset += 4 + compressedSize; + size_t compressedSize = sizes[i]; + dataio::write_int32_big(compressedSize, (ubyte*)intbuf, 0); + offset += 4 + compressedSize; - file.write(intbuf, 4); - file.write((const char*)chunk, compressedSize); - } - } - for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { - dataio::write_int32_big(offsets[i], (ubyte*)intbuf, 0); - file.write(intbuf, 4); - } + file.write(intbuf, 4); + file.write((const char*)chunk, compressedSize); + } + } + for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { + dataio::write_int32_big(offsets[i], (ubyte*)intbuf, 0); + file.write(intbuf, 4); + } } void WorldFiles::writeRegions(regionsmap& regions, const fs::path& folder, int layer) { - for (auto& it : regions){ - WorldRegion* region = it.second.get(); - if (region->getChunks() == nullptr || !region->isUnsaved()) - continue; - glm::ivec2 key = it.first; - writeRegion(key[0], key[1], region, folder, layer); - } + for (auto& it : regions){ + WorldRegion* region = it.second.get(); + if (region->getChunks() == nullptr || !region->isUnsaved()) + continue; + glm::ivec2 key = it.first; + writeRegion(key[0], key[1], region, folder, layer); + } } void WorldFiles::write(const World* world, const Content* content) { - fs::path regionsFolder = getRegionsFolder(); - fs::path lightsFolder = getLightsFolder(); + fs::path regionsFolder = getRegionsFolder(); + fs::path lightsFolder = getLightsFolder(); fs::path inventoriesFolder = getInventoriesFolder(); - fs::create_directories(regionsFolder); + fs::create_directories(regionsFolder); fs::create_directories(inventoriesFolder); - fs::create_directories(lightsFolder); + fs::create_directories(lightsFolder); if (world) { - writeWorldInfo(world); - writePacks(world); - } - if (generatorTestMode) { - return; + writeWorldInfo(world); + writePacks(world); } - - writeIndices(content->getIndices()); - writeRegions(regions, regionsFolder, REGION_LAYER_VOXELS); - writeRegions(lights, lightsFolder, REGION_LAYER_LIGHTS); + if (generatorTestMode) { + return; + } + + writeIndices(content->getIndices()); + writeRegions(regions, regionsFolder, REGION_LAYER_VOXELS); + writeRegions(lights, lightsFolder, REGION_LAYER_LIGHTS); writeRegions(storages, inventoriesFolder, REGION_LAYER_INVENTORIES); } @@ -514,64 +514,64 @@ void WorldFiles::writePacks(const World* world) { return; } - const auto& packs = world->getPacks(); - std::stringstream ss; - ss << "# autogenerated; do not modify\n"; - for (const auto& pack : packs) { - ss << pack.id << "\n"; - } - files::write_string(packsFile, ss.str()); + const auto& packs = world->getPacks(); + std::stringstream ss; + ss << "# autogenerated; do not modify\n"; + for (const auto& pack : packs) { + ss << pack.id << "\n"; + } + files::write_string(packsFile, ss.str()); } void WorldFiles::writeIndices(const ContentIndices* indices) { - dynamic::Map root; + dynamic::Map root; uint count; - auto& blocks = root.putList("blocks"); - count = indices->countBlockDefs(); - for (uint i = 0; i < count; i++) { - const Block* def = indices->getBlockDef(i); - blocks.put(def->name); - } + auto& blocks = root.putList("blocks"); + count = indices->countBlockDefs(); + for (uint i = 0; i < count; i++) { + const Block* def = indices->getBlockDef(i); + blocks.put(def->name); + } auto& items = root.putList("items"); - count = indices->countItemDefs(); - for (uint i = 0; i < count; i++) { - const ItemDef* def = indices->getItemDef(i); - items.put(def->name); - } + count = indices->countItemDefs(); + for (uint i = 0; i < count; i++) { + const ItemDef* def = indices->getItemDef(i); + items.put(def->name); + } - files::write_json(getIndicesFile(), &root); + files::write_json(getIndicesFile(), &root); } void WorldFiles::writeWorldInfo(const World* world) { - files::write_json(getWorldFile(), world->serialize().get()); + files::write_json(getWorldFile(), world->serialize().get()); } bool WorldFiles::readWorldInfo(World* world) { - fs::path file = getWorldFile(); - if (!fs::is_regular_file(file)) { - std::cerr << "warning: world.json does not exists" << std::endl; - return false; - } + fs::path file = getWorldFile(); + if (!fs::is_regular_file(file)) { + std::cerr << "warning: world.json does not exists" << std::endl; + return false; + } - auto root = files::read_json(file); + auto root = files::read_json(file); world->deserialize(root.get()); - return true; + return true; } void WorldFiles::writePlayer(std::shared_ptr player) { - files::write_json(getPlayerFile(), player->serialize().release()); + files::write_json(getPlayerFile(), player->serialize().release()); } bool WorldFiles::readPlayer(std::shared_ptr player) { - fs::path file = getPlayerFile(); - if (!fs::is_regular_file(file)) { - std::cerr << "warning: player.json does not exists" << std::endl; - return false; - } + fs::path file = getPlayerFile(); + if (!fs::is_regular_file(file)) { + std::cerr << "warning: player.json does not exists" << std::endl; + return false; + } - player->deserialize(files::read_json(file).get()); - return true; + player->deserialize(files::read_json(file).get()); + return true; } void WorldFiles::addPack(const World* world, const std::string& id) { @@ -585,10 +585,32 @@ void WorldFiles::addPack(const World* world, const std::string& id) { auto packs = files::read_list(file); packs.push_back(id); - std::stringstream ss; - ss << "# autogenerated; do not modify\n"; - for (const auto& pack : packs) { - ss << pack << "\n"; - } - files::write_string(file, ss.str()); + std::stringstream ss; + ss << "# autogenerated; do not modify\n"; + for (const auto& pack : packs) { + ss << pack << "\n"; + } + files::write_string(file, ss.str()); +} + +void WorldFiles::removePack(const World* world, const std::string& id) { + fs::path file = getPacksFile(); + if (!fs::is_regular_file(file)) { + if (!fs::is_directory(directory)) { + fs::create_directories(directory); + } + writePacks(world); + } + auto packs = files::read_list(file); + auto found = std::find(packs.begin(), packs.end(), id); + if (found != packs.end()) { + packs.erase(found); + } + + std::stringstream ss; + ss << "# autogenerated; do not modify\n"; + for (const auto& pack : packs) { + ss << pack << "\n"; + } + files::write_string(file, ss.str()); } diff --git a/src/files/WorldFiles.h b/src/files/WorldFiles.h index a3a9a483..3269d4dd 100644 --- a/src/files/WorldFiles.h +++ b/src/files/WorldFiles.h @@ -47,22 +47,22 @@ public: }; class WorldRegion { - ubyte** chunksData; - uint32_t* sizes; - bool unsaved = false; + ubyte** chunksData; + uint32_t* sizes; + bool unsaved = false; public: - WorldRegion(); - ~WorldRegion(); + WorldRegion(); + ~WorldRegion(); - void put(uint x, uint z, ubyte* data, uint32_t size); - ubyte* getChunkData(uint x, uint z); - uint getChunkDataSize(uint x, uint z); + void put(uint x, uint z, ubyte* data, uint32_t size); + ubyte* getChunkData(uint x, uint z); + uint getChunkDataSize(uint x, uint z); - void setUnsaved(bool unsaved); - bool isUnsaved() const; + void setUnsaved(bool unsaved); + bool isUnsaved() const; - ubyte** getChunks() const; - uint32_t* getSizes() const; + ubyte** getChunks() const; + uint32_t* getSizes() const; }; struct regfile { @@ -77,85 +77,94 @@ typedef std::unordered_map> regionsmap; class WorldFiles { std::unordered_map> openRegFiles; - void writeWorldInfo(const World* world); + void writeWorldInfo(const World* world); fs::path getRegionFilename(int x, int y) const; - fs::path getWorldFile() const; - fs::path getIndicesFile() const; - fs::path getPacksFile() const; - - WorldRegion* getRegion(regionsmap& regions, int x, int z); - WorldRegion* getOrCreateRegion(regionsmap& regions, int x, int z); + fs::path getWorldFile() const; + fs::path getIndicesFile() const; + fs::path getPacksFile() const; + + WorldRegion* getRegion(regionsmap& regions, int x, int z); + WorldRegion* getOrCreateRegion(regionsmap& regions, int x, int z); - /* Compress buffer with extrle - @param src source buffer - @param srclen length of source buffer - @param len (out argument) length of result buffer */ - ubyte* compress(const ubyte* src, size_t srclen, size_t& len); + /// @brief Compress buffer with extrle + /// @param src source buffer + /// @param srclen length of the source buffer + /// @param len (out argument) length of result buffer + /// @return compressed bytes array + ubyte* compress(const ubyte* src, size_t srclen, size_t& len); - /* Decompress buffer with extrle - @param src compressed buffer - @param srclen length of compressed buffer - @param dstlen max expected length of source buffer */ - ubyte* decompress(const ubyte* src, size_t srclen, size_t dstlen); + /// @brief Decompress buffer with extrle + /// @param src compressed buffer + /// @param srclen length of compressed buffer + /// @param dstlen max expected length of source buffer + /// @return decompressed bytes array + ubyte* decompress(const ubyte* src, size_t srclen, size_t dstlen); - ubyte* readChunkData(int x, int y, - uint32_t& length, - fs::path folder, - int layer); - void fetchChunks(WorldRegion* region, int x, int y, - fs::path folder, int layer); + ubyte* readChunkData(int x, int y, uint32_t& length, fs::path folder, int layer); - void writeRegions(regionsmap& regions, - const fs::path& folder, int layer); + void fetchChunks(WorldRegion* region, int x, int y, fs::path folder, int layer); - ubyte* getData(regionsmap& regions, - const fs::path& folder, - int x, int z, int layer, bool compression); + void writeRegions(regionsmap& regions, const fs::path& folder, int layer); + + ubyte* getData(regionsmap& regions, const fs::path& folder, int x, int z, int layer, bool compression); regfile* getRegFile(glm::ivec3 coord, const fs::path& folder); fs::path getLightsFolder() const; - fs::path getInventoriesFolder() const; + fs::path getInventoriesFolder() const; public: static bool parseRegionFilename(const std::string& name, int& x, int& y); fs::path getRegionsFolder() const; fs::path getPlayerFile() const; - regionsmap regions; + regionsmap regions; regionsmap storages; - regionsmap lights; - fs::path directory; - std::unique_ptr compressionBuffer; - bool generatorTestMode; - bool doWriteLights; + regionsmap lights; + fs::path directory; + std::unique_ptr compressionBuffer; + bool generatorTestMode; + bool doWriteLights; - WorldFiles(fs::path directory, const DebugSettings& settings); - ~WorldFiles(); + WorldFiles(fs::path directory, const DebugSettings& settings); + ~WorldFiles(); - void put(Chunk* chunk); + void put(Chunk* chunk); void put(int x, int z, const ubyte* voxelData); int getVoxelRegionVersion(int x, int z); int getVoxelRegionsVersion(); - ubyte* getChunk(int x, int z); - light_t* getLights(int x, int z); - chunk_inventories_map fetchInventories(int x, int z); + ubyte* getChunk(int x, int z); + light_t* getLights(int x, int z); + chunk_inventories_map fetchInventories(int x, int z); - bool readWorldInfo(World* world); - bool readPlayer(std::shared_ptr player); + bool readWorldInfo(World* world); + bool readPlayer(std::shared_ptr player); - void writeRegion(int x, int y, - WorldRegion* entry, - fs::path file, - int layer); - void writePlayer(std::shared_ptr player); - /* @param world world info to save (nullable) */ - void write(const World* world, const Content* content); - void writePacks(const World* world); - void writeIndices(const ContentIndices* indices); - /* Append pack to packs.list without duplicate check */ + void writeRegion(int x, int y, WorldRegion* entry, fs::path file, int layer); + + /// @brief Write player data to world files + /// @param player target player + void writePlayer(std::shared_ptr player); + + /// @brief Write all unsaved data to world files + /// @param world target world + /// @param content world content + void write(const World* world, const Content* content); + + void writePacks(const World* world); + void writeIndices(const ContentIndices* indices); + + /// @brief Append pack to the packs list without duplicate check and + /// dependencies resolve + /// @param world target world + /// @param id pack id void addPack(const World* world, const std::string& id); + + /// @brief Remove pack from the list (does not remove indices) + /// @param world target world + /// @param id pack id + void removePack(const World* world, const std::string& id); static const char* WORLD_FILE; }; diff --git a/src/frontend/WorldRenderer.cpp b/src/frontend/WorldRenderer.cpp index 64c9a728..109de8f5 100644 --- a/src/frontend/WorldRenderer.cpp +++ b/src/frontend/WorldRenderer.cpp @@ -170,6 +170,7 @@ void WorldRenderer::draw(const GfxContext& pctx, Camera* camera, bool hudVisible shader->uniform1f("u_gamma", settings.graphics.gamma); shader->uniform1f("u_fogFactor", fogFactor); shader->uniform1f("u_fogCurve", settings.graphics.fogCurve); + shader->uniform1f("u_dayTime", level->world->daytime); shader->uniform3f("u_cameraPos", camera->position); shader->uniform1i("u_cubemap", 1); { diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index 2e69be9b..b053f709 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -342,7 +342,8 @@ std::shared_ptr create_packs_panel( const std::vector& packs, Engine* engine, bool backbutton, - packconsumer callback + packconsumer callback, + packconsumer remover ){ auto assets = engine->getAssets(); auto panel = std::make_shared(vec2(550, 200), vec4(5.0f)); @@ -358,7 +359,12 @@ std::shared_ptr create_packs_panel( callback(pack); }); } - auto idlabel = std::make_shared