Arduino Realtime Audio Processing

For the Linux Audio Conference 2008 we prepared some examples to do realtime audio processing with arduino. The documentaion is now online:

http://interface.khm.de/index.php/labor/experimente/arduino-realtime-audio-processing/

We also put the link on the arduino playground.
Fell free to use it.

Excellent, that should answer a lot of beginners questions we get on this forum. Thanks :slight_smile:

For the Linux Audio Conference 2008 we prepared some examples to do realtime audio processing with arduino.

And nobody was there to make a recording?
What about some samples?
I always liked that LoFi (3kHz) approach :slight_smile:

Eberhard

Hi David,
thanks for the response,
the sampling rate is determind by the timer2 interrupt and set to 31,25KHz. Since we measure two inputs, the audiosignal and the pot position alternately, the effective sampling rate is 15,625 KHz.
With a prescaler of 64 and a 16000KHz Clock and 13 Cycles for one sample we get a samplerate of 16000 / 64 / 13 = 19,2 KHz
So the ADC is just ready before the sample is processsed .
Maybe i made error in reasoning but i think it should be correct so far.

martin

ps.

i put this answer also to the Arduino Development Forum
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?board=dev

Am 12.02.2009 um 04:43 schrieb David H. T. Harrison (home):

Sir,

I really enjoyed your web page on real time audio processing, it helped me greatly in thinking about analog input on the arduino. I noticed in your setup() code the following:
// set adc prescaler to 64 for 19kHz sampling frequency
cbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);

I looked at the ATMega168 manual, and it shows table 21-4
<moz-screenshot-1.jpg>

I believe that your code is giving you 8 uSec of ADC time (adc prescaler of 16) instead of 32 uSec (adc prescaler of 64). This may add noise to your signal?

Sincerely,

David

Hi David,
you where right. After having a look to it i figured out this configuration :

// set adc prescaler to 32 for 38 kHz sampling frequency
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);

regards
martin

Am 12.02.2009 um 16:13 schrieb David H. T. Harrison (home):

Martin,

After I sent my E-mail I figured out where the 19.2 KHz came from. However, the main point of my E-mail was that 011 (ADPS2,ADPS1,ADPS0) corresponds to an adc prescaler of 8 NOT 64 (110).

David

P.S. - How hard can you push the sampling rate in 8 bit mode?

Martin,

I wanted to write some code that would measure pulse widths and this is what I came up with:

void setup(void) {
  
  Serial.begin(9600);
  
// set the adc prescaler to 16 this gives an adc clock of 1 Mhz. 
// (I believe this is the limit of the adc)
// To convert the analog to ADHC takes 13 cycles, so I should have a 
// resolution of about 13 uS
  sbi(ADCSRA, ADPS2);
  cbi(ADCSRA, ADPS1);
  cbi(ADCSRA, ADPS0);
}

Make an array of values reading as quickly as I possibly can. This lets me evaluate what the arduino is seeing. I am using the pwm function to generate a standard pulse train, thus I have connected pin 11 (pwmPin) to analogPin 2 (readPin).

// Start pulse train
  analogWrite(pwmPin,6);
  start = hpticks();
  delayMicroseconds(100);
  endtime = hpticks() - start;
  delay(333);
  Serial.print("100 microseconds is ");
  Serial.print(endtime);
  Serial.println(" hpticks");
  start = hpticks();
// Clear the adch by reading it
  myAnalogRead(readPin);
  for(int i=0;i<SHOTS;i++) snapshot[i] = myAnalogRead(readPin);
  endtime = hpticks() - start;

Now I determine how long a pulse train is on and off by systematically changing the pwmPin and measuring how long the pulse is on or off.

  for(int i=1;i<255;i++) {
    analogWrite(pwmPin,i);
    Serial.print(i);
    Serial.print(" of 255 ");
    int single;
    
//  Watch the pulse train turn off
    do {
      single = myAnalogRead(readPin);
    } while (single < 128);
    do {
      single = myAnalogRead(readPin);
    } while (single > 128);
// Start the measurement
    start = hpticks();
    do {
      single = myAnalogRead(readPin);
    } while (single < 128);
    endtime = hpticks();
    Serial.print("Off for  ");
    Serial.print((endtime - start)*4);
    Serial.print(" microseconds ");
    
// Now watch the pulse train turn ON
    do {
      single = myAnalogRead(readPin);
    } while (single > 128);
    do {
       single = myAnalogRead(readPin);
    } while (single < 128);
    start = hpticks();
    do {
      single = myAnalogRead(readPin);
    } while (single > 128);
    endtime = hpticks();
    Serial.print("On for  ");
    Serial.print((endtime - start)*4);
    Serial.println(" microseconds");
  }

