Simple Music Maker

Enter Your Electronics & Design Project for Your Chance to Win a Fog Machine and a $100 Shopping Cart!

Back to The Project14 homepage

Project14 Home
Monthly Themes
Monthly Theme Poll

 

"What harmony is this? My good friends, hark!"


Alonso. Tempest[III,3]. William Shakespeare.

 

Previous blogs

 

A Simple Arduino Music Box

Simple Arduino Music Box: Voices

 

Introduction

 

This is more on the Arduino music box that I did for the Project14  Simple Music Maker competition.

 

The original project played one note at a time. That's a bit tame - let's see if we can manage some polyphony and play some

chords. Although playing chords might seem to be considerably more complex, in practice it is quite easy and naturally falls

out of what I've done so far. To get multiple notes playing I just need a table index for each note and the appropriate step

size to give the required note. Then I simply sum the results and scale for the output. It is more processing, but it might

work, so let's give it a try.

 

The Sketch

 

Here's the sketch. The include at the start is

 

#include <Arduino.h>

 

/* Arduino Music Box: Chords */
#include <Arduino.h>
volatile unsigned int tableOffset1 = 0; // wave table index, note 1
volatile unsigned int tableOffset2 = 0; // wave table index, note 2
volatile unsigned int tableOffset3 = 0; // wave table index, note 3
volatile unsigned int tableOffset4 = 0; // wave table index, note 3
volatile unsigned int tableStep1 = 0;   // wave table step, note 1
volatile unsigned int tableStep2 = 0;   // wave table step, note 1
volatile unsigned int tableStep3 = 0;   // wave table step, note 1
volatile unsigned int tableStep4 = 0;   // wave table step, note 1
unsigned int noteTime = 0xc35;          // note duration count
unsigned int outputValue = 0;           // 
int i=0;
// Twinkle, Twinkle, Little Star
// Each line is four notes and a duration. 0 if no note.
// Line of 5 zeroes marks the end.
unsigned int tuneNotes[] = {
    343, 229, 144, 0, 0x4000,
    385, 229, 153, 0, 0x4000,
    343, 229, 144, 0, 0x4000,
    385, 229, 153, 0, 0x4000,
//
    343, 229, 144, 0, 0x4000,
    385, 216, 153, 0, 0x4000,
    343, 229, 144, 0, 0x8000,
//
    343, 229, 192, 0, 0x4000,
    306, 229, 0, 0, 0x4000,
    288, 229, 171, 0, 0x4000,
    229, 144, 0, 0, 0x4000,
//
    192, 162, 0, 0, 0x4000,
    306, 216, 128, 0, 0x4000,
    288, 229, 114, 0, 0x8000,
//
    171, 85, 0, 0, 0x4000,
    192, 96, 0, 0, 0x4000,
    229, 114, 0, 0, 0x4000,
    257, 128, 0, 0, 0x4000,
//
    288, 144, 0, 0, 0x4000,
    306, 162, 0, 0, 0x4000,
    343, 171, 0, 0, 0x8000,
//
    343, 216, 171, 144, 0x4000,
    288, 0, 0, 0, 0x4000,
    229, 192, 144, 96, 0x4000,
    306, 192, 162, 64, 0x4000,
//
    257, 216, 162, 108, 0x4000,
    257, 216, 162, 48, 0x4000,
    229, 144, 64, 0, 0x8000,
//    
    0,0,0,0,0
    };
