RTTTL

 

In this section we add the capability to play songs from cellphone ringtones

We had a functioning theremin by the end of the previous tutorial, but only one song transcribed. I am not a musician, and it is beyond my skill to transcribe songs, I ended up getting someone else to do the transcription for the last tutorial for me. I really wanted find a way to play songs that came in electronic format already, and not bother with transcribing. I was able to to do this by incorporating cell phone ringtones using RTTTL (Ring Tone Text Transfer Language). In other words it’s the sheet music for cell phone ring tones in a format that we can use, and you can find a rather stupendous number of songs online here. This is not the format that was used to transcribe the first song, at first I thought of just using the RTTTL format for all songs, but this wasn’t quite right because RTTTL does not have a volume specification, so I decided to write a routine to convert the RTTTL data to our custom Song data type. Then we can experiment with adding in some volume control.

There is an example sketch in Tone library that has most of what we need for this conversion. To load the example, open the Arduino IDE and Click

File->Examples->Tone->RTTTL.

There is nothing particularly complicated about the RTTTL2Song() function it just takes a bit of work to do all the necessary conversion. You need to understand a little bit about the RTTTL specification. We specify a song as a string.

char *RTTLSongs[] = {"Indiana Jones:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6"};

This is actually an array of strings with just one entry, we’ll add more in minute. The format is

song name: default values: song data:

For the above example, the song name is “Indiana Jones”, the default duration is 4, which means 1/4 note, the default octave is 5, and the default tempo is 250 beats per minute. In the data section, notes will be specified as duration,octave,note,dot. If the given data is not there, then the default is used. For example 8e6 would mean play note e6 for a duration of 8. If the entry was simply e, then this would play note e5 ( because 5 is the default octave) for the default duration of 4. If a dot appears as in 1f.6, this would mean play note f6 for a duration of 1.5. The dot means increase the duration by half. Here is the code that does that

    // now, get optional '.' dotted note
    if(*p == '.')
    {
      duration += duration/2;
      p++;
    }

So far I have just been talking about a note duration as some integer, how does that translate into seconds? The duration calculation is done with the following two snippets of code, bpm is the beats per minute, and is set in the RTTTL2Song() function.


   wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole note (in milliseconds)
    // first, get note duration, if available
    num = 0;
    while(isdigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }

    if(num) duration = wholenote / num;
    else duration = wholenote / default_dur;  // we will need to check if we are a dotted note after

The line

num = (num * 10) + (*p++ - '0');

converts a multiple digit string into the correct number, e.g. the chars ‘4’ and ‘3’ becomes the integer 43. Then if we have read an actual number, the duration is equal to wholenote divided by the number we read. For example if bpm was set to 60, and we had 8e6, the 8 (i.e. 1/8th note ) would get read, and we would have

(60 * 1000) / 60 * 4 = 4000 / 8 = 500 milliseconds.

If we don’t read a digit, then the calculation above is done with the default duration.

If you are new to programming or C programming in particular, the way the data is being passed to and from the RTTTL2Song() function might be a little bit confusing in that we are using pass by reference, and pointers to dynamically allocated user defined structs, these are very confusing topics for someone learning the C programming language for the first time. I can remember the first time I learned about it, it took me a long time to get my head wrapped around the concept, and it still confuses the crap out of me at times. More on this in a minute. Let’s look at how the songs array get populated. First we want to load up all our song strings into the header file.

