Teensy 4.1: Lots of noise on higher gains + clipping/peaking

Fermuto

Member
Hello,

I am currently trying to create an 8-band equalizer with amplifier that uses 3.5mm input/output. I have attached a picture of my setup as well as my code at the end.

Currently, I am facing two problems. The first is that when testing a single band of the EQ, at higher gains I get really bad noise. Here is a sample of 1760Hz pure tone with the 1k-2kHz band being set to +10dB, then +45dB, then +30dB.

http://sndup.net/cgrp

Finally, when using the equalizer with a sample gain profile or from a friend who uses hearing aids, there is massive saturation going on. You can see this in the picture below:

MicrosoftTeams-image.png

The top is the raw audio and the bottom is filtered.

Can anyone shed some light as to what is going on?

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Tympan_Library.h>
#include <U8g2lib.h>
#include <EEPROM.h>
#define B_RE(signal, state) (state=(state<<1)|signal)==B00001111
#define B_FE(signal, state) (state=(state<<1)|signal)==B11110000

//GLOBAL VARIABLES
int N_Bands = 8;
const int myInput = AUDIO_INPUT_LINEIN;
const float samplerate = 44100.0f ;  
const int num_blocks = 128;
unsigned long PreviousMillis = 0; 
const long interVal = 5;

int MenuIndex = 0;
int SelectionIndex = 10;
int InputHold = 0;
int ValueChanged = 0;
int InitialDraw = 1;
int ButtonPressed = 0;
byte ValB0, ValB1, ValB2, ValB3, ValB4, ValB5, ValB6, ValB7, ValA;
byte ButtonFalling, ButtonRising;


//CONSTRUCTORS
AudioSettings_F32             audio_settings(samplerate, num_blocks);
AudioControlSGTL5000          sgtl5000_1;  
AudioInputI2S                 i2s_in;
AudioConvert_I16toF32         convertIN_L;
AudioConvert_I16toF32         convertIN_R;
AudioConvert_F32toI16         convertOUT_L;
AudioConvert_F32toI16         convertOUT_R;
AudioFilterbankBiquad_F32     filterbank_L(audio_settings);
AudioFilterbankBiquad_F32     filterbank_R(audio_settings);
AudioEffectGain_F32           gain_L[8];
AudioEffectGain_F32           gain_R[8];
AudioMixer8_F32               mixer_L(audio_settings);
AudioMixer8_F32               mixer_R(audio_settings);
AudioEffectGain_F32           gainFINAL_L;
AudioEffectGain_F32           gainFINAL_R;
AudioOutputI2S                i2s_out;

U8G2_SSD1327_MIDAS_128X128_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

//WIRE TIME
AudioConnection           patchCord1(i2s_in, 0, convertIN_L, 0);
AudioConnection           patchCord2(i2s_in, 1, convertIN_R, 0);

AudioConnection_F32       patchCord3(convertIN_L, 0, filterbank_L, 0);
AudioConnection_F32       patchCord4(convertIN_R, 0, filterbank_R, 0);

AudioConnection_F32       patchCord11(filterbank_L, 0, gain_L[0], 0);
AudioConnection_F32       patchCord12(filterbank_L, 1, gain_L[1], 0);
AudioConnection_F32       patchCord13(filterbank_L, 2, gain_L[2], 0);
AudioConnection_F32       patchCord14(filterbank_L, 3, gain_L[3], 0);
AudioConnection_F32       patchCord15(filterbank_L, 4, gain_L[4], 0);
AudioConnection_F32       patchCord16(filterbank_L, 5, gain_L[5], 0);
AudioConnection_F32       patchCord17(filterbank_L, 6, gain_L[6], 0);
AudioConnection_F32       patchCord18(filterbank_L, 7, gain_L[7], 0);

AudioConnection_F32       patchCord21(filterbank_R, 0, gain_R[0], 0);
AudioConnection_F32       patchCord22(filterbank_R, 1, gain_R[1], 0);
AudioConnection_F32       patchCord23(filterbank_R, 2, gain_R[2], 0);
AudioConnection_F32       patchCord24(filterbank_R, 3, gain_R[3], 0);
AudioConnection_F32       patchCord25(filterbank_R, 4, gain_R[4], 0);
AudioConnection_F32       patchCord26(filterbank_R, 5, gain_R[5], 0);
AudioConnection_F32       patchCord27(filterbank_R, 6, gain_R[6], 0);
AudioConnection_F32       patchCord28(filterbank_R, 7, gain_R[7], 0);

