Using analog input for audio library filter

Status
Not open for further replies.
I'm just getting started with the audio shield and audio library, it's great fun.

I'm working on a little test sampler which records a single channel of audio. That part works great.

I'd also like to use a potentiometer to control a filter on the playback. It's just about working in the sense that I can hear the filter, however there is also loads of crackly noise. I'm nearly certain that this is a result of the filter frequency value being updated hundreds of times a second from the analogRead() reading from the potentiometer.

What's the best way to avoid this? I'd like to have the potentiometer readings update as fast as possible, to get nice smooth control of the filter.

Thanks

(code below, though it's a total mess!)
Code:
// Record sound as raw data to a SD card, and play it back.
//
// Requires the audio shield:
//   http://www.pjrc.com/store/teensy3_audio.html
//
// Three pushbuttons need to be connected:
//   Record Button: pin 0 to GND
//   Stop Button:   pin 1 to GND
//   Play Button:   pin 2 to GND
//
// This example code is in the public domain.

#include <Bounce.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>

// GUItool: begin automatically generated code
AudioPlaySdRaw           playRaw1;       //xy=190,295
AudioInputI2S            i2s1;           //xy=226,112
AudioFilterBiquad        biquad1;        //xy=399,354
AudioRecordQueue         queue1;         //xy=431,191
AudioOutputI2S           i2s2;           //xy=768,436
AudioConnection          patchCord1(playRaw1, biquad1);
AudioConnection          patchCord2(i2s1, 0, queue1, 0);
AudioConnection          patchCord3(i2s1, 1, queue1, 0);
AudioConnection          patchCord4(biquad1, 0, i2s2, 0);
AudioConnection          patchCord5(biquad1, 0, i2s2, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=281,607
// GUItool: end automatically generated code


const int numReadings = 400;

int readings[numReadings];      // the readings from the analog input
int avgindex = 0;                  // the avgindex of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

int inputPin = 20;


// Bounce objects to easily and reliably read the buttons
Bounce buttonRecord = Bounce(0, 8);
Bounce buttonStop =   Bounce(1, 8);  // 8 = 8 ms debounce time
Bounce buttonPlay =   Bounce(2, 8);
float filtercontrol;
float filterfreqcal = 15000;

// which input on the audio shield will be used?
const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;

// Remember which mode we're doing
int mode = 0;  // 0=stopped, 1=recording, 2=playing

// The file where data is recorded
File frec;

void setup() {
  // Configure the pushbutton pins
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  
  biquad1.setLowpass(0, 15000, 0.707);

  // Audio connections require memory, and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(60);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);

  // Initialize the SD card
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    // stop here if no SD card, but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
for (int thisReading = 0; thisReading < numReadings; thisReading++)
    readings[thisReading] = 0;    
}


void loop() {
  // First, read the buttons
  

  buttonRecord.update();
  buttonStop.update();
  buttonPlay.update();


setFilter();

  
  


  // Respond to button presses
  if (buttonRecord.fallingEdge()) {
    Serial.println("Record Button Press");
    if (mode == 2) stopPlaying();
    if (mode == 0) startRecording();
  }
  if (buttonStop.fallingEdge()) {
    Serial.println("Stop Button Press");
    if (mode == 1) stopRecording();
    if (mode == 2) stopPlaying();
  }
  if (buttonPlay.fallingEdge()) {
    Serial.println("Play Button Press");
    if (mode == 1) stopRecording();
    if (mode == 0) startPlaying();
    if (mode == 2) startPlaying();
  }


  // If we're playing or recording, carry on...
  if (mode == 1) {
    continueRecording();
  }
  if (mode == 2) {
    continuePlaying();
  }

  // when using a microphone, continuously adjust gain
  if (myInput == AUDIO_INPUT_MIC) adjustMicLevel();
}


void startRecording() {
  Serial.println("startRecording");
  if (SD.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD.RAW");
  }
  frec = SD.open("RECORD.RAW", FILE_WRITE);
  if (frec) {
    queue1.begin();
    mode = 1;
  }
}

void continueRecording() {
  if (queue1.available() >= 2) {
    byte buffer[512];
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    memcpy(buffer+256, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    // write all 512 bytes to the SD card
    elapsedMicros usec = 0;
    frec.write(buffer, 512);
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);
  }
}

void stopRecording() {
  Serial.println("stopRecording");
  queue1.end();
  if (mode == 1) {
    while (queue1.available() > 0) {
      frec.write((byte*)queue1.readBuffer(), 256);
      queue1.freeBuffer();
    }
    frec.close();
  }
  mode = 0;
}


void startPlaying() {
  Serial.println("startPlaying");
  playRaw1.play("RECORD.RAW");
  mode = 2;
}

void continuePlaying() {
  if (!playRaw1.isPlaying()) {
  playRaw1.play("RECORD.RAW");
  //  mode = 0;
  }
}

void stopPlaying() {
  Serial.println("stopPlaying");
  if (mode == 2) playRaw1.stop();
  mode = 0;
}

void setFilter() {
    // subtract the last reading:
  total= total - readings[avgindex];         
  // read from the sensor:  
  readings[avgindex] = analogRead(inputPin); 
  // add the reading to the total:
  total= total + readings[avgindex];       
  // advance to the next position in the array:  
  avgindex = avgindex + 1;                    

  // if we're at the end of the array...
  if (avgindex >= numReadings)              
    // ...wrap around to the beginning: 
    avgindex = 0;                           

  // calculate the average:
  average = total / numReadings;         
  // send it to the computer as ASCII digits
 
  delay(1);        // delay in between reads for stability       
  
   
  filtercontrol = map(average,0,1023,100,15000);
    biquad1.setLowpass(0, filtercontrol, 0.707);
     Serial.println(average);   
  
}

void adjustMicLevel() {
  // TODO: read the peak1 object and adjust sgtl5000_1.micGain()
  // if anyone gets this working, please submit a github pull request :-)
}
 
ah ha, I think I've got the solution - using the filter (rather than the biquad), and using a signal input to control it...

if that definitely works i'll post the fixed code in case anyone else makes the same mistake.

(any other tips are still welcome, of course!)
 
I expect you can significantly reduce the noise by not updating the filter parameters quite as often (and needlessly) as you are.

Take a look at https://github.com/PaulStoffregen/A...adToneControlDAP/CalcBiquadToneControlDAP.ino It is using two methods to avoid hammering the filter parameters.

The first thing is it avoids updating the filter faster than once every 10ms, at least if there is an actual difference every 10ms for a couple hundred of milliseconds any noise the adjustments introduce will be like a 'tap... tap... tap...' rather than 'flickflickflickflickflickflickflickflick'

The second thing it does is to map the value derived directly from the pot into a value which effectively cuts the pot reading from having 1024 unique possible values to only 140 unique possible values and then it checks that this value is actually different from the last value used before using it. This is where noise introduction is definitely reduced IMHO.

Last time I tried that CalcBiquadToneControlDAP.ino it seemed pretty good, if there had been more noise when I was turning the pot I probably would have put a 10uF Electro from A to B and a 10nF non-pol from A to W (assuming A was the GND end) on the pot before touching the code.

HTH
 
Thanks a bunch robsoles, some really great feedback and suggestions there. I've had some good results with another method as well, so I'll do some comparisons and report back on what work best!
 
Whenever you hear "crackly noise", always suspect clipping. Other things can cause distortion too, but clipping is the most common problem.

Filters can have gain > 1.0. If yours does, be sure to attenuate your signal before the filter.
 
Thanks for all the help, I'm pretty happy with this solution I've worked out. It uses a sine wave to set the filter frequency. Sounds nice and smooth now. Posting here for anyone who happens to find this thread.

I've now run into a separate problem, so it's time for a new thread!

Code:
#include <Bounce.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>

// GUItool: begin automatically generated code
AudioPlaySdRaw           playRaw2;       //xy=189,349
AudioPlaySdRaw           playRaw1;       //xy=190,295
AudioInputI2S            i2s1;           //xy=226,112
AudioSynthWaveformSine   sine1;          //xy=261,453
AudioMixer4              mixer1;         //xy=395,309
AudioRecordQueue         queue2;         //xy=450,219
AudioRecordQueue         queue1;         //xy=453,89
AudioFilterStateVariable filter1;        //xy=461,421
AudioOutputI2S           i2s2;           //xy=609,428
AudioConnection          patchCord1(playRaw2, 0, mixer1, 1);
AudioConnection          patchCord2(playRaw1, 0, mixer1, 0);
AudioConnection          patchCord3(i2s1, 0, queue2, 0);
AudioConnection          patchCord4(i2s1, 0, queue1, 0);
AudioConnection          patchCord5(sine1, 0, filter1, 1);
AudioConnection          patchCord6(mixer1, 0, filter1, 0);
AudioConnection          patchCord7(filter1, 0, i2s2, 0);
AudioConnection          patchCord8(filter1, 0, i2s2, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=281,607
// GUItool: end automatically generated code



const int numReadings = 20;

int readings[numReadings];      // the readings from the analog input
int avgindex = 0;                  // the avgindex of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

int inputPin = A10;


// Bounce objects to easily and reliably read the buttons
Bounce buttonRecord = Bounce(0, 8);
Bounce buttonPlay =   Bounce(1, 8);  // 8 = 8 ms debounce time

Bounce buttonRecord2 = Bounce(2, 8);
Bounce buttonPlay2 =   Bounce(3, 8);

float filtercontrol;
float filterfreqcal = 15000;

// which input on the audio shield will be used?


// Remember which mode we're doing
int mode = 0; 
int mode2 = 0;
// 0=stopped, 1=recording, 2=playing
int loopMode = 0;
// The file where data is recorded
File frec;
File frec2;

void setup() {
  // Configure the pushbutton pins
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  pinMode(20, OUTPUT);
  pinMode(21, OUTPUT);
 
  mixer1.gain(0,1);
  mixer1.gain(1,1);
  filter1.frequency(15000);
  filter1.resonance(1.5);
sine1.frequency(5);
  // Audio connections require memory, and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(120);
const int myInput = AUDIO_INPUT_LINEIN;
  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);

  // Initialize the SD card
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    // stop here if no SD card, but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
for (int thisReading = 0; thisReading < numReadings; thisReading++)
    readings[thisReading] = 0;    
}


void loop() {
  // First, read the buttons

if (digitalRead(5) == 1) {
 loopMode = 1; 
     Serial.println("Loop On");
} else {
 loopMode = 0; 
  //  Serial.println("Loop Off");
}

  buttonRecord.update();
  buttonPlay.update();
  
  buttonRecord2.update();
  buttonPlay2.update();


setFilter();



  if (mode == 1) {
 digitalWrite(16, HIGH); 
} else {
  digitalWrite(16, LOW); 
}
if (mode == 2) {
 digitalWrite(17, HIGH); 
} else {
  digitalWrite(17, LOW); 
}

  if (mode2 == 1) {
 digitalWrite(20, HIGH); 
} else {
  digitalWrite(20, LOW); 
}
if (mode2 == 2) {
 digitalWrite(21, HIGH); 
} else {
  digitalWrite(21, LOW); 
}
  


  // Respond to button presses
  if (buttonRecord.fallingEdge()) {
    Serial.println("Record Button Press");
    if (mode == 2) stopPlaying();
    if (mode == 0) startRecording();
   else if (mode == 1) stopRecording();
  }
  


if (loopMode == 1) {

  if (buttonPlay.fallingEdge()) {
    Serial.println("Play Button Press");
    if (mode == 1) stopRecording();
    if (mode == 0) startPlaying();
    if (mode == 2) startPlaying();
  }
}

if (loopMode == 0) {
    if (buttonPlay.fallingEdge()) {
    Serial.println("Play Button Press OneShot");
   if (mode == 0) startPlaying();
    if (mode == 1) {
      stopRecording();
      startPlaying();
    }
  }
    if (buttonPlay.risingEdge() && mode != 1) {
     stopPlaying(); 
    }

}

  // If we're playing or recording, carry on...
  if (mode == 1) {
    continueRecording();
  }
  if (mode == 2) {
    continuePlaying();
  }


// second channel

  // Respond to button presses
  if (buttonRecord2.fallingEdge()) {
    Serial.println("Record Button Press 2");
    if (mode2 == 2) stopPlaying2();
    if (mode2 == 0) startRecording2();
   else if (mode2 == 1) stopRecording2();
  }

if (loopMode == 1) {

  if (buttonPlay2.fallingEdge()) {
    Serial.println("Play Button Press 2");
    if (mode2 == 1) stopRecording2();
    if (mode2 == 0) startPlaying2();
    if (mode2 == 2) startPlaying2();
  }
}

if (loopMode == 0) {
    if (buttonPlay2.fallingEdge()) {
    Serial.println("Play Button Press OneShot 2");
   if (mode2 == 0) startPlaying2();
    if (mode2 == 1) {
      stopRecording2();
      startPlaying2();
    }
  }
    if (buttonPlay2.risingEdge() && mode2 != 1) {
     stopPlaying2(); 
    }

}

  // If we're playing or recording, carry on...
  if (mode2 == 1) {
    continueRecording2();
  }
  if (mode2 == 2) {
    continuePlaying2();
  }


}


void startRecording() {
  Serial.println("startRecording");
  if (SD.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD.RAW");
  }
  frec = SD.open("RECORD.RAW", FILE_WRITE);
  if (frec) {
    queue1.begin();
    mode = 1;
  }
}

void continueRecording() {
  if (queue1.available() >= 2) {
    byte buffer[512];
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    memcpy(buffer+256, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    // write all 512 bytes to the SD card
    elapsedMicros usec = 0;
    frec.write(buffer, 512);
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    Serial.print("SD write, us=");
    Serial.println(usec);
  }
}

void stopRecording() {
  Serial.println("stopRecording");
  queue1.end();
  if (mode == 1) {
    while (queue1.available() > 0) {
      frec.write((byte*)queue1.readBuffer(), 256);
      queue1.freeBuffer();
    }
    frec.close();
  }
  mode = 0;
}



void startPlaying() {
  Serial.println("startPlaying");
  playRaw1.play("RECORD.RAW");
  mode = 2;
}

void continuePlaying() {
  if (!playRaw1.isPlaying()) {
  playRaw1.play("RECORD.RAW");
  //  mode = 0;
  }
}

void stopPlaying() {

  if (mode != 0) {
      Serial.println("stopPlaying");
    playRaw1.stop();
  mode = 0;
  }
}

// second channel
void startRecording2() {
  Serial.println("startRecording2");
  if (SD.exists("RECORD2.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD2.RAW");
    
  }
  frec2 = SD.open("RECORD2.RAW", FILE_WRITE);
  if (frec2) {
    queue2.begin();
    mode2 = 1; 
    Serial.println("made it this far");
  }
}

void continueRecording2() {
  if (queue2.available() >= 2) {
    byte buffer2[512];
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer2, queue2.readBuffer(), 256);
    queue2.freeBuffer();
    memcpy(buffer2+256, queue2.readBuffer(), 256);
    queue2.freeBuffer();
    // write all 512 bytes to the SD card
    elapsedMicros usec = 0;
    frec2.write(buffer2, 512);
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);
  }
}

void stopRecording2() {
  Serial.println("stopRecording2");
  queue2.end();
  if (mode2 == 1) {
    while (queue2.available() > 0) {
      frec2.write((byte*)queue2.readBuffer(), 256);
      queue2.freeBuffer();
      
    }
    frec2.close();
  }
  mode2 = 0;
}


void startPlaying2() {
  Serial.println("startPlaying2");
  playRaw2.play("RECORD2.RAW");
  mode2 = 2;
}

void continuePlaying2() {
  if (!playRaw2.isPlaying()) {
  playRaw2.play("RECORD2.RAW");
  //  mode = 0;
  }
}

void stopPlaying2() {
  Serial.println("stopPlaying2");
  if (mode2 == 2) playRaw2.stop();
  mode2 = 0;
}


void setFilter() {
    // subtract the last reading:
  total= total - readings[avgindex];         
  // read from the sensor:  
  readings[avgindex] = analogRead(inputPin); 
  // add the reading to the total:
  total= total + readings[avgindex];       
  // advance to the next position in the array:  
  avgindex = avgindex + 1;                    

  // if we're at the end of the array...
  if (avgindex >= numReadings)              
    // ...wrap around to the beginning: 
    avgindex = 0;                           

  // calculate the average:
  average = total / numReadings;         
  // send it to the computer as ASCII digits
 
  delay(1);        // delay in between reads for stability       
  
   
  filtercontrol = map(average,0,1023,100,12000);
    filter1.frequency(filtercontrol);
    // Serial.println(filtercontrol);   
  
}
 
Status
Not open for further replies.
Back
Top