char *RTTLSongs[] = {"Indiana Jones:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6",
                     "TheSimpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6",
                     "TakeOnMe:d=4,o=4,b=160:8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5,8f#5,8e5,8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5",
                     "Entertainer:d=4,o=5,b=140:8d,8d#,8e,c6,8e,c6,8e,2c.6,8c6,8d6,8d#6,8e6,8c6,8d6,e6,8b,d6,2c6,p,8d,8d#,8e,c6,8e,c6,8e,2c.6,8p,8a,8g,8f#,8a,8c6,e6,8d6,8c6,8a,2d6",
                     "Muppets:d=4,o=5,b=250:c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,8a,8p,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,8e,8p,8e,g,2p,c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,a,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,d,8d,c",

Next we declare variables for numSongs and numRTTLSongs

int numRTTLSongs = sizeof(RTTLSongs) / sizeof(RTTLSongs[0]);
int numSongs = numRTTLSongs + 1;

Then we declare the Songs[] array, this is done by declaring a pointer to a Song and allocating enough memory to hold the number of songs we have declared. It is best to do it this way, so we don’t have to bother with changing the size of the Songs[] array every time we add or remove a song from the header file.

Song *Songs = (Song*) calloc( numSongs, sizeof(Song));

We also need to declare the RTTTL2Song() function

// convert RTTL string into Song  
void RTTL2Song( char* RTTL, Song *newSong);

The first parameter is our song represented as a string, the second parameter is a pointer to a Song.

Next we add our non RTTL type songs to the songs array

   // load songs array with non-RTTTL songs
   Songs = SomewhereOTR;
   Songs[0] = *SomewhereOTR;

Finally to load up all the RTTL Songs we use a for loop

   // load songs array with RTTTL songs
   for( int i = 0; i < numSongs; i++)
     {
      RTTL2Song(RTTLSongs[i], &Songs[i+1]); 
     }

Notice that we are using the & operator when passing Songs[i+1], this means we are passing the address of the (i+1)th element of the Songs array. This is how pass by reference works in C programming. You pass an address, then your function operates on the actual data and not a copy of it. Like I was saying earlier, this is a pretty confusing topic for beginners. A more thorough explanation can be found in any introductory book on C programming. Also There are many good explanations online, like here.

That’s about all I think I’ll cover for this project, if you have made it this far, that’s awesome, this project turned out to be a lot more involved than I thought it would be when I first started it. If not then I at least hope you learned something new. I have a few ideas on how this project could be continued, first of course is to make it a hands free instrument. This would be difficult to do with a radio frequency circuit like a true theremin, but it might be possible using ultrasonic sensors. Another idea that I’d like to do someday is to add some granular synthesis, this way the servo that controls the frequency would control a single grain, then another potentiometer would be added to control the 2nd grain. You would be able to change the character of the instrument with this second potentiometer. One last idea would be to add a third push button that alternates between a square, triangle and sin wave, similar to the simple waveform generator example from the Arduino site.

Finally here are some more videos playing various songs, followed by the complete code to build this instrument.

The Simpsons

Mission Impossible

Jeopardy

James Bond

Inspector Gadget

Imperial March

Entertainer

Airwolf


And here is the complete sketch.

songdata.h

#include <string>

#define NOTE_Cm1   0 
#define NOTE_CSm1  1
#define NOTE_Dm1   2
#define NOTE_DSm1  3
#define NOTE_Em1   4
#define NOTE_Fm1   5
#define NOTE_FSm1  6
#define NOTE_Gm1   7
#define NOTE_GSm1  8
#define NOTE_Am1   9
#define NOTE_ASm1 10
#define NOTE_Bm1 11
#define NOTE_C0  12
#define NOTE_CS0 13
#define NOTE_D0  14
#define NOTE_DS0 15
#define NOTE_E0  16
#define NOTE_F0  17
#define NOTE_FS0 18
#define NOTE_G0  19
#define NOTE_GS0 20
#define NOTE_A0  21
#define NOTE_AS0 22
#define NOTE_B0  23
#define NOTE_C1  24
#define NOTE_CS1 25
#define NOTE_D1  26
#define NOTE_DS1 27
#define NOTE_E1  28
#define NOTE_F1  29
#define NOTE_FS1 30
#define NOTE_G1  31
#define NOTE_GS1 32
#define NOTE_A1  33
#define NOTE_AS1 34
#define NOTE_B1  35
#define NOTE_C2  36
#define NOTE_CS2 37
#define NOTE_D2  38
#define NOTE_DS2 39
#define NOTE_E2  40
#define NOTE_F2  41
#define NOTE_FS2 42
#define NOTE_G2  43
#define NOTE_GS2 44
#define NOTE_A2  45
#define NOTE_AS2 46
#define NOTE_B2  47
#define NOTE_C3  48
#define NOTE_CS3 49
#define NOTE_D3  50
#define NOTE_DS3 51
#define NOTE_E3  52
#define NOTE_F3  53
#define NOTE_FS3 54
#define NOTE_G3  55
#define NOTE_GS3 56
#define NOTE_A3  57
#define NOTE_AS3 58
#define NOTE_B3  59
#define NOTE_C4  60
#define NOTE_CS4 61
#define NOTE_D4  62
#define NOTE_DS4 63
#define NOTE_E4  64
#define NOTE_F4  65
#define NOTE_FS4 66
#define NOTE_G4  67
#define NOTE_GS4 68
#define NOTE_A4  69
#define NOTE_AS4 70
#define NOTE_B4  71
#define NOTE_C5  72
#define NOTE_CS5 73
#define NOTE_D5  74
#define NOTE_DS5 75
#define NOTE_E5  76
#define NOTE_F5  77
#define NOTE_FS5 78
#define NOTE_G5  79
#define NOTE_GS5 80
#define NOTE_A5  81
#define NOTE_AS5 82
#define NOTE_B5  83
#define NOTE_C6  84
#define NOTE_CS6 85
#define NOTE_D6  86
#define NOTE_DS6 87
#define NOTE_E6  88
#define NOTE_F6  89
#define NOTE_FS6 90
#define NOTE_G6  91
#define NOTE_GS6 92
#define NOTE_A6  93
#define NOTE_AS6 94
#define NOTE_B6  95
#define NOTE_C7  96
#define NOTE_CS7 97
#define NOTE_D7  98
#define NOTE_DS7 99
#define NOTE_E7  100
#define NOTE_F7  101
#define NOTE_FS7 102
#define NOTE_G7  103
#define NOTE_GS7 104
#define NOTE_A7  105
#define NOTE_AS7 106
#define NOTE_B7  107
#define NOTE_C8  108
#define NOTE_CS8 109
#define NOTE_D8  110
#define NOTE_DS8 111
#define NOTE_E8  112
#define NOTE_F8  113
#define NOTE_FS8 114
#define NOTE_G8  115
#define NOTE_GS8 116
#define NOTE_A8  117
#define NOTE_AS8 118
#define NOTE_B8  119
#define NOTE_C9  120
#define NOTE_CS9 121
#define NOTE_D9  122
#define NOTE_DS9 123
#define NOTE_E9  124
#define NOTE_F9  125
#define NOTE_FS9 126
#define NOTE_G9  127

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Song Data
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_SONG_SIZE 200

struct songData {
  int note;
  int duration;
  int volume;
}; 

struct Song {
  int SongLength;
  char *name;
  songData *data;

  Song( char* songName, songData *newSongData, int songLength);
  Song();
};

// somewhere over the rainbow
songData SWOTR[] = {{NOTE_GS3,    67,      8},
                    {NOTE_GS4,    74,      8},
                    {NOTE_G4,     40,      9},
                    {NOTE_DS4,    16,      8},
                    {NOTE_F4,     20,      9},
                    {NOTE_G4,     38,      8},
                    {NOTE_GS4,    47,      7},
                    {NOTE_GS3,    37,      8},
                    {NOTE_F4,     75,      8},
                    {NOTE_DS4,   130,      6},
                    {NOTE_F3,     77,      8},
                    {NOTE_CS4,    73,      6},
                    {NOTE_C4,     41,      8},
                    {NOTE_GS3,    16,      7},
                    {NOTE_AS3,    19,      9},
                    {NOTE_C4,     37,      9},
                    {NOTE_CS4,    39,      6},
                    {NOTE_AS3,    42,      8},
                    {NOTE_G3,      9,      9},
                    {NOTE_GS3,    13,      7},
                    {NOTE_AS3,    23,      8},
                    {NOTE_C4,     27,      7},
                    {NOTE_GS3,   114,      7},
                    {NOTE_GS3,    72,      8},
                    {NOTE_GS4,    77,      6},
                    {NOTE_G4,     41,      7},
                    {NOTE_DS4,    19,      8},
                    {NOTE_F4,     20,      9},
                    {NOTE_G4,     42,      9},
                    {NOTE_GS4,    53,      7},
                    {NOTE_GS3,    43,      9},
                    {NOTE_F4,     79,      8},
                    {NOTE_DS4,   128,      8},
                    {NOTE_F3,     38,      9},
                    {NOTE_CS4,    78,      7},
                    {NOTE_C4,     47,      8},
                    {NOTE_GS3,    16,      8},
                    {NOTE_AS3,    18,      8},
                    {NOTE_C4,     58,      7},
                    {NOTE_CS4,    40,      7},
                    {NOTE_AS3,    43,      9},
                    {NOTE_G3,     13,      9},
                    {NOTE_GS3,    13,      8},
                    {NOTE_AS3,    51,      8},
                    {NOTE_C4,     63,      8},
                    {NOTE_GS3,    93,      5},
                    {NOTE_DS4,    97,      8},
                    {NOTE_C4,     32,      6},
                    {NOTE_DS4,    23,      5},
                    {NOTE_C4,     19,      8},
                    {NOTE_DS4,    20,      7},
                    {NOTE_C4,     17,      8},
                    {NOTE_DS4,    19,      8},
                    {NOTE_C4,     20,      8},
                    {NOTE_DS4,    20,      9},
                    {NOTE_CS4,    18,      7},
                    {NOTE_DS4,    17,      8},
                    {NOTE_CS4,    16,      7},
                    {NOTE_DS4,    18,      8},
                    {NOTE_CS4,    16,      7},
                    {NOTE_DS4,    19,      8},
                    {NOTE_CS4,    21,      8},
                    {NOTE_DS4,    26,      8},
                    {NOTE_F4,     81,      9},
                    {NOTE_F4,    148,      8},
                    {NOTE_DS4,    19,      7},
                    {NOTE_C4,     16,      7},
                    {NOTE_DS4,    17,      7},
                    {NOTE_C4,     15,      8},
                    {NOTE_DS4,    17,      7},
                    {NOTE_C4,     15,      9},
                    {NOTE_DS4,    17,      8},
                    {NOTE_C4,     19,      8},
                    {NOTE_D4,     20,      8},
                    {NOTE_D4,     17,      9},
                    {NOTE_F4,     16,      9},
                    {NOTE_D4,     15,      9},
                    {NOTE_F4,     15,      9},
                    {NOTE_D4,     16,      8},
                    {NOTE_F4,     18,      9},
                    {NOTE_D4,     21,      9},
                    {NOTE_F4,     23,      9},
                    {NOTE_G4,     82,      10},
                    {NOTE_G4,     94,      8},
                    {NOTE_AS4,   108,      9},
                    {NOTE_F4,    161,      6},
                    {NOTE_GS3,    75,      8},
                    {NOTE_GS4,    74,      8},
                    {NOTE_G4,     38,      9},
                    {NOTE_DS4,    20,      8},
                    {NOTE_F4,     19,      9},
                    {NOTE_G4,     41,      8},
                    {NOTE_GS4,    53,      8},
                    {NOTE_GS3,    44,      9},
                    {NOTE_F4,     74,      8},
                    {NOTE_DS4,   119,      7},
                    {NOTE_F3,     36,      8},
                    {NOTE_CS4,    76,      9},
                    {NOTE_C4,     43,      8},
                    {NOTE_GS3,    19,      8},
                    {NOTE_AS3,    21,      9},
                    {NOTE_C4,     49,      8},
                    {NOTE_CS4,    61,      7},
                    {NOTE_AS3,    56,      8},
                    {NOTE_G3,     53,      7},
                    {NOTE_G3,     31,      8},
                    {NOTE_AS3,    66,      8},
                    {NOTE_C4,     98,      8},
                    {NOTE_GS3,   139,      8},
                    {NOTE_DS4,    22,      8},
                    {NOTE_C4,     18,      7},
                    {NOTE_DS4,    20,      7},
                    {NOTE_C4,     17,      7},
                    {NOTE_DS4,    20,      8},
                    {NOTE_C4,     19,      8},
                    {NOTE_DS4,    23,      7},
                    {NOTE_C4,     30,      6},
                    {NOTE_DS4,    24,      7},
                    {NOTE_CS4,    23,      7},
                    {NOTE_DS4,    22,      6},
                    {NOTE_CS4,    26,      7},
                    {NOTE_DS4,    38,      7},
                    {NOTE_CS4,    51,      7},
                    {NOTE_DS4,    80,      8},
                    {NOTE_F4,    101,      9},
                    {NOTE_G4,    130,      9},
                    {NOTE_GS4,   261,      8}};

 char *RTTLSongs[] = {"Indiana Jones:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6",
                     "TheSimpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6",
                     "Entertainer:d=4,o=5,b=140:8d,8d#,8e,c6,8e,c6,8e,2c.6,8c6,8d6,8d#6,8e6,8c6,8d6,e6,8b,d6,2c6,p,8d,8d#,8e,c6,8e,c6,8e,2c.6,8p,8a,8g,8f#,8a,8c6,e6,8d6,8c6,8a,2d6",
                     "Muppets:d=4,o=5,b=250:c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,8a,8p,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,8e,8p,8e,g,2p,c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,a,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,d,8d,c",
                     "Bond:d=4,o=5,b=80:32p,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d#6,16d#6,16c#6,32d#6,32d#6,16d#6,8d#6,16c#6,16c#6,16c#6,16c#6,32e6,32e6,16e6,8e6,16d#6,16d6,16c#6,16c#7,c.7,16g#6,16f#6,g#.6",
                     "StarWars:d=4,o=5,b=45:32p,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#.6,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#6",
                     "GoodBad:d=4,o=5,b=56:32p,32a#,32d#6,32a#,32d#6,8a#.,16f#.,16g#.,d#,32a#,32d#6,32a#,32d#6,8a#.,16f#.,16g#.,c#6,32a#,32d#6,32a#,32d#6,8a#.,16f#.,32f.,32d#.,c#,32a#,32d#6,32a#,32d#6,8a#.,16g#.,d#",
                     "A-Team:d=8,o=5,b=120:4d#6,a#,2d#6,16p,g#,4a#,4d#.,p,16g,16a#,d#6,a#,f6,2d#6,16p,c#.6,16c6,16a#,g#.,2a#,d#6,a#,2d#6,16p,g#,4a#,4d#.,p,16g,16a#,d#6,a#,f6,2d#6,16p,c#.6,16c6,16a#,g#.,2a#",
                     "Flinstones:d=4,o=5,b=40:32p,16f6,16a#,16a#6,32g6,16f6,16a#.,16f6,32d#6,32d6,32d6,32d#6,32f6,16a#,16c6,d6,16f6,16a#.,16a#6,32g6,16f6,16a#.,32f6,32f6,32d#6,32d6,32d6,32d#6,32f6,16a#,16c6,a#,16a6,16d.6,16a#6,32a6,32a6,32g6,32f#6,32a6,8g6,16g6,16c.6,32a6,32a6,32g6,32g6,32f6,32e6,32g6,8f6,16f6,16a#.,16a#6,32g6,16f6,16a#.,16f6,32d#6,32d6,32d6,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#6,16c7,8a#.6",
                     "Jeopardy:d=4,o=6,b=125:c,f,c,f5,c,f,2c,c,f,c,f,a.,8g,8f,8e,8d,8c#,c,f,c,f5,c,f,2c,f.,8d,c,a#5,a5,g5,f5,p,d#,g#,d#,g#5,d#,g#,2d#,d#,g#,d#,g#,c.7,8a#,8g#,8g,8f,8e,d#,g#,d#,g#5,d#,g#,2d#,g#.,8f,d#,c#,c,p,a#5,p,g#.5,d#,g#",
                     "Gadget:d=16,o=5,b=40:32d#,32f,32f#,32g#,a#,f#,a,f,g#,f#,32d#,32f,32f#,32g#,a#,d#6,4d6,32d#,32f,32f#,32g#,a#,f#,a,f,g#,f#,8d#",
                     "MissionImp:d=16,o=6,b=75:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,a#,g,2d,32p,a#,g,2c#,32p,a#,g,2c,a#5,8c,2p,32p,a#5,g5,2f#,32p,a#5,g5,2f,32p,a#5,g5,2e,d#,8d",
                     "Airwolf:d=4,o=5,b=100:e,16a,16b,16d6,e6,16g6,16f#6,16d6,e6,16g6,16f#6,16d6,e6,8d6,16f#6,b,a,8g,16a,8f#,16d,g,16c6,16d6,16f6,g,16c6,16b6,16f6,g6,16c6,16b6,16f6,g6,8f6,16a,d6,c6,8b,16d6,8a,16f,g6,16c6,16d6,16f6,g6,16c6,16b6,16f6",
                     "StarWars:d=4,o=5,b=112:8d.,16p,8d.,16p,8d.,16p,8a#4,16p,16f,8d.,16p,8a#4,16p,16f,d.,8p,8a.,16p,8a.,16p,8a.,16p,8a#,16p,16f,8c#.,16p,8a#4,16p,16f,d.,8p,8d.6,16p,8d,16p,16d,8d6,8p,8c#6,16p,16c6,16b,16a#,8b,8p,16d#,16p,8g#,8p,8g,16p,16f#,16f,16e,8f,8p,16a#4,16p,8c#,8p,8a#4,16p,16c#,8f.,16p,8d,16p,16f,a.,8p,8d.6,16p,8d,16p,16d,8d6,8p,8c#6,16p,16c6,16b,16a#,8b,8p,16d#,16p,8g#,8p,8g,16p,16f#,16f,16e,8f,8p,16a#4,16p,8c#"};

and here is DDS_theremin.ino

//  this code reuses code from several places, for the DDS part, look here 
//
// http://rcarduino.blogspot.com/2012/12/arduino-due-dds-part-1-sinewaves-and.html
// http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/
// 
// for timer interrupts see
// http://arduino.cc/forum/index.php?action=post;topic=130423.15;num_replies=20
//
// for controlling the lcd, most was taken from the examples included with the Arduino install
//    File->Examples->LiquidCrystal->HelloWorld
//
// These are the clock frequencies available to the timers /2,/8,/32,/128
// 84Mhz/2 = 42.000 MHz
// 84Mhz/8 = 10.500 MHz
// 84Mhz/32 = 2.625 MHz
// 84Mhz/128 = 656.250 KHz
//
// 44.1Khz = CD Sample Rate
// Lets aim for as close to the CD Sample Rate as we can get -
//
// 42Mhz/44.1Khz = 952.38
// 10.5Mhz/44.1Khz = 238.09 // best fit divide by 8 = TIMER_CLOCK2 and 238 ticks per sample
// 2.625Hmz/44.1Khz = 59.5
// 656Khz/44.1Khz = 14.88 // use for servo control 13120 / 65600 = .02 ( update servos every 20 milliseconds )

// 84Mhz/44.1Khz = 1904 instructions per tick

// include the library code:
#include <LiquidCrystal.h>
#include <Servo.h>
#include "songdata.h"
#include <string>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INPUT PARAMS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define btnNext 0
#define btnSelect 1
#define btnNextPin 50
#define btnSelectPin 51
#define freqServoPin 9
#define volServoPin 8

int fMinInput = 214;  // corresponds to 26 degrees
int fMaxInput = 594;  // corresponds approx to 154 degrees
int fMinAngle = 26;
int fMaxAngle = 154;
int minNote = 50;
int maxNote = 100;
int vMinInput = 350;
int vMaxInput = 435;
int vMinAngle = 75;
int vMaxAngle = 105;
int scaleFactor = (fMaxInput - fMinInput) / (maxNote - minNote);

float fAngle = 0.0;
float vAngle = 0.0;

Servo freqServo;
Servo volServo;

int lastInterrupt = 0; // time since last interrupt, for switch debouncing
void BtnNext();
void BtnSelect();

// we are using a servo as a potentiometer to control the volume. 
#define MAX_VOL 10 // we'll have 10 volume levels that can be set in software
uint32_t volume = MAX_VOL; // var we'll actually use to control the volume

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DDS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// the phase accumulator points to the current sample in our wavetable
uint32_t ulPhaseAccumulator = 0;
// the phase increment controls the rate at which we move through the wave table
// higher values = higher frequencies
volatile uint32_t ulPhaseIncrement = 0;   // 32 bit phase increment, see below

// full waveform = 0 to SAMPLES_PER_CYCLE
// Phase Increment for 1 Hz =(SAMPLES_PER_CYCLE_FIXEDPOINT/SAMPLE_RATE) = 1Hz
// Phase Increment for frequency F = (SAMPLES_PER_CYCLE/SAMPLE_RATE)*F
#define SAMPLE_RATE 44100.0
#define SAMPLES_PER_CYCLE 600
#define SAMPLES_PER_CYCLE_FIXEDPOINT (SAMPLES_PER_CYCLE<<20)
#define TICKS_PER_CYCLE (float)((float)SAMPLES_PER_CYCLE_FIXEDPOINT/(float)SAMPLE_RATE)

// We have 521K flash and 96K ram to play with
// Create a table to hold the phase increments we need to generate midi note frequencies at our 44.1Khz sample rate
#define MIDI_NOTES 128
uint32_t nMidiPhaseIncrement[MIDI_NOTES];

// Create a table to hold pre computed sinewave, the table has a resolution of 600 samples
#define WAVE_SAMPLES 600
// default int is 32 bit, in most cases its best to use uint32_t but for large arrays its better to use smaller
// data types if possible, here we are storing 12 bit samples in 16 bit ints
uint16_t nSineTable[WAVE_SAMPLES];

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Song Data
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void displayLCD( char* line1, char* line2);

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int playingSong = false; // state variable to indicate a preloaded song should be playing
int iNote = 0; // index to indicate what note of particular song is playing
uint32_t interruptCtr = 0; // ctr used for timing servo PWM 
uint32_t volInterruptCtr = 0; // ctr used for timing servo PWM 
uint32_t noteDurationCtr = 0; // ctr used to control how long a given song note should play

Song::Song( char *songName, songData *newSongData, int songLength)
  {
    this->name = songName; 
    this->data = newSongData;
    this->SongLength = songLength;
  };

// declare all non-RTTL songs here  
Song *SomewhereOTR = new Song::Song("SWOTR", SWOTR, sizeof(SWOTR)/sizeof(SWOTR[0]));

// declare vars for the number of songs
int numRTTLSongs = sizeof(RTTLSongs) / sizeof(RTTLSongs[0]);
int numSongs = numRTTLSongs + 1;
Song *Songs = (Song*) calloc( numSongs, sizeof(Song));

int iCurrSong = 0; 

// function called to play a demo
void PlaySong( songData* song ); 

// convert RTTL string into Song  
void RTTL2Song( char* RTTL, Song *newSong);

void setup()
{
  Serial.begin(9600);
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);  

   // load songs array with non-RTTTL songs
   Songs = SomewhereOTR;

   // load songs array with RTTTL songs
   for( int i = 0; i < numSongs; i++)
    {
      RTTL2Song(RTTLSongs[i], &Songs[i+1]); 
    }

  // Print a message to the LCD.
  displayLCD("Press Sel 4Demo", Songs[0].name );

  pinMode(btnNextPin, INPUT);
  pinMode(btnSelectPin, INPUT);

  attachInterrupt(btnNextPin, BtnNext, CHANGE);
  attachInterrupt(btnSelectPin, BtnSelect, CHANGE);

  createNoteTable(SAMPLE_RATE);
  createSineTable();

  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);		 // disable write protection for pmc registers
  pmc_enable_periph_clk(ID_TC6);	 // enable peripheral clock TC6
  pmc_enable_periph_clk(ID_TC7);	 // enable peripheral clock TC7

  /* we want wavesel 01 with RC */
  TC_Configure(/* clock */TC2,/* channel */0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
  TC_Configure(/* clock */TC2,/* channel */1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
  TC_SetRC(TC2, 0, 238); // sets <> 44.1 Khz interrupt rate 
  TC_SetRC(TC2, 1, 13120);
  TC_Start(TC2, 0);
  TC_Start(TC2, 1);

  // enable timer interrupts on the timer
  TC2->TC_CHANNEL[0].TC_IER=TC_IER_CPCS;   // IER = interrupt enable register
  TC2->TC_CHANNEL[0].TC_IDR=~TC_IER_CPCS;  // IDR = interrupt disable register
  TC2->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;   // IER = interrupt enable register
  TC2->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS;  // IDR = interrupt disable register

  /* Enable the interrupt in the nested vector interrupt controller */
  /* TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number (=(1*3)+1) for timer1 channel1 */
  NVIC_EnableIRQ(TC6_IRQn);
  NVIC_EnableIRQ(TC7_IRQn);

  // this is a cheat - enable the DAC
  analogWrite(DAC1,0);
}

void loop()
{
  // read analog input 0, drop the range from 0-1024 to minNote-maxNote
  // then look up the phaseIncrement required to generate the note in our nMidiPhaseIncrement table
  uint32_t ulInput = min( max( analogRead(0), fMinInput ), fMaxInput);
  ulInput = ((ulInput - fMinInput) / scaleFactor) + minNote;  // scale to minNote to maxNote

  // get volume from analog input, then scale it to range vMinInput to vMaxInput
  volume = min( max( analogRead(1), vMinInput ), vMaxInput);
  volume = ((volume - vMinInput) / 8);

  ulPhaseIncrement = nMidiPhaseIncrement[ulInput];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT HANDLERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this is the interrupt handler that is called at our sample rate of 44100 times per second, its primary job is to update 
// the value writtento the DAC port
void TC6_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC2, 0);

  ulPhaseAccumulator += ulPhaseIncrement;   // 32 bit phase increment, see below

  // if the phase accumulator over flows - we have been through one cycle at the current pitch,
  // now we need to reset the grains ready for our next cycle
  if(ulPhaseAccumulator > SAMPLES_PER_CYCLE_FIXEDPOINT)
  {
   // DB 02/Jan/2012 - carry the remainder of the phase accumulator
   ulPhaseAccumulator -= SAMPLES_PER_CYCLE_FIXEDPOINT;
  }

  // get the current sample  
  uint32_t ulOutput = volume * nSineTable[ulPhaseAccumulator>>20]; 

  // we cheated and user analogWrite to enable the dac, but here we want to be fast so
  // write directly 
  dacc_write_conversion_data(DACC_INTERFACE, ulOutput);
}

