Forum Rule: Always post complete source code & details to reproduce any issue!
Page 3 of 3 FirstFirst 1 2 3
Results 51 to 71 of 71

Thread: New Guy Looking For Teensy DSP programming tips

  1. #51
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by MarkT View Post
    Ive not looked the the USB audio code, its not a secret, but its probably quite complex low level DMA stuff.
    I suspect the computer internally is converting to a common representation
    I agree there's no clock-mismatch but there's no "common representation" for converting from 44117(for example) to 44100Hz. It would require an interpolation by 44100 and a decimation by 44117. Smells more like an asynchronous sample rate converter, a hardware device somewhere in the chain.

  2. #52
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    By common representation I meant something like 192kSPS/32bit, ie over-sampled and good bit depth. Computer audio
    sub-systems are pretty complex these days and aren't short of hardware DSP grunt.

  3. #53
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by MarkT View Post
    By common representation I meant something like 192kSPS/32bit, ie over-sampled and good bit depth. Computer audio
    sub-systems are pretty complex these days and aren't short of hardware DSP grunt.
    Interesting. 640/147. Alotta coefs. Maybe the 44.1K is not converted exactly to 192K by using smaller ratios, then resolved later by the HW? Interesting subjects (at least for me). Also, a high performance ASRC in software is possible but don't know if anyone ever developed one.

  4. #54
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    Well maybe not, but the only step that needs a lot of coefficients is the first oversampling, as the anti-aliasing requirement is
    demanding - thereafter filter requirements are modest. Polyphase FIR techniques make it super efficient to resample by an integer ratio.

    For instance if you want to convert by a ratio of 11/7 with a 121 coeff FIR filter you might naively expect 11*121 = 1331 muladds per
    input sample, but actually you only need 121/7 = 17.3 muladds per input, 77 times less work than the naive approach.

  5. #55
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,421
    Not directly the topic - just to mention. There is a great library by ARM ("CMSIS DSP") that does interpolation etc pretty fast. It has a lot of useful things.
    I've used it to interpolate to 352kHz (x8) for a FM transmitter (with RDS) on Teensy 4

    You might like it: https://arm-software.github.io/CMSIS...tml/index.html

  6. #56
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    IE arm_fir_interpolate_f32 and arm_fir_decimate_f32?

    Doesn't seem to have a combined up-down FIR rate converter though, which is a shame as it is more general than either
    of those function and much more efficient for converting by ratios than upsampling followed by downsampling.

  7. #57
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Next problem in my Teensy adventure. Wanted to add an OLED 128x32 (Adafruit_SSD1306) display to my AGC project to monitor the left & right agcGain's. I found that the display blocks the execution of my loop for 50 msec every time I call it. If I'm processing a queue of 128 samples at 44.1K, I need to keep the loop cycle rate at 128/44100 or < 2.9 msec. As I only need to monitor the gains for the installation, I added a switch to call the display but it's not a very clean solution to glitch the audio every time I push the button. Any suggestions? What about multi-threading? Here's my latest code:

    Code:
      #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    #include <Bounce.h>
    #define OLEDbutton 15
    #define OLED_RESET 4
    //SDA=A4
    //SCL=A5
    Adafruit_SSD1306 display(OLED_RESET);
    
    //#define DEBUG
    //#define PASSTHRU
    //#define USE_SD
    
    #ifdef USE_SD
    AudioPlaySdWav            playSdWav1;
    AudioOutputUSB            audioOutput;    //Puts digital samples to Audacity input (MME, digital audio interface) for recording to wav file
    #else
    AudioInputI2S             i2s1;           
    #endif
    AudioFilterFIR            fir1;           
    AudioFilterFIR            fir2;           
    AudioRecordQueue          queue3;         
    AudioRecordQueue          queue1;         
    AudioPlayQueue            queue2;         
    AudioPlayQueue            queue4;         
    AudioOutputI2S            i2s2;           
    #ifdef USE_SD
    AudioConnection           patchCord1(playSdWav1, 0, fir1, 0);
    AudioConnection           patchCord2(playSdWav1, 1, fir2, 0);
    #else
    AudioConnection           patchCord1(i2s1, 0, fir1, 0);
    AudioConnection           patchCord2(i2s1, 1, fir2, 0);
    #endif
    AudioConnection           patchCord3(fir1, queue1);
    AudioConnection           patchCord4(fir2, queue3);
    AudioConnection           patchCord5(queue2, 0, i2s2, 0);
    AudioConnection           patchCord6(queue4, 0, i2s2, 1);
    #ifdef USE_SD
    AudioConnection           patchCord7(queue2, 0, audioOutput,0);
    AudioConnection           patchCord8(queue4, 0, audioOutput,1);
    #endif
    AudioControlSGTL5000      sgtl5000_1;     
    
    #ifdef USE_SD
    #define SDCARD_CS_PIN    10
    #define SDCARD_MOSI_PIN  7
    #define SDCARD_SCK_PIN   14
    #else 
    //const int myInput = AUDIO_INPUT_MIC;
    const int myInput = AUDIO_INPUT_LINEIN;
    #endif
    //---------------------------------------------FIR parameters ---------------------
    //#define FIRFILTER HIPASSFILTER
    #define FIRFILTER FLATFILTER
    #define NUM_COEFFS 50
    double HIPASSFILTER[]=
    {
    #include "numHipass.h" //Arbitrary xfer function using fft for hpf fc=4000 fir filter design - (choice no. 13 of calcfilt.m)
    };
    double FLATFILTER[]=
    {
    #include "numflat.h" //Arbitrary xfer function using fft for flat fir filter design - (choice no. 14 of calcfilt.m)
    };
    double *coefs1 = FIRFILTER;
    double *coefs2 = FIRFILTER;
    short int COEFS1[NUM_COEFFS];
    short int COEFS2[NUM_COEFFS];
    //---------------------------------------------------------------------------------
    int32_t agcGain1=0x08000000;//.5 in Q3.29 format
    int32_t agcGain2=0x08000000;//.5 in Q3.29 format
    int32_t ref=293504;//.07*AUDIO_BLOCK_SAMPLES in Q15 format(2293*128)
    #ifdef DEBUG
    uint32_t cyc1=0,cyc2=0;
    #endif
    Bounce button0 = Bounce(OLEDbutton, 15); //15msec debounce
    
    void setup() {
      pinMode(OLEDbutton, INPUT_PULLUP); //push button to display current agcGain's on OLED display
      Serial.begin(115200);
      Wire.begin();
      display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
      display.clearDisplay();
      display.setTextColor(WHITE);
      display.setTextSize(1);
      display.setCursor(0,0);
      displayOLED();
      display.display();  
      delay(2000);
      // allocate memory for the audio library
      AudioMemory(32);
      sgtl5000_1.enable();
      sgtl5000_1.volume(0.5);
    #ifdef USE_SD
      SPI.setMOSI(SDCARD_MOSI_PIN);
      SPI.setSCK(SDCARD_SCK_PIN);
      if (!(SD.begin(SDCARD_CS_PIN))) {
        while (1) {
          Serial.println("Unable to access the SD card");
          delay(500);
        }
      }
      delay(1000);
    #else
      sgtl5000_1.inputSelect(myInput);
    #endif 
      // Initialize the filter
      transformCoeffs( COEFS1, coefs1, NUM_COEFFS );
      transformCoeffs( COEFS2, coefs2, NUM_COEFFS );
      fir1.begin(COEFS1,NUM_COEFFS);
      fir2.begin(COEFS2,NUM_COEFFS);
      // Start the record queue
      queue1.begin();
      queue3.begin();
    #ifdef DEBUG
      Serial.println("setup done");
    #endif
    }
    
    // audio volume
    int volume = 0;
    unsigned long last_time = millis();
    
    void loop()
    {
    #ifdef USE_SD
      if (playSdWav1.isPlaying() == false) 
      {
        Serial.println("Start playing");
        playSdWav1.play("Tower.wav");
    //    playSdWav1.play("multisin.wav");
    //    playSdWav1.play("noise.wav");
        delay(10);
      }
    #endif
      button0.update();
    //--------------------------- Left channel -----------------------------
      if (queue1.available() >= 1) //Left Channel
      {
    #ifdef DEBUG
        cyc1 = ARM_DWT_CYCCNT;
        agcCalc(queue1.readBuffer(),queue2.getBuffer(),&agcGain1);
        cyc1 = ARM_DWT_CYCCNT - cyc1;
    #else
        agcCalc(queue1.readBuffer(),queue2.getBuffer(),&agcGain1);
    #endif
        // Free the input audio buffer
        queue1.freeBuffer();
        // and play it back into the audio queue
        queue2.playBuffer();
      } //if (queue1.available() >= 1)
    //--------------------------- Right channel -----------------------------
      if (queue3.available() >= 1) 
      {
    #ifdef DEBUG
        cyc2 = ARM_DWT_CYCCNT;
        agcCalc(queue3.readBuffer(),queue4.getBuffer(),&agcGain2);
        cyc2 = ARM_DWT_CYCCNT - cyc2;
    #else
        agcCalc(queue3.readBuffer(),queue4.getBuffer(),&agcGain2);
    #endif
        // Free the input audio buffer
        queue3.freeBuffer();
        // and play it back into the audio queue
        queue4.playBuffer();
      } //if (queue3.available() >= 1)
    //--------------------------- Volume control -----------------------------
      int n = analogRead(0);
      if (n != volume) 
      {
        volume = n;
        sgtl5000_1.volume((float)n / 1023.);
        //uncomment this line if your audio shield has the volume pot
        //audioShield.volume((float)n / 1023);
      }  
    //-------- oled display updated only if button0 is activated 'cuz OLED is too slow(50msec & audio queue is updated every 3msec) so causes a glitch. -------------
      if (button0.fallingEdge())
      {
        displayOLED();
        display.display();    
      }
      #ifdef DEBUG  
      // print information about resource usage
      // Proc = 18 (18),  Mem = 4 (5)
      if (millis() - last_time >= 2500) 
      {
        Serial.print("Proc = ");
        Serial.print(AudioProcessorUsage());
        Serial.print(" % ");
        Serial.print(" (");    
        Serial.print(AudioProcessorUsageMax());
        Serial.print(" %),  Mem = ");
        Serial.print(AudioMemoryUsage());
        Serial.print(" (");    
        Serial.print(AudioMemoryUsageMax());
        Serial.println(")");
        Serial.print("agc1: Gain=");
        Serial.print((float)agcGain1/(float)0x10000000);
        Serial.print(" cycles/sample=");
        Serial.println((float)cyc1/(float)AUDIO_BLOCK_SAMPLES);
        Serial.print("agc2: Gain=");
        Serial.print((float)agcGain2/(float)0x10000000);
        Serial.print(" cycles/sample=");
        Serial.println((float)cyc2/(float)AUDIO_BLOCK_SAMPLES);
        Serial.println("");
        last_time = millis();
      }
    #endif
    }
    
    void transformCoeffs( short int out[], double in[], int k )
    {
      for ( int i = 0 ; i < k ; i++ )
        out[i] = 32767 * in[i];
    }
    
    void agcCalc(int16_t *inbuf,int16_t *outbuf,int32_t *agcGain)
    {
      int16_t *bp1=inbuf,*bp2=outbuf;
    #ifdef PASSTHRU
        // Copy from input to output buffer
        for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) 
        {
          *bp2++ = *bp1++;
        }
    #else
        // Apply agcGain on input with saturation to output buffer
        int32_t sum=0,sum1=0,sum2=0,sum3=0,res,res1,res2,res3,err;
        for(int i = 0;i < AUDIO_BLOCK_SAMPLES>>2;i++) 
        { 
          //ref. dspinst.h in Teensy audio library
          res=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res1=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res2=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res3=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          *bp2++=saturate16(res);
          *bp2++=saturate16(res1);
          *bp2++=saturate16(res2);
          *bp2++=saturate16(res3);
          sum+=abs(res);
          sum1+=abs(res1);
          sum2+=abs(res2);
          sum3+=abs(res3);
        }
        sum+=(sum1+sum2+sum3);
        // Calculate new agcGain for next input buffer
        err=ref-sum; 
        *agcGain+=err>>3; //update gain 
        *agcGain=min(*agcGain,0x7ff80000); //keep agcGain < 8.0
        *agcGain=max(*agcGain,0); //keep agcGain >= 0
    #endif
    }
    void displayOLED(){
      display.clearDisplay();
      display.setCursor(0,0);
      display.print("agcGains: ");
      display.print((float)agcGain1/(float)0x10000000);
      display.print(","); 
      display.print((float)agcGain2/(float)0x10000000);
    }

  8. #58
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    I2C is very slow (for a bitmapped display), try the SPI interface, perhaps try 8MHz in the first instance and when that works try faster.

  9. #59
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by MarkT View Post
    I2C is very slow (for a bitmapped display), try the SPI interface, perhaps try 8MHz in the first instance and when that works try faster.
    I guess I chose the wrong option when I ordered it. Anyhow, seems it will be difficult to go from 50 msec to < 2.9 msec. I'll check it out. Thanx.

  10. #60
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    128x32 pixels is at most 12288 bytes for an RGB bitmap which is a few ms at even 8MHz SPI.
    Its worth looking to see if there's a DMA version of a library that drives I2C - that would free the processor.
    I've no idea if there is one for this display.

  11. #61
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by MarkT View Post
    128x32 pixels is at most 12288 bytes for an RGB bitmap which is a few ms at even 8MHz SPI.
    Its worth looking to see if there's a DMA version of a library that drives I2C - that would free the processor.
    I've no idea if there is one for this display.
    Thank you. I will look at that option.

  12. #62
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by MarkT View Post
    128x32 pixels is at most 12288 bytes for an RGB bitmap which is a few ms at even 8MHz SPI.
    Its worth looking to see if there's a DMA version of a library that drives I2C - that would free the processor.
    I've no idea if there is one for this display.
    At my level, didn't find anything so just added an ATMega328P 3.3V, 8MHz on the Teensy TX4 pin to manage the OLED. It's overkill but doesn't take much additional space and cheap.

    Click image for larger version. 