AudioConnection_F32       patchCord31(gain_L[0], 0, mixer_L, 0);
AudioConnection_F32       patchCord32(gain_L[1], 0, mixer_L, 1);
AudioConnection_F32       patchCord33(gain_L[2], 0, mixer_L, 2);
AudioConnection_F32       patchCord34(gain_L[3], 0, mixer_L, 3);
AudioConnection_F32       patchCord35(gain_L[4], 0, mixer_L, 4);
AudioConnection_F32       patchCord36(gain_L[5], 0, mixer_L, 5);
AudioConnection_F32       patchCord37(gain_L[6], 0, mixer_L, 6);
AudioConnection_F32       patchCord38(gain_L[7], 0, mixer_L, 7);

AudioConnection_F32       patchCord41(gain_R[0], 0, mixer_R, 0);
AudioConnection_F32       patchCord42(gain_R[1], 0, mixer_R, 1);
AudioConnection_F32       patchCord43(gain_R[2], 0, mixer_R, 2);
AudioConnection_F32       patchCord44(gain_R[3], 0, mixer_R, 3);
AudioConnection_F32       patchCord45(gain_R[4], 0, mixer_R, 4);
AudioConnection_F32       patchCord46(gain_R[5], 0, mixer_R, 5);
AudioConnection_F32       patchCord47(gain_R[6], 0, mixer_R, 6);
AudioConnection_F32       patchCord48(gain_R[7], 0, mixer_R, 7);


AudioConnection_F32       patchCord51(mixer_L, 0, gainFINAL_L, 0);
AudioConnection_F32       patchCord52(mixer_R, 0, gainFINAL_R, 0);

AudioConnection_F32       patchCord61(gainFINAL_L, 0, convertOUT_L, 0);
AudioConnection_F32       patchCord62(gainFINAL_R, 0, convertOUT_R, 0);

AudioConnection           patchCord71(convertOUT_L, 0, i2s_out, 0);
AudioConnection           patchCord72(convertOUT_R, 0, i2s_out, 1);

void u8g2_prepare(void) {
  //buffer write preparation
  //u8g2.setFont(u8g2_font_simple1_tf);
  u8g2.setFont(u8g2_font_squeezed_r7_tr);
  //u8g2.setFont(u8g2_font_5x8_tf);
  u8g2.setFontRefHeightExtendedText();
  u8g2.setDrawColor(1);
  u8g2.setFontPosTop();
  u8g2.setFontDirection(0);
}

void grab_eeprom(void){
  //grab saved data from EEPROM
  switch(MenuIndex){
    case 0:
      ValB0 = EEPROM.read(0);
      if (ValB0 > 80){
        ValB0 = 80;
      }
      ValB1 = EEPROM.read(1);
      if (ValB1 > 80){
        ValB1 = 80;
      }
      ValB2 = EEPROM.read(2);
      if (ValB2 > 80){
        ValB2 = 80;
      }
      ValB3 = EEPROM.read(3);
      if (ValB3 > 80){
        ValB3 = 80;
      }
      ValB4 = EEPROM.read(4);
      if (ValB4 > 80){
        ValB4 = 80;
      }
      ValB5 = EEPROM.read(5);
      if (ValB5 > 80){
        ValB5 = 80;
      }
      ValB6 = EEPROM.read(6);
      if (ValB6 > 80){
        ValB6 = 80;
      }
      ValB7 = EEPROM.read(7);
      if (ValB7 > 80){
        ValB7 = 80;
      }
      break;
    case 1:
      ValA = EEPROM.read(8);
      if (ValA > 80) {
        ValA = 80;
      }
      break;
  }
}

void set_values(void) {
  gain_L[0].setGain_dB(ValB0);
  gain_L[1].setGain_dB(ValB1);
  gain_L[2].setGain_dB(ValB2);
  gain_L[3].setGain_dB(ValB3);
  gain_L[4].setGain_dB(ValB4);
  gain_L[5].setGain_dB(ValB5);
  gain_L[6].setGain_dB(ValB6);
  gain_L[7].setGain_dB(ValB7);
  gain_R[0].setGain_dB(ValB0);
  gain_R[1].setGain_dB(ValB1);
  gain_R[2].setGain_dB(ValB2);
  gain_R[3].setGain_dB(ValB3);
  gain_R[4].setGain_dB(ValB4);
  gain_R[5].setGain_dB(ValB5);
  gain_R[6].setGain_dB(ValB6);
  gain_R[7].setGain_dB(ValB7);
  gainFINAL_L.setGain_dB(ValA);
  gainFINAL_R.setGain_dB(ValA);
}

