Go Down

Topic: Single Timer PCM 6 Voices at 21khz [Code] (Read 3 times) previous topic - next topic

WilliamK Govinda

Feb 23, 2011, 12:50 pm Last Edit: Mar 19, 2011, 11:22 pm by WilliamK Reason: 1
Edit: I did a totally new code that is easier to handle and has reverse, pitch, velocity (volume per voice) and other neat options. See my other posts below to check for the new code, but here's a direct link:

http://www.wusik.com/arduino/Libraries/WDM_PCM/Wusik_DM_Sound_V2.zip

Here's an MP3 example:

http://www.wusik.com/arduino/Libraries/WDM_PCM/WDM_Example_01.mp3

Here's a picture of my protoboard:

http://www.wusik.com/arduino/Libraries/WDM_PCM/Protoboard_01.JPG

And the youtube video:

http://www.youtube.com/watch?v=ZEeOfGOR68E

--------

Here I'm using the 16 bit timer 1 of the Arduino 2009 to reproduce PCM sounds at 21052hz sample-rate. (around 10.5khz sound) This still requires some work, but so far it sounds pretty great.

The great thing about this is that it uses Timer1 for both sample-reading-interrupt and pwm-output, so only a single timer is required for all 6 voices. Just put 10k resistor on Pins 9 and 10 and connect to a speaker or headphones. A filter may be required for better sound, but so far I'm using it connected directly to my headphones which already has a low-pass filter. (its a DJ headphone)

The sketch below requires the following file and outputs a loop sound that I set in the loop() part of the code. Just remove it and do whatever you want, or use the serial code included to trigger sounds.

I'm also going to work, next week, on a better WAV to C code so you can use any WAV file for this. The current sounds are not in the correct sample-rate, sorry for that, I will update the sounds next week, as I don't have much free time to work on this anymore.

And yes, please, contribute to our projects when possible at http://arduino.wusik.com

http://www.wusik.com/arduino/Libraries/Multi_PCM/PCM_Sound.h

http://www.wusik.com/arduino/Libraries/Multi_PCM/Wusik_DM_Sound.pde

All files + samples:

http://www.wusik.com/arduino/Libraries/Multi_PCM/Wusik_DM_Sound.zip

Code: [Select]
/*

 Created by WilliamK @ Wusik Dot Com (c) 2011 - http://arduino.wusik.com
 
 8-Bit PCM Sound using PWM on Pin 9 and Pin 10 - Uses the 16-Bit Timer1 to sum 3 voices in each Pin (total of 6 voices)
 
*/

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "PCM_Sound.h"

// ======================================================================================= //
unsigned int sampleLen[6] = {0,0,0,0,0,0};
unsigned int sample[6] = {0,0,0,0,0,0};
unsigned char* samplePCM[6] = {0,0,0,0,0,0};
unsigned long mixer[2] = {(128*3),(128*3)};
byte inByte = 0;
unsigned char voice = 0;

// ======================================================================================= //
void setup()
{  
   pinMode(9,OUTPUT);
   pinMode(10,OUTPUT);
 
   Serial.begin(9600);

   // 16 Bit Timer Setup //
   // 16000000 (CPU CLOCK) / 760 (10 Bits) = 21052 samples-per-second (put a filter to remove anything above 10.5khz)
   TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11); // Fast PWM Timer on Pins 9 and 10
   TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10); // Non-Phase Inverting - No Prescaler
   ICR1 = (760-1); // Max value of 760 (9-bits is 511 and 10-bits is 1023) - 1 (the processor counts over)
   TIMSK1 = _BV(TOIE1); // timer overflow interrupt
   sei(); // enable global interrupts
   OCR1A = (128*3); // Initial PWM Pin 9
   OCR1B = (128*3); // Initial PWM Pin 10
}

// ======================================================================================= //
unsigned char getFreeVoice(void)
{
 for (int x=0; x<6; x++)
 {
   if (samplePCM[x] == 0)
   {
     return x;
     break;
   }
 }
 
 return 5;
}

// ======================================================================================= //
unsigned char findHiHat(void)
{
 for (int x=0; x<6; x++)
 {
   if (samplePCM[x] == HHopen_PCM)
   {
     return x;
     break;
   }
 }
 
 return getFreeVoice();
}

