Having some fun with PCM multi voice code ...

Just a quick play with some PCM code found in the Playground area. :grin:

/*

  Created by WilliamK @ Wusik Dot Com (c) 2011 - http://arduino.wusik.com
  
  Code excerpts from: http://www.arduino.cc/playground/Code/PCMAudio
  Michael Smith <michael@hurts.ca>

  8-Bit Sound Test using PWM on Pin 11
  
*/

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

#define SAMPLE_RATE 44100
volatile uint16_t sample[4] = {0,0,0,0};
unsigned long mixer = 0;

// Sound Data //
// 44100 Pulse //
const unsigned char sounddata_data1[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255};

const unsigned char sounddata_data2[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255};

const unsigned char sounddata_data3[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255};

const unsigned char sounddata_data4[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255};

const int sounddata_length[4]={sizeof(sounddata_data1),sizeof(sounddata_data2),sizeof(sounddata_data3),sizeof(sounddata_data4)};

ISR(TIMER1_COMPA_vect) { nextSample(); }

inline void nextSample()
{
    mixer = pgm_read_byte(&sounddata_data1[sample[0]]);
    mixer += pgm_read_byte(&sounddata_data2[sample[1]]);
    mixer += pgm_read_byte(&sounddata_data3[sample[2]]);
    mixer += pgm_read_byte(&sounddata_data4[sample[3]]);
    mixer /= 4;
    OCR2A = (unsigned char)mixer;
    
    for (char x=0; x<4; x++)
    {
      ++sample[x];
      if (sample[x] >= sounddata_length[x]) sample[x] = 0; // Loop //
    }
}

void setup()
{    
    pinMode(11, OUTPUT); // Speaker uses Pin 11 - this is fixed and can't be changed //
    ASSR &= ~(_BV(EXCLK) | _BV(AS2));
    TCCR2A |= _BV(WGM21) | _BV(WGM20);
    TCCR2B &= ~_BV(WGM22);
    TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
    TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
    TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
    cli();
    TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
    TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
    TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
    OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000
    nextSample();
    TIMSK1 |= _BV(OCIE1A);
    sei();
}

void loop()
{
    while (true);
}

Here I was able to mix 4 different pulse waveforms, each with a different pitch. There's room for optimization, I'm sure, so if anyone would like to give some hints... :wink:

I'm using a 16 bit variable for the mixer, but I wonder if there's a better way to handle that.

Also, the code runs at 44100hz, I haven't tested how many voices can be added before sample-rate must be lowered. :.

Wk

BTW: here I just wired a small speaker to Pin 11 without any external filter and it still sounds pretty nice. :fearful:

Wk

I wonder if it would be possible to change the code and have each voice output on its own pin? I see that the 2009 board has 6 PWM outputs, could I drive each pin to a different sample instead? That way I get a better resolution, compared to voice1+voice2+voice3+voice4/4 - Not to mention even 3 stereo outputs if I wanted. :wink:

The single 16-bit timer would be shared, just the PWM timer for each output wouldn't.

Wk

Ok, my final code has 6 voices at 32000hz, and it sounds pretty good. The problem is when more than one voice plays at the same time, resolution is reduced for the mixdown. So I'm still wanting to check if I could have 6 separated fast-pwm outputs, and how to manage the ASM code for that.

I got some nice sounds in the Flash area, and got some free room for more code.

Wk

can i make sequence with pcm data?