Name:	TVagcProject.jpg 
Views:	20 
Size:	122.3 KB 
ID:	26290

  13. #63
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    I have developed a "final" version for my (Teensy+AudioAdapter+Oled) TV & Stereo agc box. It works OK but I noticed a slight 50Hz humming noise added to the sound. I am supposing it's ground loop noise. I have ordered a ground loop eliminator. Any suggestions? Thanks.

    Code:
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    
    
    //#define DEBUG
    //#define PASSTHRU
    //#define USE_SD
    
    #ifdef USE_SD
    AudioPlaySdWav            playSdWav1;
    AudioOutputUSB            audioOutput;    //Puts digital samples to Audacity input (MME, digital audio interface) for recording to wav file
    #else
    AudioInputI2S             i2s1;           
    #endif
    AudioFilterFIR            fir1;           
    AudioFilterFIR            fir2;           
    AudioRecordQueue          queue3;         
    AudioRecordQueue          queue1;         
    AudioPlayQueue            queue2;         
    AudioPlayQueue            queue4;         
    AudioOutputI2S            i2s2;           
    #ifdef USE_SD
    AudioConnection           patchCord1(playSdWav1, 0, fir1, 0);
    AudioConnection           patchCord2(playSdWav1, 1, fir2, 0);
    #else
    AudioConnection           patchCord1(i2s1, 0, fir1, 0);
    AudioConnection           patchCord2(i2s1, 1, fir2, 0);
    #endif
    AudioConnection           patchCord3(fir1, queue1);
    AudioConnection           patchCord4(fir2, queue3);
    AudioConnection           patchCord5(queue2, 0, i2s2, 0);
    AudioConnection           patchCord6(queue4, 0, i2s2, 1);
    #ifdef USE_SD
    AudioConnection           patchCord7(queue2, 0, audioOutput,0);
    AudioConnection           patchCord8(queue4, 0, audioOutput,1);
    #endif
    AudioControlSGTL5000      sgtl5000_1;     
    
    #ifdef USE_SD
    #define SDCARD_CS_PIN    10
    #define SDCARD_MOSI_PIN  7
    #define SDCARD_SCK_PIN   14
    #else 
    //const int myInput = AUDIO_INPUT_MIC;
    const int myInput = AUDIO_INPUT_LINEIN;
    #endif
    //---------------------------------------------FIR parameters ---------------------
    //#define FIRFILTER HIPASSFILTER
    #define FIRFILTER FLATFILTER
    #define NUM_COEFFS 50
    double HIPASSFILTER[]=
    {
    #include "numHipass.h" //Arbitrary xfer function using fft for hpf fc=4000 fir filter design - (choice no. 13 of calcfilt.m)
    };
    double FLATFILTER[]=
    {
    #include "numflat.h" //Arbitrary xfer function using fft for flat fir filter design - (choice no. 14 of calcfilt.m)
    };
    double *coefs1 = FIRFILTER;
    double *coefs2 = FIRFILTER;
    short int COEFS1[NUM_COEFFS];
    short int COEFS2[NUM_COEFFS];
    //---------------------------------------------------------------------------------
    int32_t agcGain1=0x08000000;//.5 in Q3.29 format
    int32_t agcGain2=0x08000000;//.5 in Q3.29 format
    int32_t ref=293504;//.07*AUDIO_BLOCK_SAMPLES in Q15 format(2293*128)
    #ifdef DEBUG
    uint32_t cyc1=0,cyc2=0;
    #endif
    
    
    void setup() {
    
      Serial.begin(115200);
      Serial4.begin(9600);
    // allocate memory for the audio library
      AudioMemory(32);
      sgtl5000_1.enable();
      sgtl5000_1.volume(0.5);
      sgtl5000_1.lineInLevel(0); //max 3.12Vp-p instead of (5)1.33
    #ifdef USE_SD
      SPI.setMOSI(SDCARD_MOSI_PIN);
      SPI.setSCK(SDCARD_SCK_PIN);
      if (!(SD.begin(SDCARD_CS_PIN))) {
        while (1) {
          Serial.println("Unable to access the SD card");
          delay(500);
        }
      }
      delay(1000);
    #else
      sgtl5000_1.inputSelect(myInput);
    #endif 
      // Initialize the filter
      transformCoeffs( COEFS1, coefs1, NUM_COEFFS );
      transformCoeffs( COEFS2, coefs2, NUM_COEFFS );
      fir1.begin(COEFS1,NUM_COEFFS);
      fir2.begin(COEFS2,NUM_COEFFS);
      // Start the record queue
      queue1.begin();
      queue3.begin();
    #ifdef DEBUG
      Serial.println("setup done");
    #endif
    }
    
    // audio volume
    int volume = 0;
    unsigned long last_time = millis();
    unsigned long last_time1 = millis();
    
    void loop()
    {
    #ifdef USE_SD
      if (playSdWav1.isPlaying() == false) 
      {
        Serial.println("Start playing");
        playSdWav1.play("Tower.wav");
    //    playSdWav1.play("multisin.wav");
    //    playSdWav1.play("noise.wav");
        delay(10);
      }
    #endif
    
    //--------------------------- Left channel -----------------------------
      if (queue1.available() >= 1) //Left Channel
      {
    #ifdef DEBUG
        cyc1 = ARM_DWT_CYCCNT;
        agcCalc(queue1.readBuffer(),queue2.getBuffer(),&agcGain1);
        cyc1 = ARM_DWT_CYCCNT - cyc1;
    #else
        agcCalc(queue1.readBuffer(),queue2.getBuffer(),&agcGain1);
    #endif
        // Free the input audio buffer
        queue1.freeBuffer();
        // and play it back into the audio queue
        queue2.playBuffer();
      } //if (queue1.available() >= 1)
    //--------------------------- Right channel -----------------------------
      if (queue3.available() >= 1) 
      {
    #ifdef DEBUG
        cyc2 = ARM_DWT_CYCCNT;
        agcCalc(queue3.readBuffer(),queue4.getBuffer(),&agcGain2);
        cyc2 = ARM_DWT_CYCCNT - cyc2;
    #else
        agcCalc(queue3.readBuffer(),queue4.getBuffer(),&agcGain2);
    #endif
        // Free the input audio buffer
        queue3.freeBuffer();
        // and play it back into the audio queue
        queue4.playBuffer();
      } //if (queue3.available() >= 1)
    //--------------------------- Volume control -----------------------------
      int n = analogRead(0);
      if (n != volume) 
      {
        volume = n;
        sgtl5000_1.volume((float)n / 1023.);
        //uncomment this line if your audio shield has the volume pot
        //audioShield.volume((float)n / 1023);
      }  
    
      if (millis() - last_time1 >= 2500) //Send agcGains to OLED display controlled by Nano
      {
        Serial4.print("agcGains: ");
        Serial4.print((float)agcGain1/(float)0x10000000);
        Serial4.print(",");
        Serial4.println((float)agcGain2/(float)0x10000000);
        last_time1=millis();
      }
    #ifdef DEBUG  
      // print information about resource usage
      // Proc = 18 (18),  Mem = 4 (5)
      if (millis() - last_time >= 2500) 
      {
        Serial.print("Proc = ");
        Serial.print(AudioProcessorUsage());
        Serial.print(" % ");
        Serial.print(" (");    
        Serial.print(AudioProcessorUsageMax());
        Serial.print(" %),  Mem = ");
        Serial.print(AudioMemoryUsage());
        Serial.print(" (");    
        Serial.print(AudioMemoryUsageMax());
        Serial.println(")");
        Serial.print("agc1: Gain=");
        Serial.print((float)agcGain1/(float)0x10000000);
        Serial.print(" cycles/sample=");
        Serial.println((float)cyc1/(float)AUDIO_BLOCK_SAMPLES);
        Serial.print("agc2: Gain=");
        Serial.print((float)agcGain2/(float)0x10000000);
        Serial.print(" cycles/sample=");
        Serial.println((float)cyc2/(float)AUDIO_BLOCK_SAMPLES);
        Serial.println("");
        last_time = millis();
      }
    #endif
    }
    
    void transformCoeffs( short int out[], double in[], int k )
    {
      for ( int i = 0 ; i < k ; i++ )
        out[i] = 32767 * in[i];
    }
    
    void agcCalc(int16_t *inbuf,int16_t *outbuf,int32_t *agcGain)
    {
      int16_t *bp1=inbuf,*bp2=outbuf;
    #ifdef PASSTHRU
        // Copy from input to output buffer
        for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) 
        {
          *bp2++ = *bp1++;
        }
    #else
        // Apply agcGain on input with saturation to output buffer
        int32_t sum=0,sum1=0,sum2=0,sum3=0,res,res1,res2,res3,err;
        for(int i = 0;i < AUDIO_BLOCK_SAMPLES>>2;i++) 
        { 
          //ref. dspinst.h in Teensy audio library
          res=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res1=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res2=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res3=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          *bp2++=saturate16(res);
          *bp2++=saturate16(res1);
          *bp2++=saturate16(res2);
          *bp2++=saturate16(res3);
          sum+=abs(res);
          sum1+=abs(res1);
          sum2+=abs(res2);
          sum3+=abs(res3);
        }
        sum+=(sum1+sum2+sum3);
        // Calculate new agcGain for next input buffer
        err=ref-sum; 
        *agcGain+=err>>3; //update gain 
        *agcGain=min(*agcGain,0x7ff80000); //keep agcGain < 8.0
        *agcGain=max(*agcGain,0); //keep agcGain >= 0
    #endif
    }

    Click image for larger version. 