// ======================================================================================= //
void loop()
{  
 if (Serial.available() > 0)
 {
   inByte = Serial.read();
   
   if (inByte == '4') voice = findHiHat(); else voice = getFreeVoice();
   sample[voice] = 0;
 
   switch (inByte)
   {
     case '1': sampleLen[voice] = Kick_Len;      samplePCM[voice] = Kick_PCM;     break;
     case '2': sampleLen[voice] = Snare_Len;     samplePCM[voice] = Snare_PCM;    break;
     case '3': sampleLen[voice] = Clap_Len;      samplePCM[voice] = Clap_PCM;     break;
     case '4': sampleLen[voice] = HHclosed_Len;  samplePCM[voice] = HHclosed_PCM; break;
     case '5': sampleLen[voice] = HHopen_Len;    samplePCM[voice] = HHopen_PCM;   break;
     case '6': sampleLen[voice] = Clav_Len;      samplePCM[voice] = Clav_PCM;     break;
     case '7': sampleLen[voice] = FX1_Len;       samplePCM[voice] = FX1_PCM;      break;
   }
 }
}

// ======================================================================================= //
ISR(TIMER1_OVF_vect)
{  
 // Do this first so the PWM is updated faster //
 OCR1A = mixer[0];
 OCR1B = mixer[1];

 // Now Calculate the next sample //
 if (sample[0] >= sampleLen[0]) samplePCM[0] = 0; if (samplePCM[0] != 0) { mixer[0] = (unsigned int)pgm_read_byte(&samplePCM[0][sample[0]]); sample[0]++; } else mixer[0] = 128;
 if (sample[1] >= sampleLen[1]) samplePCM[1] = 0; if (samplePCM[1] != 0) { mixer[0] += (unsigned int)pgm_read_byte(&samplePCM[1][sample[1]]); sample[1]++; } else mixer[0] += 128;
 if (sample[2] >= sampleLen[2]) samplePCM[2] = 0; if (samplePCM[2] != 0) { mixer[0] += (unsigned int)pgm_read_byte(&samplePCM[2][sample[2]]); sample[2]++; } else mixer[0] += 128;

 if (sample[3] >= sampleLen[3]) samplePCM[3] = 0; if (samplePCM[3] != 0) { mixer[1] = (unsigned int)pgm_read_byte(&samplePCM[3][sample[3]]); sample[3]++; } else mixer[1] = 128;
 if (sample[4] >= sampleLen[4]) samplePCM[4] = 0; if (samplePCM[4] != 0) { mixer[1] += (unsigned int)pgm_read_byte(&samplePCM[4][sample[4]]); sample[4]++; } else mixer[1] += 128;
 if (sample[5] >= sampleLen[5]) samplePCM[5] = 0; if (samplePCM[5] != 0) { mixer[1] += (unsigned int)pgm_read_byte(&samplePCM[5][sample[5]]); sample[5]++; } else mixer[1] += 128;
}

WilliamK Govinda

And here's my WAV2Code solution...

http://www.wusik.com/arduino/Libraries/WAV2Code/Wusik_WAV2Code.zip
(all files, examples and source)

http://www.wusik.com/arduino/Libraries/WAV2Code/WWav2C.exe
(windows executable)

http://www.wusik.com/arduino/Libraries/WAV2Code/builder.cpp
(source)

It works pretty well here. I will post the code I use for this in another thread. ;-)

Wk

WilliamK Govinda

Here's the final code, with new samples. It takes numbers from 1 to 7 from the Serial input (9600) and finds a free voice from the 6 voices.

http://www.wusik.com/arduino/Libraries/Multi_PCM/Wusik_DM_Sound.zip

That's the complete code + samples and everything needed. I tested with headphones, using a 10k resistor on pins 9 and 10 directly connected to the set. It sounds pretty good. Needs a filter for a better sound, but heck, it still sounds great. ;-)

Wk

WilliamK Govinda

Here's an MP3 example:

http://www.wusik.com/arduino/Libraries/Multi_PCM/8-bit-Drums.mp3

And here's the YouTube video:

http://www.youtube.com/watch?v=2Vgf8WEkG1k

Best Regards, WilliamK

xray303

#4
Mar 07, 2011, 12:20 am Last Edit: Mar 07, 2011, 12:23 am by xray303 Reason: 1
wow ... quite impressive to play 6 voices of sample with a single Arduino.

Did you check this ?  : http://elm-chan.org/works/sd20p/report.html

