audio: array of reverberators

This commit is contained in:
Zack Buhman 2026-06-03 18:23:45 -05:00
parent 057ba291d8
commit 91a44e9c5e
8 changed files with 189 additions and 275 deletions

View File

@ -23,7 +23,9 @@ CFLAGS += -Wno-format
CFLAGS += -Wno-error=unused-function
CFLAGS += -Wno-error=array-bounds
CFLAGS += -Wno-unknown-pragmas
ifneq '' '$(findstring clang++,$(CXX))'
CFLAGS += -Wno-vla-cxx-extension
endif
CFLAGS += -fno-strict-aliasing
CFLAGS += -I./include
CFLAGS += -I./data
@ -43,7 +45,7 @@ LDFLAGS += -lm
#LDFLAGS += -Wl,--gc-sections
#-Wl,--print-gc-sections
ifeq ($(UNAME),Linux)
#LDFLAGS += -Wl,-z noexecstack
LDFLAGS += -z noexecstack
endif
ifeq ($(UNAME),Darwin)
LDFLAGS += -framework Foundation -framework Cocoa -framework IOKit -framework AVFoundation -framework CoreVideo -framework CoreAudio -framework CoreMedia -framework CoreHaptics -framework AudioToolbox -framework GameController -framework ForceFeedback -framework Carbon -framework Metal -framework QuartzCore -framework UniformTypeIdentifiers
@ -114,9 +116,6 @@ endif
all: main
CC = clang
CXX = clang++
%.o: %.c
$(CC) $(ARCH) $(CSTD) $(CFLAGS) $(FLAGS) $(OPT) $(DEBUG) -c $< -o $@

View File