// this is the interrupt handler that is called every 20 mS (i.e. time between pulses sent to a servo), its job is to play a demo of a song
void TC7_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC2, 1);

  // handle servo output if we are in song playing mode
  if( playingSong )
    {
      fAngle = ((float)(Songs[iCurrSong].data[iNote].note - minNote) / (float)(maxNote - minNote)) * (fMaxAngle - fMinAngle) + fMinAngle;
      freqServo.write(fAngle);

      // test if we need to increment to the next note
      if( noteDurationCtr >= Songs[iCurrSong].data[iNote].duration )
        {
         noteDurationCtr = 0;
         // increment note and test if we have finished the song
         if( ++iNote >= Songs[iCurrSong].SongLength )
           {
            // song is over
            playingSong = false;
            iNote = 0;
            freqServo.detach();
            volServo.detach();
            displayLCD("Press sel 4Demo", Songs[iCurrSong].name );
           }   
         else
           { 
            // set volume for next note
            volume = Songs[iCurrSong].data[iNote].volume;
            vAngle = vMinAngle + volume / (float) MAX_VOL *(float)(vMaxAngle - vMinAngle);
            volServo.write(vAngle);
           }
        }
        noteDurationCtr++;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SONG RELATED FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// create the notes we want to be abale to play, i.e. a list of frequencies
void createNoteTable(float fSampleRate)
{
  for(uint32_t unMidiNote = 0; unMidiNote < 128; unMidiNote++)
  {
    float fFrequency = ((pow(2.0,(unMidiNote-69.0)/12.0)) * 440.0);
    nMidiPhaseIncrement[unMidiNote] = fFrequency*TICKS_PER_CYCLE;
  }
}

// create the individual samples for our sinewave table
void createSineTable()
{
  for(uint32_t nIndex = 0;nIndex < WAVE_SAMPLES;nIndex++)
  {
    // normalised to 12 bit range 0-4095
    nSineTable[nIndex] = (uint32_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2) / MAX_VOL;
  }
}

// function to call when a song is to be played
void PlaySong(struct songData* song)
{
  displayLCD("Playing", Songs[iCurrSong].name);

  playingSong = true;
  // enable servos for output
  freqServo.attach(9);
  volServo.attach(8);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// I/O FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// routine to display text to the LCD
void displayLCD( char* line1, char* line2)
{
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(line1);
  lcd.setCursor(0,1);
  lcd.print(line2);
}

// interrupt handler for next button
void BtnNext()
{
  if( millis() - lastInterrupt < 500 )
   {
     return;
   } 
 else
   {
     lastInterrupt = millis();
     // increment song num
     iCurrSong = ((iCurrSong+1) % numSongs); 
     displayLCD("Press sel 4Demo", Songs[iCurrSong].name );
   }
}

// interrupt handler for select button
void BtnSelect()
{
 if( millis() - lastInterrupt < 500 )
   {
     return;
   } 
 else
   {
     lastInterrupt = millis();
     // Play Current song
     PlaySong(Songs[iCurrSong].data);

   }
}

// populates newSong with dta from RTTL
void RTTL2Song(char* RTTL, Song *newSong)
{
  char* p = RTTL;
  int num=0;
  int default_dur = 4;
  int default_oct = 4;
  int bpm = 63;
  int wholenote;
  int duration = 0;
  int note;
  int iNote = 0;
  int scale;
  #define OCTAVE_OFFSET 0
  char *pstart = p;
  int numChars = 0;
  while(*p != ':') { p++; numChars++;} 
  newSong->name = (char*)calloc(numChars, sizeof(char));
  strncpy((*newSong).name, pstart, numChars); // copy name to newSong
  p++;                     // skip ':'
  // get default duration
  if(*p == 'd')
  {
    p++; p++;              // skip "d="
    num = 0;

    while(isdigit(*p))
      num = (num * 10) + (*p++ - '0');

    if(num > 0) default_dur = num;
    p++;                   // skip comma

  // get default octave
  if(*p == 'o')
  {
    p++; p++;              // skip "o="
    num = *p++ - '0';
    if(num >= 3 && num <=7) default_oct = num;
    p++;                   // skip comma
  }
  // get BPM
  if(*p == 'b')
  {
    p++; p++;              // skip "b="
    num = 0;
    while(isdigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }
    bpm = num;
    p++;                   // skip colon
  }
  wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole note (in milliseconds)
  // we need to allocate our array, count the number of commas to get the number of notes
  int newSongLength = 0;
  char *tmpPtr = p;
  while(*tmpPtr) { if(*tmpPtr++ == ',') newSongLength++;}
  newSongLength++;  // num notes = num of commas + 1
  newSong->SongLength = newSongLength; // size to pass back
  newSong->data = (songData*)calloc(newSongLength, sizeof(songData));
  // now begin note loop
  while(*p)
  {
    // first, get note duration, if available
    num = 0;
    while(isdigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }

    if(num) duration = wholenote / num;
    else duration = wholenote / default_dur;  // we will need to check if we are a dotted note after

    // now get the note
    note = 0;

    switch(*p)
    {
      case 'c':
        note = 1;
        break;
      case 'd':
        note = 3;
        break;
      case 'e':
        note = 5;
        break;
      case 'f':
        note = 6;
        break;
      case 'g':
        note = 8;
        break;
      case 'a':
        note = 10;
        break;
      case 'b':
        note = 12;
        break;
      case 'p':
      default:
        note = 0;
    }
    p++;

    // now, get optional '#' sharp
    if(*p == '#')
    {
      note++;
      p++;
    }

    // now, get optional '.' dotted note
    if(*p == '.')
    {
      duration += duration/2;
      p++;
    }

    // need to convert duration to scale for servo pulses, duration is in milliseconds, divide by 20
    // to get the number of servo pulses
    newSong->data[iNote].duration = (int)duration/20; // need to allocate for newSong

    // now, get scale
    if(isdigit(*p))
    {
      scale = *p - '0';
      p++;
    }
    else
    {
      scale = default_oct;
    }

    if(*p == ',')
      p++;       // skip comma for next note (or we may be at the end)

    newSong->data[iNote].note = ((scale + 1) * 12) + (note - 1); // our scale starts at -1, and our indices start at zero, so use scale+1, subtract 1 from result
    newSong->data[iNote].volume = 10;
    iNote++;
  }
 }
}

 

Intro->SOS->LCD Tutorial->Servo Tutorial->Timer Interrupts->Hacking Servos->Tone generator->DDS Tone Generator->Theremin 1->Theremin 2->RTTL Songs

 Posted by at 3:56 pm

  2 Responses to “RTTTL”

  1. instead of using a servo can i put the speaker in same pin slot?
    Im totally new to this whole arduino thingy

    • I’m not sure what you are asking? The servo has both an input and output wire in this project, you could hook up a speaker to any pin that is used as an output, just be careful not to draw too much current from Arduino.

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)


Time limit is exhausted. Please reload CAPTCHA.