Arduino Forum

Using Arduino => Audio => Topic started by: HCPUNK on Mar 02, 2011, 03:55 pm

Title: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 02, 2011, 03:55 pm
hello everyone! i'd like to know, is the signal that comes out from a R2R ladder (like this one http://didier.longueville.free.fr/arduinoos/wp-content/uploads/DDS00.jpg (http://didier.longueville.free.fr/arduinoos/wp-content/uploads/DDS00.jpg) ) ready to drive an 8ohm speaker or do i need to put a power amplifier (made out from an LM386, for example) between the ladder and the speaker? also, how can i send the data from a huge array of 8bit samples to the eight arduino pins all at once? could someone help me to complete this piece of code? thanks :D

char Sample[]={/*data stored in a 8bit precision*/};
for(int i=0;i<SAMPLES_NUMBER;i++)
{
  digitalWrite(pin0, /*what should go here*/);
  digitalWrite(pin1, /*and here*/);
  ...
  digitalWrite(pin7, /*and here?*/);
  delayMicroseconds(32)  //for about 31.250kHz sample rate
}
Title: Re: how to drive a speaker from a r2r ladder
Post by: AWOL on Mar 02, 2011, 04:14 pm
Code: [Select]
digitalWrite(pin0, /*what should go here*/);
  digitalWrite(pin1, /*and here*/);
  ...
  digitalWrite(pin7, /*and here?*/);

If you use digitalWrite, your output sample rate will be quite low - all the mapping of the pins slows the function down.
Have a look around the forum for direct port manipulation
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 02, 2011, 04:23 pm
The answer to the first part is that you will to amplify the output to drive a typical 4 ohm, 8 ohm, 16 ohm speaker.

For the 2nd part, is seems you are trying to perform a parallel write to the output pins?
Have you considered just doing a shiftout to a shift register instead? Something like this?
location = start_location;
while ( location <= end_location){
shiftout(datapin,clockpin, MSBFIRST, Sample[location]);
digitalWrite (output_register_clock, LOW);
digitalWrite (output_register_clock, HIGH);  // rising edge loads the data to the output pins.
location  = location +1;
}

Then put your ladder on the output of the shift register. This would give 8-bit output instead of 6 also.
I believe the arduino is fast enough that you could do several in parallel, buffer each with an op amp, and make a summing amplifier to mix them together and send that to a power amplifier for the speaker.
Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 02, 2011, 08:40 pm
thanks for the help!!

by using direct port manipulation, it should be something like this:

Code: [Select]
char Sample[]={/*data stored in a 8bit precision*/};
DDRB = B11111111;
PORTB = 00000000;
for(int i=0; i<SAMPLES_NUMBER;i++)
{
  PORTB = Sample[i];
  delayMicroseconds(32);
}

right?


on the other hand, i could use a shift register to send all the data through a single pin and then obtain an 8-pins parallel output on the other side of the register, right? in this way, the clock sets also the sample rate of the digital audio data coming out from the register, but how can i set it to a determined frequency value? however, this time the code should be something like this (arranged from a playground tutorial):

Code: [Select]
int latchPin = 8; //storage clock
int clockPin = 12; //register clok
int dataPin = 11; //serial data
char Sample[]={/*data stored in a 8bit precision*/};

void setup()
{
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}

void loop()
{
  for (int i= 0; i<SAMPLES_NUMBER; i++)
  {
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, Sample[i]); 
    digitalWrite(latchPin, HIGH);
    delayMicroseconds(32);  //not sure about that
  }
}


are those pieces of code exact? which one is more powerful in terms of sample rate respect and speed?
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 02, 2011, 10:23 pm
To have the data come out at a predetermined rate, you could have your loop only execute the a shiftout every 32uS.
This code checks for 100uS intervals to elapse & then does stuff - set up yours similar but using 32uS (1/32150)
Code: [Select]

void loop(){
  // *******************************************************************
  // start timing interval

  currentMicros = micros();  // sample the time
  if (currentMicros - previousMicros >= hundred_uS_interval) // more than our interval?
  {
    // save the last time we okayed time updates
    previousMicros = currentMicros; // save the current time for next comparison
    toggle = 1-toggle;  // results in 0 1 0 1 ...                                                                                                                                     

    // do stuff on 0.1mS intervals, 1mS intervals, ets.

   
  }  // end of time interval
} // end of void loop

Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 03, 2011, 01:01 am
thanks for your patience and kindness again, but i'm afraid i haven't understood that piece of code properly. in the way you showed me, where do i have to put my code? another question is: in this way, is still possible to do other stuff like controlling some analog pins, sending some data from the serial pin, or even do some on-the-fly operation to the data in my array? if yes, where should i put my other code?
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 03, 2011, 01:31 am
Sorry about that, re-reading I can see I wasn't too clear.
Your midi stuff goes where these lines are.

    toggle = 1-toggle;  // results in 0 1 0 1 ...                                                                                                                                     

    // do stuff on 0.1mS intervals, 1mS intervals, ets.

}  // end of time interval