Good work WillK and thanks for sharing.

WilliamK Govinda

Yeah, I saw that, but there's a typo, its not 255 "voices" but 255 sounds. From what I've seem it only play one at a time. ;-) But still a nice design.

In any event, I was wondering on how fast I could work with a SD card for even more and longer sounds. ;-) I got one here, just need to take the time to check it out. Still, not sure how many concurrent sounds I could play directly from a SD card. I saw a guy doing a MOD player and he couldn't read directly from an EEPROM so he had to use an external SRAM or something like that. Still, the SD card reads much faster than an EEPROM.  XD

Anyway, the current design, there's still room for more voices, I just didn't do it to leave room for something else instead.  :smiley-eek-blue:

Wk

WilliamK Govinda

BTW: the noise on the MP3 example is not from the PWM output, its from my sound-card input.  :smiley-red:

Another nice thing, the HiHat is in its own mute group, so the close hihat stops the open hihat. ;-)

Wk

audioguytodd

Very nice!!!!! BTW, which MIDI board are you using????

WilliamK Govinda

I'm using SparkFun's Midi breakout.  :smiley-mr-green:

http://www.sparkfun.com/products/9598

Wk

Planktron

#9
Mar 17, 2011, 10:37 pm Last Edit: Mar 17, 2011, 11:46 pm by Planktron Reason: 1
Really great job! Going to make myself a nice drum machine!! Big thanks for sharing this!

One question though. Can I make the code to playback some beats by itself so it would be more like a drum machine? I tried to give inByte some values inside the loop and separate the sounds with delay but it wouldn't play back the sounds. Any ideas?

Planktron

I went little bit further with the drum machine idea. Got stuck and can't get the beats playing continuously =(. Can't figure out what I'm doing wrong. Any ideas?

Code: [Select]
/*

  Created by WilliamK @ Wusik Dot Com (c) 2011 - http://arduino.wusik.com
 
  8-Bit PCM Sound using PWM on Pin 9 and Pin 10 - Uses the 16-Bit Timer1 to sum 3 voices in each Pin (total of 6 voices)
 
*/

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "PCM_Sound.h"

// ======================================================================================= //
unsigned int sampleLen[6] = {0,0,0,0,0,0};
unsigned int sample[6] = {0,0,0,0,0,0};
unsigned char* samplePCM[6] = {0,0,0,0,0,0};
unsigned long mixer[2] = {(128*3),(128*3)};

unsigned char voice = 0;
int delayTime = 500;

byte bar1[] = {

2, 2, 1, 6, 2, 4, 1, 2, 1, 3, 1, 2, 2, 1, 3, 4

};

// ======================================================================================= //
void setup()
{   
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);

    // 16 Bit Timer Setup //
    // 16000000 (CPU CLOCK) / 760 (10 Bits) = 21052 samples-per-second (put a filter to remove anything above 10.5khz)
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11); // Fast PWM Timer on Pins 9 and 10
    TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10); // Non-Phase Inverting - No Prescaler
    ICR1 = (760-1); // Max value of 760 (9-bits is 511 and 10-bits is 1023) - 1 (the processor counts over)
    TIMSK1 = _BV(TOIE1); // timer overflow interrupt
    sei(); // enable global interrupts
    OCR1A = (128*3); // Initial PWM Pin 9
    OCR1B = (128*3); // Initial PWM Pin 10
}

// ======================================================================================= //
unsigned char getFreeVoice(void)
{
  for (int x=0; x<6; x++)
  {
    if (samplePCM[x] == 0)
    {
      return x;
      break;
    }
  }
 
  return 5;
}

// ======================================================================================= //
unsigned char findHiHat(void)
{
  for (int x=0; x<6; x++)
  {
    if (samplePCM[x] == HHopen_PCM)
    {
      return x;
      break;
    }
  }
 
  return getFreeVoice();
}

// ======================================================================================= //
void loop()

 
    for(int j = 0; j < 16; j ++) {
   inByte(bar1[j]);
   
   }
   
}

