From b698f2cc636c8ff269d933fed8c0f512d9ff719e Mon Sep 17 00:00:00 2001 From: dashodanger <> Date: Fri, 17 Jan 2025 17:11:34 -0700 Subject: [PATCH] Add verblib-based reverb node for Dynamic Reverb --- libraries/miniaudio/ma_reverb_node.cc | 76 +++ libraries/miniaudio/ma_reverb_node.h | 42 ++ libraries/miniaudio/miniaudio.cc | 4 +- libraries/miniaudio/verblib.h | 739 ++++++++++++++++++++++++++ source_files/edge/i_sound.cc | 26 +- source_files/edge/i_sound.h | 9 +- source_files/edge/m_misc.cc | 1 - source_files/edge/m_option.cc | 3 +- source_files/edge/p_user.cc | 69 ++- source_files/edge/s_blit.cc | 1 - source_files/edge/s_blit.h | 2 +- source_files/edge/s_sound.cc | 18 +- 12 files changed, 960 insertions(+), 30 deletions(-) create mode 100644 libraries/miniaudio/ma_reverb_node.cc create mode 100644 libraries/miniaudio/ma_reverb_node.h create mode 100644 libraries/miniaudio/verblib.h diff --git a/libraries/miniaudio/ma_reverb_node.cc b/libraries/miniaudio/ma_reverb_node.cc new file mode 100644 index 000000000..8fa3b2e46 --- /dev/null +++ b/libraries/miniaudio/ma_reverb_node.cc @@ -0,0 +1,76 @@ +#include "ma_reverb_node.h" + +MA_API ma_reverb_node_config ma_reverb_node_config_init(ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_reverb_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_reverb_node_init(). */ + config.channels = channels; + config.sampleRate = sampleRate; + config.roomSize = verblib_initialroom; + config.damping = verblib_initialdamp; + config.width = verblib_initialwidth; + config.wetVolume = verblib_initialwet; + config.dryVolume = verblib_initialdry; + config.mode = verblib_initialmode; + + return config; +} + + +static void ma_reverb_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_reverb_node* pReverbNode = (ma_reverb_node*)pNode; + + (void)pFrameCountIn; + + verblib_process(&pReverbNode->reverb, ppFramesIn[0], ppFramesOut[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_reverb_node_vtable = +{ + ma_reverb_node_process_pcm_frames, + NULL, + 1, /* 1 input channel. */ + 1, /* 1 output channel. */ + MA_NODE_FLAG_CONTINUOUS_PROCESSING /* Reverb requires continuous processing to ensure the tail get's processed. */ +}; + +MA_API ma_result ma_reverb_node_init(ma_node_graph* pNodeGraph, const ma_reverb_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_reverb_node* pReverbNode) +{ + ma_result result; + ma_node_config baseConfig; + + if (pReverbNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pReverbNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (verblib_initialize(&pReverbNode->reverb, (unsigned long)pConfig->sampleRate, (unsigned int)pConfig->channels) == 0) { + return MA_INVALID_ARGS; + } + + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_reverb_node_vtable; + baseConfig.pInputChannels = &pConfig->channels; + baseConfig.pOutputChannels = &pConfig->channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pReverbNode->baseNode); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_reverb_node_uninit(ma_reverb_node* pReverbNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + /* The base node is always uninitialized first. */ + ma_node_uninit(pReverbNode, pAllocationCallbacks); +} \ No newline at end of file diff --git a/libraries/miniaudio/ma_reverb_node.h b/libraries/miniaudio/ma_reverb_node.h new file mode 100644 index 000000000..80370fbd0 --- /dev/null +++ b/libraries/miniaudio/ma_reverb_node.h @@ -0,0 +1,42 @@ +/* Include ma_reverb_node.h after miniaudio.h */ +#ifndef ma_reverb_node_h +#define ma_reverb_node_h + +#include "verblib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +The reverb node has one input and one output. +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_uint32 channels; /* The number of channels of the source, which will be the same as the output. Must be 1 or 2. */ + ma_uint32 sampleRate; + float roomSize; + float damping; + float width; + float wetVolume; + float dryVolume; + float mode; +} ma_reverb_node_config; + +MA_API ma_reverb_node_config ma_reverb_node_config_init(ma_uint32 channels, ma_uint32 sampleRate); + + +typedef struct +{ + ma_node_base baseNode; + verblib reverb; +} ma_reverb_node; + +MA_API ma_result ma_reverb_node_init(ma_node_graph* pNodeGraph, const ma_reverb_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_reverb_node* pReverbNode); +MA_API void ma_reverb_node_uninit(ma_reverb_node* pReverbNode, const ma_allocation_callbacks* pAllocationCallbacks); + +#ifdef __cplusplus +} +#endif +#endif /* ma_reverb_node_h */ diff --git a/libraries/miniaudio/miniaudio.cc b/libraries/miniaudio/miniaudio.cc index d950ec5ee..4fbc82d61 100644 --- a/libraries/miniaudio/miniaudio.cc +++ b/libraries/miniaudio/miniaudio.cc @@ -6,4 +6,6 @@ // We use our own custom minivorbis decoder #define MA_NO_VORBIS #define MINIAUDIO_IMPLEMENTATION -#include "miniaudio.h" \ No newline at end of file +#include "miniaudio.h" +#define VERBLIB_IMPLEMENTATION +#include "ma_reverb_node.cc" \ No newline at end of file diff --git a/libraries/miniaudio/verblib.h b/libraries/miniaudio/verblib.h new file mode 100644 index 000000000..b70d3555f --- /dev/null +++ b/libraries/miniaudio/verblib.h @@ -0,0 +1,739 @@ +/* Reverb Library +* Verblib version 0.5 - 2022-10-25 +* +* Philip Bennefall - philip@blastbay.com +* +* See the end of this file for licensing terms. +* This reverb is based on Freeverb, a public domain reverb written by Jezar at Dreampoint. +* +* IMPORTANT: The reverb currently only works with 1 or 2 channels, at sample rates of 22050 HZ and above. +* These restrictions may be lifted in a future version. +* +* USAGE +* +* This is a single-file library. To use it, do something like the following in one .c file. +* #define VERBLIB_IMPLEMENTATION +* #include "verblib.h" +* +* You can then #include this file in other parts of the program as you would with any other header file. +*/ + +#ifndef VERBLIB_H +#define VERBLIB_H + +#ifdef __cplusplus +extern "C" { +#endif + + /* COMPILE-TIME OPTIONS */ + + /* The maximum sample rate that should be supported, specified as a multiple of 44100. */ +#ifndef verblib_max_sample_rate_multiplier +#define verblib_max_sample_rate_multiplier 4 +#endif + + /* The silence threshold which is used when calculating decay time. */ +#ifndef verblib_silence_threshold +#define verblib_silence_threshold 80.0 /* In dB (absolute). */ +#endif + + /* PUBLIC API */ + + typedef struct verblib verblib; + + /* Initialize a verblib structure. + * + * Call this function to initialize the verblib structure. + * Returns nonzero (true) on success or 0 (false) on failure. + * The function will only fail if one or more of the parameters are invalid. + */ + int verblib_initialize ( verblib* verb, unsigned long sample_rate, unsigned int channels ); + + /* Run the reverb. + * + * Call this function continuously to generate your output. + * output_buffer may be the same pointer as input_buffer if in place processing is desired. + * frames specifies the number of sample frames that should be processed. + */ + void verblib_process ( verblib* verb, const float* input_buffer, float* output_buffer, unsigned long frames ); + + /* Set the size of the room, between 0.0 and 1.0. */ + void verblib_set_room_size ( verblib* verb, float value ); + + /* Get the size of the room. */ + float verblib_get_room_size ( const verblib* verb ); + + /* Set the amount of damping, between 0.0 and 1.0. */ + void verblib_set_damping ( verblib* verb, float value ); + + /* Get the amount of damping. */ + float verblib_get_damping ( const verblib* verb ); + + /* Set the stereo width of the reverb, between 0.0 and 1.0. */ + void verblib_set_width ( verblib* verb, float value ); + + /* Get the stereo width of the reverb. */ + float verblib_get_width ( const verblib* verb ); + + /* Set the volume of the wet signal, between 0.0 and 1.0. */ + void verblib_set_wet ( verblib* verb, float value ); + + /* Get the volume of the wet signal. */ + float verblib_get_wet ( const verblib* verb ); + + /* Set the volume of the dry signal, between 0.0 and 1.0. */ + void verblib_set_dry ( verblib* verb, float value ); + + /* Get the volume of the dry signal. */ + float verblib_get_dry ( const verblib* verb ); + + /* Set the stereo width of the input signal sent to the reverb, 0.0 or greater. + * Values less than 1.0 narrow the signal, 1.0 sends the input signal unmodified, values greater than 1.0 widen the signal. + */ + void verblib_set_input_width ( verblib* verb, float value ); + + /* Get the stereo width of the input signal sent to the reverb. */ + float verblib_get_input_width ( const verblib* verb ); + + /* Set the mode of the reverb, where values below 0.5 mean normal and values above mean frozen. */ + void verblib_set_mode ( verblib* verb, float value ); + + /* Get the mode of the reverb. */ + float verblib_get_mode ( const verblib* verb ); + + /* Get the decay time in sample frames based on the current room size setting. */ + /* If freeze mode is active, the decay time is infinite and this function returns 0. */ + unsigned long verblib_get_decay_time_in_frames ( const verblib* verb ); + + /* INTERNAL STRUCTURES */ + + /* Allpass filter */ + typedef struct verblib_allpass verblib_allpass; + struct verblib_allpass + { + float* buffer; + float feedback; + int bufsize; + int bufidx; + }; + + /* Comb filter */ + typedef struct verblib_comb verblib_comb; + struct verblib_comb + { + float* buffer; + float feedback; + float filterstore; + float damp1; + float damp2; + int bufsize; + int bufidx; + }; + + /* Reverb model tuning values */ +#define verblib_numcombs 8 +#define verblib_numallpasses 4 +#define verblib_muted 0.0f +#define verblib_fixedgain 0.015f +#define verblib_scalewet 3.0f +#define verblib_scaledry 2.0f +#define verblib_scaledamp 0.8f +#define verblib_scaleroom 0.28f +#define verblib_offsetroom 0.7f +#define verblib_initialroom 0.5f +#define verblib_initialdamp 0.25f +#define verblib_initialwet 1.0f/verblib_scalewet +#define verblib_initialdry 0.0f +#define verblib_initialwidth 1.0f +#define verblib_initialinputwidth 0.0f +#define verblib_initialmode 0.0f +#define verblib_freezemode 0.5f +#define verblib_stereospread 23 + + /* + * These values assume 44.1KHz sample rate, but will be verblib_scaled appropriately. + * The values were obtained by listening tests. + */ +#define verblib_combtuningL1 1116 +#define verblib_combtuningR1 (1116+verblib_stereospread) +#define verblib_combtuningL2 1188 +#define verblib_combtuningR2 (1188+verblib_stereospread) +#define verblib_combtuningL3 1277 +#define verblib_combtuningR3 (1277+verblib_stereospread) +#define verblib_combtuningL4 1356 +#define verblib_combtuningR4 (1356+verblib_stereospread) +#define verblib_combtuningL5 1422 +#define verblib_combtuningR5 (1422+verblib_stereospread) +#define verblib_combtuningL6 1491 +#define verblib_combtuningR6 (1491+verblib_stereospread) +#define verblib_combtuningL7 1557 +#define verblib_combtuningR7 (1557+verblib_stereospread) +#define verblib_combtuningL8 1617 +#define verblib_combtuningR8 (1617+verblib_stereospread) +#define verblib_allpasstuningL1 556 +#define verblib_allpasstuningR1 (556+verblib_stereospread) +#define verblib_allpasstuningL2 441 +#define verblib_allpasstuningR2 (441+verblib_stereospread) +#define verblib_allpasstuningL3 341 +#define verblib_allpasstuningR3 (341+verblib_stereospread) +#define verblib_allpasstuningL4 225 +#define verblib_allpasstuningR4 (225+verblib_stereospread) + + /* The main reverb structure. This is the structure that you will create an instance of when using the reverb. */ + struct verblib + { + unsigned int channels; + float gain; + float roomsize, roomsize1; + float damp, damp1; + float wet, wet1, wet2; + float dry; + float width; + float input_width; + float mode; + + /* + * The following are all declared inline + * to remove the need for dynamic allocation. + */ + + /* Comb filters */ + verblib_comb combL[verblib_numcombs]; + verblib_comb combR[verblib_numcombs]; + + /* Allpass filters */ + verblib_allpass allpassL[verblib_numallpasses]; + verblib_allpass allpassR[verblib_numallpasses]; + + /* Buffers for the combs */ + float bufcombL1[verblib_combtuningL1* verblib_max_sample_rate_multiplier]; + float bufcombR1[verblib_combtuningR1* verblib_max_sample_rate_multiplier]; + float bufcombL2[verblib_combtuningL2* verblib_max_sample_rate_multiplier]; + float bufcombR2[verblib_combtuningR2* verblib_max_sample_rate_multiplier]; + float bufcombL3[verblib_combtuningL3* verblib_max_sample_rate_multiplier]; + float bufcombR3[verblib_combtuningR3* verblib_max_sample_rate_multiplier]; + float bufcombL4[verblib_combtuningL4* verblib_max_sample_rate_multiplier]; + float bufcombR4[verblib_combtuningR4* verblib_max_sample_rate_multiplier]; + float bufcombL5[verblib_combtuningL5* verblib_max_sample_rate_multiplier]; + float bufcombR5[verblib_combtuningR5* verblib_max_sample_rate_multiplier]; + float bufcombL6[verblib_combtuningL6* verblib_max_sample_rate_multiplier]; + float bufcombR6[verblib_combtuningR6* verblib_max_sample_rate_multiplier]; + float bufcombL7[verblib_combtuningL7* verblib_max_sample_rate_multiplier]; + float bufcombR7[verblib_combtuningR7* verblib_max_sample_rate_multiplier]; + float bufcombL8[verblib_combtuningL8* verblib_max_sample_rate_multiplier]; + float bufcombR8[verblib_combtuningR8* verblib_max_sample_rate_multiplier]; + + /* Buffers for the allpasses */ + float bufallpassL1[verblib_allpasstuningL1* verblib_max_sample_rate_multiplier]; + float bufallpassR1[verblib_allpasstuningR1* verblib_max_sample_rate_multiplier]; + float bufallpassL2[verblib_allpasstuningL2* verblib_max_sample_rate_multiplier]; + float bufallpassR2[verblib_allpasstuningR2* verblib_max_sample_rate_multiplier]; + float bufallpassL3[verblib_allpasstuningL3* verblib_max_sample_rate_multiplier]; + float bufallpassR3[verblib_allpasstuningR3* verblib_max_sample_rate_multiplier]; + float bufallpassL4[verblib_allpasstuningL4* verblib_max_sample_rate_multiplier]; + float bufallpassR4[verblib_allpasstuningR4* verblib_max_sample_rate_multiplier]; + }; + +#ifdef __cplusplus +} +#endif + +#endif /* VERBLIB_H */ + +/* IMPLEMENTATION */ + +#ifdef VERBLIB_IMPLEMENTATION + +#include +#include + +#ifdef _MSC_VER +#define VERBLIB_INLINE __forceinline +#else +#ifdef __GNUC__ +#define VERBLIB_INLINE inline __attribute__((always_inline)) +#else +#define VERBLIB_INLINE inline +#endif +#endif + +#define verblib_max(x, y) (((x) > (y)) ? (x) : (y)) + +#define undenormalise(sample) sample+=1.0f; sample-=1.0f; + +/* Allpass filter */ +static void verblib_allpass_initialize ( verblib_allpass* allpass, float* buf, int size ) +{ + allpass->buffer = buf; + allpass->bufsize = size; + allpass->bufidx = 0; +} + +static VERBLIB_INLINE float verblib_allpass_process ( verblib_allpass* allpass, float input ) +{ + float output; + float bufout; + + bufout = allpass->buffer[allpass->bufidx]; + undenormalise ( bufout ); + + output = -input + bufout; + allpass->buffer[allpass->bufidx] = input + ( bufout * allpass->feedback ); + + if ( ++allpass->bufidx >= allpass->bufsize ) + { + allpass->bufidx = 0; + } + + return output; +} + +static void verblib_allpass_mute ( verblib_allpass* allpass ) +{ + int i; + for ( i = 0; i < allpass->bufsize; i++ ) + { + allpass->buffer[i] = 0.0f; + } +} + +/* Comb filter */ +static void verblib_comb_initialize ( verblib_comb* comb, float* buf, int size ) +{ + comb->buffer = buf; + comb->bufsize = size; + comb->filterstore = 0.0f; + comb->bufidx = 0; +} + +static void verblib_comb_mute ( verblib_comb* comb ) +{ + int i; + for ( i = 0; i < comb->bufsize; i++ ) + { + comb->buffer[i] = 0.0f; + } +} + +static void verblib_comb_set_damp ( verblib_comb* comb, float val ) +{ + comb->damp1 = val; + comb->damp2 = 1.0f - val; +} + +static VERBLIB_INLINE float verblib_comb_process ( verblib_comb* comb, float input ) +{ + float output; + + output = comb->buffer[comb->bufidx]; + undenormalise ( output ); + + comb->filterstore = ( output * comb->damp2 ) + ( comb->filterstore * comb->damp1 ); + undenormalise ( comb->filterstore ); + + comb->buffer[comb->bufidx] = input + ( comb->filterstore * comb->feedback ); + + if ( ++comb->bufidx >= comb->bufsize ) + { + comb->bufidx = 0; + } + + return output; +} + +static void verblib_update ( verblib* verb ) +{ + /* Recalculate internal values after parameter change. */ + + int i; + + verb->wet1 = verb->wet * ( verb->width / 2.0f + 0.5f ); + verb->wet2 = verb->wet * ( ( 1.0f - verb->width ) / 2.0f ); + + if ( verb->mode >= verblib_freezemode ) + { + verb->roomsize1 = 1.0f; + verb->damp1 = 0.0f; + verb->gain = verblib_muted; + } + else + { + verb->roomsize1 = verb->roomsize; + verb->damp1 = verb->damp; + verb->gain = verblib_fixedgain; + } + + for ( i = 0; i < verblib_numcombs; i++ ) + { + verb->combL[i].feedback = verb->roomsize1; + verb->combR[i].feedback = verb->roomsize1; + verblib_comb_set_damp ( &verb->combL[i], verb->damp1 ); + verblib_comb_set_damp ( &verb->combR[i], verb->damp1 ); + } + +} + +static void verblib_mute ( verblib* verb ) +{ + int i; + if ( verblib_get_mode ( verb ) >= verblib_freezemode ) + { + return; + } + + for ( i = 0; i < verblib_numcombs; i++ ) + { + verblib_comb_mute ( &verb->combL[i] ); + verblib_comb_mute ( &verb->combR[i] ); + } + for ( i = 0; i < verblib_numallpasses; i++ ) + { + verblib_allpass_mute ( &verb->allpassL[i] ); + verblib_allpass_mute ( &verb->allpassR[i] ); + } +} + +static int verblib_get_verblib_scaled_buffer_size ( unsigned long sample_rate, unsigned long value ) +{ + long double result = ( long double ) sample_rate; + result /= 44100.0; + result = ( ( long double ) value ) * result; + if ( result < 1.0 ) + { + result = 1.0; + } + return ( int ) result; +} + +int verblib_initialize ( verblib* verb, unsigned long sample_rate, unsigned int channels ) +{ + int i; + + if ( channels != 1 && channels != 2 ) + { + return 0; /* Currently supports only 1 or 2 channels. */ + } + else if ( sample_rate > 44100 * verblib_max_sample_rate_multiplier ) + { + return 0; /* The sample rate is too high. */ + } + + verb->channels = channels; + + /* Tie the components to their buffers. */ + verblib_comb_initialize ( &verb->combL[0], verb->bufcombL1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL1 ) ); + verblib_comb_initialize ( &verb->combR[0], verb->bufcombR1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR1 ) ); + verblib_comb_initialize ( &verb->combL[1], verb->bufcombL2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL2 ) ); + verblib_comb_initialize ( &verb->combR[1], verb->bufcombR2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR2 ) ); + verblib_comb_initialize ( &verb->combL[2], verb->bufcombL3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL3 ) ); + verblib_comb_initialize ( &verb->combR[2], verb->bufcombR3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR3 ) ); + verblib_comb_initialize ( &verb->combL[3], verb->bufcombL4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL4 ) ); + verblib_comb_initialize ( &verb->combR[3], verb->bufcombR4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR4 ) ); + verblib_comb_initialize ( &verb->combL[4], verb->bufcombL5, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL5 ) ); + verblib_comb_initialize ( &verb->combR[4], verb->bufcombR5, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR5 ) ); + verblib_comb_initialize ( &verb->combL[5], verb->bufcombL6, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL6 ) ); + verblib_comb_initialize ( &verb->combR[5], verb->bufcombR6, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR6 ) ); + verblib_comb_initialize ( &verb->combL[6], verb->bufcombL7, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL7 ) ); + verblib_comb_initialize ( &verb->combR[6], verb->bufcombR7, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR7 ) ); + verblib_comb_initialize ( &verb->combL[7], verb->bufcombL8, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningL8 ) ); + verblib_comb_initialize ( &verb->combR[7], verb->bufcombR8, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_combtuningR8 ) ); + + verblib_allpass_initialize ( &verb->allpassL[0], verb->bufallpassL1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL1 ) ); + verblib_allpass_initialize ( &verb->allpassR[0], verb->bufallpassR1, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR1 ) ); + verblib_allpass_initialize ( &verb->allpassL[1], verb->bufallpassL2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL2 ) ); + verblib_allpass_initialize ( &verb->allpassR[1], verb->bufallpassR2, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR2 ) ); + verblib_allpass_initialize ( &verb->allpassL[2], verb->bufallpassL3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL3 ) ); + verblib_allpass_initialize ( &verb->allpassR[2], verb->bufallpassR3, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR3 ) ); + verblib_allpass_initialize ( &verb->allpassL[3], verb->bufallpassL4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningL4 ) ); + verblib_allpass_initialize ( &verb->allpassR[3], verb->bufallpassR4, verblib_get_verblib_scaled_buffer_size ( sample_rate, verblib_allpasstuningR4 ) ); + + /* Set default values. */ + for ( i = 0; i < verblib_numallpasses; i++ ) + { + verb->allpassL[i].feedback = 0.5f; + verb->allpassR[i].feedback = 0.5f; + } + + verblib_set_wet ( verb, verblib_initialwet ); + verblib_set_room_size ( verb, verblib_initialroom ); + verblib_set_dry ( verb, verblib_initialdry ); + verblib_set_damping ( verb, verblib_initialdamp ); + verblib_set_width ( verb, verblib_initialwidth ); + verblib_set_input_width ( verb, verblib_initialinputwidth ); + verblib_set_mode ( verb, verblib_initialmode ); + + /* The buffers will be full of rubbish - so we MUST mute them. */ + verblib_mute ( verb ); + + return 1; +} + +void verblib_process ( verblib* verb, const float* input_buffer, float* output_buffer, unsigned long frames ) +{ + int i; + float outL, outR, input; + + if ( verb->channels == 1 ) + { + while ( frames-- > 0 ) + { + outL = 0.0f; + input = ( input_buffer[0] * 2.0f ) * verb->gain; + + /* Accumulate comb filters in parallel. */ + for ( i = 0; i < verblib_numcombs; i++ ) + { + outL += verblib_comb_process ( &verb->combL[i], input ); + } + + /* Feed through allpasses in series. */ + for ( i = 0; i < verblib_numallpasses; i++ ) + { + outL = verblib_allpass_process ( &verb->allpassL[i], outL ); + } + + /* Calculate output REPLACING anything already there. */ + output_buffer[0] = outL * verb->wet1 + input_buffer[0] * verb->dry; + + /* Increment sample pointers. */ + ++input_buffer; + ++output_buffer; + } + } + else if ( verb->channels == 2 ) + { + if ( verb->input_width > 0.0f ) /* Stereo input is widened or narrowed. */ + { + + /* + * The stereo mid/side code is derived from: + * https://www.musicdsp.org/en/latest/Effects/256-stereo-width-control-obtained-via-transfromation-matrix.html + * The description of the code on the above page says: + * + * This work is hereby placed in the public domain for all purposes, including + * use in commercial applications. + */ + + const float tmp = 1 / verblib_max ( 1 + verb->input_width, 2 ); + const float coef_mid = 1 * tmp; + const float coef_side = verb->input_width * tmp; + while ( frames-- > 0 ) + { + const float mid = ( input_buffer[0] + input_buffer[1] ) * coef_mid; + const float side = ( input_buffer[1] - input_buffer[0] ) * coef_side; + const float input_left = ( mid - side ) * ( verb->gain * 2.0f ); + const float input_right = ( mid + side ) * ( verb->gain * 2.0f ); + + outL = outR = 0.0f; + + /* Accumulate comb filters in parallel. */ + for ( i = 0; i < verblib_numcombs; i++ ) + { + outL += verblib_comb_process ( &verb->combL[i], input_left ); + outR += verblib_comb_process ( &verb->combR[i], input_right ); + } + + /* Feed through allpasses in series. */ + for ( i = 0; i < verblib_numallpasses; i++ ) + { + outL = verblib_allpass_process ( &verb->allpassL[i], outL ); + outR = verblib_allpass_process ( &verb->allpassR[i], outR ); + } + + /* Calculate output REPLACING anything already there. */ + output_buffer[0] = outL * verb->wet1 + outR * verb->wet2 + input_buffer[0] * verb->dry; + output_buffer[1] = outR * verb->wet1 + outL * verb->wet2 + input_buffer[1] * verb->dry; + + /* Increment sample pointers. */ + input_buffer += 2; + output_buffer += 2; + } + } + else /* Stereo input is summed to mono. */ + { + while ( frames-- > 0 ) + { + outL = outR = 0.0f; + input = ( input_buffer[0] + input_buffer[1] ) * verb->gain; + + /* Accumulate comb filters in parallel. */ + for ( i = 0; i < verblib_numcombs; i++ ) + { + outL += verblib_comb_process ( &verb->combL[i], input ); + outR += verblib_comb_process ( &verb->combR[i], input ); + } + + /* Feed through allpasses in series. */ + for ( i = 0; i < verblib_numallpasses; i++ ) + { + outL = verblib_allpass_process ( &verb->allpassL[i], outL ); + outR = verblib_allpass_process ( &verb->allpassR[i], outR ); + } + + /* Calculate output REPLACING anything already there. */ + output_buffer[0] = outL * verb->wet1 + outR * verb->wet2 + input_buffer[0] * verb->dry; + output_buffer[1] = outR * verb->wet1 + outL * verb->wet2 + input_buffer[1] * verb->dry; + + /* Increment sample pointers. */ + input_buffer += 2; + output_buffer += 2; + } + } + } +} + +void verblib_set_room_size ( verblib* verb, float value ) +{ + verb->roomsize = ( value * verblib_scaleroom ) + verblib_offsetroom; + verblib_update ( verb ); +} + +float verblib_get_room_size ( const verblib* verb ) +{ + return ( verb->roomsize - verblib_offsetroom ) / verblib_scaleroom; +} + +void verblib_set_damping ( verblib* verb, float value ) +{ + verb->damp = value * verblib_scaledamp; + verblib_update ( verb ); +} + +float verblib_get_damping ( const verblib* verb ) +{ + return verb->damp / verblib_scaledamp; +} + +void verblib_set_wet ( verblib* verb, float value ) +{ + verb->wet = value * verblib_scalewet; + verblib_update ( verb ); +} + +float verblib_get_wet ( const verblib* verb ) +{ + return verb->wet / verblib_scalewet; +} + +void verblib_set_dry ( verblib* verb, float value ) +{ + verb->dry = value * verblib_scaledry; +} + +float verblib_get_dry ( const verblib* verb ) +{ + return verb->dry / verblib_scaledry; +} + +void verblib_set_width ( verblib* verb, float value ) +{ + verb->width = value; + verblib_update ( verb ); +} + +float verblib_get_width ( const verblib* verb ) +{ + return verb->width; +} + +void verblib_set_input_width ( verblib* verb, float value ) +{ + verb->input_width = value; +} + +float verblib_get_input_width ( const verblib* verb ) +{ + return verb->input_width; +} + +void verblib_set_mode ( verblib* verb, float value ) +{ + verb->mode = value; + verblib_update ( verb ); +} + +float verblib_get_mode ( const verblib* verb ) +{ + if ( verb->mode >= verblib_freezemode ) + { + return 1.0f; + } + return 0.0f; +} + +unsigned long verblib_get_decay_time_in_frames ( const verblib* verb ) +{ + double decay; + + if ( verb->mode >= verblib_freezemode ) + { + return 0; /* Freeze mode creates an infinite decay. */ + } + + decay = verblib_silence_threshold / fabs ( -20.0 * log ( 1.0 / verb->roomsize1 ) ); + decay *= ( double ) ( verb->combR[7].bufsize * 2 ); + return ( unsigned long ) decay; +} + +#endif /* VERBLIB_IMPLEMENTATION */ + +/* REVISION HISTORY +* +* Version 0.5 - 2022-10-25 +* Added two functions called verblib_set_input_width and verblib_get_input_width. +* +* Version 0.4 - 2021-01-23 +* Added a function called verblib_get_decay_time_in_frames. +* +* Version 0.3 - 2021-01-18 +* Added support for sample rates of 22050 and above. +* +* Version 0.2 - 2021-01-17 +* Added support for processing mono audio. +* +* Version 0.1 - 2021-01-17 +* Initial release. +*/ + +/* LICENSE + +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT No Attribution License +Copyright (c) 2022 Philip Bennefall + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source_files/edge/i_sound.cc b/source_files/edge/i_sound.cc index fa4920f3b..f43a454e3 100644 --- a/source_files/edge/i_sound.cc +++ b/source_files/edge/i_sound.cc @@ -40,6 +40,7 @@ bool no_sound = false; int sound_device_frequency; +bool outdoor_reverb = false; std::set available_soundfonts; extern std::string game_directory; @@ -51,11 +52,13 @@ ma_engine music_engine; // Airless/Vacuum SFX sector sounds ma_lpf_node vacuum_node; // Underwater sector sounds; these two chain into each other -ma_lpf_node lowpass_node; +static ma_lpf_node underwater_lpf_node; ma_delay_node underwater_node; // Dynamic reverb -ma_delay_node reverb_node; +ma_delay_node reverb_delay_node; +ma_reverb_node reverb_node; +EDGE_DEFINE_CONSOLE_VARIABLE_CLAMPED(dynamic_reverb, "0", kConsoleVariableFlagArchive, 0, 2) void StartupAudio(void) { @@ -74,18 +77,27 @@ void StartupAudio(void) ma_uint32 channels = ma_engine_get_channels(&sound_engine); ma_engine_set_volume(&sound_engine, sound_effect_volume.f_ * 0.25f); // configure FX nodes + + // Underwater/Submerged ma_delay_node_config delay_node_config = ma_delay_node_config_init(channels, sound_device_frequency, (ma_uint32)(sound_device_frequency * 0.15f), 0.15f); ma_delay_node_init(ma_engine_get_node_graph(&sound_engine), &delay_node_config, NULL, &underwater_node); ma_lpf_node_config lpf_config = ma_lpf_node_config_init(channels, sound_device_frequency, 400.0f, 2); - ma_lpf_node_init(ma_engine_get_node_graph(&sound_engine), &lpf_config, NULL, &lowpass_node); - ma_node_attach_output_bus(&lowpass_node, 0, ma_engine_get_endpoint(&sound_engine), 0); - ma_node_attach_output_bus(&underwater_node, 0, &lowpass_node, 0); + ma_lpf_node_init(ma_engine_get_node_graph(&sound_engine), &lpf_config, NULL, &underwater_lpf_node); + ma_node_attach_output_bus(&underwater_lpf_node, 0, ma_engine_get_endpoint(&sound_engine), 0); + ma_node_attach_output_bus(&underwater_node, 0, &underwater_lpf_node, 0); + + // Vacuum/Airless lpf_config = ma_lpf_node_config_init(channels, sound_device_frequency, 200.0f, 2); ma_lpf_node_init(ma_engine_get_node_graph(&sound_engine), &lpf_config, NULL, &vacuum_node); ma_node_attach_output_bus(&vacuum_node, 0, ma_engine_get_endpoint(&sound_engine), 0); - delay_node_config = ma_delay_node_config_init(channels, sound_device_frequency, (ma_uint32)(sound_device_frequency * 0.15f), 0.25f); - ma_delay_node_init(ma_engine_get_node_graph(&sound_engine), &delay_node_config, NULL, &reverb_node); + + // Dynamic Reverb + delay_node_config = ma_delay_node_config_init(channels, sound_device_frequency, (ma_uint32)(sound_device_frequency * 0.25f), 0.20f); + ma_delay_node_init(ma_engine_get_node_graph(&sound_engine), &delay_node_config, NULL, &reverb_delay_node); + ma_reverb_node_config reverb_node_config = ma_reverb_node_config_init(2, sound_device_frequency); + ma_reverb_node_init(ma_engine_get_node_graph(&sound_engine), &reverb_node_config, NULL, &reverb_node); ma_node_attach_output_bus(&reverb_node, 0, ma_engine_get_endpoint(&sound_engine), 0); + ma_node_attach_output_bus(&reverb_delay_node, 0, &reverb_node, 0); } if (!no_music) diff --git a/source_files/edge/i_sound.h b/source_files/edge/i_sound.h index 51ea61acb..0be729c0d 100644 --- a/source_files/edge/i_sound.h +++ b/source_files/edge/i_sound.h @@ -21,12 +21,17 @@ #include #include +#include "con_var.h" #include "miniaudio.h" +#include "ma_reverb_node.h" extern std::set available_soundfonts; extern ma_engine sound_engine; extern ma_engine music_engine; -extern ma_delay_node reverb_node; +extern ma_reverb_node reverb_node; extern ma_delay_node underwater_node; -extern ma_lpf_node vacuum_node; \ No newline at end of file +extern ma_delay_node reverb_delay_node; +extern ma_lpf_node vacuum_node; +extern bool outdoor_reverb; // governs node attachment for dynamic reverb +extern ConsoleVariable dynamic_reverb; \ No newline at end of file diff --git a/source_files/edge/m_misc.cc b/source_files/edge/m_misc.cc index ee4045d90..e7b103edb 100644 --- a/source_files/edge/m_misc.cc +++ b/source_files/edge/m_misc.cc @@ -83,7 +83,6 @@ static ConfigurationDefault defaults[] = { {kConfigInteger, "displaymode", ¤t_window_mode, EDGE_DEFAULT_DISPLAYMODE}, {kConfigBoolean, "pc_speaker_mode", &pc_speaker_mode, 0}, - {kConfigBoolean, "dynamic_reverb", &dynamic_reverb, 0}, // -ES- 1998/11/28 Save fade settings {kConfigInteger, "reduce_flash", &reduce_flash, 0}, diff --git a/source_files/edge/m_option.cc b/source_files/edge/m_option.cc index 39bf509d2..599a1fb84 100644 --- a/source_files/edge/m_option.cc +++ b/source_files/edge/m_option.cc @@ -550,7 +550,8 @@ static OptionMenuItem soundoptions[] = { "Music will be Off while this is enabled", nullptr, 0, 0, 0, ""}, #endif {kOptionMenuItemTypePlain, "", nullptr, 0, nullptr, nullptr, nullptr, nullptr, 0, 0, 0, ""}, - {kOptionMenuItemTypeBoolean, "Dynamic Reverb", YesNo, 2, &dynamic_reverb, nullptr, nullptr, nullptr, 0, 0, 0, ""}, + {kOptionMenuItemTypeSwitch, "Dynamic Reverb", "None/Headphones/Speakers", 3, &dynamic_reverb.d_, + OptionMenuUpdateConsoleVariableFromInt, "Adds reverb to sounds in normal areas. Headphones-Low, Speakers-High", &dynamic_reverb, 0, 0, 0, ""}, {kOptionMenuItemTypePlain, "", nullptr, 0, nullptr, nullptr, nullptr, nullptr, 0, 0, 0, ""}, }; diff --git a/source_files/edge/p_user.cc b/source_files/edge/p_user.cc index 8383f60c0..9177a2e13 100644 --- a/source_files/edge/p_user.cc +++ b/source_files/edge/p_user.cc @@ -900,34 +900,79 @@ bool PlayerThink(Player *player) player->kick_offset_ /= 1.6f; // Adjust reverb node parameters if applicable - if (players[console_player] == player && dynamic_reverb) + if (players[console_player] == player && dynamic_reverb.d_) { HMM_Vec2 room_checker; - float line_lengths = 0; + float room_check = 0; float player_x = player->map_object_->x; float player_y = player->map_object_->y; PathTraverse(player_x, player_y, player_x, 32768.0f, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += abs(room_checker.Y - player_y); + room_check += abs(room_checker.Y - player_y); PathTraverse(player_x, player_y, 32768.0f + player_x, 32768.0f + player_y, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); + room_check += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); PathTraverse(player_x, player_y, -32768.0f + player_x, 32768.0f + player_y, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); + room_check += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); PathTraverse(player_x, player_y, player_x, -32768.0f, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += abs(player_y - room_checker.Y); + room_check += abs(player_y - room_checker.Y); PathTraverse(player_x, player_y, -32768.0f + player_x, -32768.0f + player_y, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); + room_check += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); PathTraverse(player_x, player_y, 32768.0f + player_x, -32768.0f + player_y, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); + room_check += PointToDistance(player_x, player_y, room_checker.X, room_checker.Y); PathTraverse(player_x, player_y, -32768.0f, player_y, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += abs(player_x - room_checker.X); + room_check += abs(player_x - room_checker.X); PathTraverse(player_x, player_y, 32768.0f, player_y, kPathAddLines, P_RoomPath, &room_checker); - line_lengths += abs(room_checker.X - player_x); - // not reverb per se, but approximately matches how we used to do "reverb" - Dasho - ma_delay_node_set_decay(&reverb_node, log1p(line_lengths * 0.125f) * .020f); + room_check += abs(room_checker.X - player_x); + if (dynamic_reverb.d_ == 1) // Headphones + { + if (EDGE_IMAGE_IS_SKY(player->map_object_->subsector_->sector->ceiling)) + { + outdoor_reverb = true; + ma_delay_node_set_decay(&reverb_delay_node, room_check * 0.000005395f); + verblib_set_damping(&reverb_node.reverb, 1.0f); + verblib_set_dry(&reverb_node.reverb, 0.75f); + verblib_set_wet(&reverb_node.reverb, 0.20f); + } + else + { + outdoor_reverb = false; + verblib_set_dry(&reverb_node.reverb, 0.75f); + verblib_set_wet(&reverb_node.reverb, 0.25f); + room_check *= 0.125f; + if (room_check > 750.0f) + { + verblib_set_damping(&reverb_node.reverb, 0.25f); + verblib_set_room_size(&reverb_node.reverb, 0.75f); + } + else + { + room_check *= 0.001f; + verblib_set_damping(&reverb_node.reverb, 1.0f - room_check); + verblib_set_room_size(&reverb_node.reverb, room_check); + } + } + } + else // Speakers + { + if (EDGE_IMAGE_IS_SKY(player->map_object_->subsector_->sector->ceiling)) + { + outdoor_reverb = true; + ma_delay_node_set_decay(&reverb_delay_node, room_check * 0.000005395f); + verblib_set_damping(&reverb_node.reverb, 1.0f); + verblib_set_dry(&reverb_node.reverb, 0.75f); + verblib_set_wet(&reverb_node.reverb, 0.20f); + } + else + { + outdoor_reverb = false; + verblib_set_damping(&reverb_node.reverb, 0.5f); + verblib_set_dry(&reverb_node.reverb, 0.75f); + verblib_set_wet(&reverb_node.reverb, 0.25f); + } + } } return should_think; diff --git a/source_files/edge/s_blit.cc b/source_files/edge/s_blit.cc index 8651bd2be..4d921e7ba 100644 --- a/source_files/edge/s_blit.cc +++ b/source_files/edge/s_blit.cc @@ -48,7 +48,6 @@ int total_channels; bool vacuum_sound_effects = false; bool submerged_sound_effects = false; -bool dynamic_reverb = false; EDGE_DEFINE_CONSOLE_VARIABLE(sound_effect_volume, "0.15", kConsoleVariableFlagArchive) diff --git a/source_files/edge/s_blit.h b/source_files/edge/s_blit.h index a4b82d047..da83cda15 100644 --- a/source_files/edge/s_blit.h +++ b/source_files/edge/s_blit.h @@ -73,7 +73,7 @@ extern int total_channels; extern bool vacuum_sound_effects; extern bool submerged_sound_effects; -extern bool dynamic_reverb; +extern ConsoleVariable dynamic_reverb; void InitializeSoundChannels(int total); void FreeSoundChannels(void); diff --git a/source_files/edge/s_sound.cc b/source_files/edge/s_sound.cc index eb42a1c10..6d8ee92da 100644 --- a/source_files/edge/s_sound.cc +++ b/source_files/edge/s_sound.cc @@ -325,8 +325,13 @@ static void S_PlaySound(int idx, SoundEffectDefinition *def, int category, Posit ma_node_attach_output_bus(&chan->channel_sound_, 0, &vacuum_node, 0); else if (submerged_sound_effects) ma_node_attach_output_bus(&chan->channel_sound_, 0, &underwater_node, 0); - else if (dynamic_reverb) - ma_node_attach_output_bus(&chan->channel_sound_, 0, &reverb_node, 0); + else if (dynamic_reverb.d_) + { + if (outdoor_reverb) + ma_node_attach_output_bus(&chan->channel_sound_, 0, &reverb_delay_node, 0); + else + ma_node_attach_output_bus(&chan->channel_sound_, 0, &reverb_node, 0); + } else ma_node_attach_output_bus(&chan->channel_sound_, 0, &sound_engine, 0); } @@ -339,8 +344,13 @@ static void S_PlaySound(int idx, SoundEffectDefinition *def, int category, Posit ma_node_attach_output_bus(&chan->channel_sound_, 0, &vacuum_node, 0); else if (submerged_sound_effects) ma_node_attach_output_bus(&chan->channel_sound_, 0, &underwater_node, 0); - else if (dynamic_reverb) - ma_node_attach_output_bus(&chan->channel_sound_, 0, &reverb_node, 0); + else if (dynamic_reverb.d_) + { + if (outdoor_reverb) + ma_node_attach_output_bus(&chan->channel_sound_, 0, &reverb_delay_node, 0); + else + ma_node_attach_output_bus(&chan->channel_sound_, 0, &reverb_node, 0); + } else ma_node_attach_output_bus(&chan->channel_sound_, 0, &sound_engine, 0); }