Merge pull request #784 from MihailRis/acoustics

acoustics
This commit is contained in:
MihailRis 2026-02-13 02:05:35 +03:00 • committed by GitHub
commit 2aa9042fa4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 447 additions and 60 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -1,5 +1,6 @@
{
"steps-sound": "steps/grass",
"place-sound": "steps/grass",
"break-sound": "steps/grass"
"break-sound": "steps/grass",
"sound-absorption": 0.85
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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(
"<checkbox consumer='function(x) app.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
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

View file

@ -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")

View file

@ -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

View file

@ -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=Включает прозрачность блоков, таких как листья
@ -111,6 +112,7 @@ settings.Windowed=Оконный
settings.Borderless=Безрамочный
settings.Microphone=Микрофон
settings.microphone.None=Нет
settings.Acoustic effects=Акустические эффекты
# Управление
chunks.reload=Перезагрузить Чанки

View file

@ -40,6 +40,22 @@ 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;
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>& pcm, bool keepPCM
)
@ -376,12 +392,19 @@ 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 && 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])); // 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));
}
@ -455,15 +478,22 @@ 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,
const AudioSettings& settings
)
: device(device),
context(context),
settings(settings),
useEffects(useEffects) {
ALCint size;
alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size);
std::vector<ALCint> attrs(size);
@ -487,6 +517,11 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
}
alDopplerFactor(1.0 / 3.0);
alGetError();
if (useEffects) {
this->useEffects = initEffects();
}
}
ALAudio::~ALAudio() {
@ -513,6 +548,80 @@ ALAudio::~ALAudio() {
context = nullptr;
}
template<typename T>
static bool get_proc_address(const char* name, T& ptr) {
ptr = (T) alGetProcAddress(name);
return ptr != nullptr;
}
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)
&& 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);
}
bool ALAudio::initEffects() {
if (!request_efx_ext_functions()) {
logger.error() << "could not get effects extension function pointers";
return false;
}
for (uint i = 0; i < maxEffectSlots; i++) {
effectSlots.emplace_back();
alGenAuxiliaryEffectSlots(1, &effectSlots[i]);
if (alGetError() != AL_NO_ERROR) {
break;
}
}
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;
return false;
}
}
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])) {
logger.error() << "could not to create filter #" << i;
return false;
}
}
alEffecti(effects[REVERB_EFFECT], AL_EFFECT_TYPE, AL_EFFECT_REVERB);
if (alGetError() != AL_NO_ERROR) {
logger.error() << "reverb effect is not supported";
return false;
}
alFilteri(filters[LOWPASS_FILTER], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
if (alGetError() != AL_NO_ERROR) {
logger.error() << "lowpass filter is not supported";
return false;
}
alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAIN, 1.0f);
alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAINHF, 0.01f);
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<Sound> ALAudio::createSound(
std::shared_ptr<PCM> pcm, bool keepPCM
) {
@ -592,19 +701,36 @@ std::unique_ptr<InputDevice> ALAudio::openInputDevice(
);
}
std::unique_ptr<ALAudio> ALAudio::create() {
std::unique_ptr<ALAudio> ALAudio::create(const AudioSettings& settings) {
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";
logger.info() << "initialized";
return std::make_unique<ALAudio>(device, context);
return std::make_unique<ALAudio>(device, context, effects, settings);
}
uint ALAudio::getFreeSource() {
@ -664,3 +790,84 @@ void ALAudio::setListener(
void ALAudio::update(double) {
}
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,
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,
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);
}

View file

@ -3,19 +3,19 @@
#include <glm/glm.hpp>
#include <queue>
#include <string>
#include <array>
#include <unordered_map>
#include <vector>
#include "typedefs.hpp"
#include "audio/audio.hpp"
#include "audio/effects.hpp"
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
#include <AL/alext.h>
struct AudioSettings;
namespace audio {
struct ALBuffer;
@ -176,10 +176,25 @@ namespace audio {
std::vector<uint> allbuffers;
std::vector<uint> freebuffers;
uint maxSources = 256;
uint maxEffectSlots = 64;
const AudioSettings& settings;
bool initEffects();
public:
ALAudio(ALCdevice* device, ALCcontext* context);
std::vector<uint> effectSlots;
std::vector<uint> effects;
std::array<uint, 1> filters;
bool useEffects;
ALAudio(
ALCdevice* device,
ALCcontext* context,
bool effects,
const AudioSettings& settings
);
~ALAudio();
uint getFreeSource();
@ -214,10 +229,12 @@ namespace audio {
void update(double delta) override;
void setAcoustics(Acoustics acoustics) override;
bool isDummy() const override {
return false;
}
static std::unique_ptr<ALAudio> create();
static std::unique_ptr<ALAudio> create(const AudioSettings& settings);
};
}

View file

@ -1,17 +1,12 @@
#pragma once
#include "typedefs.hpp"
#include <cstdint>
#include <string>
#include <type_traits>
#include "typedefs.hpp"
#ifdef __APPLE__
#include <OpenAL/al.h>
#else
#include <AL/al.h>
#endif
#include <glm/glm.hpp>
#define AL_CHECK(STATEMENT) \

View file

@ -104,6 +104,9 @@ namespace audio {
void update(double delta) override {
}
void setAcoustics(Acoustics) override {
}
bool isDummy() const override {
return true;
}

View file

@ -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;
@ -159,7 +164,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) {
@ -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<Channel>(name));
channels.emplace_back(std::make_unique<Channel>(name, effects));
return channels.size() - 1;
}
@ -496,6 +502,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) {

View file

@ -7,6 +7,7 @@
#include "typedefs.hpp"
#include "settings.hpp"
#include "io/fwd.hpp"
#include "effects.hpp"
namespace audio {
/// @brief playing speaker uid
@ -42,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;
@ -72,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
@ -394,6 +399,7 @@ namespace audio {
virtual std::vector<std::string> getInputDeviceNames() = 0;
virtual std::vector<std::string> 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
@ -547,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
@ -580,6 +587,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);

38
src/audio/effects.hpp Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <map>
#include <string>
#include <variant>
#include <optional>
namespace audio {
struct Effect {
struct Property {
using Value = std::variant<float, int>;
int id;
Value def;
Value min;
Value max;
};
std::map<std::string, Property> 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;
};
struct Acoustics {
float reverbGain;
float reverbReflectionsDelay;
float reverbLateReflectionsDelay;
float reverbDecayTime;
float reverbRoomRolloff;
float reverbAbsorption;
};
}

View file

@ -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,90 @@ 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<Emitter>(
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<Emitter>(
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(34621U);
auto& chunks = *player.chunks;
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++) {
float u1 = random.randFloat();
float u2 = random.randFloat();
float z = 2.0f * u1 - 1.0f;
float phi = 2.0f * glm::pi<float>() * 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);
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;
averageAbsorption += material->soundAbsorption;
}
if (hit > 0) {
averageDistance /= hit;
averageAbsorption /= hit;
}
float decayTime = 20.0f * (averageDistance / rayLength);
float escapeRatio = static_cast<float>(rays - hit) / static_cast<float>(rays);
acoustics.reverbGain = glm::pow(hit / static_cast<float>(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));
}
void Decorator::updateRandom(
@ -283,4 +357,5 @@ void Decorator::update(
}
updateBlockEmitters(camera);
updateTextNotes();
updateAcoustics(camera);
}

View file

@ -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,

View file

@ -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);

View file

@ -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 {

View file

@ -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)

View file

@ -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;