Interestingly this code shows me that the pwm frequency is about 490Hz [1/(t(ON) + t(OFF))] . I was expecting about twice that.
Now to read the analog in one byte mode

byte myAnalogRead(byte pin) {
  
  ADMUX = (1 << 6) | (pin & 0x0f);
  sbi(ADMUX,ADLAR);  // 8-Bit ADC in ADCH Register

      // start the conversion
  sbi(ADCSRA, ADSC);

      // ADSC is cleared when the conversion finishes
  while (bit_is_set(ADCSRA, ADSC));

  return(ADCH);
}

I should note that I have used these code fragments to watch various home-built LED-flashers with some success :).
I've noticed that your library code is interrupt driven using timer2. Is there a better way to approach this problem?

Thanks

Nice project, indeed, and very well documented. Will be really useful for some audio projects. Thx

Heh. My scope says it's about 1000Hz on pin5 and pin6, and about 500Hz on the other PWM outputs.

That's ... interesting. I wonder if it's intentional, or whether it's a bug?
The pin5/6 PWM outputs share the timer with the core millis() timer, so they pretty much have to ~1000Hz.
The comments say that the prescaler factors are all being set to the 64, but they set different bits...

Hello,
in some examples i used some pins to monitor the timing with a scope. Maybe thats the reason why you see a signal there. The Audio PWM output has an freq. of 64 KHz anyway.

here is the correct prescaler for the adc again
// set adc prescaler to 32 for 38 kHz sampling frequency
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);

martin

Hi guys,

I just finished rewriting an interrupt based pulse width analyzer http://www.byrote.org/arduino/. It has 16 uSec resolution and will average 100 cycles. If you think it might be appropriate for the "playground" near the frequency monitor feel free to set up a link (I have yet to figure out the playground). Thanks for the back and forth. :slight_smile:

/me

Hi Martijn,
its good for audio frequencies , but a period length measurement is for audio is more suitable.
For the input you need a squarewave signal , so a preamp when using an electret mic is a must.
On our SensorAktor Board you will see an mic amp with an TLC272 opamp.
http://interface.khm.de/wp-content/uploads/2009/02/sa_shield_v2_sch.pdf

here is an article that describes a audio pulse measurement
http://www.byrote.org/arduino/

regards
martin

Hi Martin,