Name:	agcBox1.jpg 
Views:	13 
Size:	84.4 KB 
ID:	26340Click image for larger version. 

Name:	agcBox2.jpg 
Views:	15 
Size:	123.3 KB 
ID:	26341Click image for larger version. 

Name:	groundLoopEliminator.jpg 
Views:	15 
Size:	13.9 KB 
ID:	26342

  14. #64
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    In fact, I solved my problem. I browsed around the forum and found out that I shouldn't connect audio shield headphones output to line in of another external device (in my case a bluetooth transmitter). I had connected headphones L & R outputs to device L & R inputs and VG(virtual ground) to device Gnd input. I rewired the audio shield line out instead and increased the line out amplitude to compensate (sgtl5000_1.lineOutLevel(13)). No more noise. Can someone please explain to me what VG is and what it's used for? Thanks.

  15. #65
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    Its a Virtual (ac) Ground for the headphone amplifier - its got a DC offset from actual ground, so that the
    headphone amp doesn't need large bulky electrolytic coupling caps (this is a chip used in phones etc).

    Since the supply is single ended there are only two ways to be able swing an output as AC - a series coupling
    capacitor (aka DC-blocker), or using a mid-rail virtual ground. For low impedance headphones (15 ohms) the
    coupling caps would need to be about 1000F or more for full bass response.

    The line-outs are high impedance (10k--47k is typical) and a few F is sufficient, taking up much less space.

  16. #66
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    I guess the trace on the audio shield is just a test point then?
    I can see the danger of connecting a virtual ground with a DC offset to an input device(BT xmitter) at REAL ground.
    I guess the ground loop noise is introduced by the BT xmitter? When I listened to the sound via headphones, with the BT xmitter disconnected, there is no audible noise.
    I guess it shoulda been obvious to not connect the headphone traces to the input of the BT xmitter but I didn't realize that the level at the audio shield lineout could be programmed with sgtl5000_1.lineOutLevel().
    I'm doing a lotta guessing and surfing on the forum. I don't want to be critical about such a marvelous product but there is still not enough documentation and application notes for new guy's.

    Thanx for your help, you & others on this forum!

  17. #67
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    I guess the trace on the audio shield is just a test point then?
    I'd assume its so you can run a remote jack in a chassis for headphones. For a mysterious reason its
    not a through-hole though?

    If you've specific ideas to improve the webpage about the Audio shield(s) perhaps add here:
    https://forum.pjrc.com/threads/60862...-few-updates-)
    I think what's lacking is a couple of example connection setups and an explicit warning about the headphone ground

  18. #68
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by chipaudette View Post
    If all you want is AGC, then the audio processing block that you're looking for is a "compressor". I've always been suprised that there isn't one in the Teensy Audio Library.
    Chip

    Attachment 26052
    I implemented my AGC algo. It works great for music. However, I might use your compressor for watching TV movies as my hearing is not so good for comprehension. I found that I can store the test signals directly in Teensy 4.0 program memory. No need for the audio shield using the USB output. I used 0dB & 20dB gains for the sweep test. I modified your program to be able to use the program memory. See attachments, figures. Thanx.

    Code:
     /*
       BasicCompressor_Float
    
       Created: Chip Audette, Dec 2016 - Jan 2017
       Purpose: Process audio by applying a single-band compressor
                Demonstrates audio processing using floating point data type.
    
       Uses Teensy Audio Adapter.
       Assumes microphones (or whatever) are attached to the LINE IN (stereo)
    
       MIT License.  use at your own risk.
    */
    
    //Chip Audette blog is:    https://openaudio.blogspot.com/search/label/Compression
    
    //These are the includes from the Teensy Audio Library
    #include <Audio.h>   //Teensy Audio Librarya
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    
    #include <OpenAudio_ArduinoLibrary.h> //for AudioConvert_I16toF32, AudioConvert_F32toI16, and AudioEffectGain_F32
    
    //create audio library objects for handling the audio
    #define DO_USB  1    //set to 1 to enable USB audio.  Be sure to go under the "Tools" menu and do "USB Type" -> "Audio"
    #if DO_USB
    //#define USE_SD //input test wav files can be stored on SD card instead of USB audio
    #define USE_PLAYMEM //input test wav files can be stored stored in PROGMEM
    #endif 
    #ifdef USE_SD
    AudioPlaySdWav            playSdWav1;
    #endif
    #ifdef USE_PLAYMEM
    #include "AudioSampleSweepamp.h"
    #include "AudioSampleStepTone.h"
    AudioPlayMemory    sound0;
    #endif
    AudioControlSGTL5000_Extended    sgtl5000;    //controller for the Teensy Audio Board
    AudioInputI2S             i2s_in;             //Digital audio *from* the Teensy Audio Board ADC.  Sends Int16.  Stereo.
    AudioConvert_I16toF32     int2Float1, int2Float2;    //Converts Int16 to Float.  See class in AudioStream_F32.h
    AudioEffectCompressor_F32 comp1, comp2;
    AudioConvert_F32toI16     float2Int1, float2Int2;    //Converts Float to Int16.  See class in AudioStream_F32.h
    AudioOutputI2S            i2s_out;            //Digital audio *to* the Teensy Audio Board DAC.  Expects Int16.  Stereo
    
    //Make all of the audio connections, with the option of USB audio in and out
    //note that you ALWAYS have to have an I2S connection (either in or out) to have a clock
    //that drives the system.
    
    #if DO_USB
    #if defined(USE_SD) || defined(USE_PLAYMEM)
    #ifdef USE_SD
          AudioConnection           patchCord1(playSdWav1, 0, int2Float1, 0);
          AudioConnection           patchCord2(playSdWav1, 1, int2Float2, 0);
    #endif
    #ifdef USE_PLAYMEM
          AudioConnection           patchCord1(sound0, 0, int2Float1, 0);
          AudioConnection           patchCord2(sound0, 0, int2Float2, 0);
    #endif
    #else
        AudioInputUSB           usb_in;
        AudioConnection         patchCord1(usb_in, 0, int2Float1, 0);
        AudioConnection         patchCord2(usb_in, 1, int2Float2, 0);
    #endif
    #else
      AudioConnection         patchCord1(i2s_in, 0, int2Float1, 0);   //connect the Left input to the Left Int->Float converter
      AudioConnection         patchCord2(i2s_in, 1, int2Float2, 0);   //connect the Right input to the Right Int->Float converter
    #endif
    AudioConnection_F32     patchCord10(int2Float1, 0, comp1, 0); //Left.  makes Float connections between objects
    AudioConnection_F32     patchCord11(int2Float2, 0, comp2, 0); //Right.  makes Float connections between objects
    AudioConnection_F32     patchCord12(comp1, 0, float2Int1, 0); //Left.  makes Float connections between objects
    AudioConnection_F32     patchCord13(comp2, 0, float2Int2, 0); //Right.  makes Float connections between objects
    AudioConnection         patchCord20(float2Int1, 0, i2s_out, 0);  //connect the Left float processor to the Left output
    AudioConnection         patchCord21(float2Int2, 0, i2s_out, 1);  //connect the Right float processor to the Right output
    #if DO_USB
      AudioOutputUSB          usb_out;
      AudioConnection         patchCord30(float2Int1, 0, usb_out, 0);  //connect the Left float processor to the Left output
      AudioConnection         patchCord31(float2Int2, 0, usb_out, 1);  //connect the Right float processor to the Right output
    #endif
    #if defined(USE_SD) || defined(USE_PLAYMEM)
    #ifdef USE_SD
    #define SDCARD_CS_PIN    10
    #define SDCARD_MOSI_PIN  7
    #define SDCARD_SCK_PIN   14
    #endif
    #else 
      // which input on the audio shield will be used?
      const int myInput = AUDIO_INPUT_LINEIN;
      //const int myInput = AUDIO_INPUT_MIC;
    #endif
    //I have a potentiometer on the Teensy Audio Board
    #define POT_PIN A0  //potentiometer is tied to this pin
    
    //define a function to setup the Teensy Audio Board how I like it
    void setupMyAudioBoard(void) {
      sgtl5000.enable();                   //start the audio board
      sgtl5000.volume(0.8);                //volume can be 0.0 to 1.0.  0.5 seems to be the usual default.
    #if defined(USE_SD) || defined(USE_PLAYMEM)
    #ifdef USE_SD
      SPI.setMOSI(SDCARD_MOSI_PIN);
      SPI.setSCK(SDCARD_SCK_PIN);
      if (!(SD.begin(SDCARD_CS_PIN))) {
        while (1) {
          Serial.println("Unable to access the SD card");
          delay(500);
        }
      }
      delay(1000);
    #endif
    #else
      sgtl5000.inputSelect(myInput);       //choose line-in or mic-in
    #endif
      sgtl5000.lineInLevel(10, 10);        //level can be 0 to 15.  5 is the Teensy Audio Library's default
      sgtl5000.adcHighPassFilterDisable(); //reduces noise.  https://forum.pjrc.com/threads/27215-24-bit-audio-boards?p=78831&viewfull=1#post78831
      sgtl5000.micBiasEnable(3.0);         //enable the mic bias voltage...only in AudioControlSGTL5000_Extended
    }
    
    //define a function to configure the left and right compressors
    void setupMyCompressors(boolean use_HP_filter, float knee_dBFS, float comp_ratio, float attack_sec, float release_sec) {
      comp1.enableHPFilter(use_HP_filter);   comp2.enableHPFilter(use_HP_filter);
      comp1.setThresh_dBFS(knee_dBFS);       comp2.setThresh_dBFS(knee_dBFS);
      comp1.setCompressionRatio(comp_ratio); comp2.setCompressionRatio(comp_ratio);
    
      float fs_Hz = AUDIO_SAMPLE_RATE;
      comp1.setAttack_sec(attack_sec, fs_Hz);       comp2.setAttack_sec(attack_sec, fs_Hz);
      comp1.setRelease_sec(release_sec, fs_Hz);     comp2.setRelease_sec(release_sec, fs_Hz);
    }
    
    // define the overall setup() function, the function that is called once when the device is booting
    void setup() {
      Serial.begin(115200);   //open the USB serial link to enable debugging messages
      delay(500);             //give the computer's USB serial system a moment to catch up.
      Serial.println("Teensy Hearing Aid: BasicCompressor_Float..."); //identify myself over the USB serial
    
      // Audio connections require memory, and the record queue uses this memory to buffer incoming audio.
      AudioMemory(14);  //allocate Int16 audio data blocks
      AudioMemory_F32(16); //allocate Float32 audio data blocks
    
      // Enable the audio shield, select input, and enable output
      setupMyAudioBoard();
    
      //choose the compressor parameters...note that preGain is set by the potentiometer in the main loop()
      boolean use_HP_filter = true; //enable the software HP filter to get rid of DC?
      float knee_dBFS, comp_ratio, attack_sec, release_sec;
      if (true) {
          Serial.println("Configuring Compressor for fast response for use as a limitter.");
          knee_dBFS = -15.0f; comp_ratio = 5.0f;  attack_sec = 0.005f;  release_sec = 0.200f;
      } else {
          Serial.println("Configuring Compressor for slow response more like an automatic volume control.");
          knee_dBFS = -50.0; comp_ratio = 5.0;  attack_sec = 1.0;  release_sec = 2.0;
      }
    
      //configure the left and right compressors with the desired settings
      setupMyCompressors(use_HP_filter, knee_dBFS, comp_ratio, attack_sec, release_sec);
    
      // setup any other other features
      pinMode(POT_PIN, INPUT); //set the potentiometer's input pin as an INPUT
    
    } //end setup()
    
    
    // define the loop() function, the function that is repeated over and over for the life of the device
    unsigned long updatePeriod_millis = 100; //how many milliseconds between updating gain reading?
    unsigned long lastUpdate_millis = 0;
    unsigned long curTime_millis = 0;
    int prev_gain_dB = 0;
    unsigned long lastMemUpdate_millis = 0;
    void loop() {
    #if defined(USE_SD) || defined(USE_PLAYMEM)
    #ifdef USE_SD
      if (playSdWav1.isPlaying() == false) 
      {
        Serial.println("Start playing");
        playSdWav1.play("sweepamp.wav");
    //    playSdWav1.play("steptone.wav");
        delay(10);
      }
    #endif
    #ifdef USE_PLAYMEM
      if (sound0.isPlaying() == false)
      {
        //sound0.play(AudioSampleSweepamp);
        sound0.play(AudioSampleSteptone);
      }
    #endif
    #else
      //choose to sleep ("wait for interrupt") instead of spinning our wheels doing nothing but consuming power
      asm(" WFI");  //ARM-specific.  Will wake on next interrupt.  The audio library issues tons of interrupts, so we wake up often.
    #endif
      //has enough time passed to try updating the GUI?
      curTime_millis = millis(); //what time is it right now
      if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
      if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
    
        //read potentiometer
        float32_t val = float(analogRead(POT_PIN)) / 1024.0f; //0.0 to 1.0
        val = 0.1 * (float)((int)(10.0 * val + 0.5)); //quantize so that it doesn't chatter
    
        //compute desired digital gain
        const float min_gain_dB = -20.0, max_gain_dB = 40.0; //set desired gain range
        float gain_dB = min_gain_dB + (max_gain_dB - min_gain_dB) * val; //computed desired gain value in dB
    
        //if the gain is different than before, set the new gain value
        if (abs(gain_dB - prev_gain_dB) > 1.0) { //is it different than before
          prev_gain_dB = gain_dB;  //we will use this value the next time around
          
          gain_dB = 0.0; //force to 0 dB for debugging
          //gain_dB = 20.0;
          
          comp1.setPreGain_dB(gain_dB);  //set the gain of the Left-channel gain processor
          comp2.setPreGain_dB(gain_dB);  //set the gain of the Right-channel gain processor
          Serial.print("Setting Digital Pre-Gain dB = "); Serial.println(gain_dB); //print text to Serial port for debugging
        }
    
        lastUpdate_millis = curTime_millis; //we will use this value the next time around.
      } // end if
    
    
      //print status information to the Serial port
      if ((curTime_millis - lastMemUpdate_millis) > 2000) {  // print a summary of the current & maximum usage
        //printCompressorState(&Serial);
        printCPUandMemoryUsage(&Serial);
        lastMemUpdate_millis = curTime_millis; //we will use this value the next time around.
      }
    
    } //end loop();
    
    void printCompressorState(Stream *s) {
      s->print("Current Compressor: Pre-Gain (dB) = ");
      s->print(comp1.getPreGain_dB());
      s->print(", Level (dBFS) = ");
      s->print(comp1.getCurrentLevel_dBFS());
      s->print(", ");
      s->print(comp2.getCurrentLevel_dBFS());
      s->print(", Dynamic Gain L/R (dB) = ");
      s->print(comp1.getCurrentGain_dB());
      s->print(", ");
      s->print(comp2.getCurrentGain_dB());
      s->println();
    };
    
    void printCPUandMemoryUsage(Stream *s) {
      s->print("Usage/Max: ");
      s->print("comp1 CPU = "); s->print(comp1.processorUsage()); s->print("/"); s->print(comp1.processorUsageMax()); s->print(", ");
      s->print("all CPU = " ); s->print(AudioProcessorUsage()); s->print("/");  s->print(AudioProcessorUsageMax()); s->print(", ");
      s->print("Int16 Mem = "); s->print(AudioMemoryUsage()); s->print("/"); s->print(AudioMemoryUsageMax()); s->print(", ");
      s->print("Float Mem = "); s->print(AudioMemoryUsage_F32()); s->print("/"); s->print(AudioMemoryUsageMax_F32()); s->print(", ");
      s->println();
    };
    Click image for larger version. 