void setup() {
  AudioMemory(12);
  AudioMemory_F32(32);

  sgtl5000_1.enable();
  sgtl5000_1.adcHighPassFilterDisable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);
  
  float crossfreq[N_Bands - 1]={125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0}; 
  int test_L = filterbank_L.designFilters(N_Bands, 6, samplerate, num_blocks, crossfreq);
  int test_R = filterbank_R.designFilters(N_Bands, 6, samplerate, num_blocks, crossfreq);
  
  if (test_L < 0 || test_R < 0){
    Serial.println("It broke :(");
  }
  else{
    Serial.println("It didn't break :)");
  }

  pinMode(13, INPUT_PULLUP);
  pinMode(38, INPUT_PULLUP);
  pinMode(37, INPUT_PULLUP);
  pinMode(36, INPUT_PULLUP);
  
  Serial.println("Done!");
  u8g2.begin();

}

void loop() {
  //LOOP SPECIFIC VARIABLES
  int D_Val = !(digitalRead(13));
  int U_Val = !(digitalRead(36));
  int R_Val = !(digitalRead(37));
  int L_Val = !(digitalRead(38));
  unsigned long CurrentMillis = millis();
  static unsigned long PreviousDebounce;
  


  //DRAW DISPLAY + UPDATE VALUES)
  if ((CurrentMillis - PreviousMillis >= interVal) && ((ValueChanged == 1) || (InitialDraw == 1))) { 
    PreviousMillis = CurrentMillis;
    Serial.println(PreviousMillis);
    
    grab_eeprom();
    set_values();
    
    u8g2.clearBuffer();
    u8g2_prepare();
    switch(MenuIndex){
      case 0:
        u8g2.drawStr( 0, -1, "Equalizer (dB)");
        u8g2.drawStr( 90, -1, "Next Menu");
        u8g2.drawHLine(0, 9, 128);
        u8g2.drawBox(1, (107 - (ValB0 + 1)),14,(ValB0 + 1));
        u8g2.drawBox(17,(107 - (ValB1 + 1)),14,(ValB1 + 1));
        u8g2.drawBox(33,(107 - (ValB2 + 1)),14,(ValB2 + 1));
        u8g2.drawBox(49,(107 - (ValB3 + 1)),14,(ValB3 + 1));
        u8g2.drawBox(65,(107 - (ValB4 + 1)),14,(ValB4 + 1));
        u8g2.drawBox(81,(107 - (ValB5 + 1)),14,(ValB5 + 1));
        u8g2.drawBox(97,(107 - (ValB6 + 1)),14,(ValB6 + 1));
        u8g2.drawBox(113,(107 - (ValB7 + 1)),14,(ValB7 + 1));
        u8g2.drawHLine(0, 107, 128);
        u8g2.setCursor(5, 109);
        u8g2.print(ValB0);
        u8g2.setCursor(21, 109);
        u8g2.print(ValB1);
        u8g2.setCursor(37, 109);
        u8g2.print(ValB2);
        u8g2.setCursor(53, 109);
        u8g2.print(ValB3);
        u8g2.setCursor(69, 109);
        u8g2.print(ValB4);
        u8g2.setCursor(85, 109);
        u8g2.print(ValB5);
        u8g2.setCursor(101, 109);
        u8g2.print(ValB6);
        u8g2.setCursor(117, 109);
        u8g2.print(ValB7);
        u8g2.drawHLine(0, 118, 128);
        u8g2.drawStr(12, 120, "125");
        u8g2.drawStr(28, 120, "250");
        u8g2.drawStr(43, 120, "500");
        u8g2.drawStr(61, 120, "1k");
        u8g2.drawStr(78, 120, "2k");
        u8g2.drawStr(94, 120, "4k");
        u8g2.drawStr(110, 120, "8k");

        switch(SelectionIndex) {
          case 0: u8g2.drawTriangle(4,(107 - (ValB0 + 1) - 6), 12,(107 - (ValB0 + 1) - 6), 8,(107 - (ValB0 + 1) - 2)); break;
          case 1: u8g2.drawTriangle(20,(107 - (ValB1 + 1) - 6), 28,(107 - (ValB1 + 1) - 6), 24,(107 - (ValB1 + 1) - 2)); break;
          case 2: u8g2.drawTriangle(36,(107 - (ValB2 + 1) - 6), 44,(107 - (ValB2 + 1) - 6), 40,(107 - (ValB2 + 1) - 2)); break;
          case 3: u8g2.drawTriangle(52,(107 - (ValB3 + 1) - 6), 60,(107 - (ValB3 + 1) - 6), 56,(107 - (ValB3 + 1) - 2)); break;
          case 4: u8g2.drawTriangle(68,(107 - (ValB4 + 1) - 6), 76,(107 - (ValB4 + 1) - 6), 72,(107 - (ValB4 + 1) - 2)); break;
          case 5: u8g2.drawTriangle(84,(107 - (ValB5 + 1) - 6), 92,(107 - (ValB5 + 1) - 6), 88,(107 - (ValB5 + 1) - 2)); break;
          case 6: u8g2.drawTriangle(100,(107 - (ValB6 + 1) - 6), 108,(107 - (ValB6 + 1) - 6), 104,(107 - (ValB6 + 1) - 2)); break;
          case 7: u8g2.drawTriangle(116,(107 - (ValB7 + 1) - 6), 124,(107 - (ValB7 + 1) - 6), 120,(107 - (ValB7 + 1) - 2)); break;
          case 10: u8g2.drawTriangle(85,0, 85,8, 89,4); break;
        }
        break;
      case 1:
        u8g2.drawStr(0, -1, "Amplifier (dB)");
        u8g2.drawStr(90, -1, "Next Menu");
        u8g2.drawHLine(0, 9, 128);  
        u8g2.setCursor(0, 12);
        u8g2.print(ValA);
        u8g2.drawBox(24,52,ValA,10);
        u8g2.drawVLine(24, 50, 14);
        u8g2.drawVLine(104, 50, 14);
        switch(SelectionIndex) {
          case 0: u8g2.drawTriangle((21 + ValA), 47, (27 + ValA),47, (24 + ValA),50); break;
          case 10: u8g2.drawTriangle(85,0, 85,8, 89,4); break;
        }
        break;
    }
    u8g2.sendBuffer();
    InitialDraw = 0;
    ValueChanged = 0;
  }
  
  if (CurrentMillis - PreviousDebounce >= interVal) {
    PreviousDebounce = CurrentMillis;

    if (B_RE((D_Val || U_Val || R_Val || L_Val), ButtonRising)) {
      Serial.print ("Rising edge (button depressed) StateVar: ");
      Serial.println (ButtonRising);
      ButtonPressed = 1;
    }

    if (B_FE((D_Val || U_Val || R_Val || L_Val), ButtonFalling)) {
      Serial.print ("Falling edge (button released) StateVar: ");
      Serial.println (ButtonFalling);
    }
  }
  
  
  //NAVIGATION AND VALUE CHANGE
  if (ButtonPressed == 1){
    switch(MenuIndex){
      case 0: 
        if (D_Val == 1 && InputHold == 0){
          InputHold = 1;
          switch(SelectionIndex) {
            case 0: ValB0 = ValB0 - 1; EEPROM.write(0, ValB0); ValueChanged = 1; break;
            case 1: ValB1 = ValB1 - 1; EEPROM.write(1, ValB1); ValueChanged = 1; break;
            case 2: ValB2 = ValB2 - 1; EEPROM.write(2, ValB2); ValueChanged = 1; break;
            case 3: ValB3 = ValB3 - 1; EEPROM.write(3, ValB3); ValueChanged = 1; break;
            case 4: ValB4 = ValB4 - 1; EEPROM.write(4, ValB4); ValueChanged = 1; break;
            case 5: ValB5 = ValB5 - 1; EEPROM.write(5, ValB5); ValueChanged = 1; break;
            case 6: ValB6 = ValB6 - 1; EEPROM.write(6, ValB6); ValueChanged = 1; break;
            case 7: ValB7 = ValB7 - 1; EEPROM.write(7, ValB7); ValueChanged = 1; break;
            case 10: break;
          }
        }
        if (U_Val == 1 && InputHold == 0){
          InputHold = 1;
          switch(SelectionIndex) {
            case 0: ValB0 = ValB0 + 1; EEPROM.write(0, ValB0); ValueChanged = 1; break;
            case 1: ValB1 = ValB1 + 1; EEPROM.write(1, ValB1); ValueChanged = 1; break;
            case 2: ValB2 = ValB2 + 1; EEPROM.write(2, ValB2); ValueChanged = 1; break;
            case 3: ValB3 = ValB3 + 1; EEPROM.write(3, ValB3); ValueChanged = 1; break;
            case 4: ValB4 = ValB4 + 1; EEPROM.write(4, ValB4); ValueChanged = 1; break;
            case 5: ValB5 = ValB5 + 1; EEPROM.write(5, ValB5); ValueChanged = 1; break;
            case 6: ValB6 = ValB6 + 1; EEPROM.write(6, ValB6); ValueChanged = 1; break;
            case 7: ValB7 = ValB7 + 1; EEPROM.write(7, ValB7); ValueChanged = 1; break;
            case 10: MenuIndex = 1; ValueChanged = 1; break;
          }
        }
        if (R_Val == 1 && InputHold == 0){
          InputHold = 1;
          switch(SelectionIndex) {
            case 7: SelectionIndex = 10; ValueChanged = 1; break;
            case 10: SelectionIndex = 7; ValueChanged = 1; break;
            default: SelectionIndex = SelectionIndex + 1; ValueChanged = 1; break;
          }
        }
        if (L_Val == 1 && InputHold == 0){
          InputHold = 1;
          switch(SelectionIndex) {
            case 0: SelectionIndex = 10; ValueChanged = 1; break;
            case 10: SelectionIndex = 0; ValueChanged = 1; break;
            default: SelectionIndex = SelectionIndex - 1; ValueChanged = 1; break;
          }
        }
        break;
      case 1:
        if (D_Val == 1  && InputHold == 0){
          InputHold = 1; 
          switch(SelectionIndex){
            case 0: break;
            case 10: SelectionIndex = 0; ValueChanged = 1; break;
          }
        }
        if (U_Val == 1  && InputHold == 0){
          InputHold = 1; 
          switch(SelectionIndex){
            case 0: SelectionIndex = 10; ValueChanged = 1; break;
            case 10: MenuIndex = 0; ValueChanged = 1; break;
          }
        }
        if (R_Val == 1  && InputHold == 0){
          InputHold = 1; 
          switch(SelectionIndex){
            case 0: ValA = ValA + 1; EEPROM.write(8, ValA); ValueChanged = 1; break;
            case 10: break;
          }
        }
        if (L_Val == 1  && InputHold == 0){
          InputHold = 1; 
          switch(SelectionIndex){
            case 0: ValA = ValA - 1; EEPROM.write(8, ValA); ValueChanged = 1; break;
            case 10: break;
          }
        }
        break;
    }
    InputHold = 0;
    ButtonPressed = 0;
  }

}

