Notes From James
Home | About | Find Stuff | Download
Arduino music   James - Friday, December 30, 2011, 8:11 pm

Here is a nice code sample that I put together that plays music on the Arduino. Just hook up a speaker to pin 8, add a resistor if necessary, and it will play a few songs, one with multiple voices (polyphonic music).


// Arduino Music Demo
// James S. Ghofulpo
//
// Plays multi voice music.  Documentation for writing your own music is below.

#define TS         ( 130 ) // Time for a 1/16th note, in ms
#define STACATTO   ( 80 )  // Time for a stacatto note, in ms
#define OUTPUT_PIN ( 8 )   // Which pin on the Arduino are we modulating
#define NUM_VOICES ( 2 )   // Number of voices supported
#define MAX_CYCLES ( 10 )  // On and off cycles before switch

// Data structure for each voice

typedef struct {
     float f; // Frequency
     long  t; // Time left to play this note
     long  decay_time; // Time left to decay (turn off) note
     long  on_pt;  // On phase time
     long  off_pt; // Off phase time
     int   synchronizing; // Set to 1 if we are waiting to synchronize
     int   current_voice; // which voice is being analyzed in the muisc
     const char * music;
     const char * start_of_music;
     const char * return_pos;
} voice_info_t;

// Function to play a song
//
// Tone Commands:
// a-g - Select low octave note
// A-G - Select high octave note
// +   - This note, up one octave
// -   - This note, down one octave
// #   - Sharp
// &   - Flat
// t   - Note is a triplet (1/3 of timing)
// .   - Note is dotted (timing * 1.5)
// ,   - Note is stacatto
// r   - Rest
// s, e, q, h, w - Timing (1/16, 1/8, 1/4, 1/2, 1) of note.  Note plays when time is selected!
//
// Voice / flow commands:
//
// V  - This is part of the next voice
// >X - Call part X
// [X yyyyy] - Define part x to be yyyyy
// |  - End of measure.  Allows for synchronization, in case of misprogrammed music.
// k  - The previously mentioned note selects the key signature (i.e. fk selects F major).

