From 923c694ab445debc23027aa0a5fa1814a4a52774 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Feb 2025 23:15:56 +0300 Subject: [PATCH 01/23] add effects extension support --- src/audio/AL/ALAudio.cpp | 127 +++++++++++++++++++++++++++++++++++++-- src/audio/AL/ALAudio.hpp | 14 ++++- 2 files changed, 134 insertions(+), 7 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 41ce83c08..3255195f1 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -40,6 +40,20 @@ static bool check_alc_errors(ALCdevice* device, const char* context) { return true; } +static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti = nullptr; +static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots = nullptr; +static LPALGENEFFECTS alGenEffects = nullptr; +static LPALDELETEEFFECTS alDeleteEffects = nullptr; +static LPALISEFFECT alIsEffect = nullptr; +static LPALEFFECTI alEffecti = nullptr; +static LPALEFFECTF alEffectf = nullptr; +static LPALGENFILTERS alGenFilters = nullptr; +static LPALDELETEFILTERS alDeleteFilters = nullptr; +static LPALISFILTER alIsFilter = nullptr; +static LPALFILTERI alFilteri = nullptr; +static LPALFILTERF alFilterf = nullptr; + + ALSound::ALSound( ALAudio* al, uint buffer, const std::shared_ptr& pcm, bool keepPCM ) @@ -382,6 +396,10 @@ void ALSpeaker::play() { AL_GAIN, volume * p_channel->getVolume() )); + if (al->useEffects) { + AL_CHECK(alSource3i(source, AL_AUXILIARY_SEND_FILTER, al->effectSlots[0], 0, al->filters[0])); + //AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, al->filters[0])); + } AL_CHECK(alSourcePlay(source)); } @@ -455,15 +473,14 @@ int ALSpeaker::getPriority() const { return priority; } - bool ALSpeaker::isManuallyStopped() const { return manuallyStopped; } static bool alc_enumeration_ext = false; -ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) - : device(device), context(context) { +ALAudio::ALAudio(ALCdevice* device, ALCcontext* context, bool useEffects) + : device(device), context(context), useEffects(useEffects) { ALCint size; alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size); std::vector attrs(size); @@ -487,6 +504,11 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) } alDopplerFactor(1.0 / 3.0); + alGetError(); + + if (useEffects) { + initEffects(); + } } ALAudio::~ALAudio() { @@ -513,6 +535,58 @@ ALAudio::~ALAudio() { context = nullptr; } +void ALAudio::initEffects() { + for (uint i = 0; i < effectSlots.size(); i++) { + alGenAuxiliaryEffectSlots(1, &effectSlots[i]); + if (alGetError() != AL_NO_ERROR) { + logger.error() << "could not to create aux effect slot #" << i; + useEffects = false; + return; + } + } + for (uint i = 0; i < effects.size(); i++) { + alGenEffects(1, &effects[i]); + if (alGetError() != AL_NO_ERROR || !alIsEffect(effects[i])) { + logger.error() << "could not to create effect #" << i; + useEffects = false; + return; + } + } + for (uint i = 0; i < filters.size(); i++) { + alGenFilters(1, &filters[i]); + if (alGetError() != AL_NO_ERROR || !alIsFilter(filters[i])) { + logger.error() << "could not to create filter #" << i; + useEffects = false; + return; + } + } + + // Create reverb effect + alEffecti(effects[0], AL_EFFECT_TYPE, AL_EFFECT_REVERB); + if (alGetError() != AL_NO_ERROR) { + logger.error() << "reverb effect is not supported"; + useEffects = false; + return; + } + alEffectf(effects[0], AL_REVERB_DECAY_TIME, 1.0f); + + // Create lowpass filter + alFilteri(filters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS); + if (alGetError() != AL_NO_ERROR) { + logger.error() << "lowpass filter is not supported"; + useEffects = false; + return; + } + alFilterf(filters[0], AL_LOWPASS_GAIN, 1.0f); + alFilterf(filters[0], AL_LOWPASS_GAINHF, 0.1f); + + // Attach effect to aux effect slot + alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, effects[0]); + if (alGetError() == AL_NO_ERROR) { + logger.info() << "successfully loaded effect into effect slot"; + } +} + std::unique_ptr ALAudio::createSound( std::shared_ptr pcm, bool keepPCM ) { @@ -592,19 +666,62 @@ std::unique_ptr ALAudio::openInputDevice( ); } +template +static bool get_proc_address(const char* name, T& ptr) { + ptr = (T) alGetProcAddress(name); + return ptr != nullptr; +} + std::unique_ptr ALAudio::create() { alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); ALCdevice* device = alcOpenDevice(nullptr); - if (device == nullptr) return nullptr; + if (device == nullptr) { + return nullptr; + } + bool effects = true; + ALint attribs[4] {}; + if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) { + logger.info() << "AL effects extension present"; + attribs[0] = ALC_MAX_AUXILIARY_SENDS; + attribs[1] = 4; + } else { + logger.warning() << "AL effects extension is not supported"; + effects = false; + } ALCcontext* context = alcCreateContext(device, nullptr); if (!alcMakeContextCurrent(context)) { alcCloseDevice(device); return nullptr; } AL_CHECK(); + + ALCint sends = 0; + alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &sends); + logger.info() << "device supports " << sends << " aux sends per source"; + + if (effects) { + bool ok = get_proc_address("alAuxiliaryEffectSloti", alAuxiliaryEffectSloti) + && get_proc_address("alGenAuxiliaryEffectSlots", alGenAuxiliaryEffectSlots) + && get_proc_address("alGenEffects", alGenEffects) + && get_proc_address("alDeleteEffects", alDeleteEffects) + && get_proc_address("alIsEffect", alIsEffect) + && get_proc_address("alEffecti", alEffecti) + && get_proc_address("alEffectf", alEffectf) + && get_proc_address("alGenFilters", alGenFilters) + && get_proc_address("alDeleteFilters", alDeleteFilters) + && get_proc_address("alIsFilter", alIsFilter) + && get_proc_address("alFilteri", alFilteri) + && get_proc_address("alFilterf", alFilterf) + ; + if (!ok) { + logger.error() << "could not get effects extension function pointers"; + effects = false; + } + } + logger.info() << "initialized"; - return std::make_unique(device, context); + return std::make_unique(device, context, effects); } uint ALAudio::getFreeSource() { diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 5642f222b..8551e6697 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -12,9 +13,11 @@ #ifdef __APPLE__ #include #include +#include #else #include #include +#include #endif namespace audio { @@ -176,10 +179,17 @@ namespace audio { std::vector allbuffers; std::vector freebuffers; - uint maxSources = 256; + + void initEffects(); public: - ALAudio(ALCdevice* device, ALCcontext* context); + std::array effectSlots; + std::array effects; + std::array filters; + + bool useEffects; + + ALAudio(ALCdevice* device, ALCcontext* context, bool effects); ~ALAudio(); uint getFreeSource(); From 743e178f0a6164bc7bd56efbb6874c140ff73daf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 19 Feb 2025 22:22:00 +0300 Subject: [PATCH 02/23] add effects control (WIP) --- src/audio/AL/ALAudio.cpp | 59 +++++++++++++++++++++++----------------- src/audio/AL/ALAudio.hpp | 6 ++-- src/audio/effects.hpp | 29 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 27 deletions(-) create mode 100644 src/audio/effects.hpp diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 3255195f1..4ac4785d8 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -535,16 +535,42 @@ ALAudio::~ALAudio() { context = nullptr; } +template +static bool get_proc_address(const char* name, T& ptr) { + ptr = (T) alGetProcAddress(name); + return ptr != nullptr; +} + void ALAudio::initEffects() { - for (uint i = 0; i < effectSlots.size(); i++) { + bool ok = get_proc_address("alAuxiliaryEffectSloti", alAuxiliaryEffectSloti) + && get_proc_address("alGenAuxiliaryEffectSlots", alGenAuxiliaryEffectSlots) + && get_proc_address("alGenEffects", alGenEffects) + && get_proc_address("alDeleteEffects", alDeleteEffects) + && get_proc_address("alIsEffect", alIsEffect) + && get_proc_address("alEffecti", alEffecti) + && get_proc_address("alEffectf", alEffectf) + && get_proc_address("alGenFilters", alGenFilters) + && get_proc_address("alDeleteFilters", alDeleteFilters) + && get_proc_address("alIsFilter", alIsFilter) + && get_proc_address("alFilteri", alFilteri) + && get_proc_address("alFilterf", alFilterf) + ; + if (!ok) { + logger.error() << "could not get effects extension function pointers"; + useEffects = false; + return; + } + for (uint i = 0; i < maxEffectSlots; i++) { + effectSlots.emplace_back(); alGenAuxiliaryEffectSlots(1, &effectSlots[i]); if (alGetError() != AL_NO_ERROR) { - logger.error() << "could not to create aux effect slot #" << i; - useEffects = false; - return; + break; } } - for (uint i = 0; i < effects.size(); i++) { + logger.info() << "created " << effectSlots.size() << " effect slots"; + + for (uint i = 0; i < 4; i++) { + effects.emplace_back(); alGenEffects(1, &effects[i]); if (alGetError() != AL_NO_ERROR || !alIsEffect(effects[i])) { logger.error() << "could not to create effect #" << i; @@ -552,6 +578,8 @@ void ALAudio::initEffects() { return; } } + logger.info() << "created " << effects.size() << " effects"; + for (uint i = 0; i < filters.size(); i++) { alGenFilters(1, &filters[i]); if (alGetError() != AL_NO_ERROR || !alIsFilter(filters[i])) { @@ -569,6 +597,7 @@ void ALAudio::initEffects() { return; } alEffectf(effects[0], AL_REVERB_DECAY_TIME, 1.0f); + alEffectf(effects[0], AL_REVERB_ROOM_ROLLOFF_FACTOR, 0.07f); // default is 0 // Create lowpass filter alFilteri(filters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS); @@ -700,26 +729,6 @@ std::unique_ptr ALAudio::create() { alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &sends); logger.info() << "device supports " << sends << " aux sends per source"; - if (effects) { - bool ok = get_proc_address("alAuxiliaryEffectSloti", alAuxiliaryEffectSloti) - && get_proc_address("alGenAuxiliaryEffectSlots", alGenAuxiliaryEffectSlots) - && get_proc_address("alGenEffects", alGenEffects) - && get_proc_address("alDeleteEffects", alDeleteEffects) - && get_proc_address("alIsEffect", alIsEffect) - && get_proc_address("alEffecti", alEffecti) - && get_proc_address("alEffectf", alEffectf) - && get_proc_address("alGenFilters", alGenFilters) - && get_proc_address("alDeleteFilters", alDeleteFilters) - && get_proc_address("alIsFilter", alIsFilter) - && get_proc_address("alFilteri", alFilteri) - && get_proc_address("alFilterf", alFilterf) - ; - if (!ok) { - logger.error() << "could not get effects extension function pointers"; - effects = false; - } - } - logger.info() << "initialized"; return std::make_unique(device, context, effects); } diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 8551e6697..93cc7b31b 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -9,6 +9,7 @@ #include "typedefs.hpp" #include "audio/audio.hpp" +#include "audio/effects.hpp" #ifdef __APPLE__ #include @@ -180,11 +181,12 @@ namespace audio { std::vector allbuffers; std::vector freebuffers; uint maxSources = 256; + uint maxEffectSlots = 64; void initEffects(); public: - std::array effectSlots; - std::array effects; + std::vector effectSlots; + std::vector effects; std::array filters; bool useEffects; diff --git a/src/audio/effects.hpp b/src/audio/effects.hpp new file mode 100644 index 000000000..01d2eedd5 --- /dev/null +++ b/src/audio/effects.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +namespace audio { + struct Effect { + struct Property { + using Value = std::variant; + int id; + Value def; + Value min; + Value max; + }; + std::map properties; + }; + + class EffectControl { + public: + EffectControl(const Effect* effect) : effect(effect) {}; + virtual ~EffectControl() = default; + + virtual void set(const std::string& name, Effect::Property::Value value) = 0; + protected: + const Effect* effect = nullptr; + }; +} From 53ebe566b0471ce0c141c2e16e49f37cf1a4532b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 20:25:12 +0300 Subject: [PATCH 03/23] remove duplicating template --- src/audio/AL/ALAudio.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 4ac4785d8..5151cf823 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -695,12 +695,6 @@ std::unique_ptr ALAudio::openInputDevice( ); } -template -static bool get_proc_address(const char* name, T& ptr) { - ptr = (T) alGetProcAddress(name); - return ptr != nullptr; -} - std::unique_ptr ALAudio::create() { alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); From 35238163783826b85f6a29086a120f1270c9ab73 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 23:45:43 +0300 Subject: [PATCH 04/23] minor refactor --- src/audio/AL/ALAudio.cpp | 56 +++++++++++++++++++++++----------------- src/audio/AL/ALAudio.hpp | 2 +- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 5151cf823..8f48c4aea 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -53,6 +53,8 @@ static LPALISFILTER alIsFilter = nullptr; static LPALFILTERI alFilteri = nullptr; static LPALFILTERF alFilterf = nullptr; +static inline constexpr int REVERB_EFFECT = 0; +static inline constexpr int LOWPASS_FILTER = 0; ALSound::ALSound( ALAudio* al, uint buffer, const std::shared_ptr& pcm, bool keepPCM @@ -398,7 +400,7 @@ void ALSpeaker::play() { )); if (al->useEffects) { AL_CHECK(alSource3i(source, AL_AUXILIARY_SEND_FILTER, al->effectSlots[0], 0, al->filters[0])); - //AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, al->filters[0])); + //AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, al->filters[LOWPASS_FILTER])); } AL_CHECK(alSourcePlay(source)); } @@ -507,7 +509,7 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context, bool useEffects) alGetError(); if (useEffects) { - initEffects(); + this->useEffects = initEffects(); } } @@ -541,8 +543,8 @@ static bool get_proc_address(const char* name, T& ptr) { return ptr != nullptr; } -void ALAudio::initEffects() { - bool ok = get_proc_address("alAuxiliaryEffectSloti", alAuxiliaryEffectSloti) +static bool request_efx_ext_functions() { + return get_proc_address("alAuxiliaryEffectSloti", alAuxiliaryEffectSloti) && get_proc_address("alGenAuxiliaryEffectSlots", alGenAuxiliaryEffectSlots) && get_proc_address("alGenEffects", alGenEffects) && get_proc_address("alDeleteEffects", alDeleteEffects) @@ -553,12 +555,13 @@ void ALAudio::initEffects() { && get_proc_address("alDeleteFilters", alDeleteFilters) && get_proc_address("alIsFilter", alIsFilter) && get_proc_address("alFilteri", alFilteri) - && get_proc_address("alFilterf", alFilterf) - ; - if (!ok) { + && get_proc_address("alFilterf", alFilterf); +} + +bool ALAudio::initEffects() { + if (!request_efx_ext_functions()) { logger.error() << "could not get effects extension function pointers"; - useEffects = false; - return; + return false; } for (uint i = 0; i < maxEffectSlots; i++) { effectSlots.emplace_back(); @@ -574,8 +577,7 @@ void ALAudio::initEffects() { alGenEffects(1, &effects[i]); if (alGetError() != AL_NO_ERROR || !alIsEffect(effects[i])) { logger.error() << "could not to create effect #" << i; - useEffects = false; - return; + return false; } } logger.info() << "created " << effects.size() << " effects"; @@ -584,36 +586,32 @@ void ALAudio::initEffects() { alGenFilters(1, &filters[i]); if (alGetError() != AL_NO_ERROR || !alIsFilter(filters[i])) { logger.error() << "could not to create filter #" << i; - useEffects = false; - return; + return false; } } // Create reverb effect - alEffecti(effects[0], AL_EFFECT_TYPE, AL_EFFECT_REVERB); + alEffecti(effects[REVERB_EFFECT], AL_EFFECT_TYPE, AL_EFFECT_REVERB); if (alGetError() != AL_NO_ERROR) { logger.error() << "reverb effect is not supported"; - useEffects = false; - return; + return false; } - alEffectf(effects[0], AL_REVERB_DECAY_TIME, 1.0f); - alEffectf(effects[0], AL_REVERB_ROOM_ROLLOFF_FACTOR, 0.07f); // default is 0 // Create lowpass filter - alFilteri(filters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilteri(filters[LOWPASS_FILTER], AL_FILTER_TYPE, AL_FILTER_LOWPASS); if (alGetError() != AL_NO_ERROR) { logger.error() << "lowpass filter is not supported"; - useEffects = false; - return; + return false; } - alFilterf(filters[0], AL_LOWPASS_GAIN, 1.0f); - alFilterf(filters[0], AL_LOWPASS_GAINHF, 0.1f); + alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAIN, 1.0f); + alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAINHF, 0.01f); // Attach effect to aux effect slot alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, effects[0]); if (alGetError() == AL_NO_ERROR) { logger.info() << "successfully loaded effect into effect slot"; } + return true; } std::unique_ptr ALAudio::createSound( @@ -782,5 +780,15 @@ void ALAudio::setListener( AL_CHECK(alListenerf(AL_GAIN, get_channel(0)->getVolume())); } -void ALAudio::update(double) { +void ALAudio::update(double delta) { + if (!useEffects) { + return; + } + int reverbEffect = effects[REVERB_EFFECT]; + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_DECAY_TIME, 1.0f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, 0.07f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAIN, 0.0f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAINHF, 0.0f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, 0.0f)); + alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, reverbEffect); } diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 93cc7b31b..e01967d33 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -183,7 +183,7 @@ namespace audio { uint maxSources = 256; uint maxEffectSlots = 64; - void initEffects(); + bool initEffects(); public: std::vector effectSlots; std::vector effects; From 72aba4d31d7033f3d04fe28017efe0def20397af Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 23:46:00 +0300 Subject: [PATCH 05/23] test acoustics --- src/audio/AL/ALAudio.cpp | 15 ++++--- src/audio/AL/ALAudio.hpp | 2 + src/audio/NoAudio.hpp | 3 ++ src/audio/audio.cpp | 4 ++ src/audio/audio.hpp | 4 ++ src/audio/effects.hpp | 4 ++ src/graphics/render/Decorator.cpp | 65 +++++++++++++++++++++++++------ src/graphics/render/Decorator.hpp | 1 + 8 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 8f48c4aea..555336e93 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -780,15 +780,18 @@ void ALAudio::setListener( AL_CHECK(alListenerf(AL_GAIN, get_channel(0)->getVolume())); } -void ALAudio::update(double delta) { +void ALAudio::update(double) { +} + +void ALAudio::setAcoustics(Acoustics acoustics) { if (!useEffects) { return; } int reverbEffect = effects[REVERB_EFFECT]; - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_DECAY_TIME, 1.0f)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, 0.07f)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAIN, 0.0f)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAINHF, 0.0f)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, 0.0f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_DECAY_TIME, std::max(0.1f, acoustics.reverbDecayTime))); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, 0.14f)); + // AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAIN, 0.0f)); + // AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAINHF, 0.0f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, 0.5f)); alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, reverbEffect); } diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index e01967d33..c2cba3961 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -226,6 +226,8 @@ namespace audio { void update(double delta) override; + void setAcoustics(Acoustics acoustics) override; + bool isDummy() const override { return false; } diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index 0da1b4d63..c97794f36 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -104,6 +104,9 @@ namespace audio { void update(double delta) override { } + void setAcoustics(Acoustics) override { + } + bool isDummy() const override { return true; } diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index dc0e57463..a45a876f6 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -496,6 +496,10 @@ void audio::update(double delta) { } } +void audio::set_acoustics(Acoustics acoustics) { + backend->setAcoustics(std::move(acoustics)); +} + void audio::reset_channel(int index) { auto channel = get_channel(index); if (channel == nullptr) { diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 642b9235d..365f6bce9 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -7,6 +7,7 @@ #include "typedefs.hpp" #include "settings.hpp" #include "io/fwd.hpp" +#include "effects.hpp" namespace audio { /// @brief playing speaker uid @@ -394,6 +395,7 @@ namespace audio { virtual std::vector getInputDeviceNames() = 0; virtual std::vector getOutputDeviceNames() = 0; virtual void update(double delta) = 0; + virtual void setAcoustics(Acoustics acoustics) = 0; /// @brief Check if backend is an abstraction that does not internally /// work with actual audio data or play anything @@ -580,6 +582,8 @@ namespace audio { /// @param delta time elapsed since the last update (seconds) void update(double delta); + void set_acoustics(Acoustics acoustics); + /// @brief Stop all playing audio in channel, reset channel state void reset_channel(int channel); diff --git a/src/audio/effects.hpp b/src/audio/effects.hpp index 01d2eedd5..e292621d3 100644 --- a/src/audio/effects.hpp +++ b/src/audio/effects.hpp @@ -26,4 +26,8 @@ namespace audio { protected: const Effect* effect = nullptr; }; + + struct Acoustics { + float reverbDecayTime; + }; } diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 420f6d0dc..77aaa5cda 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -23,9 +23,12 @@ #include "io/io.hpp" #include "audio/audio.hpp" #include "maths/util.hpp" +#include "debug/Logger.hpp" namespace fs = std::filesystem; +static debug::Logger logger("decorator"); + /// @brief Not greater than 64 for this BIG_PRIME value inline constexpr int UPDATE_AREA_DIAMETER = 32; /// @brief Number of blocks in the volume @@ -70,19 +73,56 @@ Decorator::Decorator( void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { const auto& found = blockEmitters.find(pos); - if (found == blockEmitters.end()) { - auto treg = util::get_texture_region( - assets, def.particles->texture, "" - ); - blockEmitters[pos] = renderer.particles->add(std::make_unique( - level, - glm::vec3{pos.x + 0.5, pos.y + 0.5, pos.z + 0.5}, - *def.particles, - treg.texture, - treg.region, - -1 - )); + if (found != blockEmitters.end()) { + return; } + auto treg = util::get_texture_region( + assets, def.particles->texture, "" + ); + blockEmitters[pos] = renderer.particles->add(std::make_unique( + level, + glm::vec3{pos.x + 0.5, pos.y + 0.5, pos.z + 0.5}, + *def.particles, + treg.texture, + treg.region, + -1 + )); +} + +void Decorator::updateAcoustics(const Camera& camera) { + audio::Acoustics acoustics {}; + util::PseudoRandom random(0x34621B361); + + auto& chunks = *player.chunks; + const auto& start = camera.position; + float rayLength = 40.0f; + + int rays = 100; + int hit = 0; + float averageDistance = 0.0f; + for (int i = 0; i < rays; i++) { + glm::vec3 dir { + random.randFloat() - 0.5f, + random.randFloat() - 0.5f, + random.randFloat() - 0.5f, + }; + dir = glm::normalize(dir); + auto end = chunks.rayCastToObstacle(start, dir, rayLength); + auto distance = glm::distance(start, end); + if (distance >= rayLength * 0.98f) { + continue; + } + hit++; + averageDistance += distance * 0.2f; + } + float decayTime = (averageDistance / hit) * ((hit / static_cast(rays)) - 0.6f) * 1.6f; + decayTime *= 2.5f; + decayTime *= decayTime; + logger.info() << "rays hit " << hit << "/" << rays << " decay-time: " << decayTime; + + + acoustics.reverbDecayTime = decayTime; + audio::set_acoustics(std::move(acoustics)); } void Decorator::updateRandom( @@ -283,4 +323,5 @@ void Decorator::update( } updateBlockEmitters(camera); updateTextNotes(); + updateAcoustics(camera); } diff --git a/src/graphics/render/Decorator.hpp b/src/graphics/render/Decorator.hpp index 181fdcb5d..4fe52c50a 100644 --- a/src/graphics/render/Decorator.hpp +++ b/src/graphics/render/Decorator.hpp @@ -48,6 +48,7 @@ class Decorator { void updateBlockEmitters(const Camera& camera); void updateTextNotes(); void addParticles(const Block& def, const glm::ivec3& pos); + void updateAcoustics(const Camera& camera); public: Decorator( Engine& engine, From 9f9c7074f7ed9cd09b5bd79e0d767f15b2529378 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 6 Jan 2026 15:39:53 +0300 Subject: [PATCH 06/23] fix --- src/audio/AL/ALAudio.cpp | 2 +- src/graphics/render/Decorator.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 555336e93..66b5b8c8c 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -792,6 +792,6 @@ void ALAudio::setAcoustics(Acoustics acoustics) { AL_CHECK(alEffectf(reverbEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, 0.14f)); // AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAIN, 0.0f)); // AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAINHF, 0.0f)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, 0.5f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, 0.99f)); alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, reverbEffect); } diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 77aaa5cda..85e86ba40 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -91,7 +91,7 @@ void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { void Decorator::updateAcoustics(const Camera& camera) { audio::Acoustics acoustics {}; - util::PseudoRandom random(0x34621B361); + util::PseudoRandom random(34621U); auto& chunks = *player.chunks; const auto& start = camera.position; @@ -115,7 +115,7 @@ void Decorator::updateAcoustics(const Camera& camera) { hit++; averageDistance += distance * 0.2f; } - float decayTime = (averageDistance / hit) * ((hit / static_cast(rays)) - 0.6f) * 1.6f; + float decayTime = (averageDistance / hit) * glm::max(0.0f, glm::pow(hit / static_cast(rays), 9.0f) - 0.6f) * 1.6f; decayTime *= 2.5f; decayTime *= decayTime; logger.info() << "rays hit " << hit << "/" << rays << " decay-time: " << decayTime; From b355e4568df2cc26bd5c104415358e70f94db4c0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 10 Feb 2026 23:48:24 +0300 Subject: [PATCH 07/23] improve acoustics --- src/graphics/render/Decorator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 85e86ba40..f8a6c8372 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -115,7 +115,10 @@ void Decorator::updateAcoustics(const Camera& camera) { hit++; averageDistance += distance * 0.2f; } - float decayTime = (averageDistance / hit) * glm::max(0.0f, glm::pow(hit / static_cast(rays), 9.0f) - 0.6f) * 1.6f; + float decayTime = + glm::sqrt(averageDistance / hit) * + glm::max(0.0f, glm::pow(hit / static_cast(rays), 3.0f) - 0.6f) * + 1.6f; decayTime *= 2.5f; decayTime *= decayTime; logger.info() << "rays hit " << hit << "/" << rays << " decay-time: " << decayTime; From fcbbf4814a10b6db09ba30e90b21be7dc0c96d9f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 14:21:49 +0300 Subject: [PATCH 08/23] fix advanced render setting tooltip --- res/texts/en_US.txt | 1 + res/texts/ru_RU.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index ae19130e3..ebee0acbd 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -21,6 +21,7 @@ graphics.gamma.tooltip=Lighting brightness curve graphics.backlight.tooltip=Backlight to prevent total darkness graphics.dense-render.tooltip=Enables transparency in blocks like leaves graphics.soft-lighting.tooltip=Enables blocks soft lighting +graphics.advanced-render.tooltip=Use graphics pipeline supporting advanced effects like shadows, SSAO # settings settings.Controls Search Mode=Search by attached button name diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 1461f854c..1cf90367a 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -42,6 +42,7 @@ graphics.gamma.tooltip=Кривая яркости освещения graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья graphics.soft-lighting.tooltip=Включает мягкое освещение у блоков +graphics.advanced-render.tooltip=Использовать графический конвейер, поддерживающий продвинутые эффекты, такие как тени и SSAO # Меню menu.Apply=Применить From 41ba0b8e656baa61c9e156723178d1d6ebb4148c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 15:26:53 +0300 Subject: [PATCH 09/23] feat: executing commands from stdin --- src/devtools/StdinReader.cpp | 34 ++++++++++++++++++++++++++++++++++ src/devtools/StdinReader.hpp | 7 +++++++ src/engine/Engine.cpp | 3 +++ 3 files changed, 44 insertions(+) create mode 100644 src/devtools/StdinReader.cpp create mode 100644 src/devtools/StdinReader.hpp diff --git a/src/devtools/StdinReader.cpp b/src/devtools/StdinReader.cpp new file mode 100644 index 000000000..16752ec31 --- /dev/null +++ b/src/devtools/StdinReader.cpp @@ -0,0 +1,34 @@ +#include "StdinReader.hpp" + +#include "logic/CommandsInterpreter.hpp" +#include "coders/json.hpp" +#include "debug/Logger.hpp" + +#include +#include + +static debug::Logger logger("stdin-reader"); + +static std::thread reader_thread; + +void start_stdin_reader(cmd::CommandsInterpreter& interpreter) { + reader_thread = std::thread([&interpreter]() { + std::string line; + while (std::getline(std::cin, line)) { + if (line.empty()) { + continue; + } + try { + auto result = interpreter.execute(line); + if (result.isString()) { + logger.info() << result.asString(); + } else { + logger.info() << json::stringify(result, true); + } + } catch (const std::exception& err) { + logger.error() << err.what(); + } + } + }); + reader_thread.detach(); +} diff --git a/src/devtools/StdinReader.hpp b/src/devtools/StdinReader.hpp new file mode 100644 index 000000000..d141db998 --- /dev/null +++ b/src/devtools/StdinReader.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace cmd { + class CommandsInterpreter; +} + +void start_stdin_reader(cmd::CommandsInterpreter& interpreter); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 9afe0824a..1e9566fea 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -15,6 +15,7 @@ #include "devtools/DebuggingServer.hpp" #include "devtools/Editor.hpp" #include "devtools/Project.hpp" +#include "devtools/StdinReader.hpp" #include "EnginePaths.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" @@ -191,6 +192,8 @@ void Engine::initialize(CoreParameters coreParameters) { if (!params.headless) { project->loadProjectClientScript(); } + + start_stdin_reader(*cmd); } void Engine::loadSettings() { From 7c0772b5b69e8383994bdef1146fc8bf95141b8e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 15:57:38 +0300 Subject: [PATCH 10/23] rename --- src/devtools/{StdinReader.cpp => stdin_reader.cpp} | 2 +- src/devtools/{StdinReader.hpp => stdin_reader.hpp} | 0 src/engine/Engine.cpp | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/devtools/{StdinReader.cpp => stdin_reader.cpp} (97%) rename src/devtools/{StdinReader.hpp => stdin_reader.hpp} (100%) diff --git a/src/devtools/StdinReader.cpp b/src/devtools/stdin_reader.cpp similarity index 97% rename from src/devtools/StdinReader.cpp rename to src/devtools/stdin_reader.cpp index 16752ec31..adaa17c4c 100644 --- a/src/devtools/StdinReader.cpp +++ b/src/devtools/stdin_reader.cpp @@ -1,4 +1,4 @@ -#include "StdinReader.hpp" +#include "stdin_reader.hpp" #include "logic/CommandsInterpreter.hpp" #include "coders/json.hpp" diff --git a/src/devtools/StdinReader.hpp b/src/devtools/stdin_reader.hpp similarity index 100% rename from src/devtools/StdinReader.hpp rename to src/devtools/stdin_reader.hpp diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 1e9566fea..14d2af807 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -15,7 +15,7 @@ #include "devtools/DebuggingServer.hpp" #include "devtools/Editor.hpp" #include "devtools/Project.hpp" -#include "devtools/StdinReader.hpp" +#include "devtools/stdin_reader.hpp" #include "EnginePaths.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" From b200d38e48f6c944a040849959f5939e18a43de4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 16:01:46 +0300 Subject: [PATCH 11/23] add '--stdin-cmd' command line flag --- src/devtools/stdin_reader.cpp | 2 ++ src/engine/CoreParameters.hpp | 1 + src/engine/Engine.cpp | 5 +++-- src/util/command_line.cpp | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/devtools/stdin_reader.cpp b/src/devtools/stdin_reader.cpp index adaa17c4c..dba26b75d 100644 --- a/src/devtools/stdin_reader.cpp +++ b/src/devtools/stdin_reader.cpp @@ -13,6 +13,8 @@ static std::thread reader_thread; void start_stdin_reader(cmd::CommandsInterpreter& interpreter) { reader_thread = std::thread([&interpreter]() { + logger.info() << "reader thread started"; + std::string line; while (std::getline(std::cin, line)) { if (line.empty()) { diff --git a/src/engine/CoreParameters.hpp b/src/engine/CoreParameters.hpp index c937bf3ad..b482f2bb5 100644 --- a/src/engine/CoreParameters.hpp +++ b/src/engine/CoreParameters.hpp @@ -6,6 +6,7 @@ struct CoreParameters { bool headless = false; bool testMode = false; + bool stdinCommands = false; std::filesystem::path resFolder = "res"; std::filesystem::path userFolder = "."; std::filesystem::path scriptFile; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 14d2af807..50a6f921b 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -192,8 +192,9 @@ void Engine::initialize(CoreParameters coreParameters) { if (!params.headless) { project->loadProjectClientScript(); } - - start_stdin_reader(*cmd); + if (params.stdinCommands) { + start_stdin_reader(*cmd); + } } void Engine::loadSettings() { diff --git a/src/util/command_line.cpp b/src/util/command_line.cpp index 7cb3614ef..e41a0ea57 100644 --- a/src/util/command_line.cpp +++ b/src/util/command_line.cpp @@ -62,6 +62,10 @@ static bool perform_keyword( params.headless = true; return true; }, "", "run in headless mode."), + ArgC("--stdin-cmd", [¶ms]() -> bool { + params.stdinCommands = true; + return true; + }, "", "run commands from stdin."), ArgC("--tps", [¶ms, &reader]() -> bool { params.tps = reader.nextInt(); return true; From 20155b88d07dec608056e2e393782384bcbc23a5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 21:19:55 +0300 Subject: [PATCH 12/23] add 'sound-absorption' block material parameter --- res/content/base/block_materials/carpet.json | 3 +- res/content/base/block_materials/glass.json | 3 +- res/content/base/block_materials/grass.json | 3 +- .../base/block_materials/grass_block.json | 3 +- res/content/base/block_materials/ground.json | 3 +- res/content/base/block_materials/metal.json | 3 +- res/content/base/block_materials/sand.json | 3 +- res/content/base/block_materials/stone.json | 3 +- res/content/base/block_materials/wood.json | 3 +- src/audio/AL/ALAudio.cpp | 16 ++++-- src/audio/effects.hpp | 5 ++ src/graphics/render/Decorator.cpp | 57 ++++++++++++++----- src/voxels/Block.cpp | 4 +- src/voxels/Block.hpp | 1 + 14 files changed, 82 insertions(+), 28 deletions(-) diff --git a/res/content/base/block_materials/carpet.json b/res/content/base/block_materials/carpet.json index 4d4d11dca..00c3688ec 100644 --- a/res/content/base/block_materials/carpet.json +++ b/res/content/base/block_materials/carpet.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/carpet", "place-sound": "blocks/carpet_place", - "break-sound": "blocks/carpet_break" + "break-sound": "blocks/carpet_break", + "sound-absorption": 0.96 } diff --git a/res/content/base/block_materials/glass.json b/res/content/base/block_materials/glass.json index fd2e23238..8ad365675 100644 --- a/res/content/base/block_materials/glass.json +++ b/res/content/base/block_materials/glass.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/glass", "place-sound": "blocks/glass_place", - "break-sound": "blocks/glass_break" + "break-sound": "blocks/glass_break", + "sound-absorption": 0.3 } diff --git a/res/content/base/block_materials/grass.json b/res/content/base/block_materials/grass.json index 148c80c09..ff091aa95 100644 --- a/res/content/base/block_materials/grass.json +++ b/res/content/base/block_materials/grass.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/grass", "place-sound": "steps/grass", - "break-sound": "steps/grass" + "break-sound": "steps/grass", + "sound-absorption": 0.85 } diff --git a/res/content/base/block_materials/grass_block.json b/res/content/base/block_materials/grass_block.json index d45672a28..98ed4618a 100644 --- a/res/content/base/block_materials/grass_block.json +++ b/res/content/base/block_materials/grass_block.json @@ -2,5 +2,6 @@ "steps-sound": "steps/grass", "place-sound": "blocks/ground_place", "break-sound": "blocks/ground_break", - "hit-sound": "blocks/ground_hit" + "hit-sound": "blocks/ground_hit", + "sound-absorption": 0.8 } diff --git a/res/content/base/block_materials/ground.json b/res/content/base/block_materials/ground.json index 951cb5dfe..8e78a8563 100644 --- a/res/content/base/block_materials/ground.json +++ b/res/content/base/block_materials/ground.json @@ -2,5 +2,6 @@ "steps-sound": "steps/ground", "place-sound": "blocks/ground_place", "break-sound": "blocks/ground_break", - "hit-sound": "blocks/ground_hit" + "hit-sound": "blocks/ground_hit", + "sound-absorption": 0.7 } diff --git a/res/content/base/block_materials/metal.json b/res/content/base/block_materials/metal.json index 6f45903e1..0ab1b2b28 100644 --- a/res/content/base/block_materials/metal.json +++ b/res/content/base/block_materials/metal.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/metal", "place-sound": "blocks/metal_place", - "break-sound": "blocks/metal_break" + "break-sound": "blocks/metal_break", + "sound-absorption": 0.1 } diff --git a/res/content/base/block_materials/sand.json b/res/content/base/block_materials/sand.json index 67316ba36..c047aab9c 100644 --- a/res/content/base/block_materials/sand.json +++ b/res/content/base/block_materials/sand.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/sand", "place-sound": "blocks/ground_place", - "break-sound": "blocks/ground_break" + "break-sound": "blocks/ground_break", + "sound-absorption": 0.8 } diff --git a/res/content/base/block_materials/stone.json b/res/content/base/block_materials/stone.json index 43f579e7a..75052af52 100644 --- a/res/content/base/block_materials/stone.json +++ b/res/content/base/block_materials/stone.json @@ -2,5 +2,6 @@ "steps-sound": "steps/stone", "place-sound": "blocks/stone_place", "break-sound": "blocks/stone_break", - "hit-sound": "blocks/stone_hit" + "hit-sound": "blocks/stone_hit", + "sound-absorption": 0.3 } diff --git a/res/content/base/block_materials/wood.json b/res/content/base/block_materials/wood.json index 0c8e9a469..edf1801c2 100644 --- a/res/content/base/block_materials/wood.json +++ b/res/content/base/block_materials/wood.json @@ -2,5 +2,6 @@ "steps-sound": "steps/wood", "place-sound": "blocks/wood_place", "break-sound": "blocks/wood_break", - "hit-sound": "blocks/wood_hit" + "hit-sound": "blocks/wood_hit", + "sound-absorption": 0.5 } diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 66b5b8c8c..eba10df17 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -788,10 +788,18 @@ void ALAudio::setAcoustics(Acoustics acoustics) { return; } int reverbEffect = effects[REVERB_EFFECT]; + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_GAIN, + glm::max(0.1f, glm::min(acoustics.reverbGain, 0.8f) * + (1.0f - glm::min(1.0f, acoustics.reverbAbsorption))) + )); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_DELAY, acoustics.reverbReflectionsDelay)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_LATE_REVERB_DELAY, acoustics.reverbLateReflectionsDelay)); AL_CHECK(alEffectf(reverbEffect, AL_REVERB_DECAY_TIME, std::max(0.1f, acoustics.reverbDecayTime))); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, 0.14f)); - // AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAIN, 0.0f)); - // AL_CHECK(alEffectf(reverbEffect, AL_REVERB_GAINHF, 0.0f)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, 0.99f)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, acoustics.reverbRoomRolloff)); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, glm::pow(acoustics.reverbGain, 2.0f))); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_LATE_REVERB_GAIN, glm::pow(acoustics.reverbGain, 2.0f))); + AL_CHECK(alEffectf(reverbEffect, AL_REVERB_DECAY_HFRATIO, 1.0f - acoustics.reverbAbsorption)); alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, reverbEffect); } diff --git a/src/audio/effects.hpp b/src/audio/effects.hpp index e292621d3..f8f10e4af 100644 --- a/src/audio/effects.hpp +++ b/src/audio/effects.hpp @@ -28,6 +28,11 @@ namespace audio { }; struct Acoustics { + float reverbGain; + float reverbReflectionsDelay; + float reverbLateReflectionsDelay; float reverbDecayTime; + float reverbRoomRolloff; + float reverbAbsorption; }; } diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index f8a6c8372..29405ef37 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -97,34 +97,63 @@ void Decorator::updateAcoustics(const Camera& camera) { const auto& start = camera.position; float rayLength = 40.0f; + auto& blocks = level.content.getIndices()->blocks; + int rays = 100; int hit = 0; float averageDistance = 0.0f; + float averageAbsorption = 0.0f; + float speedOfSound = 300.0f; + float minDistance = rayLength; for (int i = 0; i < rays; i++) { - glm::vec3 dir { - random.randFloat() - 0.5f, - random.randFloat() - 0.5f, - random.randFloat() - 0.5f, - }; + float u1 = random.randFloat(); + float u2 = random.randFloat(); + float z = 2.0f * u1 - 1.0f; + float phi = 2.0f * glm::pi() * u2; + float r = std::sqrt(1.0f - z * z); + + glm::vec3 dir = { r * std::cos(phi), r * std::sin(phi), z }; + dir = glm::normalize(dir); - auto end = chunks.rayCastToObstacle(start, dir, rayLength); + glm::vec3 end; + glm::ivec3 norm; + glm::ivec3 iend; + auto vox = chunks.rayCast(start, dir, rayLength, end, norm, iend); + if (vox == nullptr) { + continue; + } auto distance = glm::distance(start, end); if (distance >= rayLength * 0.98f) { continue; } + if (distance < minDistance) { + minDistance = distance; + } + auto def = blocks.get(vox->id); + if (def == nullptr) { + continue; + } + auto material = level.content.findBlockMaterial(def->material); + if (material == nullptr) { + return; + } hit++; - averageDistance += distance * 0.2f; + averageDistance += distance; + averageAbsorption += material->soundAbsorption; } - float decayTime = - glm::sqrt(averageDistance / hit) * - glm::max(0.0f, glm::pow(hit / static_cast(rays), 3.0f) - 0.6f) * - 1.6f; - decayTime *= 2.5f; - decayTime *= decayTime; - logger.info() << "rays hit " << hit << "/" << rays << " decay-time: " << decayTime; + averageDistance /= hit; + averageAbsorption /= hit; + float decayTime = 20.0f * (averageDistance / rayLength); + float escapeRatio = static_cast(rays - hit) / static_cast(rays); + acoustics.reverbGain = glm::pow(hit / static_cast(rays), 3.0f); + acoustics.reverbReflectionsDelay = minDistance / speedOfSound; + acoustics.reverbLateReflectionsDelay = averageDistance / speedOfSound; acoustics.reverbDecayTime = decayTime; + acoustics.reverbRoomRolloff = escapeRatio * 1.5f; + acoustics.reverbAbsorption = averageAbsorption; + audio::set_acoustics(std::move(acoustics)); } diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 65830efc5..11933a697 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -24,7 +24,8 @@ dv::value BlockMaterial::serialize() const { {"steps-sound", stepsSound}, {"place-sound", placeSound}, {"break-sound", breakSound}, - {"hit-sound", hitSound} + {"hit-sound", hitSound}, + {"sound-absorption", soundAbsorption} }); } @@ -34,6 +35,7 @@ void BlockMaterial::deserialize(const dv::value& src) { src.at("place-sound").get(placeSound); src.at("break-sound").get(breakSound); src.at("hit-sound").get(hitSound); + src.at("sound-absorption").get(soundAbsorption); } CoordSystem::CoordSystem(glm::ivec3 axisX, glm::ivec3 axisY, glm::ivec3 axisZ) diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 99b9dee70..4465b6398 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -147,6 +147,7 @@ struct BlockMaterial : Serializable { std::string placeSound; std::string breakSound; std::string hitSound; + float soundAbsorption = 0.5f; dv::value toTable() const; // for compatibility dv::value serialize() const override; From 48f5de8adf777d34d679912e057dac4c999bc29a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 23:58:42 +0300 Subject: [PATCH 13/23] fix commands execution thread --- src/devtools/stdin_reader.cpp | 26 +++++++++++++++----------- src/devtools/stdin_reader.hpp | 6 ++---- src/engine/Engine.cpp | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/devtools/stdin_reader.cpp b/src/devtools/stdin_reader.cpp index dba26b75d..683f387d3 100644 --- a/src/devtools/stdin_reader.cpp +++ b/src/devtools/stdin_reader.cpp @@ -1,5 +1,6 @@ #include "stdin_reader.hpp" +#include "engine/Engine.hpp" #include "logic/CommandsInterpreter.hpp" #include "coders/json.hpp" #include "debug/Logger.hpp" @@ -11,8 +12,9 @@ static debug::Logger logger("stdin-reader"); static std::thread reader_thread; -void start_stdin_reader(cmd::CommandsInterpreter& interpreter) { - reader_thread = std::thread([&interpreter]() { +void start_stdin_reader(Engine& engine) { + reader_thread = std::thread([&engine]() { + auto& interpreter = engine.getCmd(); logger.info() << "reader thread started"; std::string line; @@ -20,16 +22,18 @@ void start_stdin_reader(cmd::CommandsInterpreter& interpreter) { if (line.empty()) { continue; } - try { - auto result = interpreter.execute(line); - if (result.isString()) { - logger.info() << result.asString(); - } else { - logger.info() << json::stringify(result, true); + engine.postRunnable([line, &interpreter] () { + try { + auto result = interpreter.execute(line); + if (result.isString()) { + logger.info() << result.asString(); + } else { + logger.info() << json::stringify(result, true); + } + } catch (const std::exception& err) { + logger.error() << err.what(); } - } catch (const std::exception& err) { - logger.error() << err.what(); - } + }); } }); reader_thread.detach(); diff --git a/src/devtools/stdin_reader.hpp b/src/devtools/stdin_reader.hpp index d141db998..09b137311 100644 --- a/src/devtools/stdin_reader.hpp +++ b/src/devtools/stdin_reader.hpp @@ -1,7 +1,5 @@ #pragma once -namespace cmd { - class CommandsInterpreter; -} +class Engine; -void start_stdin_reader(cmd::CommandsInterpreter& interpreter); +void start_stdin_reader(Engine& engine); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 50a6f921b..6aa970f0f 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -193,7 +193,7 @@ void Engine::initialize(CoreParameters coreParameters) { project->loadProjectClientScript(); } if (params.stdinCommands) { - start_stdin_reader(*cmd); + start_stdin_reader(*this); } } From 85c085d7c360d356c199b269fafd5abcfc848540 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 23:59:56 +0300 Subject: [PATCH 14/23] rename --- src/devtools/{stdin_reader.cpp => stdin_cmd_reader.cpp} | 4 ++-- src/devtools/stdin_cmd_reader.hpp | 7 +++++++ src/devtools/stdin_reader.hpp | 5 ----- src/engine/Engine.cpp | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) rename src/devtools/{stdin_reader.cpp => stdin_cmd_reader.cpp} (92%) create mode 100644 src/devtools/stdin_cmd_reader.hpp delete mode 100644 src/devtools/stdin_reader.hpp diff --git a/src/devtools/stdin_reader.cpp b/src/devtools/stdin_cmd_reader.cpp similarity index 92% rename from src/devtools/stdin_reader.cpp rename to src/devtools/stdin_cmd_reader.cpp index 683f387d3..fccbb7f57 100644 --- a/src/devtools/stdin_reader.cpp +++ b/src/devtools/stdin_cmd_reader.cpp @@ -1,4 +1,4 @@ -#include "stdin_reader.hpp" +#include "stdin_cmd_reader.hpp" #include "engine/Engine.hpp" #include "logic/CommandsInterpreter.hpp" @@ -12,7 +12,7 @@ static debug::Logger logger("stdin-reader"); static std::thread reader_thread; -void start_stdin_reader(Engine& engine) { +void cmd::start_stdin_cmd_reader(Engine& engine) { reader_thread = std::thread([&engine]() { auto& interpreter = engine.getCmd(); logger.info() << "reader thread started"; diff --git a/src/devtools/stdin_cmd_reader.hpp b/src/devtools/stdin_cmd_reader.hpp new file mode 100644 index 000000000..992054250 --- /dev/null +++ b/src/devtools/stdin_cmd_reader.hpp @@ -0,0 +1,7 @@ +#pragma once + +class Engine; + +namespace cmd { + void start_stdin_cmd_reader(Engine& engine); +} diff --git a/src/devtools/stdin_reader.hpp b/src/devtools/stdin_reader.hpp deleted file mode 100644 index 09b137311..000000000 --- a/src/devtools/stdin_reader.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -class Engine; - -void start_stdin_reader(Engine& engine); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 6aa970f0f..0f34cb031 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -15,7 +15,7 @@ #include "devtools/DebuggingServer.hpp" #include "devtools/Editor.hpp" #include "devtools/Project.hpp" -#include "devtools/stdin_reader.hpp" +#include "devtools/stdin_cmd_reader.hpp" #include "EnginePaths.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" @@ -193,7 +193,7 @@ void Engine::initialize(CoreParameters coreParameters) { project->loadProjectClientScript(); } if (params.stdinCommands) { - start_stdin_reader(*this); + cmd::start_stdin_cmd_reader(*this); } } From 2b4c3c08f35c0ce0de85789a27b77a47ce134c53 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:23:35 +0300 Subject: [PATCH 15/23] Fix crash in headless --- res/scripts/stdcmd.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index 900371e49..779ad50bf 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -17,6 +17,7 @@ console.add_command( "clear", "Clears the console", function() + if not hud then error("command is unavailable in headless mode") return end local document = Document.new("core:console") document.log.text = "" end From a92efe581f3b6a37f3217684a7174efb50bb6ef9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 12 Feb 2026 01:35:57 +0300 Subject: [PATCH 16/23] fix: assets access attempts in headless mode leading to segfaults --- src/engine/Engine.cpp | 2 +- src/logic/scripting/lua/libs/libassets.cpp | 12 ++++++++++++ src/logic/scripting/lua/libs/libgui.cpp | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 0f34cb031..60973437c 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -420,7 +420,7 @@ EngineSettings& Engine::getSettings() { } Assets* Engine::getAssets() { - return assets->getStorage(); + return assets ? assets->getStorage() : nullptr; } AssetsLoader& Engine::acquireBackgroundLoader() { diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 0921f1b9c..27d376542 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -26,6 +26,9 @@ static void load_texture( } static int l_request_texture(lua::State* L) { + if (engine->isHeadless()) { + throw std::runtime_error("not available in headless mode"); + } std::string filename = lua::require_string(L, 1); std::string alias = lua::require_string(L, 2); auto& loader = engine->acquireBackgroundLoader(); @@ -34,6 +37,9 @@ static int l_request_texture(lua::State* L) { } static int l_load_texture(lua::State* L) { + if (engine->isHeadless()) { + throw std::runtime_error("not available in headless mode"); + } if (lua::isstring(L, 3) && lua::require_lstring(L, 3) != "png") { throw std::runtime_error("unsupportd image format"); } @@ -61,6 +67,9 @@ static int l_load_texture(lua::State* L) { } static int l_parse_model(lua::State* L) { + if (engine->isHeadless()) { + throw std::runtime_error("not available in headless mode"); + } auto format = lua::require_lstring(L, 1); auto string = lua::require_lstring(L, 2); auto name = lua::require_string(L, 3); @@ -79,6 +88,9 @@ static int l_parse_model(lua::State* L) { } static int l_to_canvas(lua::State* L) { + if (engine->isHeadless()) { + throw std::runtime_error("not available in headless mode"); + } auto& assets = *engine->getAssets(); auto alias = lua::require_lstring(L, 1); diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index fe4cabada..0ec5e6475 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -32,6 +32,9 @@ using namespace scripting; static DocumentNode get_document_node_impl( lua::State*, const std::string& name, const std::string& nodeName, bool throwable=true ) { + if (engine->isHeadless()) { + throw std::runtime_error("not available in headless mode"); + } auto doc = engine->getAssets()->get(name); if (doc == nullptr) { if (throwable) { From 8750c5b5b9421bfb164f5ab8350b49538eb832c9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 12 Feb 2026 01:44:25 +0300 Subject: [PATCH 17/23] cleanup --- src/devtools/stdin_cmd_reader.cpp | 2 +- src/engine/Engine.cpp | 10 ++++++++ src/engine/Engine.hpp | 1 + src/logic/scripting/lua/libs/libassets.cpp | 29 +++++++++------------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/devtools/stdin_cmd_reader.cpp b/src/devtools/stdin_cmd_reader.cpp index fccbb7f57..8e4a48c22 100644 --- a/src/devtools/stdin_cmd_reader.cpp +++ b/src/devtools/stdin_cmd_reader.cpp @@ -8,7 +8,7 @@ #include #include -static debug::Logger logger("stdin-reader"); +static debug::Logger logger("stdin-cmd"); static std::thread reader_thread; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 60973437c..17e9e9f80 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -423,7 +423,17 @@ Assets* Engine::getAssets() { return assets ? assets->getStorage() : nullptr; } +Assets& Engine::requireAssets() { + if (isHeadless()) { + throw std::runtime_error("assets are not available in headless mode"); + } + return *assets->getStorage(); +} + AssetsLoader& Engine::acquireBackgroundLoader() { + if (isHeadless()) { + throw std::runtime_error("assets are not available in headless mode"); + } return assets->acquireBackgroundLoader(); } diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index 4f4e68280..4f726b700 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -110,6 +110,7 @@ public: /// @brief Get active assets storage instance Assets* getAssets(); + Assets& requireAssets(); AssetsLoader& acquireBackgroundLoader(); diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 27d376542..82135ad86 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -15,10 +15,10 @@ using namespace scripting; static void load_texture( - const ubyte* bytes, size_t size, const std::string& destname + Assets& assets, const ubyte* bytes, size_t size, const std::string& destname ) { try { - engine->getAssets()->store(png::load_texture(bytes, size), destname); + assets.store(png::load_texture(bytes, size), destname); } catch (const std::runtime_error& err) { debug::Logger logger("lua.assetslib"); logger.error() << err.what(); @@ -26,9 +26,6 @@ static void load_texture( } static int l_request_texture(lua::State* L) { - if (engine->isHeadless()) { - throw std::runtime_error("not available in headless mode"); - } std::string filename = lua::require_string(L, 1); std::string alias = lua::require_string(L, 2); auto& loader = engine->acquireBackgroundLoader(); @@ -37,9 +34,8 @@ static int l_request_texture(lua::State* L) { } static int l_load_texture(lua::State* L) { - if (engine->isHeadless()) { - throw std::runtime_error("not available in headless mode"); - } + auto& assets = engine->requireAssets(); + if (lua::isstring(L, 3) && lua::require_lstring(L, 3) != "png") { throw std::runtime_error("unsupportd image format"); } @@ -53,10 +49,13 @@ static int l_load_texture(lua::State* L) { lua::pop(L); } lua::pop(L); - load_texture(buffer.data(), buffer.size(), lua::require_string(L, 2)); + load_texture( + assets, buffer.data(), buffer.size(), lua::require_string(L, 2) + ); } else { auto string = lua::bytearray_as_string(L, 1); load_texture( + assets, reinterpret_cast(string.data()), string.size(), lua::require_string(L, 2) @@ -67,16 +66,15 @@ static int l_load_texture(lua::State* L) { } static int l_parse_model(lua::State* L) { - if (engine->isHeadless()) { - throw std::runtime_error("not available in headless mode"); - } + auto& assets = engine->requireAssets(); + auto format = lua::require_lstring(L, 1); auto string = lua::require_lstring(L, 2); auto name = lua::require_string(L, 3); if (format == "xml" || format == "vcm") { auto vcmModel = vcm::parse(name, string, format == "xml"); - engine->getAssets()->store( + assets.store( std::make_unique(std::move(vcmModel.squash())), name ); } else { @@ -88,10 +86,7 @@ static int l_parse_model(lua::State* L) { } static int l_to_canvas(lua::State* L) { - if (engine->isHeadless()) { - throw std::runtime_error("not available in headless mode"); - } - auto& assets = *engine->getAssets(); + auto& assets = engine->requireAssets(); auto alias = lua::require_lstring(L, 1); size_t sep = alias.rfind(':'); From 428127f620dc2b2dc4002a46b34005d76cfb00bb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 12 Feb 2026 01:58:00 +0300 Subject: [PATCH 18/23] another cleanup --- src/logic/scripting/lua/libs/libgui.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 0ec5e6475..514f3f98d 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -32,10 +32,7 @@ using namespace scripting; static DocumentNode get_document_node_impl( lua::State*, const std::string& name, const std::string& nodeName, bool throwable=true ) { - if (engine->isHeadless()) { - throw std::runtime_error("not available in headless mode"); - } - auto doc = engine->getAssets()->get(name); + auto doc = engine->requireAssets().get(name); if (doc == nullptr) { if (throwable) { throw std::runtime_error("document '" + name + "' not found"); @@ -1029,7 +1026,7 @@ static int l_gui_str(lua::State* L) { static int l_gui_reindex(lua::State* L) { auto name = lua::require_string(L, 1); - auto doc = engine->getAssets()->get(name); + auto doc = engine->requireAssets().get(name); if (doc == nullptr) { throw std::runtime_error( "document '" + std::string(name) + "' not found" @@ -1127,7 +1124,7 @@ static int l_gui_load_document(lua::State* L) { engine->getGUI(), std::move(env), alias, filename, filename.string() ); auto document = documentPtr.get(); - engine->getAssets()->store(std::move(documentPtr), alias); + engine->requireAssets().store(std::move(documentPtr), alias); // namespace extension if (lua::istable(L, 4)) { From cd537cc1cbf490b614cdbd8ab38a3a26364c29f6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 11 Feb 2026 21:46:36 +0300 Subject: [PATCH 19/23] fix: macos uses OpenAL.Framework instead of openal-soft --- .github/workflows/macos.yml | 8 ++++++-- src/audio/AL/ALAudio.hpp | 6 ------ src/audio/AL/alutil.hpp | 9 ++------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 99466ddab..9fdd8d0b3 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -26,10 +26,14 @@ jobs: brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm - name: Configure - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}" + run: | + cmake -DOPENAL_INCLUDE_DIR=$(brew --prefix openal-soft)/include \ + -DOPENAL_LIBRARY=$(brew --prefix openal-soft)/lib/libopenal.dylib \ + -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}" - name: Build - run: cmake --build build -t install + run: | + cmake --build build -t install - name: Make fix_dylibs.sh executable run: chmod +x dev/fix_dylibs.sh diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index c2cba3961..6834db0f3 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -11,15 +11,9 @@ #include "audio/audio.hpp" #include "audio/effects.hpp" -#ifdef __APPLE__ -#include -#include -#include -#else #include #include #include -#endif namespace audio { struct ALBuffer; diff --git a/src/audio/AL/alutil.hpp b/src/audio/AL/alutil.hpp index bfa701184..56e5b0f7e 100644 --- a/src/audio/AL/alutil.hpp +++ b/src/audio/AL/alutil.hpp @@ -1,17 +1,12 @@ #pragma once +#include "typedefs.hpp" + #include #include #include -#include "typedefs.hpp" - -#ifdef __APPLE__ -#include -#else #include -#endif - #include #define AL_CHECK(STATEMENT) \ From d6bd5bd8fa010c6584671c741e02814cf12a4ab5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 12 Feb 2026 21:48:09 +0300 Subject: [PATCH 20/23] add 'audio.acoustic-effects' setting --- res/layouts/pages/settings_audio.xml.lua | 10 ++++++++++ res/layouts/pages/settings_graphics.xml.lua | 2 +- src/audio/AL/ALAudio.cpp | 20 ++++++++++++++++---- src/audio/AL/ALAudio.hpp | 13 +++++++++++-- src/audio/audio.cpp | 2 +- src/io/settings_io.cpp | 1 + src/settings.hpp | 2 ++ 7 files changed, 42 insertions(+), 8 deletions(-) diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index c9b4be4b6..37609a900 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -24,6 +24,14 @@ function update_setting(x, id, name, postfix) ) end +function create_checkbox(id, name, tooltip) + tooltip = tooltip or '' + document.root:add(string.format( + "%s", + id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") + )) +end + local initialized = false function on_open() @@ -71,4 +79,6 @@ function on_open() if info then selectbox.value = info.device_specifier end + + create_checkbox("audio.acoustic-effects", "Acoustic effects", "audio.acoustic-effects.tooltip") end diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index 3b7f7b13a..b8b9d5497 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -39,7 +39,7 @@ function on_open() create_setting("chunks.load-distance", "Load Distance", 1) create_setting("chunks.load-speed", "Load Speed", 1) create_setting("graphics.fog-curve", "Fog Curve", 0.1) - + create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") create_checkbox("graphics.soft-lighting", "Soft lighting", "graphics.soft-lighting.tooltip") create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip") diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index eba10df17..473e5bc49 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -481,8 +481,16 @@ bool ALSpeaker::isManuallyStopped() const { static bool alc_enumeration_ext = false; -ALAudio::ALAudio(ALCdevice* device, ALCcontext* context, bool useEffects) - : device(device), context(context), useEffects(useEffects) { +ALAudio::ALAudio( + ALCdevice* device, + ALCcontext* context, + bool useEffects, + const AudioSettings& settings +) + : device(device), + context(context), + settings(settings), + useEffects(useEffects) { ALCint size; alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size); std::vector attrs(size); @@ -693,7 +701,7 @@ std::unique_ptr ALAudio::openInputDevice( ); } -std::unique_ptr ALAudio::create() { +std::unique_ptr ALAudio::create(const AudioSettings& settings) { alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); ALCdevice* device = alcOpenDevice(nullptr); @@ -722,7 +730,7 @@ std::unique_ptr ALAudio::create() { logger.info() << "device supports " << sends << " aux sends per source"; logger.info() << "initialized"; - return std::make_unique(device, context, effects); + return std::make_unique(device, context, effects, settings); } uint ALAudio::getFreeSource() { @@ -787,6 +795,10 @@ void ALAudio::setAcoustics(Acoustics acoustics) { if (!useEffects) { return; } + if (!settings.acousticEffects.get()) { + alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, 0); + return; + } int reverbEffect = effects[REVERB_EFFECT]; AL_CHECK(alEffectf( reverbEffect, diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 6834db0f3..b405d1473 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -15,6 +15,8 @@ #include #include +struct AudioSettings; + namespace audio { struct ALBuffer; class ALAudio; @@ -177,6 +179,8 @@ namespace audio { uint maxSources = 256; uint maxEffectSlots = 64; + const AudioSettings& settings; + bool initEffects(); public: std::vector effectSlots; @@ -185,7 +189,12 @@ namespace audio { bool useEffects; - ALAudio(ALCdevice* device, ALCcontext* context, bool effects); + ALAudio( + ALCdevice* device, + ALCcontext* context, + bool effects, + const AudioSettings& settings + ); ~ALAudio(); uint getFreeSource(); @@ -226,6 +235,6 @@ namespace audio { return false; } - static std::unique_ptr create(); + static std::unique_ptr create(const AudioSettings& settings); }; } diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index a45a876f6..6523f76b6 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -159,7 +159,7 @@ void audio::initialize( enabled = enabled && settings.enabled.get(); if (enabled) { logger.info() << "initializing ALAudio backend"; - backend = ALAudio::create().release(); + backend = ALAudio::create(settings).release(); } if (backend == nullptr) { if (enabled) { diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index da0027c4d..8bd8402d8 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -44,6 +44,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.add("volume-ambient", &settings.audio.volumeAmbient); builder.add("volume-music", &settings.audio.volumeMusic); builder.add("input-device", &settings.audio.inputDevice); + builder.add("acoustic-effects", &settings.audio.acousticEffects); builder.addSection("display"); builder.add("width", &settings.display.width); diff --git a/src/settings.hpp b/src/settings.hpp index 0a0754235..ad597f76a 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -18,6 +18,8 @@ struct AudioSettings { NumberSetting volumeMusic {1.0f, 0.0f, 1.0f, setting_format::percent}; StringSetting inputDevice {"auto"}; + + FlagSetting acousticEffects {true}; }; struct DisplaySettings { From 92eeee5a206e9cb7b69393954f0cc3efaadd00b1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 12 Feb 2026 21:52:05 +0300 Subject: [PATCH 21/23] update res/texts/* --- res/texts/en_US.txt | 1 + res/texts/ru_RU.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index ae19130e3..1d63fa9a5 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -17,6 +17,7 @@ devtools.traceback=Traceback (most recent call first) devtools.output=Output # Tooltips +audio.acoustic-effects.tooltip=Acoustic effects may not be supported by your system graphics.gamma.tooltip=Lighting brightness curve graphics.backlight.tooltip=Backlight to prevent total darkness graphics.dense-render.tooltip=Enables transparency in blocks like leaves diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 1461f854c..e9bf368ad 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -38,6 +38,7 @@ error.dependency-version-not-met=Версия зависимости не соо pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? # Подсказки +audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой graphics.gamma.tooltip=Кривая яркости освещения graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья @@ -110,6 +111,7 @@ settings.Windowed=Оконный settings.Borderless=Безрамочный settings.Microphone=Микрофон settings.microphone.None=Нет +settings.Acoustic effects=Акустические эффекты # Управление chunks.reload=Перезагрузить Чанки From a43f41182dcd361ab3af9871d4a78623f75a94cf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 12 Feb 2026 22:04:25 +0300 Subject: [PATCH 22/23] clamp effect parameters values --- src/audio/AL/ALAudio.cpp | 70 +++++++++++++++++++++++++++---- src/graphics/render/Decorator.cpp | 6 ++- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 473e5bc49..9c3a57646 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -806,12 +806,68 @@ void ALAudio::setAcoustics(Acoustics acoustics) { glm::max(0.1f, glm::min(acoustics.reverbGain, 0.8f) * (1.0f - glm::min(1.0f, acoustics.reverbAbsorption))) )); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_DELAY, acoustics.reverbReflectionsDelay)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_LATE_REVERB_DELAY, acoustics.reverbLateReflectionsDelay)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_DECAY_TIME, std::max(0.1f, acoustics.reverbDecayTime))); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_ROOM_ROLLOFF_FACTOR, acoustics.reverbRoomRolloff)); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_REFLECTIONS_GAIN, glm::pow(acoustics.reverbGain, 2.0f))); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_LATE_REVERB_GAIN, glm::pow(acoustics.reverbGain, 2.0f))); - AL_CHECK(alEffectf(reverbEffect, AL_REVERB_DECAY_HFRATIO, 1.0f - acoustics.reverbAbsorption)); + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_REFLECTIONS_DELAY, + glm::clamp( + acoustics.reverbReflectionsDelay, + AL_REVERB_MIN_REFLECTIONS_DELAY, + AL_REVERB_MAX_REFLECTIONS_DELAY + ) + )); + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_LATE_REVERB_DELAY, + glm::clamp( + acoustics.reverbLateReflectionsDelay, + AL_REVERB_MIN_LATE_REVERB_DELAY, + AL_REVERB_MAX_LATE_REVERB_DELAY + ) + )); + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_DECAY_TIME, + glm::clamp( + acoustics.reverbDecayTime, + AL_REVERB_MIN_DECAY_TIME, + AL_REVERB_MAX_DECAY_TIME + ) + )); + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_ROOM_ROLLOFF_FACTOR, + glm::clamp( + acoustics.reverbRoomRolloff, + AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR, + AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR + ) + )); + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_REFLECTIONS_GAIN, + glm::clamp( + glm::pow(acoustics.reverbGain, 2.0f), + AL_REVERB_MIN_REFLECTIONS_GAIN, + AL_REVERB_MAX_REFLECTIONS_GAIN + ) + )); + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_LATE_REVERB_GAIN, + glm::clamp( + glm::pow(acoustics.reverbGain, 2.0f), + AL_REVERB_MIN_LATE_REVERB_GAIN, + AL_REVERB_MAX_LATE_REVERB_GAIN + ) + )); + AL_CHECK(alEffectf( + reverbEffect, + AL_REVERB_DECAY_HFRATIO, + glm::clamp( + 1.0f - acoustics.reverbAbsorption, + AL_REVERB_MIN_DECAY_HFRATIO, + AL_REVERB_MAX_DECAY_HFRATIO + ) + )); alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, reverbEffect); } diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 29405ef37..b1957c697 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -141,8 +141,10 @@ void Decorator::updateAcoustics(const Camera& camera) { averageDistance += distance; averageAbsorption += material->soundAbsorption; } - averageDistance /= hit; - averageAbsorption /= hit; + if (hit > 0) { + averageDistance /= hit; + averageAbsorption /= hit; + } float decayTime = 20.0f * (averageDistance / rayLength); float escapeRatio = static_cast(rays - hit) / static_cast(rays); From 310d79be476940722d5fa612848954e87d36ff49 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 13 Feb 2026 01:40:35 +0300 Subject: [PATCH 23/23] disable effects in non-world channels --- src/audio/AL/ALAudio.cpp | 14 +++++++------- src/audio/audio.cpp | 24 +++++++++++++++--------- src/audio/audio.hpp | 9 +++++++-- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 9c3a57646..e7494d306 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -392,15 +392,18 @@ void ALSpeaker::setLoop(bool loop) { void ALSpeaker::play() { paused = false; manuallyStopped = false; - auto p_channel = get_channel(this->channel); + auto channel = get_channel(this->channel); AL_CHECK(alSourcef( source, AL_GAIN, - volume * p_channel->getVolume() + volume * channel->getVolume() )); - if (al->useEffects) { + if (al->useEffects && channel->isEffectsApplied()) { AL_CHECK(alSource3i(source, AL_AUXILIARY_SEND_FILTER, al->effectSlots[0], 0, al->filters[0])); - //AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, al->filters[LOWPASS_FILTER])); + //AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, al->filters[LOWPASS_FILTER])); // TODO: use lowpass filter + } else { + AL_CHECK(alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, 0)); + AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, 0)); } AL_CHECK(alSourcePlay(source)); } @@ -598,14 +601,12 @@ bool ALAudio::initEffects() { } } - // Create reverb effect alEffecti(effects[REVERB_EFFECT], AL_EFFECT_TYPE, AL_EFFECT_REVERB); if (alGetError() != AL_NO_ERROR) { logger.error() << "reverb effect is not supported"; return false; } - // Create lowpass filter alFilteri(filters[LOWPASS_FILTER], AL_FILTER_TYPE, AL_FILTER_LOWPASS); if (alGetError() != AL_NO_ERROR) { logger.error() << "lowpass filter is not supported"; @@ -614,7 +615,6 @@ bool ALAudio::initEffects() { alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAIN, 1.0f); alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAINHF, 0.01f); - // Attach effect to aux effect slot alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, effects[0]); if (alGetError() == AL_NO_ERROR) { logger.info() << "successfully loaded effect into effect slot"; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 6523f76b6..e8fd96e14 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -27,7 +27,8 @@ namespace { static bool input_enabled = false; } -Channel::Channel(std::string name) : name(std::move(name)) { +Channel::Channel(std::string name, bool effects) + : name(std::move(name)), effects(effects) { } float Channel::getVolume() const { @@ -56,6 +57,10 @@ bool Channel::isPaused() const { return paused; } +bool Channel::isEffectsApplied() const { + return effects; +} + size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) { if (!isOpen()) { return 0; @@ -171,15 +176,16 @@ void audio::initialize( struct { std::string name; NumberSetting* setting; + bool effects; } builtin_channels[] { - {"master", &settings.volumeMaster}, - {"regular", &settings.volumeRegular}, - {"music", &settings.volumeMusic}, - {"ambient", &settings.volumeAmbient}, - {"ui", &settings.volumeUI} + {"master", &settings.volumeMaster, false}, + {"regular", &settings.volumeRegular, true}, + {"music", &settings.volumeMusic, false}, + {"ambient", &settings.volumeAmbient, true}, + {"ui", &settings.volumeUI, false} }; for (auto& channel : builtin_channels) { - create_channel(channel.name); + create_channel(channel.name, channel.effects); objects_keeper.keepAlive(channel.setting->observe([=](auto value) { audio::get_channel(channel.name)->setVolume(value * value); }, true)); @@ -419,12 +425,12 @@ Speaker* audio::get_speaker(speakerid_t id) { return found->second.get(); } -int audio::create_channel(const std::string& name) { +int audio::create_channel(const std::string& name, bool effects) { int index = get_channel_index(name); if (index != -1) { return index; } - channels.emplace_back(std::make_unique(name)); + channels.emplace_back(std::make_unique(name, effects)); return channels.size() - 1; } diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 365f6bce9..39304fd41 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -43,8 +43,9 @@ namespace audio { /// @brief Channel volume setting float volume = 1.0f; bool paused = false; + bool effects; public: - Channel(std::string name); + Channel(std::string name, bool effects); /// @brief Get channel volume float getVolume() const; @@ -73,6 +74,9 @@ namespace audio { /// @brief Check if the channel is paused bool isPaused() const; + + /// @brief Check if the channel uses common acoustic effects / filters + bool isEffectsApplied() const; }; /// @brief Pulse-code modulation data @@ -549,8 +553,9 @@ namespace audio { /// @brief Create new channel. /// All non-builtin channels will be destroyed on audio::reset() call /// @param name channel name + /// @param effects use common acoustic effects / filters /// @return new channel index - int create_channel(const std::string& name); + int create_channel(const std::string& name, bool effects); /// @brief Get channel index by name /// @param name channel name