Thank you for putting this library online
(http://interface.khm.de/index.php/labor/experimente/arduino-frequency-counter-library).

I hope I may steal some of your time and ask you one question, since I'm not
very familiar with audio amplification.

I started playing with the library today, to get to know it better. I hooked
up an electret mic to the schematic of the one-transistor amplifier (using
the BC547), but the output of the Arduino is:

Frequency Counter
0 Freq: 0
1 Freq: 65536
2 Freq: 0
3 Freq: 0
4 Freq: 0
(etc.)

Now I wonder about two things: (1) is this schematic fit for /audio/
frequencies, or only for radio/HF?, (2) is an electret mic suitable?

I'm using the example provide with the library:

This project looks great, thanks to everyone involved with making it.

I'm sorry to post here with extreme newbie questions, but a quick look through the forums left me clueless as to a better place to ask. My questions are:

  1. Where can I find out more about the _SFR_BYTE and _BV functions (macros?) and about writing ISR functions? I know I don't need to understand them to use the code, but I really need to understand this stuff better if I want modify the code or hack my own solutions. I think I understand that the main reason to use this low level stuff is to change the interrupt frequency so that we can make the PWM frequency high enough to be useful for audio... is this kind of correct?

  2. (Off topic RE arduino) RE the output stage: I think that the main reason we need a low pass filter is to filter out the "carrier" signal of the pwm.... is this correct? I know a little bit about amplitude and frequency modulation/demodulation so I am trying to frame the example here in terms that I'm familiar with.
    BTW, Is there any reason to use an inductor in the output "low pass filter"? I know it does change the output frequency response, but you could get a simple low pass without it right?

  3. Can anyone give me a little hint as to what the multi-plexer is doing in this example? My practical knowledge of multiplexing is non-existent. It looks like its part of switching between monitoring the potentiometer voltage and the audio input signal but I don't understand at a hardware level why there's a multiplexer involved when there are two separate input channels... are the signals really routed through the same physical path?

--
Once again, apologies for the newbie post. As you can probably tell, most of my experience is in high level languages, and I've never had to deal much with hardware interrupts etc. I am currently reading all the links I can find about interrupts on the arduino, etc, but nonetheless I hope someone has the time to offer me a link or some brief guidance.

Many thanks,

  • MM

Hi again,

I have to admit that I mixed up the idea of this thread, which is about Realtime Audio with Arduino generally, with the specific example provided by Lab3 in the first post. Sorry about that.

I'm now going through the ATmega168 manual (or at least the bits that seem relevant) to try to understand the low level code from that example a bit better. I'd still love some extra explanations if anyone can spare the time.

Thanks,
MM

This is pretty darn exciting - how about wrapping this work up into a decent FreqOut function /library, instead of my somewhat lame bit-banged one? (in the playground)

It could have sine, triangle and square to start - but I realize there might be issues with having two or more wavetables since the memory overhead is high (maybe wavetables in PROGMEM)? Or are there speed issues with recovering data from PROGMEM fast enough.

I'll be happy to help anyway I can.

PaulB

Hi PaulB,
i think its too special for "libraying" it. The analoRead , delay , and pwm would not work properly in the arduino anymore. but if you like to try it out you find in may frequency counter library
http://interface.khm.de/index.php/lab/experiments/arduino-frequency-counter-library/
some hints how to deal with interrupts in librays.

wavetables in PROGMEM are possible but i did not try it out yet. Maybe the access to the Flash is to slow but copying the wavetable from the PROGMEM into an wavearray solves that.

martin

sorry to bump such an old thread, but I'm really interested in your project. One of the limitations I see is your 3KHz limitation in audio signal. Out of curiosity, what would need to exist for that limit to be lower?

This is very very very cool. Thanks for the work and the great documentation. I'm definitely using this in my Audio Electronics class. (probably a lot)

MMarvelous, regarding the audio out, I was able to use a different circuit since I didn't have an inductor handy. The circuit I used is here:
http://www.tigoe.net/pcomp/img/audio-out.jpg
from this page, near the bottom:
http://www.tigoe.net/pcomp/code/input-output/analog-output
It seems to work ok.
Edit: Actually, the low pass is pretty strong on that filter, and cutting it in half seems a better fit.

fux, the analog inputs in the diagram on that page are flipped: the audio input is labeled "analog input 0" and the effect control is labeled "analog input 1", but in the code (the reverb and phasor code at least) and the text descriptions it's the other way around. This threw me off at first, so I thought you'd like to know if you hadn't noticed it already.

Thanks again!

Just got together a pitch dropping effect by modifying one of these examples. It's got a good bit of noise, but it's ok for a creepy, sloppy sort of effect, or maybe someone could figure out how to get rid of the noise. I've been trying to find smoother granular methods, but no dice so far. Anyway, I thought someone might find it interesting:

/* Arduino Audio Pitch Drop
 *
 * Arduino Realtime Audio Processing
 * 2 ADC 8-Bit Mode
 * anaöog input 1 is used to sample the audio signal
 * analog input 0 is used to control an audio effect
 * PWM DAC with Timer2 as analog output
 
 
 
 * KHM 2008 / Lab3/  Martin Nawrath nawrath@khm.de
 * Kunsthochschule fuer Medien Koeln
 * Academy of Media Arts Cologne
 
 */


#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))


int ledPin = 13;                 // LED connected to digital pin 13
int testPin = 7;


boolean div32;
boolean div16;
// interrupt variables accessed globally
volatile boolean f_sample;
volatile byte badc0;
volatile byte badc1;
volatile byte ibb;



int cnta;
int icnt0;
int icnt1;
int icnt2;
int cnt2;
int iw1;
int iw2;
int iw3;
int iw;
byte bb;

byte dry[512];  // Audio Memory Array 8-Bit


void setup()
{
  pinMode(ledPin, OUTPUT);      // sets the digital pin as output
  pinMode(testPin, OUTPUT);
  Serial.begin(57600);        // connect to the serial port
  Serial.println("Arduino Audio Pitch drop");





  // set adc prescaler  to 64 for 19kHz sampling frequency
  cbi(ADCSRA, ADPS2);
  sbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);




  sbi(ADMUX,ADLAR);  // 8-Bit ADC in ADCH Register
  sbi(ADMUX,REFS0);  // VCC Reference
  cbi(ADMUX,REFS1);
  cbi(ADMUX,MUX0);   // Set Input Multiplexer to Channel 0
  cbi(ADMUX,MUX1);
  cbi(ADMUX,MUX2);
  cbi(ADMUX,MUX3);


  // Timer2 PWM Mode set to fast PWM 
  cbi (TCCR2A, COM2A0);
  sbi (TCCR2A, COM2A1);
  sbi (TCCR2A, WGM20);
  sbi (TCCR2A, WGM21);

  cbi (TCCR2B, WGM22);




  // Timer2 Clock Prescaler to : 1 
  sbi (TCCR2B, CS20);
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Timer2 PWM Port Enable
  sbi(DDRB,3);                    // set digital pin 11 to output

  //cli();                         // disable interrupts to avoid distortion
  cbi (TIMSK0,TOIE0);              // disable Timer0 !!! delay is off now
  sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt


  Serial.print("ADC offset=");     // trim to 127
  iw1=badc0;  
  Serial.println(iw1);
}

