3.11. MusicSequenceDemo Arduino Sketch

This sketch is used by Exercise: Music Sequencer.

3.11.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
// 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) {
      digitalWrite( output_pin, output_value);
    } else {
      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);
}
/****************************************************************/