//
//  --- Wave table - generated by waveTable.exe 
//
unsigned int waveTable[512] = {
   0x8000,0x862d,0x8c57,0x927a,0x9890,0x9e98,0xa48c,0xaa69,0xb02c,0xb5d1,0xbb55,0xc0b4,0xc5eb,0xcaf7,0xcfd5,0xd483,
   0xd8fd,0xdd41,0xe14d,0xe520,0xe8b6,0xec0e,0xef27,0xf1ff,0xf496,0xf6ea,0xf8fa,0xfac6,0xfc4f,0xfd93,0xfe93,0xff4f,
   0xffc8,0xfffe,0xfff4,0xffa8,0xff1e,0xfe57,0xfd54,0xfc17,0xfaa2,0xf8f8,0xf71a,0xf50c,0xf2d0,0xf069,0xedd9,0xeb24,
   0xe84d,0xe556,0xe243,0xdf17,0xdbd6,0xd883,0xd520,0xd1b2,0xce3c,0xcac0,0xc743,0xc3c8,0xc051,0xbce2,0xb97e,0xb628,
   0xb2e2,0xafb0,0xac94,0xa990,0xa6a8,0xa3dc,0xa130,0x9ea5,0x9c3e,0x99fb,0x97de,0x95e8,0x941b,0x9278,0x90fe,0x8fb0,
   0x8e8d,0x8d95,0x8cc8,0x8c27,0x8bb0,0x8b64,0x8b42,0x8b49,0x8b77,0x8bcd,0x8c47,0x8ce6,0x8da7,0x8e89,0x8f8a,0x90a8,
   0x91e1,0x9332,0x949a,0x9617,0x97a5,0x9943,0x9aee,0x9ca3,0x9e61,0xa024,0xa1eb,0xa3b1,0xa577,0xa737,0xa8f1,0xaaa2,
   0xac48,0xade0,0xaf69,0xb0e0,0xb243,0xb391,0xb4c8,0xb5e6,0xb6eb,0xb7d3,0xb89f,0xb94e,0xb9de,0xba4e,0xba9f,0xbad0,
   0xbae0,0xbad0,0xba9f,0xba4e,0xb9de,0xb94e,0xb89f,0xb7d3,0xb6eb,0xb5e6,0xb4c8,0xb391,0xb243,0xb0e0,0xaf69,0xade0,
   0xac48,0xaaa2,0xa8f1,0xa737,0xa577,0xa3b1,0xa1eb,0xa024,0x9e61,0x9ca3,0x9aee,0x9943,0x97a5,0x9617,0x949a,0x9332,
   0x91e1,0x90a8,0x8f8a,0x8e89,0x8da7,0x8ce6,0x8c47,0x8bcd,0x8b77,0x8b49,0x8b42,0x8b64,0x8bb0,0x8c27,0x8cc8,0x8d95,
   0x8e8d,0x8fb0,0x90fe,0x9278,0x941b,0x95e8,0x97de,0x99fb,0x9c3e,0x9ea5,0xa130,0xa3dc,0xa6a8,0xa990,0xac94,0xafb0,
   0xb2e2,0xb628,0xb97e,0xbce2,0xc051,0xc3c8,0xc743,0xcac0,0xce3c,0xd1b2,0xd520,0xd883,0xdbd6,0xdf17,0xe243,0xe556,
   0xe84d,0xeb24,0xedd9,0xf069,0xf2d0,0xf50c,0xf71a,0xf8f8,0xfaa2,0xfc17,0xfd54,0xfe57,0xff1e,0xffa8,0xfff4,0xffff,
   0xffc8,0xff4f,0xfe93,0xfd93,0xfc4f,0xfac6,0xf8fa,0xf6ea,0xf496,0xf1ff,0xef27,0xec0e,0xe8b6,0xe520,0xe14d,0xdd41,
   0xd8fd,0xd483,0xcfd5,0xcaf7,0xc5eb,0xc0b4,0xbb55,0xb5d1,0xb02c,0xaa69,0xa48c,0x9e98,0x9890,0x927a,0x8c57,0x862d,
   0x8000,0x79d2,0x73a8,0x6d85,0x676f,0x6167,0x5b73,0x5596,0x4fd3,0x4a2e,0x44aa,0x3f4b,0x3a14,0x3508,0x302a,0x2b7c,
   0x2702,0x22be,0x1eb2,0x1adf,0x1749,0x13f1,0x10d8,0x0e00,0x0b69,0x0915,0x0705,0x0539,0x03b0,0x026c,0x016c,0x00b0,
   0x0037,0x0001,0x000b,0x0057,0x00e1,0x01a8,0x02ab,0x03e8,0x055d,0x0707,0x08e5,0x0af3,0x0d2f,0x0f96,0x1226,0x14db,
   0x17b2,0x1aa9,0x1dbc,0x20e8,0x2429,0x277c,0x2adf,0x2e4d,0x31c3,0x353f,0x38bc,0x3c37,0x3fae,0x431d,0x4681,0x49d7,
   0x4d1d,0x504f,0x536b,0x566f,0x5957,0x5c23,0x5ecf,0x615a,0x63c1,0x6604,0x6821,0x6a17,0x6be4,0x6d87,0x6f01,0x704f,
   0x7172,0x726a,0x7337,0x73d8,0x744f,0x749b,0x74bd,0x74b6,0x7488,0x7432,0x73b8,0x7319,0x7258,0x7176,0x7075,0x6f57,
   0x6e1e,0x6ccd,0x6b65,0x69e8,0x685a,0x66bc,0x6511,0x635c,0x619e,0x5fdb,0x5e14,0x5c4e,0x5a88,0x58c8,0x570e,0x555d,
   0x53b7,0x521f,0x5096,0x4f1f,0x4dbc,0x4c6e,0x4b37,0x4a19,0x4914,0x482c,0x4760,0x46b1,0x4621,0x45b1,0x4560,0x452f,
   0x451f,0x452f,0x4560,0x45b1,0x4621,0x46b1,0x4760,0x482c,0x4914,0x4a19,0x4b37,0x4c6e,0x4dbc,0x4f1f,0x5096,0x521f,
   0x53b7,0x555d,0x570e,0x58c8,0x5a88,0x5c4e,0x5e14,0x5fdb,0x619e,0x635c,0x6511,0x66bc,0x685a,0x69e8,0x6b65,0x6ccd,
   0x6e1e,0x6f57,0x7075,0x7176,0x7258,0x7319,0x73b8,0x7432,0x7488,0x74b6,0x74bd,0x749b,0x744f,0x73d8,0x7337,0x726a,
   0x7172,0x704f,0x6f01,0x6d87,0x6be4,0x6a17,0x6821,0x6604,0x63c1,0x615a,0x5ecf,0x5c23,0x5957,0x566f,0x536b,0x504f,
   0x4d1d,0x49d7,0x4681,0x431d,0x3fae,0x3c37,0x38bc,0x353f,0x31c3,0x2e4d,0x2adf,0x277c,0x2429,0x20e8,0x1dbc,0x1aa9,
   0x17b2,0x14db,0x1226,0x0f96,0x0d2f,0x0af3,0x08e5,0x0707,0x055d,0x03e8,0x02ab,0x01a8,0x00e1,0x0057,0x000b,0x0000,
   0x0037,0x00b0,0x016c,0x026c,0x03b0,0x0539,0x0705,0x0915,0x0b69,0x0e00,0x10d8,0x13f1,0x1749,0x1adf,0x1eb2,0x22be,
   0x2702,0x2b7c,0x302a,0x3508,0x3a14,0x3f4b,0x44aa,0x4a2e,0x4fd3,0x5596,0x5b73,0x6167,0x676f,0x6d85,0x73a8,0x79d2};
