Skip to content

Commit 8c8f3b2

Browse files
committed
DRAFT Equalizer3BandsPerChannel
1 parent 547b006 commit 8c8f3b2

File tree

1 file changed

+308
-2
lines changed

1 file changed

+308
-2
lines changed

src/AudioTools/CoreAudio/AudioFilter/Equalizer.h

Lines changed: 308 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ struct ConfigEqualizer3Bands : public AudioInfo {
2727
sample_rate = 44100;
2828
}
2929

30-
ConfigEqualizer3Bands(const ConfigEqualizer3Bands &) = delete;
31-
3230
// Frequencies
3331
int freq_low = 880;
3432
int freq_high = 5000;
@@ -237,4 +235,312 @@ class Equalizer3Bands : public ModifyingStream {
237235
}
238236
};
239237

238+
/**
239+
* @brief 3 Band Equalizer with per-channel frequency and gain control
240+
* Allows independent frequency and gain settings for each audio channel.
241+
* Each channel can have different low/high frequency cutoffs and different
242+
* gain values for low, medium, and high frequency bands.
243+
* @ingroup equilizer
244+
* @author pschatzmann
245+
*/
246+
class Equalizer3BandsPerChannel : public ModifyingStream {
247+
public:
248+
/// Constructor with Print output
249+
/// @param out Print stream for output
250+
Equalizer3BandsPerChannel(Print &out) { setOutput(out); }
251+
252+
/// Constructor with Stream input
253+
/// @param in Stream for input
254+
Equalizer3BandsPerChannel(Stream &in) { setStream(in); }
255+
256+
/// Constructor with AudioOutput
257+
/// @param out AudioOutput for output with automatic audio change notifications
258+
Equalizer3BandsPerChannel(AudioOutput &out) {
259+
setOutput(out);
260+
out.addNotifyAudioChange(*this);
261+
}
262+
263+
/// Constructor with AudioStream
264+
/// @param stream AudioStream for input/output with automatic audio change notifications
265+
Equalizer3BandsPerChannel(AudioStream &stream) {
266+
setStream(stream);
267+
stream.addNotifyAudioChange(*this);
268+
}
269+
270+
~Equalizer3BandsPerChannel() {
271+
if (state != nullptr) delete[] state;
272+
}
273+
274+
/// Defines/Changes the input & output
275+
void setStream(Stream &io) override {
276+
p_print = &io;
277+
p_stream = &io;
278+
};
279+
280+
/// Defines/Changes the output target
281+
void setOutput(Print &out) override { p_print = &out; }
282+
283+
ConfigEqualizer3Bands &config() { return cfg; }
284+
285+
ConfigEqualizer3Bands &defaultConfig() { return config(); }
286+
287+
/// Initialize the equalizer with the given configuration
288+
/// @param config Configuration settings for the equalizer
289+
/// @return true if initialization was successful
290+
bool begin(ConfigEqualizer3Bands config) {
291+
p_cfg = &config;
292+
return begin();
293+
}
294+
295+
/// Initialize the equalizer using the current configuration
296+
/// @return true if initialization was successful
297+
bool begin(){
298+
// Ensure per-channel arrays are allocated
299+
ensureChannelArraysAllocated();
300+
301+
// Ensure that EQSTATE is allocated
302+
if (p_cfg->channels > max_state_count) {
303+
if (state != nullptr) delete[] state;
304+
state = new EQSTATE[p_cfg->channels];
305+
max_state_count = p_cfg->channels;
306+
}
307+
308+
// Setup state for each channel with its own parameters
309+
for (int j = 0; j < p_cfg->channels; j++) {
310+
memset(&state[j], 0, sizeof(EQSTATE));
311+
312+
// Calculate filter cutoff frequencies per channel
313+
state[j].lf = 2 * sin((float)PI * ((float)freq_low[j] / (float)p_cfg->sample_rate));
314+
state[j].hf = 2 * sin((float)PI * ((float)freq_high[j] / (float)p_cfg->sample_rate));
315+
}
316+
return true;
317+
}
318+
319+
virtual void setAudioInfo(AudioInfo info) override {
320+
p_cfg->sample_rate = info.sample_rate;
321+
p_cfg->channels = info.channels;
322+
p_cfg->bits_per_sample = info.bits_per_sample;
323+
begin(*p_cfg);
324+
}
325+
326+
/// Set frequency parameters for a specific channel
327+
void setChannelFrequencies(int channel, int freq_low_val, int freq_high_val) {
328+
ensureChannelArraysAllocated();
329+
if (channel >= 0 && channel < p_cfg->channels && !freq_low.empty()) {
330+
freq_low[channel] = freq_low_val;
331+
freq_high[channel] = freq_high_val;
332+
333+
// Recalculate filter coefficients for this channel
334+
if (state != nullptr) {
335+
state[channel].lf = 2 * sin((float)PI * ((float)freq_low_val / (float)p_cfg->sample_rate));
336+
state[channel].hf = 2 * sin((float)PI * ((float)freq_high_val / (float)p_cfg->sample_rate));
337+
}
338+
}
339+
}
340+
341+
/// Set gain parameters for a specific channel
342+
void setChannelGains(int channel, float gain_low_val, float gain_medium_val, float gain_high_val) {
343+
ensureChannelArraysAllocated();
344+
if (channel >= 0 && channel < p_cfg->channels && !gain_low.empty()) {
345+
gain_low[channel] = gain_low_val;
346+
gain_medium[channel] = gain_medium_val;
347+
gain_high[channel] = gain_high_val;
348+
}
349+
}
350+
351+
/// Get frequency parameters for a specific channel
352+
bool getChannelFrequencies(int channel, int &freq_low_val, int &freq_high_val) {
353+
if (channel >= 0 && channel < p_cfg->channels && !freq_low.empty()) {
354+
freq_low_val = freq_low[channel];
355+
freq_high_val = freq_high[channel];
356+
return true;
357+
}
358+
return false;
359+
}
360+
361+
/// Get gain parameters for a specific channel
362+
bool getChannelGains(int channel, float &gain_low_val, float &gain_medium_val, float &gain_high_val) {
363+
if (channel >= 0 && channel < p_cfg->channels && !gain_low.empty()) {
364+
gain_low_val = gain_low[channel];
365+
gain_medium_val = gain_medium[channel];
366+
gain_high_val = gain_high[channel];
367+
return true;
368+
}
369+
return false;
370+
}
371+
372+
size_t write(const uint8_t *data, size_t len) override {
373+
filterSamples(data, len);
374+
return p_print->write(data, len);
375+
}
376+
377+
int availableForWrite() override { return p_print->availableForWrite(); }
378+
379+
/// Provides the data from all streams mixed together
380+
size_t readBytes(uint8_t *data, size_t len) override {
381+
size_t result = 0;
382+
if (p_stream != nullptr) {
383+
result = p_stream->readBytes(data, len);
384+
filterSamples(data, len);
385+
}
386+
return result;
387+
}
388+
389+
int available() override {
390+
return p_stream != nullptr ? p_stream->available() : 0;
391+
}
392+
393+
protected:
394+
ConfigEqualizer3Bands cfg;
395+
ConfigEqualizer3Bands *p_cfg = &cfg;
396+
const float vsa = (1.0 / 4294967295.0); // Very small amount (Denormal Fix)
397+
Print *p_print = nullptr; // support for write
398+
Stream *p_stream = nullptr; // support for write
399+
int max_state_count = 0;
400+
401+
// Per-channel frequency settings using Vector
402+
Vector<int> freq_low;
403+
Vector<int> freq_high;
404+
405+
// Per-channel gain controls using Vector
406+
Vector<float> gain_low;
407+
Vector<float> gain_medium;
408+
Vector<float> gain_high;
409+
410+
struct EQSTATE {
411+
// Filter #1 (Low band)
412+
float lf; // Frequency
413+
float f1p0; // Poles ...
414+
float f1p1;
415+
float f1p2;
416+
float f1p3;
417+
418+
// Filter #2 (High band)
419+
float hf; // Frequency
420+
float f2p0; // Poles ...
421+
float f2p1;
422+
float f2p2;
423+
float f2p3;
424+
425+
// Sample history buffer
426+
float sdm1; // Sample data minus 1
427+
float sdm2; // 2
428+
float sdm3; // 3
429+
430+
} *state = nullptr;
431+
432+
/// Ensures that per-channel arrays are allocated and properly sized
433+
void ensureChannelArraysAllocated() {
434+
if (freq_low.size() != p_cfg->channels) {
435+
allocateChannelArrays(p_cfg->channels);
436+
}
437+
}
438+
439+
/// Allocates and initializes per-channel frequency and gain arrays
440+
/// @param num_channels Number of channels to allocate arrays for
441+
void allocateChannelArrays(int num_channels) {
442+
// Resize all vectors to accommodate the number of channels
443+
freq_low.resize(num_channels);
444+
freq_high.resize(num_channels);
445+
gain_low.resize(num_channels);
446+
gain_medium.resize(num_channels);
447+
gain_high.resize(num_channels);
448+
449+
// Initialize with config default values
450+
for (int i = 0; i < num_channels; i++) {
451+
freq_low[i] = p_cfg->freq_low;
452+
freq_high[i] = p_cfg->freq_high;
453+
gain_low[i] = p_cfg->gain_low;
454+
gain_medium[i] = p_cfg->gain_medium;
455+
gain_high[i] = p_cfg->gain_high;
456+
}
457+
}
458+
459+
/// Applies per-channel 3-band equalization to audio samples
460+
/// @param data Pointer to audio data buffer
461+
/// @param len Length of the data buffer in bytes
462+
void filterSamples(const uint8_t *data, size_t len) {
463+
if (state == nullptr){
464+
LOGE("You need to call begin() before using the equalizer");
465+
return;
466+
}
467+
switch (p_cfg->bits_per_sample) {
468+
case 16: {
469+
int16_t *p_dataT = (int16_t *)data;
470+
size_t sample_count = len / sizeof(int16_t);
471+
for (size_t j = 0; j < sample_count; j += p_cfg->channels) {
472+
for (int ch = 0; ch < p_cfg->channels; ch++) {
473+
p_dataT[j + ch] = NumberConverter::fromFloat(sample(ch, NumberConverter::toFloat(p_dataT[j + ch], 16)), 16);
474+
}
475+
}
476+
} break;
477+
case 24: {
478+
int24_t *p_dataT = (int24_t *)data;
479+
size_t sample_count = len / sizeof(int24_t);
480+
for (size_t j = 0; j < sample_count; j += p_cfg->channels) {
481+
for (int ch = 0; ch < p_cfg->channels; ch++) {
482+
p_dataT[j + ch] = NumberConverter::fromFloat(sample(ch, NumberConverter::toFloat(p_dataT[j + ch], 24)), 24);
483+
}
484+
}
485+
} break;
486+
case 32: {
487+
int32_t *p_dataT = (int32_t *)data;
488+
size_t sample_count = len / sizeof(int32_t);
489+
for (size_t j = 0; j < sample_count; j += p_cfg->channels) {
490+
for (int ch = 0; ch < p_cfg->channels; ch++) {
491+
p_dataT[j + ch] = NumberConverter::fromFloat(sample(ch, NumberConverter::toFloat(p_dataT[j + ch], 32)), 32);
492+
}
493+
}
494+
} break;
495+
496+
default:
497+
LOGE("Only 16 bits supported: %d", p_cfg->bits_per_sample);
498+
break;
499+
}
500+
}
501+
502+
/// Processes a single sample through the 3-band equalizer for a specific channel
503+
/// @param channel The channel number to process
504+
/// @param sample_val The input sample value
505+
/// @return The processed sample value with per-channel equalization applied
506+
float sample(int channel, float sample_val) {
507+
EQSTATE &es = state[channel];
508+
509+
// Locals
510+
float l, m, h; // Low / Mid / High - Sample Values
511+
512+
// Filter #1 (lowpass)
513+
es.f1p0 += (es.lf * (sample_val - es.f1p0)) + vsa;
514+
es.f1p1 += (es.lf * (es.f1p0 - es.f1p1));
515+
es.f1p2 += (es.lf * (es.f1p1 - es.f1p2));
516+
es.f1p3 += (es.lf * (es.f1p2 - es.f1p3));
517+
518+
l = es.f1p3;
519+
520+
// Filter #2 (highpass)
521+
es.f2p0 += (es.hf * (sample_val - es.f2p0)) + vsa;
522+
es.f2p1 += (es.hf * (es.f2p0 - es.f2p1));
523+
es.f2p2 += (es.hf * (es.f2p1 - es.f2p2));
524+
es.f2p3 += (es.hf * (es.f2p2 - es.f2p3));
525+
526+
h = es.sdm3 - es.f2p3;
527+
528+
// Calculate midrange (signal - (low + high))
529+
m = es.sdm3 - (h + l);
530+
531+
// Scale with per-channel gains
532+
l *= gain_low[channel];
533+
m *= gain_medium[channel];
534+
h *= gain_high[channel];
535+
536+
// Shuffle history buffer
537+
es.sdm3 = es.sdm2;
538+
es.sdm2 = es.sdm1;
539+
es.sdm1 = sample_val;
540+
541+
// Return result
542+
return (l + m + h);
543+
}
544+
};
545+
240546
} // namespace audio_tools

0 commit comments

Comments
 (0)