// 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() { // 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); } /****************************************************************/