Theremin 2

 

In this section we are going to add several enhancements to our theremin. We’ll start with the LCD and push button controls. After that we’re going to calibrate the frequency servo, and then add the code to play a song in demo mode.

Let’s start with the LCD, there really isn’t much new here. I kept the LCD functionality as simple as possible. There are only two possible things to display on the screen. The first screen says ‘Press SEL for demo’, and displays a song name. The second simply displays ‘Playing’ and then the name of the song it is playing.

LCD_messg_2

 

 

 

 

 

 

 

 

 

LCD_messg_1

 

 

 

 


 

 

 

 

 

There are two buttons, ‘next’ and ‘select’. Pressing ‘next’ will allow you to cycle through the different songs that can be played as a demonstration. Pressing select will start the demo. This is pretty simple, the push buttons are used to trigger interrupts on digital input ports 50 and 51, just like was done with the simple dice game in the LCD tutorial. First we do some setup for the LCD and interrupts.


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

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

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

Then we define the Interrupt Service Routines ( ISR )

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

If you recall the LCD tutorial, the first if statement is checking the time since the last interrupt to correct for switch bounce. When the next button is pressed, we need to increment iCurrSong which is a global variable (its an array index) that keeps track of the current song. The modulus operator (%) returns the remainder when iCurrSong is divided by numSongs, so if there are 4 songs, iCurrSong will cycle through the values 0, 1, 2, 3, .. 0, 1, 2, 3, .. 0, 1, 2 , 3.

In the last section we added some code that was for calibrating our volume level control. We also need to do this for the frequency control. I started by doing a little experiment with calls to servo.write(). I tried various angles until I found angles that looked and felt good, these angles were 26° and 154°, I found it is better to have a large range of movement to make it easier to play notes that are fairly close to one another. Then using those values and servo.write(), I used the serial monitor to see what input value is read by the Arduino. At 26° the input was 214, and at 154° it was 594. You have to remember that using a hacked servo as a potentiometer we don’t get the ideal range of 0 to 3.3 V that’s why we’re not seeing the whole range of 0 to 1024, but this is more than enough resolution for our purposes. You should test these values yourself, they may be different from mine. ( the drawing is not to scale, the range of movement is ~ 128°)

We need seven new variables to accomplish the calibration for the frequency servo

  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 scaleFactor = (fMaxInput - fMinInput) / (maxNote - minNote);

The first 6 are pretty self explanatory, the last one ‘scale factor’ represents “number of input values per note”.

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

In the loop() section, we read the input and convert it to ulInput which is used to obtain the phase increment for a corresponding note (more on that in just a second). To ensure our value does not go below the smallest input we will accept, we call the max() function with analogRead(0) and fMinInput as arguments. To ensure that we don’t exceed the largest value we want to accept, we call the min() function with the call to max() and fMaxInput as arguments. At this point we have a valid input between fMinInput and fMaxInput. Next we need to rescale our input to correspond to range of minNote to maxNote. Think of it like this, we have 128 degrees of movement that change the frequency, if you only want to play 10 notes, then you’ll have to move the servo 12.8 deg to get a new note, if you want to play 50 notes, then its 2.56 deg per note, we are scaling our input to range of notes that we chosen to play as indicated by fMinNote and fMaxNote. You can change these settings so that it is comfortable for whatever you want to play, here is a video playing 50 notes over 128 degrees.

The last thing we need to do is add code that will play one of our songs as a demonstration.First we need to discuss how a song is represented, I wanted to make this as simple as possible and since a theremin has two inputs volume and pitch, we will need both of those parameters, the only other thing that will need is how long a note is played. So the data format we need can be a structure with three members, an integer representing the note to be played, and integer representing how long that note should be played, and an integer representing the volume that that note should be played at. I called this structure ‘songData’

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

When we actually transcribe a song every note needs to be a multiple of 20 ms, we can initialize a songData array like this

// somewhere over the rainbow
songData newSong[] = {{NOTE_GS3, 67, 8},
                      {NOTE_GS4, 74, 8},
                      {NOTE_G4, 40, 9},
                      ....
                      {NOTE_GS4, 261, 8}};

The middle number represents the duration in multiples of 20 mS, e.g.the first entry means play G sharp 3, for

67 * .02 = 1.34 seconds