@ -7,88 +7,57 @@ namespace audio {
constexpr int sample_rate = 48000;
constexpr int channels = 2;
template <int maxDelay>
struct FeedbackCombFilter {
private:
//float * buffer;
float buffer[maxDelay];
int index;
public:
int delay;
float gain0;
float gainM;
FeedbackCombFilter(int delay, float gain0, float gainM);
void reset();
float feed(float value);
};
template <int maxDelay>
struct FeedforwardCombFilter {
private:
//float * buffer;
float buffer[maxDelay];
int index;
public:
int delay;
float gain0;
float gainM;
FeedforwardCombFilter(int delay, float gain0, float gainM);
void reset();
float feed(float value);
};
template <int maxDelay>
struct AllpassFilter {
private:
//float * buffer;
float buffer[maxDelay];
struct DelayFilter {
float * buffer;
int index;
public:
const int maxDelay;
int delay;
float gain0;
float gainM;
float gain;
DelayFilter(int maxDelay, int delay, float gain);
AllpassFilter(int delay, float gain0, float gainM);
void reset();
float feed(float x);
virtual float feed(float value) = 0;
};
using FBCF = FeedbackCombFilter<15000>;
using FFCF = FeedforwardCombFilter<15000>;
using AP = AllpassFilter<2500>;
struct FeedbackCombFilter : DelayFilter {
FeedbackCombFilter(int maxDelay, int delay, float gain);
float feed(float value) override;
};
struct FeedforwardCombFilter : DelayFilter {
public:
FeedforwardCombFilter(int maxDelay, int delay, float gain);
float feed(float value) override;
};
struct AllpassFilter : DelayFilter {
AllpassFilter(int maxDelay, int delay, float gain);
float feed(float x) override;
};
using FBCF = FeedbackCombFilter;
using FFCF = FeedforwardCombFilter;
using AP = AllpassFilter;
struct lr { float l; float r; };
struct FBReverb {
struct Reverb {
static constexpr int apCount = 3;
static constexpr int cfCount = 4;
static constexpr int apCount = 3;
FBCF cf[cfCount];
AP ap[apCount];
DelayFilter * cf;
DelayFilter * ap;
FBReverb();
void reset();
lr feed(float x);
};
struct FFReverb {
static constexpr int __cfCount = 4;
static constexpr int apCount = 3;
FFCF cf[__cfCount];
AP ap[apCount];
FFReverb();
Reverb(DelayFilter * cf, DelayFilter * ap);
void reset();
lr feed(float x);
};
extern int reverbIndex;
extern FBReverb fbreverb;
extern FFReverb ffreverb;
extern Reverb * reverbs[];
extern int const reverbsCount;
extern float wetGain;
extern float dryGain;

View File

@ -4,6 +4,7 @@
namespace ui
{
void init();
void draw(MappedInstanceData<SolidInstance> & data,
MappedInstanceData<font::BitmapInstance> & fontData);
void update(float mx, float my, bool mLeft, bool mEdge);

View File

@ -86,6 +86,9 @@ namespace ui::widget
T maxExtent;
T * value;
Slider()
{}
Slider(char const * label,
int left, int top, int width, int height,
T minValue, T maxValue,
@ -117,30 +120,27 @@ namespace ui::widget
char const * label;
Slider<int, true> delay;
Slider<float, true> gain0;
Slider<float, true> gainM;
Slider<float, true> gain;
DelayGainSlider()
{}
DelayGainSlider(char const * label,
int left, int top,
int delayMin, int delayMax, int * delayValue,
float gainMin, float gainMax,
float * gain0Value, float * gainMValue)
float * gainValue)
: label(label)
, delay("delay",
left, top, 150, 14,
delayMin, delayMax,
delayMin, delayMax,
delayValue)
, gain0("gain0",
, gain("gain",
left, top + 30, 150, 14,
gainMin, gainMax,
gainMin, gainMax,
gain0Value)
, gainM("gainM",
left, top + 60, 150, 14,
gainMin, gainMax,
gainMin, gainMax,
gainMValue)
gainValue)
{}
void draw(MappedInstanceData<SolidInstance> & data,

View File

@ -13,14 +13,14 @@
#include "poem.h"
namespace audio {
static int const frame_samples = 960; // 20 milliseconds @ 48kHz
//static int const frame_samples = 960; // 20 milliseconds @ 48kHz
static int const sample_size = (sizeof (int16_t));
static int const max_frame_size = 960 * 3; // 20ms at 48kHz
static int const max_packet_size = 1275;
//static int const max_packet_size = 1275;
static int const half_period_samples = sample_rate / 30;
static int const half_period_size = half_period_samples * sample_size * channels;
//static int const half_period_size = half_period_samples * sample_size * channels;
struct AudioBuffer {
renpy::language::audio const * audio;
@ -28,111 +28,87 @@ namespace audio {
uint32_t sample_count;
};
template <int maxDelay>
FeedbackCombFilter<maxDelay>::FeedbackCombFilter(int delay, float gain0, float gainM)
: delay(delay), gain0(gain0), gainM(gainM)
//////////////////////////////////////////////////////////////////////
// DelayFilter
//////////////////////////////////////////////////////////////////////
void DelayFilter::reset()
{
//buffer = (float *)malloc((sizeof (float)) * maxDelay);
memset(buffer, 0, maxDelay * (sizeof (float)));
}
DelayFilter::DelayFilter(int maxDelay, int delay, float gain)
: index(0)
, maxDelay(maxDelay)
, delay(delay)
, gain(gain)
{
buffer = (float *)malloc(maxDelay * (sizeof (float)));
reset();
}
template <int maxDelay>
void FeedbackCombFilter<maxDelay>::reset()
{
index = 0;
memset(buffer, 0, (sizeof (float)) * maxDelay);
}
//////////////////////////////////////////////////////////////////////
// FeedbackCombFilter
//////////////////////////////////////////////////////////////////////
template <int maxDelay>
float FeedbackCombFilter<maxDelay>::feed(float value)
FeedbackCombFilter::FeedbackCombFilter(int maxDelay, int delay, float gain)
: DelayFilter(maxDelay, delay, gain)
{}
float FeedbackCombFilter::feed(float value)
{
float y = gain0 * value + gainM * buffer[index];
float y = gain * value + gain * buffer[index];
buffer[index] = y;
index = (index + 1) % delay;
return y;
}
template <int maxDelay>
FeedforwardCombFilter<maxDelay>::FeedforwardCombFilter(int delay, float gain0, float gainM)
: delay(delay), gain0(gain0), gainM(gainM)
{
//buffer = (float *)malloc((sizeof (float)) * maxDelay);
reset();
}
//////////////////////////////////////////////////////////////////////
// FeedForwardCombFilter
//////////////////////////////////////////////////////////////////////
template <int maxDelay>
void FeedforwardCombFilter<maxDelay>::reset()
{
index = 0;
memset(buffer, 0, (sizeof (float)) * maxDelay);
}
FeedforwardCombFilter::FeedforwardCombFilter(int maxDelay, int delay, float gain)
: DelayFilter(maxDelay, delay, gain)
{}
template <int maxDelay>
float FeedforwardCombFilter<maxDelay>::feed(float value)
float FeedforwardCombFilter::feed(float value)
{
float y = gain0 * value + gainM * buffer[index];
float y = gain * value + gain * buffer[index];
buffer[index] = value;
index = (index + 1) % delay;
return y;
}
template <int maxDelay>
AllpassFilter<maxDelay>::AllpassFilter(int delay, float gain0, float gainM)
: delay(delay), gain0(gain0), gainM(gainM)
{
//buffer = (float *)malloc((sizeof (float)) * maxDelay);
reset();
}
//////////////////////////////////////////////////////////////////////
// AllpassFilter
//////////////////////////////////////////////////////////////////////
template <int maxDelay>
void AllpassFilter<maxDelay>::reset()
{
index = 0;
memset(buffer, 0, (sizeof (float)) * maxDelay);
}
AllpassFilter::AllpassFilter(int maxDelay, int delay, float gain)
: DelayFilter(maxDelay, delay, gain)
{}
template <int maxDelay>
float AllpassFilter<maxDelay>::feed(float x)
float AllpassFilter::feed(float x)
{
float v = x + -gainM * buffer[index];
float y = buffer[index] + gain0 * v;
float v = x + -gain * buffer[index];
float y = buffer[index] + gain * v;
buffer[index] = v;
index = (index + 1) % delay;
return y;
}
struct AudioInstance {
int audio_index;
AudioBuffer * audio_buffer;
uint32_t sample_index;
uint32_t tail_index;
uint32_t fadeout_end;
uint32_t fadeout_index;
poem::poem const * poem;
};
//////////////////////////////////////////////////////////////////////
// Reverberators
//////////////////////////////////////////////////////////////////////
FBReverb::FBReverb()
: cf{FBCF(3229, 1.0f, 0.733f), // 1687
FBCF(3079, 1.0f, 0.802f), // 1601
FBCF(3943, 1.0f, 0.753f), // 2053
FBCF(4327, 1.0f, 0.733f), // 2251
}
, ap{AP(661, 0.7f, 0.7f), // 347
AP(257, 0.7f, 0.7f), // 113
AP(71, 0.7f, 0.7f), // 37
}
{ }
void FBReverb::reset()
void Reverb::reset()
{
for (int i = 0; i < cfCount; i++)
cf[i].reset();
for (int i = 0; i < apCount; i++)
ap[i].reset();
}
lr FBReverb::feed(float x)
lr Reverb::feed(float x)
{
for (int i = 0; i < apCount; i++) {
x = ap[i].feed(x);
@ -151,54 +127,54 @@ namespace audio {
return {a, b};
}
Reverb::Reverb(DelayFilter * cf, DelayFilter * ap)
: cf(cf), ap(ap)
{}
FFReverb::FFReverb()
: cf{FFCF{9209, 1.0f, 0.742f},
FFCF{9601, 1.0f, 0.733f},
FFCF{10369, 1.0f, 0.715f},
FFCF{11131, 1.0f, 0.697f},
}
, ap{AP{2017, 0.7f, 0.7f},
AP{647, 0.7f, 0.7f},
AP{137, 0.7f, 0.7f},
}
{ }
static FFCF forwardCF[4]{FFCF{15000, 9209, 0.742f},
FFCF{15000, 9601, 0.733f},
FFCF{15000, 10369, 0.715f},
FFCF{15000, 11131, 0.697f}};
void FFReverb::reset()
{
for (int i = 0; i < __cfCount; i++) {
printf("reset cf %d\n", i);
cf[i].reset();
}
static AP forwardAP[3]{AP{2500, 2017, 0.7f},
AP{2500, 647, 0.7f},
AP{2500, 137, 0.7f}};
Reverb forwardReverb(forwardCF, forwardAP);
for (int i = 0; i < apCount; i++)
ap[i].reset();
}
lr FFReverb::feed(float x)
{
for (int i = 0; i < apCount; i++) {
x = ap[i].feed(x);
}
float x0 = cf[0].feed(x);
float x1 = cf[1].feed(x);
float x2 = cf[2].feed(x);
float x3 = cf[3].feed(x);
float s0 = x0 + x1 + x2 + x3;
//float s0 = x0 + x1 + x2;
return {s0, s0};
}
static FBCF backCF[4]{FBCF{10000, 3229, 0.733f}, // 1687
FBCF{10000, 3079, 0.802f}, // 1601
FBCF{10000, 3943, 0.753f}, // 2053
FBCF{10000, 4327, 0.733f}}; // 2251
static AP backAP[3]{AP{1500, 661, 0.7f}, // 347
AP{1500, 257, 0.7f}, // 113
AP{1500, 71, 0.7f}};
Reverb backReverb(backCF, backAP);
int reverbIndex = 0;
FBReverb fbreverb;
FFReverb ffreverb;
Reverb * reverbs[] = {
&backReverb,
&forwardReverb,
};
int const reverbsCount = (sizeof (reverbs)) / (sizeof (reverbs[0]));
float dryGain = 1.0;
float wetGain = 0.25;
float mixChannelGain[mixChannelCount];
//////////////////////////////////////////////////////////////////////
// AudioInstance
//////////////////////////////////////////////////////////////////////
struct AudioInstance {
int audio_index;
AudioBuffer * audio_buffer;
uint32_t sample_index;
uint32_t tail_index;
uint32_t fadeout_end;
uint32_t fadeout_index;
poem::poem const * poem;
};
//
static SDL_AudioStream * audio_stream;
@ -221,8 +197,8 @@ namespace audio {
SDL_ResumeAudioStreamDevice(audio_stream);
audio_instances_count = 0;
fbreverb.reset();
ffreverb.reset();
for (int i = 0; i < reverbsCount; i++)
reverbs[i]->reset();
for (int i = 0; i < mixChannelCount; i++) {
mixChannelGain[i] = 1.0f;
@ -566,12 +542,8 @@ namespace audio {
float value = channel_buffer[mix_channel::voice][i * channels + 0];
lr wet;
if (reverbIndex == 0) {
wet = fbreverb.feed(value);
}
else {
wet = ffreverb.feed(value);
}
assert(reverbIndex >= 0 && reverbIndex < reverbsCount);
wet = reverbs[reverbIndex]->feed(value);
float left = value * dryGain + wet.l * wetGain;
float right = value * dryGain + wet.r * wetGain;
channel_buffer[mix_channel::voice][i * channels + 0] = left;

View File

@ -1000,6 +1000,12 @@ int main()
// 2.17
printf("audio_time %f\n", audio_time);
//////////////////////////////////////////////////////////////////////
// ui
//////////////////////////////////////////////////////////////////////
ui::init();
//////////////////////////////////////////////////////////////////////
// interpreter
//////////////////////////////////////////////////////////////////////

View File

@ -12,76 +12,13 @@ namespace ui
constexpr int combLeft = 700;
constexpr int outputMixLeft = 1000;
#define BREVERB audio::fbreverb
#define FREVERB audio::ffreverb
widget::DelayGainSlider fap[3] {
widget::DelayGainSlider("allpass 0",
allpassLeft, top + ySpace * 0,
1, 2500, &FREVERB.ap[0].delay,
0.0, 1.0, &FREVERB.ap[0].gain0, &FREVERB.ap[0].gainM),
widget::DelayGainSlider("allpass 1",
allpassLeft, top + ySpace * 1,
1, 2500, &FREVERB.ap[1].delay,
0.0, 1.0, &FREVERB.ap[1].gain0, &FREVERB.ap[1].gainM),
widget::DelayGainSlider("allpass 2",
allpassLeft, top + ySpace * 2,
1, 2500, &FREVERB.ap[2].delay,
0.0, 1.0, &FREVERB.ap[2].gain0, &FREVERB.ap[2].gainM),
struct ap_comb {
widget::DelayGainSlider ap[3];
widget::DelayGainSlider comb[4];
};
widget::DelayGainSlider fcomb[4] {
widget::DelayGainSlider("comb 0",
combLeft, top + ySpace * 0,
100, 15000, &FREVERB.cf[0].delay,
0.0, 1.0, &FREVERB.cf[0].gain0, &FREVERB.cf[0].gainM),
widget::DelayGainSlider("comb 1",
combLeft, top + ySpace * 1,
100, 15000, &FREVERB.cf[1].delay,
0.0, 1.0, &FREVERB.cf[1].gain0, &FREVERB.cf[1].gainM),
widget::DelayGainSlider("comb 2",
combLeft, top + ySpace * 2,
100, 15000, &FREVERB.cf[2].delay,
0.0, 1.0, &FREVERB.cf[2].gain0, &FREVERB.cf[2].gainM),
widget::DelayGainSlider("comb 3",
combLeft, top + ySpace * 3,
100, 15000, &FREVERB.cf[3].delay,
0.0, 1.0, &FREVERB.cf[3].gain0, &FREVERB.cf[3].gainM),
};
widget::DelayGainSlider bap[3] {
widget::DelayGainSlider("allpass 0",
allpassLeft, top + ySpace * 0,
1, 2500, &BREVERB.ap[0].delay,
0.0, 1.0, &BREVERB.ap[0].gain0, &BREVERB.ap[0].gainM),
widget::DelayGainSlider("allpass 1",
allpassLeft, top + ySpace * 1,
1, 2500, &BREVERB.ap[1].delay,
0.0, 1.0, &BREVERB.ap[1].gain0, &BREVERB.ap[1].gainM),
widget::DelayGainSlider("allpass 2",
allpassLeft, top + ySpace * 2,
1, 2500, &BREVERB.ap[2].delay,
0.0, 1.0, &BREVERB.ap[2].gain0, &BREVERB.ap[2].gainM),
};
widget::DelayGainSlider bcomb[4] {
widget::DelayGainSlider("comb 0",
combLeft, top + ySpace * 0,
100, 15000, &BREVERB.cf[0].delay,
0.0, 1.0, &BREVERB.cf[0].gain0, &BREVERB.cf[0].gainM),
widget::DelayGainSlider("comb 1",
combLeft, top + ySpace * 1,
100, 15000, &BREVERB.cf[1].delay,
0.0, 1.0, &BREVERB.cf[1].gain0, &BREVERB.cf[1].gainM),
widget::DelayGainSlider("comb 2",
combLeft, top + ySpace * 2,
100, 15000, &BREVERB.cf[2].delay,
0.0, 1.0, &BREVERB.cf[2].gain0, &BREVERB.cf[2].gainM),
widget::DelayGainSlider("comb 3",
combLeft, top + ySpace * 3,
100, 15000, &BREVERB.cf[3].delay,
0.0, 1.0, &BREVERB.cf[3].gain0, &BREVERB.cf[3].gainM),
};
ap_comb reverberatorSliders[2];
widget::Slider<float, false> dryGain("dry gain",
outputMixLeft, top + yMixSpace * 0, 150, 14,
@ -118,6 +55,38 @@ namespace ui
120, 0,
&audio::reverbIndex);
void init()
{
static const char * allpassLabels[3] = {
"allpass 0",
"allpass 1",
"allpass 2",
};
static const char * combLabels[4] = {
"comb 0",
"comb 1",
"comb 2",
"comb 3",
};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
reverberatorSliders[i].ap[j] =
widget::DelayGainSlider(allpassLabels[j],
allpassLeft, top + ySpace * j,
1, audio::reverbs[i]->ap[j].maxDelay, &audio::reverbs[i]->ap[j].delay,
0.0, 1.0, &audio::reverbs[i]->ap[j].gain);
}
for (int j = 0; j < 4; j++) {
reverberatorSliders[i].comb[j] =
widget::DelayGainSlider(combLabels[j],
combLeft, top + ySpace * j,
100, audio::reverbs[i]->cf[j].maxDelay, &audio::reverbs[i]->cf[j].delay,
0.0, 1.0, &audio::reverbs[i]->cf[j].gain);
}
}
}
void draw(MappedInstanceData<SolidInstance> & data,
MappedInstanceData<font::BitmapInstance> & fontData)
{
@ -127,8 +96,8 @@ namespace ui
0x80000000,
});
widget::DelayGainSlider * ap = (audio::reverbIndex == 0) ? bap : fap;
widget::DelayGainSlider * comb = (audio::reverbIndex == 0) ? bcomb : fcomb;
widget::DelayGainSlider * ap = reverberatorSliders[audio::reverbIndex].ap;
widget::DelayGainSlider * comb = reverberatorSliders[audio::reverbIndex].comb;
for (int i = 0; i < 3; i++)
ap[i].draw(data, fontData);
@ -148,8 +117,8 @@ namespace ui
void update(float mx, float my, bool mLeft, bool mEdge)
{
widget::DelayGainSlider * ap = (audio::reverbIndex == 0) ? bap : fap;
widget::DelayGainSlider * comb = (audio::reverbIndex == 0) ? bcomb : fcomb;
widget::DelayGainSlider * ap = reverberatorSliders[audio::reverbIndex].ap;
widget::DelayGainSlider * comb = reverberatorSliders[audio::reverbIndex].comb;
for (int i = 0; i < 3; i++)
ap[i].update(mx, my, mLeft, mEdge);

View File

@ -179,15 +179,13 @@ namespace ui::widget
drawStringCentered(fontData, delay.slider.left + delay.slider.width / 2, delay.slider.top - 16, label);
delay.draw(data, fontData);
gain0.draw(data, fontData);
gainM.draw(data, fontData);
gain.draw(data, fontData);
}
void DelayGainSlider::update(float mx, float my, bool mLeft, bool mEdge)
{
delay.update(mx, my, mLeft, mEdge);
gain0.update(mx, my, mLeft, mEdge);
gainM.update(mx, my, mLeft, mEdge);
gain.update(mx, my, mLeft, mEdge);
}
template <int optionCount>