Then your other stuff occurs.

So, loop starts, if 32uS have gone by you write out the next part of the waveform.

After that, or if 32uS have not gone by, your other stuff occurs.

Then, you go back to the top & start again:
} // end of void loop


Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 04, 2011, 09:20 pm
i'm still not sure about that: what that code does is to check again and again if "N"uS has passed and then run the instructions i want, which should be:
Code: [Select]
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, Sample[i]); 
digitalWrite(latchPin, HIGH);
i++;

right? but what's the purpose of the toggle variable? furthermore, where do i have to put my for-loop? please check this:
Code: [Select]
while(i<SAMPLES_NUMBER)
{
  currentMicros = micros();
  if (currentMicros - previousMicros >= 32)
  {
    previousMicros = currentMicros;
    toggle = 1-toggle;
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, Sample[i]);
    digitalWrite(latchPin, HIGH);
    i++;
  }
};

is it what i was supposed to write? i'm wondering if direct port manipulation would simplify the whole thing..
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 05, 2011, 01:27 am
Take out the toggle, I was just using that as an example. In my code I write that out every pass thru, then read it back thru a different pin to see if a switch is closed.
In your case, I would add some code to see if the shifting out was supposed to start, and a check to see when it ends to stop shifting.
Which your while (i<samples_number) does.
If you're only playing out the 1 sound, then sitting in the while loop would be fine.

I'd be tempted instead to set some flags so you could sense when multiple sounds were to start and to let multiple sounds play at once.

So something along these lines:

void loop(){
if (play1flag not set){
read switch1
if (switch1 pressed) {set play1flag}
}
if (play2flag not set){
read switch2
if (switch2 pressed) {set play1flag}
}

currentMicros = micros();
if (currentMicros - previousMicros >= 32)
  {
    previousMicros = currentMicros;
if play1flag set{
shiftout
i=i+1
if i = end {
i=0,
clear play1flag}
}

if play2flagset{
shiftout[j]
j=j+1
if j = end {
j=0,
clear play2flag}
}
} // end time interval check
} // end void loop
Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 08, 2011, 12:41 am
playing 2 or more different sound samples each one triggered by its own button? sound really exciting!! Thanks again for your important help.. this should be my last request: i rearranged (a little piece of) my code with your useful advices, so could you please give it a glance and tell me if it's everything ok and what i can do to improve it? here it is:
Code: [Select]
#define dataP 11      //data pin for the shift reg
#define clockP 1      //clock pin for the shift reg
#define latchP 8      //latch clock pin for the shift reg
#define LAST_SAMPLE 4096    //max size of every sample
#define PIEZOTHRESHOLD 150  //the fewest value that can be read
#define TOTAL_PINS 6        //number of analog devices that trim the samples

//those are the audio samples, codified in 8bit
//the last array contains all the previous data
uint8_t Sample1[LAST_SAMPLE];
uint8_t Sample2[LAST_SAMPLE];
uint8_t Sample3[LAST_SAMPLE];
uint8_t Sample4[LAST_SAMPLE];
uint8_t Sample5[LAST_SAMPLE];
uint8_t Sample6[LAST_SAMPLE];
uint8_t* Samples[TOTAL_PINS]={Sample1,Sample2,Sample3,Sample4,Sample5,Sample6};

int playflags[TOTAL_PINS]={0,0,0,0,0,0};  //flags: 0=unset 1=set
int piezovals[TOTAL_PINS];                //values read from each pin
int i[TOTAL_PINS]={0,0,0,0,0,0};          //the counters, every sample has its own
int pins;
int samp;
int currentMicros;
int previousMicros;

void setup()
{
  //set up the shift reg pins
  pinMode(latchP, OUTPUT);
  pinMode(clockP, OUTPUT);
  pinMode(dataP, OUTPUT);
}

