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