void setup() {
  
  // set the digital pins as outputs:
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  
  pinMode(8, OUTPUT);     // test output
 
  // timer 2 set up
  cli();                  // disable interrupts
  TCCR2A = 0;             // control register all 0
  TCCR2B = 0;             // control register all 0
  TCNT2 = 0;              // set count to 0
//  OCR2A = 159;            // period = 160 x 1/16MHz = 10uS [100ksps]
  OCR2A = 239;            // period = 240 x 1/16MHz = 15uS [66.6ksps]
  TCCR2A |= (1 << WGM21); // mode is clear on match
  TCCR2B |= (1 << CS20);  // no prescaler   
  TIMSK2 |= (1 << OCIE2A);  // enable interrupt on match
  sei();                  // enable interrupts
}
ISR(TIMER2_COMPA_vect) {
  PORTB |= 0x01;   
  if(tableStep1 == 0) {
    PORTD = 0x80;
    noteTime = noteTime - 1;
    if(noteTime==0) {
      tableStep1 = tuneNotes[i++];
      tableStep2 = tuneNotes[i++];
      tableStep3 = tuneNotes[i++];
      tableStep4 = tuneNotes[i++];
      noteTime = tuneNotes[i++];
      tableOffset1 = 0;
      tableOffset2 = 0;
      tableOffset3 = 0;
      }
    }
  else {
    tableOffset1 = tableOffset1 + tableStep1;
    tableOffset2 = tableOffset2 + tableStep2;
    tableOffset3 = tableOffset3 + tableStep3;
    tableOffset4 = tableOffset4 + tableStep4;
    outputValue = waveTable[tableOffset1 >> 7] >> 8;
    outputValue = outputValue + (waveTable[tableOffset2 >> 7] >> 8);
    outputValue = outputValue + (waveTable[tableOffset3 >> 7] >> 8);
    outputValue = outputValue + (waveTable[tableOffset4 >> 7] >> 8);
    PORTD = outputValue >> 2;
    noteTime = noteTime - 1;
    if(noteTime==0) {
      tableOffset1 = 0;
      tableOffset2 = 0;
      tableOffset3 = 0;
      tableOffset4 = 0;
      tableStep1 = 0;
      tableStep2 = 0;
      tableStep3 = 0;
      tableStep4 = 0;
      noteTime = 0xc35;
      if (tuneNotes[i]==0)
        i=0;
      }
    }
    PORTB &= 0xFE; 
}
void loop() {
}

 

It's much as I had before except everything is done four times over - four step additions and four look-ups. The division by

four of the summed values, to scale the final result, is done with a shift. It does work, but the time it takes is around

the same as the 10uS that the timer period was set to. To give it a bit more space, I've slowed the sample rate down to

66.6ksps (15uS). That transposes all my notes down and slows the note durations but it still sounds ok (I'm feeling too lazy

to go back and recalculate them). The relationships between the notes remain the same, so unless you have perfect pitch you

won't know that the notes are now wrong.

 

The way I knew that it was taking all of the processing time was to set an output pin at the start and reset it at the end

of the interrupt code. That's the write to PORTB that you can see in the code. Here's the scope trace, but with a longer

timer period. Bear in mind that the interrupt needs time for entry and exit too, so it was over-running the 10uS.

 

 

An Example

 

And here's what it sounds like. This is the Twinkle, Twinkle tune that I used for the original music box but played with

chords rather than just the melody line. It's now much more complex in sound, even though it's all a bit basic in hardware

terms (keep in mind we're at the grunge end of the hardware spectrum, not the hi-fi end).

 

 

I'm quite proud of that, even though it is just a silly piece of hackery.

 

Next thing to look at is adding an envelope generator: Simple Arduino Music Box: Envelopes