// ======================================================================================= //

    void inByte(byte beat) {
    if (beat == 1) {
      sampleLen[voice] = Kick_Len;
      samplePCM[voice] = Kick_PCM;
    }
    else if (beat == 2) {sampleLen[voice] = Snare_Len;     samplePCM[voice] = Snare_PCM;}
    else if (beat == 3) {sampleLen[voice] = Clap_Len;      samplePCM[voice] = Clap_PCM;}
    else if (beat == 4) {sampleLen[voice] = HHclosed_Len;  samplePCM[voice] = HHclosed_PCM;}
    else if (beat == 5) {sampleLen[voice] = HHopen_Len;    samplePCM[voice] = HHopen_PCM;}
    else if (beat == 6) {sampleLen[voice] = Clav_Len;      samplePCM[voice] = Clav_PCM;}
    else if (beat == 7) {sampleLen[voice] = FX1_Len;       samplePCM[voice] = FX1_PCM;}
    delay(delayTime);
}

ISR(TIMER1_OVF_vect)
{   
  // Do this first so the PWM is updated faster //
  OCR1A = mixer[0];
  OCR1B = mixer[1];

  // Now Calculate the next sample //
  if (sample[0] >= sampleLen[0]) samplePCM[0] = 0; if (samplePCM[0] != 0) { mixer[0] = (unsigned int)pgm_read_byte(&samplePCM[0][sample[0]]); sample[0]++; } else mixer[0] = 128;
  if (sample[1] >= sampleLen[1]) samplePCM[1] = 0; if (samplePCM[1] != 0) { mixer[0] += (unsigned int)pgm_read_byte(&samplePCM[1][sample[1]]); sample[1]++; } else mixer[0] += 128;
  if (sample[2] >= sampleLen[2]) samplePCM[2] = 0; if (samplePCM[2] != 0) { mixer[0] += (unsigned int)pgm_read_byte(&samplePCM[2][sample[2]]); sample[2]++; } else mixer[0] += 128;

  if (sample[3] >= sampleLen[3]) samplePCM[3] = 0; if (samplePCM[3] != 0) { mixer[1] = (unsigned int)pgm_read_byte(&samplePCM[3][sample[3]]); sample[3]++; } else mixer[1] = 128;
  if (sample[4] >= sampleLen[4]) samplePCM[4] = 0; if (samplePCM[4] != 0) { mixer[1] += (unsigned int)pgm_read_byte(&samplePCM[4][sample[4]]); sample[4]++; } else mixer[1] += 128;
  if (sample[5] >= sampleLen[5]) samplePCM[5] = 0; if (samplePCM[5] != 0) { mixer[1] += (unsigned int)pgm_read_byte(&samplePCM[5][sample[5]]); sample[5]++; } else mixer[1] += 128;
}


WilliamK Govinda

I won't have time to help you today, sorry, but I will try to make a small sequencer for this soon.

In the meanwhile, check this out:

https://www.kickstarter.com/projects/ruggedcircuits/arduino-drum-machine-step-sequencer-groove-box/posts

;-)

Wk

WilliamK Govinda

I'm doing some big changes to the code, to make it more usable, and also adding some extra features, but the processor can only do so much... Still, I will post the updated code soon.  8)

I'm trying to add velocity to the voices, but the processor really hates divisions, so I'm thinking on doing a small matrix of 5 velocities, which would take around 1k of RAM, but should be much faster than a regular \ in the code. I will see if I have some free time to test this out tomorrow. I'm also trying to add rate to the thing. So far regular 1, 2, 3, 4 rates are easy, but I want to add fine-rates too maybe with some basic linear interpolation, but we will see if that works out. ;-)

Wk

Planktron

Wow… these are some really great features. Can't wait to test this stuff out. :)

I have played around with the Little-scale drum machine (http://little-scale.blogspot.com/2008/04/arduino-drum-machine.html) code and have always wondered if it is possible to add sample volume and trigger it with something like MPC pads. This would be really great leap forward on Arduino live drumming  8)

WilliamK Govinda

I did a totally new code that is easier to handle and has reverse, pitch, velocity (volume per voice) and other neat options. Here's a direct link:

http://www.wusik.com/arduino/Libraries/WDM_PCM/Wusik_DM_Sound_V2.zip

Here's an MP3 example:

http://www.wusik.com/arduino/Libraries/WDM_PCM/WDM_Example_01.mp3

Here's a picture of my protoboard:

http://www.wusik.com/arduino/Libraries/WDM_PCM/Protoboard_01.JPG

And the youtube video:

http://www.youtube.com/watch?v=ZEeOfGOR68E

Don't forget to check our other projects and also contribute when possible. ;-)

http://arduino.wusik.com

Best Regards, WilliamK

Go Up