at volume level = 8.

I also defined a Song structure, which includes an array of songData, a string for the songName, and an integer that holds the songLength ( i.e. the number of notes in the songData array )

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

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

This program is getting kinda long, especially now that we are going to want to add songs. It is time to add a header file, where we can store some of our data types and song data. To add a header file you need to make your own library, I covered this in the Tone Generator tutorial. The Arduino site has a tutorial as well. So far whenever we have talked about notes, we were in effect talking about integers. Now that we want to specify a song we should make some #define statements so that we can write notes in a more human readable form, the header file is the place to do this e.g.

  #define NOTE_DS4 63
  #define NOTE_E4  64
  ....

The header file also contains the definition of our first song, “Somewhere over the Rainbow”, and our Song and SongData type definitions.

How do we actually play a song? First we need to take a detailed look at how the servo position gets converted into a musical note. A song is played is in ‘reverse’ order, by reverse order I mean we are going to specify a sequence of notes to be played with corresponding duration and volume. That sequence gets converted into an integer that represents an angle which is then passed to servo.write(), then the servo will move to that position and a note will be played, a way to think of this is that the main loop and interrupt routine are not aware that a song is being played, within the main loop() and Timer interrupt there is no way to tell if a person is moving the paddles. ( actually you could check a global variable, but I think its easier to understand how things work if you think about it this way). The limiting factor as to how fast we can play a song is with how fast the servo can respond, and as we discussed before we control a servo by writing a pulse every 20 ms. Therefore the shortest duration of note that we can play is 20 ms, this is fast enough. The way that we actually implement the song playing is to add another timer interrupt ( lets call it the song interrupt), this timer interrupt runs at a much lower rate than the sampling interrupt, it needs to occur once every 20 ms, to match to servo pulse train.

The way it works is that there is a global state variable named playingSong, every time the song interrupt occurs it checks if playingSong is true. If playingSong is true, the note and volume that needs to be played is obtained from the global Songs[] array, and the appropriate angle for the frequency and volume is written to the corresponding servo. The duration an individual note is played is controlled by counting the number of interrupts since the note last changed. We also need some code to check when a song is finished.

// 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 + ((float)(vMaxAngle - vMinAngle) / (float) MAX_VOL) * volume;
            volServo.write(vAngle);
           }
        }
        noteDurationCtr++;
    }
}

On the lines

      fAngle = ((float)(Songs[iCurrSong].data[iNote].note - minNote) / (float)(maxNote - minNote)) * (fMaxAngle - fMinAngle) + fMinAngle;
      freqServo.write(fAngle);

we are converting the note we obtained from the Songs array into an angle to be passed to servo.write(). Lets decompose this line. What we are doing is rescaling from our range of notes to our range of angles. The first term is

(Note2Play – minNote) / (maxNote – minNote)

think of this as the fraction of the total note range that our current note represents. We want to multiply this by the range of angle that we are using.

((Note2Play – minNote) / (maxNote – minNote)) * (fMaxAngle – fMinAngle)

think of this as the fraction of the note range that our current note represents converted into an angle. The only thing remaining is to take this angle and add it to the min angle that we are using.

((Note2Play – minNote) / (maxNote – minNote)) * (fMaxAngle – fMinAngle) + fMinAngle

The volume is converted into an angle in an analogous way.

            vAngle = vMinAngle + volume / (float) MAX_VOL) *((float)(vMaxAngle - vMinAngle);
            volServo.write(vAngle);

It is a little easier to think about since I have assumed that the minimum volume level is always zero. So that

((volume2Use – minVol)/(maxVol – minVol)) * (vMaxAngle – vMinAngle) + vMinAngle

is equivalent to

(volume2Use/ (maxVol ) * (vMaxAngle – vMinAngle) + vMinAngle

Here is a video of the theremin playing a demo, and the full code to build this project

//  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 our song  
Song *SomewhereOTR = new Song::Song("SWOTR", SWOTR, sizeof(SWOTR)/sizeof(SWOTR[0]));

// declare vars for the number of songs
int numSongs = 1;
Song *Songs = (Song*) calloc( numSongs, sizeof(Song));

int iCurrSong = 0; 

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

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

   // load songs array 
   Songs = SomewhereOTR;

  // 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);

   }
}

and the header file 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}};

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

 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.