void play_song( const char * this_song )
{
   int inx = 0;
   int iny;
   int duration = 0;
   int note_offset = -1;
   voice_info_t info[NUM_VOICES]; // Each voices' info
   int triplet = 0;
   int key[12];
   long shortest_time = 999999;
   int current_phase = 0;
   long current_time;
   int in_combo;
   long time_in_micros;
   float on_duty_cycle = 0.1;
   int duty_cycle_direction = 0;
   int current_voice = 0;
   int cycle_count = 0;
   int dotted = 0;
   int stacatto = 0;
   char search_target;
   int everybody_is_done = 0;
   int accidental = 0;
      
   for ( inx = 0; inx < NUM_VOICES; inx++ )
   {
       info[inx].music = this_song;
       info[inx].start_of_music = this_song;
       info[inx].return_pos = this_song;
       info[inx].f = 0;
       info[inx].t = 0;
       info[inx].decay_time = 0;
       info[inx].on_pt = 0; // Frequency of this voice
       info[inx].off_pt = 0;
       info[inx].synchronizing = 0;
       info[inx].current_voice = 0;
   }
   
   // Initialize key to C major
   
   for ( inx = 0; inx < 12; inx++ )
   {
       key[inx] = 0;
   }
   
   //Serial.println( "Starting loop" );
   
   while ( !everybody_is_done )
   {       
       int everybody_is_synchronizing = 1;
       everybody_is_done = 1; // Assume we are all done
       
       for ( inx = 0; inx < NUM_VOICES; inx++ ) 
       {
           if ( *info[inx].music != 0 )
           {
               everybody_is_done = 0;
          
               if ( !info[inx].synchronizing ) // Somebody is still running...
               {
                   everybody_is_synchronizing = 0;
                   break;
               }
           }           
       }
       
       // If everybody is synchronizing, then we can continue.
       
       if ( everybody_is_synchronizing )
       {
           for ( inx = 0; inx < NUM_VOICES; inx++ ) 
           {
               info[inx].synchronizing = 0;
           }
       }
       
       // Code to check for the next note/sets of notes
       
       for ( inx = 0; inx < NUM_VOICES; inx++ ) // Check all voices
       {
           while (  ( info[inx].t <= 0 ) // out of time, get next note
                 && ( *info[inx].music != 0 ) // Still music left to play
                 && ( !info[inx].synchronizing ) ) // And not waiting for everybody to be ready
           {
               // Serial.print( *info[inx].music );
               
               switch( *info[inx].music )
               {
                   case '[': // Skip section  [ music ]                   
                       while ( ( *info[inx].music != 0 ) && ( *info[inx].music != ']' ) )
                       { 
                           info[inx].music++;
                       }
                   break;
                   case ']':
                       info[inx].synchronizing = 1; // Wait for everybody to get done
                       info[inx].music = info[inx].return_pos; 
                       info[inx].current_voice = 0;
                   break;
                   case 'V': // End of this part
                       info[inx].current_voice++;
                       break; // Do nothing: End marker
                   case '>': // Call a subroutine
                       info[inx].music++; // Get the marker
                       search_target = *info[inx].music;
                       info[inx].return_pos = info[inx].music; // The return position will be +1
                       info[inx].music = info[inx].start_of_music; // Rewind
                       while ( ( *info[inx].music != '[' ) || ( *(info[inx].music+1) != search_target ) )
                       {
                           info[inx].music++;
                       }
                       info[inx].music++; // Skip the bracket
                       info[inx].music++; // Skip the marker      
                       info[inx].current_voice = 0; // Starting with voice 0    
                       info[inx].synchronizing = 1; // Wait for everybody to get done
                       break;
                   case '|': // Force synchronize between voices             
                       if ( info[inx].current_voice == inx )
                       { 
                           info[inx].synchronizing = 1; // Wait for everybody to get done
                       }
                   break;
                   case 'c': note_offset = 15; break;
                   case 'd': note_offset = 17; break;
                   case 'e': if ( note_offset == -1 )
                             note_offset = 19;
                         else
                             duration = TS * 2; // Eighth
                   break;
                   case 'f': note_offset = 20; break;
                   case 'g': note_offset = 22; break;
                   case 'a': note_offset = 24; break;
                   case 'b': note_offset = 26; break;
                   case 'C': note_offset = 27; break;
                   case 'D': note_offset = 29; break;
                   case 'E': note_offset = 31; break;
                   case 'F': note_offset = 32; break;
                   case 'G': note_offset = 34; break;   
                   case 'A': note_offset = 36; break;
                   case 'B': note_offset = 38; break;        
                   case '+': note_offset += 12; break; // Up an octave
                   case '-': note_offset -= 12; break; // Down an octave
                   case '#': accidental = 1; break; // Up a half step
                   case '&': accidental = -1; break; // Down a half step
                   case 's': duration = TS; break;     // Sixteenth
                   case 'q': duration = TS * 4; break; // Quarter
                   case 'h': duration = TS * 8; break; // Half
                   case 'w': duration = TS * 16; break; // Whole
                   case 'r': note_offset = -2; break;  // Rest, not really necessary, except for eighth notes
                   case 't': triplet = 1; break; // Duration is a triplet
                   case '.': dotted = 1; break; // Dotted value
                   case ',': stacatto = 1; break; // Stacatto
                   // default: break; // Unhandled command
               }
       
               if ( *info[inx].music == 'k' ) // Handle keys
               {
                   int base = note_offset % 12;
            
                   for ( iny = 0; iny < 12; iny++ )
                   {
                        key[iny] = 0;
                   }
                   
                   int num_sharps = 0;
                   int num_flats = 0;
            
                   switch ( base )
                   {
                       case 0: key[3] = key[8] = key[10] = 1; break; // A
                       case 1: key[2] = key[7] = -1; break;
                       case 2: key[0] = key[3] = key[5] = key[8] = key[10] = 1; break; // B
                       case 3: break; // C
                       case 4: key[0] = key[2] = key[5] = key[7] = key[10] = -1; break;
                       case 5: key[3] = key[8] = 1; break; // D
                       case 6: key[0] = key[2] = key[7] = -1; break;
                       case 7: key[3] = key[5] = key[8] = key[10] = 1; break; // E
                       case 8: key[2] = -1; break; // F
                       case 9: key[0] = key[2] = key[3] = key[5] = key[7] = key[10] = -1; break;
                       case 10: key[8] = 1; break; // G
                       case 11: key[0] = key[2] = key[5] = key[7] = -1; break;                
                   }      
         
                   note_offset = -1;
               }
               
               if ( duration != 0 ) // Note is complete
               {
                   if ( triplet )
                   {
                       duration /= 3;
                   }
                   
                   if ( dotted )
                   {
                       duration = ((float)duration * 1.5);
                   }
                
                   if ( info[inx].current_voice == inx ) // This note is for this voice
                   {   
                       info[inx].t = millis() + duration;
                       info[inx].decay_time = info[inx].t - ( stacatto ? duration - STACATTO : 0 );
                       int base = note_offset % 12;               
                   
                       // Sample code to add phasing effects to music
                   /*
                   if ( duty_cycle_direction == 0 )
                   {
                       on_duty_cycle += 0.1;
                       
                       if ( on_duty_cycle > 0.8 )
                       {
                            duty_cycle_direction = 1;
                       }
                   }
                   else
                   {
                       on_duty_cycle -= 0.1;
                       
                       if ( on_duty_cycle < 0.2 )
                       {
                            duty_cycle_direction = 0;
                       }
                   }                   
                   */                     
               
                       if ( note_offset >= 0 )
                       {
                           info[inx].f = pow( 1.059460646483, note_offset + key[base] + accidental ) * 220.0;
                           float pt = 1000000 / ( info[inx].f );
                           
                           // The on phase time can be set here to add 'phasing' type effects
                       /*
                       on_pt[inx] = (long)( pt * on_duty_cycle );    
                       if ( on_pt[inx] < 50 )
                       {
                          on_pt[inx] = 50;
                       }   
                       on_pt[inx] = (long)( pt * on_duty_cycle );     */
                           info[inx].on_pt  = 50;   
                           info[inx].off_pt =(long)(  pt - info[inx].on_pt );
                       }
                       else // Rest
                       {
                           info[inx].f = 0;
                       }                   
                   } // Else, this note is not for us
                   
                   // Reset parser
                   note_offset = -1;
                   duration = 0;
                   triplet = 0;
                   dotted = 0;
                   stacatto = 0;
                   accidental = 0;
               }
               
               info[inx].music++; // Next character
           }
       } // Checked all voices

       // Play the note / set of tones, until end of somebody's note
       
       in_combo = 1;
    
       while ( in_combo )
       {               
           time_in_micros = micros();
           
           current_phase = !current_phase;
           
           if ( info[current_voice].f == 0 ) // Rest all voices
           {
               digitalWrite( OUTPUT_PIN, LOW );
           }
           else
           {          
               digitalWrite( OUTPUT_PIN, current_phase ? HIGH : LOW ); 
           }
           
           if ( cycle_count++ > MAX_CYCLES ) // Check if we need to change voices
           {
               cycle_count = 0;
               current_time = millis();
               int start_voice = current_voice;
               
               do
               {
                   current_voice = ( current_voice + 1 ) % NUM_VOICES;
               }
               while ( ( info[current_voice].f == 0 ) && ( current_voice != start_voice ) );
               
               for ( inx = 0; inx < NUM_VOICES; inx++ )
               {
                   if ( current_time > info[inx].t ) // If this note has expired
                   {
                       info[inx].t = 0; // Request the next note
                       info[inx].f = 0; // Turn off the note
                       in_combo = 0; // Get next note                       
                   }
                   else if ( current_time > info[inx].decay_time ) // stop this note
                   {
                       info[inx].f = 0;
                   }
               } 
           }           
           
           if ( current_phase )
           {
               shortest_time = info[current_voice].on_pt;
           }
           else
           {
               shortest_time = info[current_voice].off_pt;
           }      
           
           time_in_micros = micros() - time_in_micros;
           
           delayMicroseconds( shortest_time - time_in_micros ); // Sleep the time minus time to process this loop           
       }
   } // While looping
}