void loop()
{
  for(pins=0;pins<TOTAL_PINS;pins++){   //Search for any not-yet-activated pin,
    if(playflags[pins]==0){             //then check if some of them have been pressed
      piezovals[pins]=readPiezo(pins);  //using a function that return its "velocity" or 0.
      if(piezovals[pins]!=0)            //If the value is different from 0 (in other words, if
        playflags[pins]=1;              //the botton has been pressed) set his flag.
    }
  }

  currentMicros=micros();                  //Read the current time, checking if 32us have passed
  if(currentMicros-previousMicros>=32){
    previousMicros=currentMicros;

    for(pins=0;pins<TOTAL_PINS;pins++){    //Search among all the pins
      if(playflags[pins]==1){              //the ones whose flag has been set.
        samp=i[pins];                      //Temporarily save the current counter
        digitalWrite(latchP, LOW);         //Toggle the latch clock
        shiftOut(dataP, clockP, MSBFIRST, Samples[pins][samp]);  //Send the data to the shift reg
        digitalWrite(latchP, HIGH);        //Toggle the latch clock again
        i[pins]++;                         //Increase the counter
        if(i[pins]==LAST_SAMPLE){          //If the the last sample has been reached,
          i[pins]=0;                       //reset the current counter and
          playflags[pins]=0;               //toggle his respective flag.
        }
      }
    }
  }
}

//This function reads a given analog pin, and returns his average
//velocity, checking how many times his value stays above threshold/2.
//In this way, is possible to play sounds at different velocities, only
//multiply or divide the last shiftOut parameter by a certain value
//(related to the velocity we've read). I'm wondering if all this might
//add to much delay between the pression and the play.

int readPiezo(int piezoPin)
{
  int val, t;
  val=analogRead(piezoPin);
  if(val>=PIEZOTHRESHOLD){
    t=0;
    while(analogRead(piezoPin)>=PIEZOTHRESHOLD/2)
      t++;
    return(t*2);
  }
  return(0);
}


thanks in advance!!
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 08, 2011, 03:33 am
That is very clever the way you have it do 6 sounds like that!  I guess there is no reason that couldn't be expanded if the needed # of instructions could occur in time (32uS/62.5nS = 512 instructions per pass thru the loop).

So, every 32uS you will go thru all 6 pins and if playing shift out a byte of data, all going thru the same shift register?
I was thinking each would have its own shift register/ladder then  be mixed together with a summing op amp and some lowpass fitering to take out higher frequency switching noise. (use common shift clock, data line, and have 6 latch lines)

This way may work very well. I am wondering what would happen soundwise when you have 1 sound playing vs 6 sounds for instance, and if it would matter if the sound was say 1,2 then nothing for 3,4,5, and then 6, or nothing for 1,2,3, then 4,5 and not 6. You can see how it would appear to have data going  out at different data rates, from every 32uS to something that might look  like every 5.3uS or something in between that is not necessarily evenly spaced.

I'd say for now if you have the 4K sound samples, go give it a try.  Where is the 24K of data being stored? In the 32K of flash memory?
Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 08, 2011, 03:22 pm
maybe i forgot to tell you i'm not English, however i'm afraid i haven't understood a word.. sometimes it's hard to understand what I've just written too..
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 08, 2011, 04:30 pm
I will try to simplify.
The way you wrote  your array checking, updating, everything is very clever, nice & compact, that part is fine.

On playback, you may have 1 sound being played, and you may 6 sounds being played.  All these sounds are going thru  shift register to 1 R2R ladder.

Will that impact the sound?  I don't know.  You may notice the sound being different when 1 is being played compared to two being played.
I don't have a good way to draw a picture.
Imagine this: Sound 1 is playing a sine wive, going from 0V up to 5V, nice and smooth.
Now sound 2 starts - so at 32uS sound1 goes out, then  some hundreds of nanoseconds later sound 2 goes out, and now the level stays at sound2 until the next 32uS when sound 1 is updated.  Will that impact the way sound1 is heard?
Imagine another example: Sound 1 is playing a sine wive, going from 0V up to 5V, nice and smooth.
Now sound 5 starts - so at 32uS sound1 goes out, then  some more hundreds of nanoseconds later sound 5 goes out, (delayed by the checking of flags for sounds 2,3,4) and now the level stays at sound5 until the next 32uS when sound 1 is updated.    Will that impact the way sound1 is heard?

Maybe it will all mix together nice, maybe not. If you find the sound is not as expected, then having each sound go thru separate R2R ladders may be needed instead.


Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 08, 2011, 11:22 pm
well, i never thought i could receive compliments here, so i'm kind of happy :)

however, the second part of your explanation made me a bit cofused.. you've "raised" an important issue.. the first thing i thought was to resolve the problem by software, in this way (or something similiar):
Code: [Select]
  currentMicros=micros();                  //Read the current time, checking if 32us have passed
  if(currentMicros-previousMicros>=32){
    previousMicros=currentMicros;
    sample=0;

    for(pins=0;pins<TOTAL_PINS;pins++){    //Search among all the pins
      if(playflags[pins]==1)               //the ones whose flag has been set and
        sample+=Samples[pins][i];          //sum together their respective samples.
    }
    digitalWrite(latchP, LOW);                       //Toggle the latch clock
    shiftOut(dataP, clockP, MSBFIRST, (sample>>8));  //Send the first half of data to the shift reg
    shiftOut(dataP, clockP, MSBFIRST, sample);       //Send the second half of data to the shift reg
    digitalWrite(latchP, HIGH);                      //Toggle the latch clock again
    i++;                                             //Increase the counter
    if(i==LAST_SAMPLE){                          //If the the last sample has been reached,
      i=0;                                       //reset the counter and
      for(pins=0;pins<TOTAL_PINS;pins++)         //all the play flags.
        playflags[pins]=0;
    }
  }