boolean update_toggle = false;

void loop()
{
  while (!f_sample) {     // wait for Sample Value from ADC
  }                       // Cycle 15625 KHz = 64uSec 

  f_sample=false;

  bb=badc1;
  
  dry[icnt0]=bb;          // write to buffer
  
  iw1=dry[icnt1] ;              // read the delay buffer
  iw2=dry[icnt2] ;              // read the delay buffer
  iw3 = ((iw1*icnt1) + (iw2*(256-icnt1)))/256; 

  if(badc0 != 255){
    badc0=badc0/16+2;            // linit poti value to 512
  }
  
  icnt0++;
  if(update_toggle!=1){
    icnt1++;
  }
  update_toggle = (update_toggle + 1) % badc0;
  icnt0 = icnt0 & 511;         // limit index 0..511  
  icnt1 = icnt1 & 255;         // limit index 0..255
  icnt2 = icnt1 + 256;         // limit index 256..511

  
  bb = iw3;



  OCR2A=bb;            // Sample Value to PWM Output



} // loop


//******************************************************************
// Timer2 Interrupt Service at 62.5 KHz
// here the audio and pot signal is sampled in a rate of:  16Mhz / 256 / 2 / 2 = 15625 Hz
// runtime : xxxx microseconds
ISR(TIMER2_OVF_vect) {

  PORTB = PORTB  | 1 ;

  div32=!div32;                            // divide timer2 frequency / 2 to 31.25kHz
  if (div32){ 
    div16=!div16;  // 
    if (div16) {                       // sample channel 0 and 1 alternately so each channel is sampled with 15.6kHz
      badc0=ADCH;                    // get ADC channel 0
      sbi(ADMUX,MUX0);               // set multiplexer to channel 1
    }
    else
    {
      badc1=ADCH;                    // get ADC channel 1
      cbi(ADMUX,MUX0);               // set multiplexer to channel 0
      f_sample=true;
    }
    ibb++; 
    ibb--; 
    ibb++; 
    ibb--;    // short delay before start conversion
    sbi(ADCSRA,ADSC);              // start next conversion
  }

}

Hi, real noob to audio processing here as well...I'm still trying to process and understand a lot of what's going on here, but I'm hoping that the core principles here will serve my needs without too much modification.

I'm interested in an application where I would want to sample 5 or 10 inputs at a rate of something not too much less than 1kHz. From what I figure, if you can sample 2 inputs at ~15kHz, then I should be able to sample 10 at ~3kHz, right? Does it change very much if I'm doing an external multiplex into one pin (as opposed to the 2 of your setup)?

Another goal here is to dump the digitally sampled data off of the Arduino over serial to a computer. marnaw said earlier that the timing modifications we're making here would screw up some of the other functions like delay() and analogRead(), so are we stuck with having to somehow reimplement the Serial library if we want to achieve this? My first thought was that I might require another "normal" Arduino to accomplish this, but then, oh, how do I get the data between the two Arduinos without serial?

Any help/advice would be greatly appreciated! Cheers! --Brandon

Ok, so I gave it a bit more thought, and I'm still trying to decipher this timing/interrupt scheme here. It's not intuitive to me yet...

I've been using a 4051 multiplexer (Arduino Playground - 4051) for other things in the past, and I wanted to use one here to test my idea out, so I looked into the various delays involved in addressing/signal switching on that thing before going much further.

Seems it's not so bad, actually! The 4051 multiplexer has a max (not typical) 1000 nanosec delay between switching an address bit and output signal. Add that to 7 µs digitalWrite() overhead and we've got (1 +7N) µsec delay to address the multiplexer and so have to wait (assuming N=3 address bits) 22 µsec between reads? Using all 8 inputs on the mux, then do I have to wait at least 176 µsec between reads on an individual channel? If true, then that makes my sampling frequency (1/(2*176 µsec) ~ 3 kHz? Again, maybe I'm thinking too linearly here and don't fundamentally understand the timing/interrupt thing that's going on here...

Can I actually get this to work, or am I just crazy? Would I have to go to an Arduino Mega to be able to read a bunch of channels (with 16 analog inputs, as opposed to being limited to 6) or can you see any way I can still use a Duemilanove with two 4051 multiplexers feeding two analog pins being able to read 16 channels?

Also, is there any way to still put the ADC in 10-bit mode and not sacrifice to much? Could you still do this audio processing in a 10-bit mode? I assume you'd be sacrificing the high-end of your frequency space by being forced to sample at a lower rate, but remember that this is not a problem for me. I only care about frequencies under 1kHz, so any sampling frequency above that is fine!

Oh, and also remember that I don't want PWM analog output here, I want a stream of digital data that I'll be dumping over to a computer. Thanks!