// Here are some sample pieces of music.  All songs, except for 'bourree', are single voice

  // Super Mario Bros.
  char super_mario_bros[] =
          "[A EsEsrsEsrsCsEsrsG,ererq]" // Intro
          "[B C,ersg,ers,e,ersa,eb,ea#sasrs gtqEtqGtqA,eFsGsrsE,eCsDsbsre]" // Main music
          "[C reGsF#sFsD#srsEsrsg#sasCsrsasCsDs]" // Falling theme
          "[D reGsF#sFsD#srsEsrsC+srsC+sC+srsre]" // Falling end 1
          "[E reD#sreDsreCersrerq]" // Falling end 2
          "[F CsCsrsCsrsCsDsrsEsCsrsasgsrsre CsCsrsCsrsCsDsEsrh CsCsrsCsrsCsDsrsEsCsrsasgsrsre EsEsrsEsrsCsEsrsGsrsrerq]" // Finish
          "[G g-tqctqetqCtqEtqGqEq g-#tqctqe&tqg#gCtqE&tqG#qE&q a-#tqdtqftqa#tqDtqFtqA#qA#tqA#tqA#tq C+qqh]" // Finale
          "[H EsCsrsg,ersg#,easFsrsFsasrsre btqAtqAtqAtqGtqFtqEsCsrsasgsrsre EsCsrsg,ersg#,easFsrsFsasrsre bsFsrsFsFtqEtqDtqC,ererq]"
          ">A >B >B >C >D >C >E >F" // Play the intro and the main tune
          "   >B >B >C >D >C >E >F" // Play the main tune again
          ">H >H >G";               // Play the second theme, followed by the finale
  
  // char air[] = "ek eegefebegefses febegeCqbqa#e bi"; // Key of E
  
  // Bourree by G.F. Handel, adapted from 'Music' by Software Technology Corp.
  char bourree[] = "[A"
                   "gk Dq | D,qb,qCebea,eg,e | EqGhFeEe | DqC,e,beaebec,ea,e |" // M 1-4
                   "   bqghaq | beC#ed,eb,eC#eDeE,eC#,e | DeEeF,eD,eE,eFeG,eE,e |" // M 5-7
                   "   FeGeA+ereC#e | D.h"                            // M 8-9
                   "V  bq | a,qg,qfeb-q | gqEqDqgere | fqrqgqfq |" // M1-4
                   "   gqdhfq | gqaqgqeq | aqaqaqC#q | dhaqeq | f.h]" // m5-9
                   "[B"
                   "gk A+q | AqFqGeFeEeDe | GqBhEq | D#qEqFqGeAe |" // M17-20
                   "   GqEhDq | DqCebeCqCq | CqbeaebqDq | EeFeGeEeFeGeA+eFe |" // M21-24
                   "   GeAeBeGeAeBeC+eAe | BqAeGeFeGeAeFe | GeAeBeAqCq | bqaegefegeaege |" // M25-28
                   "   geaebegeaebeceae | beCeDqbqaege | g.h" // M29-31
                   "V  Fq | FqDqDererq | dqb-ecedqeq | fqgqEqDq |" // M17-20
                   "   bqEhDq | eqeheq | dqdhgq | ghah |" // M21-24
                   "   DhDh | DqCebeaqDq | DqDqDqCq | dqrqrh |" // M25-28
                   "   derereredererq | dererqdqcq | b-.h]" // M29-31
                   ">A >A >B >B"; // Play the two parts

  char deck_the_halls[] = "[A g.qfeeqdq cqdqeqcq] " // Deck the halls with boughs of holly
                          "[B deeefedee.qde cqb-qch] " // Fa la la la la la la la la
                          "[C aeaeaeaeg.q fe eqdqch ]" // Last fa la la la
                          ">A >B >A >B "
                          "d.qeefqdq e.qfegqdq eefegqaebeCq bqaqgh "
                          ">A >C";
                          
  char nyan_cat[] = 
      "bk " // Key of B
      "[A D+sE+sF+eB+eD+sE+sF+sB+sC++sD++sC++sA+sB+e |"
      "   F+ed+se+sF+eB+eC++sA+sB+sC++sE++sD++sE++sC++s]" // Intro music
      "[B FeGeDsDsrsbsD&sCsbsrsbeCe | D&eD&sCsbsCsDsFsGsdsfscsdsbscsbs " // Main theme
      "   DeFeGsDsFsCsDsbsD&sDsD&sCsbscs | D&ebsCsDsFsCsDsCsbsCebece ]"
      "[C befsgsbefsgsbsCsDsbsesDsEsFs | bebefsgsbsfsEsDsCsbsfsdsesfs |" // Chorus
      "   befsgsbefsgsbsbsCsDsbsfsgsfs ]"
      "[D bebsasbsfsgsbsEsDsEsFsbeae ]" // Chorus end 1
      "[E bebsasbsfsgsbsEsDsEsFsbeCe ]" // Chorus end 2
      "[F bebsasbsfsgsbsEsDsEsFsbe ]" // Chorus end 2, without last note
      ">A >B >B >C >D >C >E "
      "   >B >B >C >D >C >F";     

  //char chord0[] = "cw";
  //char chord1[] = "rwCwCwCwCwCwCw V cwdwewfwgwAwBwCwDwEwFwGw";  
  //char chord[] = "CwCwCw V gwgwgw V cedeeefegeAeBeCeDeEeFeGe";
  char silence[] = "rwrwrw";
 
 void setup() {
   pinMode( OUTPUT_PIN, OUTPUT );
   //Serial.begin( 115200 );
       
   play_song( super_mario_bros );
   play_song( silence );
   
   play_song( nyan_cat );
   play_song( silence );
   
   play_song( deck_the_halls );
   play_song( silence );
   
   play_song( bourree );
   play_song( silence );
}

void loop() {
  // no need to repeat the melody.
}



[BicycleErie]  [Arduino Bike Computer]