add smoothing to split eq mode transitions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "audio_math.h"
|
||||
#include "smoother.h"
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
@@ -224,6 +225,11 @@ inline void aw_filter_process_block(aw_filter& f, float* audio, int frames)
|
||||
}
|
||||
}
|
||||
|
||||
enum spliteq_mode {
|
||||
CASCADE_SUM,
|
||||
LINKWITZ_RILEY
|
||||
};
|
||||
|
||||
struct spliteq {
|
||||
aw_filter lp_l, lp_r, hp_l, hp_r; // lowpass and highpass filters
|
||||
|
||||
@@ -257,7 +263,10 @@ struct spliteq {
|
||||
float mid_gain_adj = 1.0f;
|
||||
float treble_gain_adj = 1.0f;
|
||||
|
||||
bool linkwitz_riley_enabled = false;
|
||||
spliteq_mode current_mode = LINKWITZ_RILEY;
|
||||
spliteq_mode target_mode = LINKWITZ_RILEY;
|
||||
bool transitioning = false;
|
||||
smoother transition_smoother;
|
||||
};
|
||||
|
||||
inline void spliteq_init(spliteq& eq, double samplerate, double low_mid_crossover, double mid_high_crossover)
|
||||
@@ -367,41 +376,44 @@ inline void spliteq_init(spliteq& eq, double samplerate, double low_mid_crossove
|
||||
eq.treble2_r.cutoff = mid_high_crossover;
|
||||
eq.treble2_r.x1 = eq.treble2_r.x2 = eq.treble2_r.y1 = eq.treble2_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.treble2_r, samplerate);
|
||||
|
||||
smoother_init(eq.transition_smoother, samplerate, 50.0f, 1.0f);
|
||||
}
|
||||
|
||||
// Process block (stereo)
|
||||
inline void spliteq_process_block(spliteq& eq, float** audio, int frames)
|
||||
inline void spliteq_set_mode(spliteq& eq, spliteq_mode mode)
|
||||
{
|
||||
aw_filter_process_block(eq.hp_l, audio[0], frames);
|
||||
aw_filter_process_block(eq.hp_r, audio[1], frames);
|
||||
if (eq.target_mode != mode) {
|
||||
eq.target_mode = mode;
|
||||
eq.transitioning = true;
|
||||
smoother_set_target(eq.transition_smoother, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < frames; i++) {
|
||||
if (eq.linkwitz_riley_enabled) {
|
||||
// butterwort/linkwitz-riley
|
||||
|
||||
// Left channel
|
||||
inline void linkwitz_riley_process(spliteq& eq, float**& audio, int& i)
|
||||
{
|
||||
// left channel
|
||||
double input_l = audio[0][i];
|
||||
// Bass
|
||||
// bass
|
||||
double bass_l = butterworth_biquad_process(eq.bass1_l, input_l);
|
||||
bass_l = butterworth_biquad_process(eq.bass2_l, bass_l);
|
||||
// Mid
|
||||
// mid
|
||||
double mid_l = butterworth_biquad_process(eq.mid_hp1_l, input_l);
|
||||
mid_l = butterworth_biquad_process(eq.mid_hp2_l, mid_l);
|
||||
mid_l = butterworth_biquad_process(eq.mid_lp1_l, mid_l);
|
||||
mid_l = butterworth_biquad_process(eq.mid_lp2_l, mid_l);
|
||||
// Treble
|
||||
// treble
|
||||
double treble_l = butterworth_biquad_process(eq.treble1_l, input_l);
|
||||
treble_l = butterworth_biquad_process(eq.treble2_l, treble_l);
|
||||
|
||||
// Apply gains
|
||||
// apply gains
|
||||
bass_l *= eq.bass_gain;
|
||||
mid_l *= eq.mid_gain;
|
||||
treble_l *= eq.treble_gain;
|
||||
|
||||
// Sum bands
|
||||
// sum bands
|
||||
audio[0][i] = bass_l + mid_l + treble_l;
|
||||
|
||||
// Right channel
|
||||
// right channel
|
||||
double input_r = audio[1][i];
|
||||
double bass_r = butterworth_biquad_process(eq.bass1_r, input_r);
|
||||
bass_r = butterworth_biquad_process(eq.bass2_r, bass_r);
|
||||
@@ -419,8 +431,10 @@ inline void spliteq_process_block(spliteq& eq, float** audio, int frames)
|
||||
treble_r *= eq.treble_gain;
|
||||
|
||||
audio[1][i] = bass_r + mid_r + treble_r;
|
||||
} else {
|
||||
// cascade/sum
|
||||
}
|
||||
|
||||
inline void cascade_sum_process(spliteq& eq, float**& audio, int& i)
|
||||
{
|
||||
double input_l = audio[0][i];
|
||||
double input_r = audio[1][i];
|
||||
|
||||
@@ -433,7 +447,7 @@ inline void spliteq_process_block(spliteq& eq, float** audio, int frames)
|
||||
double mid_l = input_l - bass_l - treble_l;
|
||||
double mid_r = input_r - bass_r - treble_r;
|
||||
|
||||
// Apply gains
|
||||
// apply gains
|
||||
bass_l *= eq.bass_gain_adj;
|
||||
bass_r *= eq.bass_gain_adj;
|
||||
mid_l *= eq.mid_gain_adj;
|
||||
@@ -441,12 +455,42 @@ inline void spliteq_process_block(spliteq& eq, float** audio, int frames)
|
||||
treble_l *= eq.treble_gain_adj;
|
||||
treble_r *= eq.treble_gain_adj;
|
||||
|
||||
// Sum bands
|
||||
// sum bands
|
||||
audio[0][i] = bass_l + mid_l + treble_l;
|
||||
audio[1][i] = bass_r + mid_r + treble_r;
|
||||
}
|
||||
|
||||
inline void spliteq_process_block(spliteq& eq, float** audio, int frames)
|
||||
{
|
||||
// highpass filters
|
||||
aw_filter_process_block(eq.hp_l, audio[0], frames);
|
||||
aw_filter_process_block(eq.hp_r, audio[1], frames);
|
||||
|
||||
for (int i = 0; i < frames; i++) {
|
||||
float smooth_gain = 1.0f;
|
||||
|
||||
if (eq.transitioning) {
|
||||
smooth_gain = smoother_process_sample(eq.transition_smoother);
|
||||
|
||||
if (smooth_gain == 0.f) {
|
||||
smoother_set_target(eq.transition_smoother, 1.0);
|
||||
eq.current_mode = eq.target_mode;
|
||||
} else if (smooth_gain == 1.f) {
|
||||
eq.transitioning = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (eq.current_mode == LINKWITZ_RILEY) {
|
||||
linkwitz_riley_process(eq, audio, i);
|
||||
} else if (eq.current_mode == CASCADE_SUM) {
|
||||
cascade_sum_process(eq, audio, i);
|
||||
}
|
||||
|
||||
audio[0][i] *= smooth_gain;
|
||||
audio[1][i] *= smooth_gain;
|
||||
}
|
||||
|
||||
// lowpass filters
|
||||
aw_filter_process_block(eq.lp_l, audio[0], frames);
|
||||
aw_filter_process_block(eq.lp_r, audio[1], frames);
|
||||
}
|
||||
|
||||
@@ -12,4 +12,6 @@ inline double lin_2_db(double lin)
|
||||
inline double db_2_lin(double db) { return pow(10.0, db / 20.0); }
|
||||
|
||||
inline float midi_to_frequency(float midi_note) { return 440.0 * powf(2.0, ((float)midi_note - 69.0) / 12.0); }
|
||||
|
||||
inline float ms_to_samples(float ms, double sample_rate) { return (ms * 0.001f) * (float)sample_rate; }
|
||||
} // namespace trnr
|
||||
60
util/smoother.h
Normal file
60
util/smoother.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "audio_math.h"
|
||||
|
||||
namespace trnr {
|
||||
|
||||
struct smoother {
|
||||
float samplerate;
|
||||
float time_samples;
|
||||
|
||||
float current;
|
||||
float target;
|
||||
float increment;
|
||||
int32_t remaining;
|
||||
};
|
||||
|
||||
inline void smoother_init(smoother& s, double samplerate, float time_ms, float initial_value = 0.0f)
|
||||
{
|
||||
s.samplerate = fmax(0.0, samplerate);
|
||||
s.time_samples = ms_to_samples(time_ms, s.samplerate);
|
||||
s.current = initial_value;
|
||||
s.target = initial_value;
|
||||
s.increment = 0.0f;
|
||||
s.remaining = 0;
|
||||
}
|
||||
|
||||
inline void smoother_set_target(smoother& s, float newTarget)
|
||||
{
|
||||
s.target = newTarget;
|
||||
|
||||
// immediate if time is zero or too short
|
||||
if (s.time_samples <= 1.0f) {
|
||||
s.current = s.target;
|
||||
s.increment = 0.0f;
|
||||
s.remaining = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t n = static_cast<int32_t>(fmax(1.0f, ceilf(s.time_samples)));
|
||||
s.remaining = n;
|
||||
// protect against denormals / tiny differences, but we want exact reach at the end
|
||||
s.increment = (s.target - s.current) / static_cast<float>(s.remaining);
|
||||
}
|
||||
|
||||
// process a single sample and return the smoothed value
|
||||
inline float smoother_process_sample(smoother& s)
|
||||
{
|
||||
if (s.remaining > 0) {
|
||||
s.current += s.increment;
|
||||
--s.remaining;
|
||||
if (s.remaining == 0) {
|
||||
// ensure exact target at the end to avoid FP drift
|
||||
s.current = s.target;
|
||||
s.increment = 0.0f;
|
||||
}
|
||||
}
|
||||
return s.current;
|
||||
}
|
||||
|
||||
} // namespace trnr
|
||||
Reference in New Issue
Block a user