in this way, i'm able to send the data once, at twice of the depth, using only 2 registers, but i'll need a bigger R2R ladder. i'm still wondering if arduino's computing capabilities are enough to do all those instructions in just 32us (that are about 512 clock cycles).

Anyway, if i would use different regs and ladders for every sample, how can i mix the sounds together?
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 09, 2011, 02:29 am
I was discussing this with college professer, he suggested the same. Add all together, shift out to an 11 or 12 bit R2R ladder.

Otherwise, seperate R2Rs, mix with a non-inverting op amp summing amplifier - can be readily found with google search - am running out, can send when I get back in a couple hours.
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 09, 2011, 06:11 am
http://masteringelectronicsdesign.com/how-to-derive-the-summing-amplifier-transfer-function/

Use 10K resistors for all, increase R4 if need more gain.
Op Amp like a TL072, any kind of low noise op amp. 
Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 11, 2011, 10:07 pm
i'm sorry, i don't know how a non-inverting amplifier works, i discovered the op-amps just a week ago, at school. however, as soon as i'll get my Arduino, i'll verify by myself if the "software" solution (the one described above) would work properly, or if i should use those non-inverting amplifier to mix the signals. another issue is the one related to the memory: i know i can store my samples data into the flash memory, but how can i retrieve them? i gave a glance to the PROGMEM reference page, but i didn't understand how the reading functions work
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 11, 2011, 10:51 pm
// read back a 2-byte int
displayInt = pgm_read_word_near(charSet + k)  <<< tells arduino to get the data from location k of array charSet

// read back a char
myChar =  pgm_read_byte_near(signMessage + k); <<< tells arduino to get the data from location k of array signMessage

Not clear to me that you can tell the compiler where to start the array at, or if you need to define the arrays as being 4096 bytes long in the
pre-setup code, such as:
byte sound_array1[4096];



Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 12, 2011, 12:02 am
Not very clear to me too.. however, i've estimated that the average length of each sample, played at 1byte/32us is just 0.131s, not enough to reproduce a drum snare, hi-hats, kick, cymbal.. but i can't make bigger arrays, because i'm already out of memory.. any ideas?
Title: Re: how to drive a speaker from a r2r ladder
Post by: CrossRoads on Mar 12, 2011, 06:12 am
External memory, make samples a big as you want.
2 seconds of drum sound forced to decay off to fit in memory, sampled at 32K to achieve 16KHz bandwithd, is 64K of memory. Adds up quick! And not realistic sounding either.
Go with external memory, read back the samples and feed into separate D/A converters and mix together in analo world.

I have a design I did, because I wanted to go electronic and stop lugging my set all over, where I used a PC to load up sounds into shared memory, then had my playback circuit read it back from memory thru D/As. Trigger was a microphone inside a practice pad, hardness of the strilke was captured used to control volume of playback by having the D/A output go thru a voltage controlled ampifier.
Had all the pieces prototyped, tested with components that gave good results, had a method to load up banks of memory using a PC Junior (this was like 1987).
Then we packed up to buy a house, things got delayed a few months, we finally moved, the Roland type digital drum sets came out, and Ijust never got back to doing anything with it.
Now, everything less expensive, especially memory (4 gig USB stick, $8!, surprised they are not free in cereal boxes), speeds are faster. I could see buildiing a ATMega per sound channel with meg of memory or something, build up 16 channels or something, master controller to have pc connected to each download to each one to load up memory for playback by the atmega with a strike detection circuit per card or something.

I'm gonna have to dig up my old design, update it for these new times!
Title: Re: how to drive a speaker from a r2r ladder
Post by: HCPUNK on Mar 16, 2011, 04:22 pm
it's such a pity that you gave up with your project, it sound really interesting!!

what i'd like to build is something like the Roland Octapad, and it should be the project i'll show during my high school exams.
it should feature 6 or 8 pad (multiplexed into only one analog pin), a midi out (whose channel and patch should be selected from the arduino itself, and velocity read from the function described above), a few sound banks (drum sounds and some sine, square waves and a rough piano sound as well), a built-in speaker, a headphone out and a vu-meter. so yes, an external memory (and a lot of work) is what i need. any hints about the first one?



PS: in italy we usually find fingers or hairs or mice into any kind of food box.. maybe someone has found a memory stick as well, who knows :)