Name:	sweep.jpg 
Views:	11 
Size:	117.6 KB 
ID:	26418 Click image for larger version. 

Name:	step.jpg 
Views:	11 
Size:	106.2 KB 
ID:	26419

  19. #69
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by chipaudette View Post
    If all you want is AGC, then the audio processing block that you're looking for is a "compressor". I've always been suprised that there isn't one in the Teensy Audio Library. I'd do one myself, but my integer math skills are lacking. Instead...

    You're running a Teensy 4, which means that you can do floating point math as fast as integer math. Therefore, you are not limited to the Teensy Audio Library (with it's focus on integer math). There are several folks with floating-point extensions of the Teensy Audio Library. You can run any one of them without penalty.

    One choice is the OpenAudio library (https://github.com/chipaudette/OpenAudio_ArduinoLibrary). I started it, but it is now maintained by others. Most important for you is that it has two different compressors that you can use. I'd start with the simpler one. One of the examples that comes with the library shows you how to use it: https://github.com/chipaudette/OpenA...mpressor_Float

    Here's a blog post explaining its approach...it's a totally standard dynamic range compressor: https://openaudio.blogspot.com/search/label/Compression

    Chip

    Attachment 26052
    Hello,

    I have now successfully integrated the Compressor code AND the AGC code on a single platform. There is a Teensy 4.0, an Audio Adapter, an OLED display controlled by an ATMega328(3.3V 8 MHz), and a pushbutton for selecting AGCmode(music) or CompressorMode(voice for TV, films for the old slightly deaf folks like me). I find that the compressor with 20dB of gain works VERY WELL for understanding dialogs in a film or fast-talking weather announcers who are pressed to spit out their forecast before the next commercial. I heard somewhere that you or someone was thinking about adding some filtering to the compressor to accent certain frequencies(especially highfreq's). I was wondering if there is any news on this subject. Included are the details(if you're interested) of my implementation. Thanx


    Click image for larger version. 

Name:	1_AudioSetup.jpg 
Views:	11 
Size:	27.9 KB 
ID:	26546 Click image for larger version. 

Name:	2_SetupCompressorAGC.jpg 
Views:	14 
Size:	93.7 KB 
ID:	26547 Click image for larger version. 

Name:	3_agcBox3.jpg 
Views:	11 
Size:	77.1 KB 
ID:	26548 Click image for larger version. 

Name:	4_agcBox2.jpg 
Views:	6 
Size:	123.3 KB 
ID:	26549 Click image for larger version. 

Name:	5_agcMode.jpg 
Views:	10 
Size:	131.4 KB 
ID:	26550 Click image for larger version. 

Name:	6_compressorMode.jpg 
Views:	8 
Size:	282.7 KB 
ID:	26551
    Code:
    /*
       BasicCompressor_Float
    
       Created: Chip Audette, Dec 2016 - Jan 2017
       Purpose: Process audio by applying a single-band compressor
                Demonstrates audio processing using floating point data type.
    
       Uses Teensy Audio Adapter.
       Assumes microphones (or whatever) are attached to the LINE IN (stereo)
    
       MIT License.  use at your own risk.
    */
    
    //Chip Audette blog is:    https://openaudio.blogspot.com/search/label/Compression
    //This code has been modified to have 2 functioning MODES 1. MODE_Compressor(for TV movies or programs) or 2. MODE_AGC(for music) 
    
    //These are the includes from the Teensy Audio Library
    #include <Audio.h>   //Teensy Audio Librarya
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    #include <Bounce.h>
    #include <OpenAudio_ArduinoLibrary.h> //for AudioConvert_I16toF32, AudioConvert_F32toI16, and AudioEffectGain_F32
    
    #define CompressorInput 0 //used for mixers
    #define AGCInput 1
    
    //create audio library objects for handling the audio
     
    //------------- compressor objects ---------------------
    AudioControlSGTL5000_Extended    sgtl5000;    //controller for the Teensy Audio Board
    AudioInputI2S             i2s_in;             //Digital audio *from* the Teensy Audio Board ADC.  Sends Int16.  Stereo.
    AudioConvert_I16toF32     int2Float1, int2Float2;    //Converts Int16 to Float.  See class in AudioStream_F32.h
    AudioEffectCompressor_F32 comp1, comp2;
    AudioConvert_F32toI16     float2Int1, float2Int2;    //Converts Float to Int16.  See class in AudioStream_F32.h
    AudioOutputI2S            i2s_out;            //Digital audio *to* the Teensy Audio Board DAC.  Expects Int16.  Stereo
    
    //-------------- agc objects ------------
    AudioRecordQueue         queue_inLeft; 
    AudioRecordQueue         queue_inRight;
    AudioPlayQueue           queue_outLeft; 
    AudioPlayQueue           queue_outRight;
    AudioMixer4              mixerLeft; 
    AudioMixer4              mixerRight; 
    
    //---------------- compressor connections --------------------------------------------------
    AudioConnection         patchCord1(i2s_in, 0, int2Float1, 0);   //connect the Left input to the Left Int->Float converter
    AudioConnection         patchCord2(i2s_in, 1, int2Float2, 0);   //connect the Right input to the Right Int->Float converter
    AudioConnection_F32     patchCord10(int2Float1, 0, comp1, 0); //Left.  makes Float connections between objects
    AudioConnection_F32     patchCord11(int2Float2, 0, comp2, 0); //Right.  makes Float connections between objects
    AudioConnection_F32     patchCord12(comp1, 0, float2Int1, 0); //Left.  makes Float connections between objects
    AudioConnection_F32     patchCord13(comp2, 0, float2Int2, 0); //Right.  makes Float connections between objects
    AudioConnection         patchCord20(float2Int1, 0, mixerLeft, CompressorInput);
    AudioConnection         patchCord21(float2Int2, 0, mixerRight, CompressorInput);
    AudioConnection         patchCord30(mixerLeft, 0, i2s_out, 0);
    AudioConnection         patchCord31(mixerRight, 0, i2s_out, 1);
    
    //-------------- agc connections ------------------
    AudioConnection         patchCord1a(i2s_in, 0, queue_inLeft, 0);
    AudioConnection         patchCord2a(i2s_in, 1, queue_inRight, 0);
    AudioConnection         patchCord10a(queue_outLeft, 0, mixerLeft, AGCInput);
    AudioConnection         patchCord11a(queue_outRight, 0, mixerRight, AGCInput);
    
    Bounce button = Bounce(14, 15); //pin14 15msec debounce time
    
    // which input on the audio shield will be used?
    const int myInput = AUDIO_INPUT_LINEIN;
    //const int myInput = AUDIO_INPUT_MIC;
    
    //Global AGC Variables 
    int32_t agcGain1=0x08000000;//.5 in Q3.29 format
    int32_t agcGain2=0x08000000;//.5 in Q3.29 format
    int32_t ref=146752;//.035*AUDIO_BLOCK_SAMPLES in Q15 format(1146*128)
    int SelectCompressor=0; //starting mode is AGC
    
    //define a function to setup the Teensy Audio Board how I like it
    void setupMyAudioBoard(void) {
      sgtl5000.enable();                   //start the audio board
      sgtl5000.volume(0.8);                //volume can be 0.0 to 1.0.  0.5 seems to be the usual default.
      sgtl5000.inputSelect(myInput);       //choose line-in or mic-in
    //  sgtl5000.lineInLevel(10, 10);        //level can be 0 to 15.  5 is the Teensy Audio Library's default
      sgtl5000.adcHighPassFilterDisable(); //reduces noise.  https://forum.pjrc.com/threads/27215-24-bit-audio-boards?p=78831&viewfull=1#post78831
      sgtl5000.micBiasEnable(3.0);         //enable the mic bias voltage...only in AudioControlSGTL5000_Extended
    }
    
    //define a function to configure the left and right compressors
    void setupMyCompressors(boolean use_HP_filter, float knee_dBFS, float comp_ratio, float attack_sec, float release_sec) {
      comp1.enableHPFilter(use_HP_filter);   comp2.enableHPFilter(use_HP_filter);
      comp1.setThresh_dBFS(knee_dBFS);       comp2.setThresh_dBFS(knee_dBFS);
      comp1.setCompressionRatio(comp_ratio); comp2.setCompressionRatio(comp_ratio);
    
      float fs_Hz = AUDIO_SAMPLE_RATE;
      comp1.setAttack_sec(attack_sec, fs_Hz);       comp2.setAttack_sec(attack_sec, fs_Hz);
      comp1.setRelease_sec(release_sec, fs_Hz);     comp2.setRelease_sec(release_sec, fs_Hz);
    }
    
    //Select processed Compressor or AGC audio for the output
    void updateMode(int SelCompressor) //select Compressor or AGC outputs
    {
      if (SelCompressor)
      {
        mixerLeft.gain(CompressorInput,1.0); //connect Compressor to output
        mixerRight.gain(CompressorInput,1.0);
        mixerLeft.gain(AGCInput,0.0);
        mixerRight.gain(AGCInput,0.0);
      }
      else
      {
        mixerLeft.gain(CompressorInput,0.0); //connect AGC to output
        mixerRight.gain(CompressorInput,0.0);
        mixerLeft.gain(AGCInput,1.0);
        mixerRight.gain(AGCInput,1.0);   
      }
    }
    
    // define the overall setup() function, the function that is called once when the device is booting
    void setup() {
      Serial.begin(115200);   //open the USB serial link to enable debugging messages
      Serial4.begin(9600); //serial interface to UNO OLED controller
      delay(500);             //give the computer's USB serial system a moment to catch up.
      Serial.println("Teensy Hearing Aid: BasicCompressor_Float..."); //identify myself over the USB serial
    
      // Audio connections require memory, and the record queue uses this memory to buffer incoming audio.
      AudioMemory(32);  //allocate Int16 audio data blocks
      AudioMemory_F32(16); //allocate Float32 audio data blocks
      
      // Enable the audio shield, select input, and enable output
      setupMyAudioBoard();
    
      //choose the compressor parameters...note that preGain is set by the potentiometer in the main loop()
      boolean use_HP_filter = true; //enable the software HP filter to get rid of DC?
      float knee_dBFS, comp_ratio, attack_sec, release_sec;
      if (true) {
          Serial.println("Configuring Compressor for fast response for use as a limitter.");
          knee_dBFS = -15.0f; comp_ratio = 5.0f;  attack_sec = 0.005f;  release_sec = 0.200f;
      } else {
          Serial.println("Configuring Compressor for slow response more like an automatic volume control.");
          knee_dBFS = -50.0; comp_ratio = 5.0;  attack_sec = 1.0;  release_sec = 2.0;
      }
    
      //configure the left and right compressors with the desired settings
      setupMyCompressors(use_HP_filter, knee_dBFS, comp_ratio, attack_sec, release_sec);
    
      //configure compressor pregains
      float gain_dB = 20.0;
      comp1.setPreGain_dB(gain_dB);  //set the gain of the Left-channel gain processor
      comp2.setPreGain_dB(gain_dB);  //set the gain of the Right-channel gain processor
      
      pinMode(14, INPUT_PULLUP); //pushbutton to change AGC/Compressor modes
      updateMode(SelectCompressor); //Initially configure to AGC mode (SelectCompressor=0)
    
      delay(100);
      
      // Start the record queue
      queue_inLeft.begin();
      queue_inRight.begin();
      
    } //end setup()
    
    // define the loop() function, the function that is repeated over and over for the life of the device
    unsigned long curTime_millis = 0;
    unsigned long lastMemUpdate_millis = 0;
    unsigned long last_time1 = millis();
    void loop() {
      
    //Left channel AGC 
      if (queue_inLeft.available() >= 1) //Left Channel
      {
        agcCalc(queue_inLeft.readBuffer(),queue_outLeft.getBuffer(),&agcGain1);
        // Free the input audio buffer
        queue_inLeft.freeBuffer();
        // and play it back into the audio queue
        queue_outLeft.playBuffer();
      } //if (queue_inLeft.available() >= 1)
      
    //Right channel AGC 
      if (queue_inRight.available() >= 1) 
      {
        agcCalc(queue_inRight.readBuffer(),queue_outRight.getBuffer(),&agcGain2);
        // Free the input audio buffer
        queue_inRight.freeBuffer();
        // and play it back into the audio queue
        queue_outRight.playBuffer();
      } //if (queue_inRight.available() >= 1)
    
      //print status information to the Serial port
      if ((curTime_millis - lastMemUpdate_millis) > 2000) {  // print a summary of the current & maximum usage
        //printCompressorState(&Serial);
        printCPUandMemoryUsage(&Serial);
        lastMemUpdate_millis = curTime_millis; //we will use this value the next time around.
      }
      
      if (millis() - last_time1 >= 2500) //Send info to OLED display via Arduino UNO
      {
        if (SelectCompressor)
        {
          Serial4.print("CompressorActive");
        }
        else
        {
          Serial4.print("agcGains: ");
          Serial4.print((float)agcGain1/(float)0x10000000);
          Serial4.print(",");
          Serial4.println((float)agcGain2/(float)0x10000000);
        }
        last_time1=millis();
      }
      
      button.update(); //push button to change mode AGC/Compressor
      if (button.fallingEdge()) 
      {
        if (SelectCompressor==0)
          SelectCompressor=1;
        else
          SelectCompressor=0;
        updateMode(SelectCompressor);
      }
    } //end loop();
    
    void printCompressorState(Stream *s) {
      s->print("Current Compressor: Pre-Gain (dB) = ");
      s->print(comp1.getPreGain_dB());
      s->print(", Level (dBFS) = ");
      s->print(comp1.getCurrentLevel_dBFS());
      s->print(", ");
      s->print(comp2.getCurrentLevel_dBFS());
      s->print(", Dynamic Gain L/R (dB) = ");
      s->print(comp1.getCurrentGain_dB());
      s->print(", ");
      s->print(comp2.getCurrentGain_dB());
      s->println();
    };
    
    void printCPUandMemoryUsage(Stream *s) {
      s->print("Usage/Max: ");
      s->print("comp1 CPU = "); s->print(comp1.processorUsage()); s->print("/"); s->print(comp1.processorUsageMax()); s->print(", ");
      s->print("all CPU = " ); s->print(AudioProcessorUsage()); s->print("/");  s->print(AudioProcessorUsageMax()); s->print(", ");
      s->print("Int16 Mem = "); s->print(AudioMemoryUsage()); s->print("/"); s->print(AudioMemoryUsageMax()); s->print(", ");
      s->print("Float Mem = "); s->print(AudioMemoryUsage_F32()); s->print("/"); s->print(AudioMemoryUsageMax_F32()); s->print(", ");
      s->println();
    };
    
    void agcCalc(int16_t *inbuf,int16_t *outbuf,int32_t *agcGain)
    {
        int16_t *bp1=inbuf,*bp2=outbuf;
        // Apply agcGain on input with saturation to output buffer
        int32_t sum=0,sum1=0,sum2=0,sum3=0,res,res1,res2,res3,err;
        for(int i = 0;i < AUDIO_BLOCK_SAMPLES>>2;i++) 
        { 
          //ref. dspinst.h in Teensy audio library
          res=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res1=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res2=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          res3=signed_multiply_32x16b(*agcGain>>12,*bp1++);
          *bp2++=saturate16(res);
          *bp2++=saturate16(res1);
          *bp2++=saturate16(res2);
          *bp2++=saturate16(res3);
          sum+=abs(res);
          sum1+=abs(res1);
          sum2+=abs(res2);
          sum3+=abs(res3);
        }
        sum+=(sum1+sum2+sum3);
        // Calculate new agcGain for next input buffer
        err=ref-sum; 
        *agcGain+=err>>3; //update gain 
        *agcGain=min(*agcGain,0x40000000); //keep agcGain < 4.0
        *agcGain=max(*agcGain,0); //keep agcGain >= 0
    }
    Code:
     #include <Wire.h>
    #include <SPI.h>
    #include <SoftwareSerial.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    #define OLED_RESET 4
    //SDA=A4
    //SCL=A5
    Adafruit_SSD1306 display(OLED_RESET);
    SoftwareSerial Seriala(6, 7);
    unsigned long last_time = millis();
    char readString[]="agcGains: 0.5,0.5";
    void setup() {
      Serial.begin(115200);
      Seriala.begin(9600);
      Wire.begin();
      display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
      display.clearDisplay();
      display.setRotation(2); //add this!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      display.setTextColor(WHITE);
      display.setTextSize(1);
      display.setCursor(0,0);
      displayOLED();
      display.display();  
      delay(2000);
      while (Seriala.available()>0) Seriala.read(); //start with an MT softwareserial buffer
    }
    
    
    void loop()
    {
      char c;
      int i;
      unsigned long WaitTime,StartTime;
      if (Seriala.available()>0)
      {
        i=0;
        WaitTime=1000UL;
        StartTime=millis();
        while((millis()-StartTime)<=WaitTime);
        {
        }
        while (Seriala.available()>0)
        {
          c=Seriala.read();
          readString[i]=c;
          i++;
        }
        readString[i]='\0'; //terminate string
      }
      if (millis() - last_time >= 2500)
      {
        displayOLED();
        display.display();
        last_time = millis();  
      }
    }
    
    
    void displayOLED(){
      display.clearDisplay();
      display.setCursor(0,0);
      display.print(readString);
    }

  20. #70
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    Glad to see its all integrated and working for you!

  21. #71
    Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    78
    Quote Originally Posted by MarkT View Post
    Glad to see its all integrated and working for you!
    Thanx. Yeah, I've learned a lot from y'all! It's a good forum!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •