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/****************************************************************/