@@ -27,8 +27,6 @@ struct ConfigEqualizer3Bands : public AudioInfo {
27
27
sample_rate = 44100 ;
28
28
}
29
29
30
- ConfigEqualizer3Bands (const ConfigEqualizer3Bands &) = delete ;
31
-
32
30
// Frequencies
33
31
int freq_low = 880 ;
34
32
int freq_high = 5000 ;
@@ -237,4 +235,312 @@ class Equalizer3Bands : public ModifyingStream {
237
235
}
238
236
};
239
237
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
+
240
546
} // namespace audio_tools
0 commit comments