https://i.imgur.com/m9Ce2IA.jpg
 
Last edited:
Just a couple of thoughts. Chip and the Tympan group might be a useful resource . Also, using the F32 objects prevents overload and clipping, such as you are seeing, up to the point of conversion to I16. At that conversion point, the range of values must be between -1.0 and 1.0 or it will clip.

Tympan does not seem to have a class for measuring peak values. There is a class for this, AudioAnalyzePeak_F32, in the Teensy Floating Point Audio Library. See also the Design Tool for that library. The two libraries are compatible in interfaces so it should be possible to drop the .h and .cpp files in and use them, for a test. You need an #include for the .h file in the .INO file. A pull request might put them in permanently. If you move the connection to the peak measuring object around, you can trace the reason for clipping.

Good Luck! Bob
 
Just a couple of thoughts. Chip and the Tympan group might be a useful resource . Also, using the F32 objects prevents overload and clipping, such as you are seeing, up to the point of conversion to I16. At that conversion point, the range of values must be between -1.0 and 1.0 or it will clip.

Tympan does not seem to have a class for measuring peak values. There is a class for this, AudioAnalyzePeak_F32, in the Teensy Floating Point Audio Library. See also the Design Tool for that library. The two libraries are compatible in interfaces so it should be possible to drop the .h and .cpp files in and use them, for a test. You need an #include for the .h file in the .INO file. A pull request might put them in permanently. If you move the connection to the peak measuring object around, you can trace the reason for clipping.

