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:
And the youtube video:
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
/*
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;
}