11.14. MusicSequenceDemo Arduino Sketch¶
This sketch is used by Exercise: Music Sequencer.
11.14.1. Full Source Code¶
The full code is all in one file MusicSequenceDemo.ino.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | // MusicSequenceDemo.ino : demonstrate generation of two simultaneous tones at different rates and patterns
// The example program generates audio-frequency square waves at different
// pitches and patterns on pins 4 and 5 to demonstrate an event-loop control
// structure allowing parallel execution of multiple timed tasks with pattern
// generation.
// Define the pin numbers on which the outputs are generated.
const int outputPin1 = 4;
const int outputPin2 = 5;
/****************************************************************/
// Define the rhythm patterns for the two outputs using a simple pattern language.
// Note values:
// G = high pitch
// C = low pitch
// R = rest
// Note durations:
// q = quarter note
// e = eighth note
// s = sixteenth note
// A note value symbol affects the pitch generated by the successive note duration symbols.
// Any unknown symbol (including spaces) are ignored.
const char *rhythm1 = "Cq Rq Cq Rq Ge Re Ge Re Ge Re Ge Re Gq Rq Ge Rq";
const char *rhythm2 = "Cs Rs Cs Rs Cs Rs Cs Rs Ge Re Ge Re";
// Define the timing constants for the rhythmic elements.
const long quarter_duration = 500000; // 120 bpm
/****************************************************************/
// Define the timing constants for the audio output.
// The low pitch is middle-C, the high pitch is the G a fifth above it. Given
// A3 of 220 Hz and equal temperament, middle C4 has a frequency
// 220*pow(2, 3.0/12) = 261.626 Hz.
// The half-period in microseconds is 1e6/(2*261.626), rounded to an integer:
const long low_pitch_half_period = 1911;
// The just intonation ratio for a musical fifth is 3/2, so
// G4 = 1.5*261.626 = 392.438 Hz, and the half period duration in
// microseconds is 1e6/(2*392.438):
const long high_pitch_half_period = 1274;
/****************************************************************/
// C++ class to generate a rhythmic sound pattern on a single output.
class MelodyGenerator {
private:
// number of the pin to use for output
int output_pin;
// current output state
int output_value;
/// the time elapsed in microseconds since the last waveform update occurred
unsigned long tone_elapsed;
/// the time elapsed in microseconds since the last pattern update occurred
unsigned long pattern_elapsed;
// interval between output waveform transitions in microseconds
long tone_interval;
// flag which indicates that no tone is generated
bool resting;
// interval between pattern transitions in microseconds
long pattern_interval;
// current pattern string
const char *pattern_string;
// current position within the pattern string
int pattern_pos;
public:
// Constructor to initialize an instance of the class. This does not
// configure the hardware, only the internal state.
MelodyGenerator( int pin, const char *pattern );
// Update function to be called as frequently as possible to generate the
// output. It requires the number of microseconds elapsed since the last
// update.
void update(long interval);
};
// Constructor for an instance of the class.
MelodyGenerator::MelodyGenerator(int pin, const char *pattern)
{
// initialize the state variables
output_pin = pin;
output_value = LOW;
tone_elapsed = 0;
pattern_elapsed = 0;
tone_interval = low_pitch_half_period;
resting = false;
pattern_interval = quarter_duration;
pattern_string = pattern;
pattern_pos = 0;
}
// Update polling function for an instance of the class.
void MelodyGenerator::update(long interval)
{
// Check whether the next transition time has been reached, and if so, update
// the state and hardware output.
tone_elapsed += interval;
if (tone_elapsed >= tone_interval) {
// Reset the timer according to the desired interval to produce a correct
// average rate even if extra time has passed.
tone_elapsed -= tone_interval;
// Update the output pin to generate the audio waveform.
output_value = !output_value;
if (!resting) {
// cycle the speaker to create a tone
digitalWrite( output_pin, output_value);
} else {
// resting, turn speaker off
digitalWrite( output_pin, LOW);
}
}
//-----------------------------------------------
// Check whether the pattern interval has expired.
pattern_elapsed += interval;
if (pattern_elapsed >= pattern_interval) {
pattern_elapsed -= pattern_interval;
// Process one or more symbols from the rhythm pattern. This will process
// any note value symbols until a note duration symbol is reached.
for(;;) {
char next_symbol = pattern_string[pattern_pos];
// Advance counter to next pattern string position.
pattern_pos++;
// if the next symbol is the end of the string, recycle to the beginning.
if (next_symbol == 0) {
pattern_pos = 0;
continue;
} else if (next_symbol == 'G') {
tone_interval = high_pitch_half_period;
resting = false;
continue;
} else if (next_symbol == 'C') {
tone_interval = low_pitch_half_period;
resting = false;
continue;
} else if (next_symbol == 'R') {
resting = true;
continue;
} else if (next_symbol == 'q') {
pattern_interval = quarter_duration;
break; // leave the symbol-reading loop
} else if (next_symbol == 'e') {
pattern_interval = quarter_duration / 2;
break; // leave the symbol-reading loop
} else if (next_symbol == 's') {
pattern_interval = quarter_duration / 4;
break; // leave the symbol-reading loop
} else {
// all other symbols are ignored
continue;
}
}
}
}
/****************************************************************/
// Global variables.
// Declare two instances of the pattern generator.
MelodyGenerator generator1( outputPin1, rhythm1 );
MelodyGenerator generator2( outputPin2, rhythm2 );
// The timestamp in microseconds for the last polling cycle, used to compute
// the exact interval between output updates.
unsigned long last_update_clock = 0;
/****************************************************************/
/****************************************************************/
// This function is called once after reset to initialize the program.
void setup()
{
// Initialize two digital output pins, one for each pattern generator.
pinMode( outputPin1, OUTPUT );
pinMode( outputPin2, OUTPUT );
}
/****************************************************************/
// This function is called repeatedly as fast as possible from within the
// built-in library to poll program events.
void loop()
{
// The timestamp in microseconds for the last polling cycle, used to compute
// the exact interval between output updates.
static unsigned long last_update_clock = 0;
// Read the microsecond clock.
unsigned long now = micros();
// Compute the time elapsed since the last poll. This will correctly handle wrapround of
// the 32-bit long time value given the properties of twos-complement arithmetic.
unsigned long interval = now - last_update_clock;
last_update_clock = now;
// update the pattern generators
generator1.update(interval);
generator2.update(interval);
}
/****************************************************************/
|