Good Luck! Bob
Hey Bob! I've come back to this project after a busy year or two. Are you referring to the last AudioConnection for the i2s? While I do see that peak detection and equalization of values would preserve the amplification relation, wouldn't that still limit the overall output?
 
In Teensy audio, including Tympan, variables are either I16 16-bit signed integers, -32768 to 32767 in range, or they are F32 floating point with practical limits to their range. The problem is that the hardware is always I16. Somehow, the signal going to the output hardware must be in that integer range.

I suggest you follow the signal levels through all the blocks involved and see where the big amplification is happening. The peak detector block is one way to do that.

One note that may apply, or not, is that there is a simple equalizer block available in the Floating Point Audio Library, linked above, that might make it easier to see what the gains are. It is AudioFilterEqualizer_F32 with info at http://www.janbob.com/electron/OpenAudio_Design_Tool/index.html?info=AudioFilterEqualizer_F32. There is more about that at https://forum.pjrc.com/index.php?threads/audio-equalizer-using-fir.60928/

Oh, also, there are multiple methods of controlling the system gain based on the level. Chip has done a lot with that in Tympan. A general form of this is also at http://www.janbob.com/electron/OpenAudio_Design_Tool/index.html?info=AudioEffectCompressor2_F32

Good luck!
 
Back
Top