Enter Your Electronics & Design Project for Your Chance to Win a Fog Machine and a $100 Shopping Cart! | Project14 Home | |
Monthly Themes | ||
Monthly Theme Poll |
"What harmony is this? My good friends, hark!"
Alonso. Tempest[III,3]. William Shakespeare.
Previous blogs
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
Top Comments