/*
* speaker_pcm_sequencer
*
* modification by D. Cuartielles at iMAL, Brussels. This code will
* use 7 sound sequences of 1000 samples each to create a sequencer.
*
* v0006 - release notes:
*
* - iMAL performance tool
*
* v0005 - release notes:
*
* - master volume control --> wish, not there
*
* v0004 - release notes:
*
* - STEREO WORKS (using channels 3 for Right and 11 for Left)
*
* v0003 - release notes:
*
* - what about STEREO? ;-) --> weird sound mixing on both channels
*
* v0002 - release notes:
*
* - includes a tracker with the sounds to play as an array
*
* Plays 8-bit PCM audio on pin 11 using pulse-width modulation (PWM).
* For Arduino with Atmega168 at 16 MHz.
*
* Uses two timers. The first changes the sample value 8000 times a second.
* The second holds pin 11 high for 0-255 ticks out of a 256-tick cycle,
* depending on sample value. The second timer repeats 62500 times per second
* (16000000 / 256), much faster than the playback rate (8000 Hz), so
* it almost sounds halfway decent, just really quiet on a PC speaker.
*
* Takes over Timer 1 (16-bit) for the 8000 Hz timer. This breaks PWM
* (analogWrite()) for Arduino pins 9 and 10. Takes Timer 2 (8-bit)
* for the pulse width modulation, breaking PWM for pins 11 & 3.
*
* References:
* http://www.uchobby.com/index.php/2007/11/11/arduino-sound-part-1/
* http://www.atmel.com/dyn/resources/prod_documents/doc2542.pdf
* http://www.evilmadscientist.com/article.php/avrdac
* http://gonium.net/md/2006/12/27/i-will-think-before-i-code/
* http://fly.cc.fer.hr/GDM/articles/sndmus/speaker2.html
* http://www.gamedev.net/reference/articles/article442.asp
*
* Michael Smith <michael@hurts.ca>
*/
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#define SAMPLE_RATE 8000
/*
* The audio data needs to be unsigned, 8-bit, 8000 Hz, and small enough
* to fit in flash. 10000-13000 samples is about the limit.
*
* sounddata.h should look like this:
* const int sounddata_length=10000;
* const unsigned char sounddata_data[] PROGMEM = { ..... };
*
* You can use wav2c from GBA CSS:
* http://thieumsweb.free.fr/english/gbacss.html
* Then add "PROGMEM" in the right place. I hacked it up to dump the samples
* as unsigned rather than signed, but it shouldn't matter.
*
* http://musicthing.blogspot.com/2005/05/tiny-music-makers-pt-4-mac-startup.html
* mplayer -ao pcm macstartup.mp3
* sox audiodump.wav -v 1.32 -c 1 -r 8000 -u -1 macstartup-8000.wav
* sox macstartup-8000.wav macstartup-cut.wav trim 0 10000s
* wav2c macstartup-cut.wav sounddata.h sounddata
*/
#include "sound.h"
int ledPin = 13;
int speakerPinL = 11;
int speakerPinR = 3;
volatile uint16_t sampleL;
volatile uint16_t sampleR;
byte lastSampleL;
byte lastSampleR;
byte currentSound = 0;
byte playingSample = false;
byte silence = '.';
byte trackerL[] = "........";
byte trackerR1[] = "1...1...";
byte trackerR2[] = "12..1...";
byte trackerR3[] = "1...1.1.";
byte trackerR4[] = "1...121.";
byte trackerR5[] = "5555....";
byte trackerR[] = "1234560.";
int sizeof_tracker = 8;
// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect) {
byte stopPlayingSample = false;
// LEFT
if (trackerL[currentSound] != silence) {
if (sampleL >= sounddata_length*(trackerL[currentSound]-48 + 1)) {
if (sampleL == sounddata_length*(trackerL[currentSound]-48 + 1) + lastSampleL) {
stopPlayingSample = true;
}
else {
// Ramp down to zero to reduce the click at the end of playback.
OCR2A = sounddata_length*(trackerL[currentSound]-48 + 1) + lastSampleL - sampleL;
}
}
else {
OCR2A = pgm_read_byte(&sounddata_data[sampleL]);
}
} else {
OCR2A = 0;
if (sampleL >= sounddata_length + lastSampleL) {
stopPlayingSample = true;
}
}
// INCREASE COUNTERS
++sampleL;
if (stopPlayingSample) stopPlaybackL();
}
ISR(TIMER1_COMPB_vect) {
byte stopPlayingSample = false;
// RIGHT
if (trackerR[currentSound] != silence) {
if (sampleR >= sounddata_length*(trackerR[currentSound]-48 + 1)) {
if (sampleR == sounddata_length*(trackerR[currentSound]-48 + 1) + lastSampleR) {
stopPlayingSample = true;
}
else {
// Ramp down to zero to reduce the click at the end of playback.
OCR2B = sounddata_length*(trackerR[currentSound]-48 + 1) + lastSampleR - sampleR;
}
}
else {
OCR2B = pgm_read_byte(&sounddata_data[sampleR]);
}
} else {
OCR2B = 0;
if (sampleR >= sounddata_length + lastSampleR) {
stopPlayingSample = true;
}
}
// INCREASE COUNTERS
++sampleR;
if (stopPlayingSample) stopPlaybackR();
}
void startPlayback(int theSoundL, int theSoundR)
{
pinMode(speakerPinL, OUTPUT);
pinMode(speakerPinR, OUTPUT);
playingSample = true;
// Set up Timer 2 to do pulse width modulation on the speaker
// pin.
// Use internal clock (datasheet p.160)
ASSR &= ~(_BV(EXCLK) | _BV(AS2));
// Set fast PWM mode (p.157)
TCCR2A |= _BV(WGM21) | _BV(WGM20);
TCCR2B &= ~_BV(WGM22);
// Do non-inverting PWM on pin OC2A (p.155)
// On the Arduino this is pin 11.
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
// TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0);
// No prescaler (p.158)
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set initial pulse width to the first sample.
if (theSoundL != silence) OCR2A = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundL-48)]);
else OCR2A = 0;
if (theSoundR != silence) OCR2B = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundR-48)]);
else OCR2B = 0;
// Set up Timer 1 to send a sample every interrupt.
cli();
// Set CTC mode (Clear Timer on Compare Match) (p.133)
// Have to set OCR1A *after*, otherwise it gets reset to 0!
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// No prescaler (p.134)
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set the compare register (OCR1A).
// OCR1A/OCR1B are 16-bit registers, so we have to do this with
// interrupts disabled to be safe.
OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000
OCR1B = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000
// Enable interrupt when TCNT1 == OCR1A OR OCR1B (p.136)
TIMSK1 |= _BV(OCIE1A) | _BV(OCIE1B);
if (theSoundL != silence) {
lastSampleL = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundL-48 + 1)-1]);
sampleL = sounddata_length*(theSoundL-48);
} else {
lastSampleL = 128;
sampleL = 0;
}
if (theSoundR != silence) {
lastSampleR = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundR-48 + 1)-1]);
sampleR = sounddata_length*(theSoundR-48);
} else {
lastSampleR = 128;
sampleR = 0;
}
sei();
}
void stopPlaybackL()
{
// Disable playback per-sample interrupt.
TIMSK1 &= ~_BV(OCIE1A);
// Disable the per-sample timer completely.
TCCR1B &= ~_BV(CS10);
// Disable the PWM timer.
TCCR2B &= ~_BV(CS10);
digitalWrite(speakerPinL, LOW);
playingSample = false;
}
void stopPlaybackR()
{
// Disable playback per-sample interrupt.
TIMSK1 &= ~_BV(OCIE1B);
// Disable the per-sample timer completely.
TCCR1B &= ~_BV(CS10);
// Disable the PWM timer.
TCCR2B &= ~_BV(CS10);
digitalWrite(speakerPinR, LOW);
playingSample = false;
}
void copySequence(int num) {
switch (num) {
case 1:
for (int counter = 0; counter < sizeof_tracker; counter++) {
trackerR[counter] = trackerR1[counter];
}
break;
case 2:
for (int counter = 0; counter < sizeof_tracker; counter++) {
trackerR[counter] = trackerR2[counter];
}
break;
case 3:
for (int counter = 0; counter < sizeof_tracker; counter++) {
trackerR[counter] = trackerR3[counter];
}
break;
case 4:
for (int counter = 0; counter < sizeof_tracker; counter++) {
trackerR[counter] = trackerR4[counter];
}
break;
case 5:
for (int counter = 0; counter < sizeof_tracker; counter++) {
trackerR[counter] = trackerR5[counter];
}
break;
default:
for (int counter = 0; counter < sizeof_tracker; counter++) {
trackerR[counter] = silence;
}
break;
}
}
void setup()
{
pinMode(ledPin, OUTPUT);
for (int i = 4; i <= 8; i++) {
pinMode(i, INPUT);
digitalWrite(i, HIGH);
}
Serial.begin(9600);
}
void loop()
{
if (!playingSample) {
if (Serial.available()) {
int inByte = Serial.read();
switch (inByte) {
case 'L':
for (int counter = 0; counter < sizeof_tracker; counter++) {
while (!Serial.available()) {};
trackerL[counter] = Serial.read();
Serial.print(trackerL[counter]);
}
Serial.println();
break;
case 'R':
for (int counter = 0; counter < sizeof_tracker; counter++) {
while (!Serial.available()) {};
trackerR[counter] = Serial.read();
Serial.print(trackerR[counter]);
}
Serial.println();
break;
case '1':
copySequence(1);
break;
case '2':
copySequence(2);
break;
case '3':
copySequence(3);
break;
case '4':
copySequence(4);
break;
case '5':
copySequence(5);
break;
case '+':
break;
case '-':
break;
case 's':
for (int counter = 0; counter < sizeof_tracker; counter++) trackerL[counter] = silence;
for (int counter = 0; counter < sizeof_tracker; counter++) trackerR[counter] = silence;
break;
default:
break;
}
}
if (!digitalRead(4)) {
copySequence(1);
} else
if (!digitalRead(5)) {
copySequence(2);
} else
if (!digitalRead(6)) {
copySequence(3);
} else
if (!digitalRead(7)) {
copySequence(4);
} else
if (!digitalRead(8)) {
copySequence(5);
}
if (!digitalRead(4) && !digitalRead(5)) copySequence(0); // go silent
currentSound++;
currentSound%=sizeof_tracker;
//Serial.println(currentSound,DEC);
//if (tracker[currentSound] != '.')
startPlayback(trackerL[currentSound], trackerR[currentSound]);
//else
// startPlayback(-1);
}
}