Friday, August 03, 2012

TINY (This Is Not space invaYders)

Or "Yet Another Arduino Blinken Light Thing" :)

So we were chatting on the TASLUG (Tasmanian Linux Users Group) mailing list talking about a new hackerspace development and whatnot when one of the fellows said he hoped that it didn't become a club of Arduino fanboys.  I said what's up with Arduino, it's awesome, and he said there's far too much boring blinkenlights and not enough actual cool stuff.  It was a bit tongue in cheek, but regardless, my nerd pride was offended and I managed to interpret it as a challenge :)   A month or so later and I had created TINY:


I'm pretty sure nerd etiquette precludes me from proclaiming success, but it did have an awesome time doing it.  I am curious though, what do you think? ;)
/*
  Space Invaders
  Turns on an LED on for one second, then off for one second, repeatedly.
 
  This example code is in the public domain.
 */
 
// pin assignments
int annodes0 = 2;int annodes1 = 3;int annodes2 = 4;int annodes3 = 5;int annodes4 = 6;
//pin 7 is spare
int cathodes0 = 8;int cathodes1 = 9;int cathodes2 = 10;int cathodes3 = 11;int cathodes4 = 12;
int IndicatorLED = 13;
int InputPot = A0;
int C_ButtonPin = 18; //ie analog4, in use as a digital pin
int C_SpeakerPin = 19; //ie analog5, in use as a digital pin

int mycursor=2;  // set initial position of the cursor
//int difficulty = 1; // where 1 is easy and 3 is hard.

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the pins
  pinMode(annodes0, OUTPUT); pinMode(annodes1, OUTPUT); pinMode(annodes2, OUTPUT); pinMode(annodes3, OUTPUT); pinMode(annodes4, OUTPUT);
  pinMode(cathodes0, OUTPUT); pinMode(cathodes1, OUTPUT); pinMode(cathodes2, OUTPUT); pinMode(cathodes3, OUTPUT); pinMode(cathodes4, OUTPUT);
  pinMode(IndicatorLED, OUTPUT); 
  pinMode(C_ButtonPin, INPUT);  pinMode(InputPot, INPUT);
 
  Serial.begin(9600);
}

void loop() {
   //basicMultiplexing(); 
   //SoundEffectGenViaPot();  
   //MakeASmiley();   
   //lightled(6,2000); delay(2000);
   //int buttonState = digitalRead(C_ButtonPin); if (buttonState == HIGH) { sound(7); }  
   
   int difficulty = introSequence(); delay(200);
   SnotVaders(difficulty);     delay(200);
   MakeASmiley();   delay(1000);
}

int introSequence(){
   int difficultyX = SimpleVelocityLoop();
   //int difficulty = map(difficultyX, 0, 1023, 3, 1); 
   int difficulty = 0;
   if (difficultyX < 315) { difficulty = 3; }
   else if (difficultyX > 648 ) { difficulty = 1; }
   else { difficulty = 2; }
      
   Serial.print("difficultyX = "); Serial.println(difficultyX); 
   Serial.print("difficulty = "); Serial.println(difficulty); 

   delay(200);
   int mydelay=10;
   for (int i = 1; i <= 25; i++) {
      sound(1);
      lightled(i,mydelay); delay(mydelay);       
      lightled(25-i,mydelay); delay(mydelay);  
   }
   sound(7);  
   delay(400);
   return difficulty;
}

void SnotVaders(int difficulty){
   // (number of displaycycles per tick will be determined by gameroundtickspeed[v_round].
   // the number of rounds is determined by the length of gameroundtickspeed[].
   // we determine the ticks per round based on the difficulty :)
   int gameroundtickspeed[]= {0,0,0,0,0,0,0,0,0};
   switch (difficulty){
      case 1: // easy
         { int gameroundtickspeedX[]= {28,22,23,20,21,17,17,17,17};  for (int z = 0; z < 9; z++) { gameroundtickspeed[z]=gameroundtickspeedX[z];} break; } 
      case 2: // medium
         { int gameroundtickspeedX[]= {20,20,20,19,18,14,14,14,11}; for (int z = 0; z < 9; z++) { gameroundtickspeed[z]=gameroundtickspeedX[z];} break; } 
      case 3: // hard
         { int gameroundtickspeedX[]= {7,7,7,7,7,7,7,7,7}; for (int z = 0; z < 9; z++) { gameroundtickspeed[z]=gameroundtickspeedX[z];} break; } 
   }
   
   int rounds = sizeof(gameroundtickspeed)/sizeof(int);
   int shipLocation=1; int notdead=1; int idealFlikerRate=24;
   int buttonStateToggle = 1; int baddieDetails[] = {0,0,0,0,0};
    
   // play successive rounds, each terminating when either all baddies or the player dies
   for (int round = 1; round <= rounds; round++){   
      // output the round to the serial console for debug
      //Serial.print(" round = "); Serial.println(round);
      
      // baddies move closer to the player each round.  
      // and they will do that faster depending on the round number.
      // if they hit the player, then the player dies, game over etc.
      
      // we define baddie starting location based on the round.
      // note that locations < 1 is "no location" and wont be displayed at all.  
      
      // i was unable to have the switch populate baddieDetails[] directly, so that's why
      // we have this odd baddieDetails[z]=locations[z] arrangement.
      switch (round) {
         case 1: { 
            int locations[]= {15, -1, -1, -1, -1}; for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;
         case 2: { 
            int locations[]= {5, 25, -1, -1, -1}; for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;       
         case 3: { 
            int locations[]= {5, 14, 25, -1, -1}; for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;                       
         case 4: { 
            int locations[]= {4, 15, 24, -1, -1}; for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;         
         case 5: { 
            int locations[]= {4, 10, 15, 20, 24};  for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;         
         case 6: { 
            int locations[]= {3, 9, 15, 19, 23};  for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;         
         case 7: { 
            int locations[]= {4, 8, 15, 18, 24};  for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;         
         case 8: { 
            int locations[]= {3, 8, 15, 18, 23};  for (int z = 0; z <= 5; z++) { baddieDetails[z]=locations[z];} } break;         
            
      } int baddieDetailsarraylen = sizeof(baddieDetails)/sizeof(int); 
              
      while(notdead) {
         // grunt noise
         sound(1);  
         
         // update the display X times to do the flicker thing
         for (int displaycycle = 1; displaycycle <= gameroundtickspeed[round-1]; displaycycle++) {
            // we plot LEDs once per displaycycle.  we do that repeatedly to give us a picture.
            // (number of displaycycles per tick will be determined by gameroundtickspeed[v_round]
            // each plotXX() function will illuminate and also extinguish it's own LEDs
            // we want this to minimise the number of simultaneously lit leds; ie flicker.)
            
            // first plot the baddies based on baddieDetails[] and check to see if they're all dead
            notdead = PlotBaddiesAndDetectAllDead(baddieDetails, baddieDetailsarraylen, idealFlikerRate); 
            if (notdead == 0) { break; } //terminate processing the while loop
            
            // check to see if any of the baddies have hit the ground, if so terminate the game.
            int Zinstruction[] = {1, 6, 11, 16, 21};  
            for(int z = 0; z < baddieDetailsarraylen; z++) { 
               for(int q = 0; q < 5; q++){
                  if (baddieDetails[z] == Zinstruction[q]){
                     // then a baddie has hit the ground, play wah wah wahhh and end
                     sound(5); delay (500); software_Reset(); 
            }  }  }
            
            // check to see if the player is shooting and if so, plot the laser and return the path it took.
            // the toggle stuff is to make sure they're not just holding the fire button
            int buttonState = digitalRead(C_ButtonPin); 
            if (buttonState == HIGH) { 
               if (buttonStateToggle) {
                  PlotShootAndDetectHit(baddieDetails, baddieDetailsarraylen, shipLocation);
                  buttonStateToggle = 0;
               }
            } else { buttonStateToggle = 1; }
            
            // move ship and return our current location
            shipLocation=PlotAndControlShipV2(idealFlikerRate); //ie; PlotAndControlShip(delay)
                     
            // output debug stuff
            //Serial.print(" dc = "); Serial.print(displaycycle); 
            //Serial.print(" rounds = "); Serial.print(rounds); 
            //Serial.print(" round = "); Serial.println(round);
            //Serial.print(" shipLocation = "); Serial.println(shipLocation);
         } // end of displaycycle
         
         // make the baddies move closer to the player
         for(int z = 0; z < baddieDetailsarraylen; z++) { 
         baddieDetails[z]--; 
         Serial.print(" baddieDetails["); Serial.print(z); Serial.print("] "); Serial.println( baddieDetails[z]);
         }         
         //}
       } // end of roundlengthcounter, ie end of round
       
       delay(500); sound(4); delay(1000); //delay between rounds
       notdead = 1;
   } // end of round, ie end of the game.
   
   sound(6); // play endgame music   
}

void software_Reset() { 
   // Restarts program from beginning but does not reset the peripherals and registers
   // taken from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1246541553
   asm volatile ("  jmp 0");  
}

void MakeASmiley(){
   int instructions[]= {9, 19, 2, 6, 11, 16, 22}; 
   //int instructions[]= {10, 20, 14, 22, 21, 16, 11, 6, 1, 2}; //froggy
   int instructionsArraylen = sizeof(instructions)/sizeof(int); 
   
   for (int i = 0; i < instructionsArraylen; i++){
      lightled(instructions[i],350);      
   }
   delay(200);   
   for (int flickercounter = 0; flickercounter < 1008 ; flickercounter ++) {
      // iterate through the array, starting at element 0
      for (int i = 0; i < instructionsArraylen; i++){
         lightled(instructions[i],1);
         //delay(100);
      }
   }
   delay(1000);
}

int PlotBaddiesAndDetectAllDead(int baddieDetails[], int baddieDetailsarraylen, int mydelay){
   int deadBaddieCounter=0;
   // note the last element in the array is arraylen -1.
   int lastarrayelement = baddieDetailsarraylen - 1;
                
   // iterate through the array, starting at element 1, not 0.  and while we're at it
   // we'll calculate the score too
   int score = 0;
   for (int i = 0; i < baddieDetailsarraylen; i++){
      // blink the leds according to the baddieDetails      
      if (baddieDetails[i] < 0){ // this is a dead baddie, so don't plot it.  instead count it.
         deadBaddieCounter++;
         if (deadBaddieCounter == baddieDetailsarraylen) { // all baddies are dead
            score = score + baddieDetails[i];  score = score * -1; score = 5 - score;
            Serial.print(" score = "); Serial.println( score);
            return 0;
         }
      } else {
         lightled(baddieDetails[i],mydelay);
      }
   }     
}

void PlotShootAndDetectHit(int baddieDetails[], int baddieDetailsarraylen, int shipLocation){
   //note this assumes that the ship won't leave the planet surface and that the 
   //planet surface is defined explicityly as 1, 6, 11, 16, 21.  if that changes 
   //then this will look odd.
   int shootdelay = 12;
  
   // to start with, simply play the shoot noise
   sound(2);  //shoot
   
   // then take the ship location, and for each one, calculate the laser trajectory
   // each led upon that trajectory is called laserLocation.  when we have that laserLocation
   // also iterate through the baddieDetails array to see if that laserLocation coincides with 
   // where a baddie is, if so, play the explode noise.
   switch(shipLocation){ // this will send the laser towards the baddies :)
      case 1:
         for(int laserLocation=2; laserLocation <=5; laserLocation++){ // plot the laser trajectory
            lightled(laserLocation,shootdelay); // plot the led(note singular) corresponding to the laserLocation
            for (int blc = 0; blc < baddieDetailsarraylen; blc ++){ 
               if (laserLocation == baddieDetails[blc]) { // if the laser is where a baddie is
                  sound(3);  // they hit the target!! so play the explode noise
                  baddieDetails[blc]=-1; // set that baddie to dead; ie don't display it                 
         }  }  }   
         break;
      case 6:
        for(int laserLocation=7; laserLocation <=10; laserLocation++){
            lightled(laserLocation,shootdelay); // plot the led(note singular) corresponding to the laserLocation
            for (int blc = 0; blc < baddieDetailsarraylen; blc ++){ 
               if (laserLocation == baddieDetails[blc]) { // if the laser is where a baddie is
                  sound(3);  // they hit the target!! so play the explode noise
                  baddieDetails[blc]=-1; // set that baddie to dead; ie don't display it
         }  }  }   
         break;
      case 11:
          for(int laserLocation=12; laserLocation <=15; laserLocation++){
            lightled(laserLocation,shootdelay); // plot the led(note singular) corresponding to the laserLocation
            for (int blc = 0; blc < baddieDetailsarraylen; blc ++){ 
               if (laserLocation == baddieDetails[blc]) { // if the laser is where a baddie is
                  sound(3);  // they hit the target!! so play the explode noise
                  baddieDetails[blc]=-1; // set that baddie to dead; ie don't display it
         }  }  }   
         break;
      case 16:
          for(int laserLocation=17; laserLocation <=20; laserLocation++){
            lightled(laserLocation,shootdelay); // plot the led(note singular) corresponding to the laserLocation
            for (int blc = 0; blc < baddieDetailsarraylen; blc ++){ 
               if (laserLocation == baddieDetails[blc]) { // if the laser is where a baddie is
                  sound(3);  // they hit the target!! so play the explode noise
                  baddieDetails[blc]=-1; // set that baddie to dead; ie don't display it
         }  }  }   
         break;      
      case 21:
          for(int laserLocation=22; laserLocation <=25; laserLocation++){
            lightled(laserLocation,shootdelay); // plot the led(note singular) corresponding to the laserLocation
            for (int blc = 0; blc < baddieDetailsarraylen; blc ++){ 
               if (laserLocation == baddieDetails[blc]) { // if the laser is where a baddie is
                  sound(3);  // they hit the target!! so play the explode noise
                  baddieDetails[blc]=-1; // set that baddie to dead; ie don't display it
         }  }  }   
         break;
    }
} 

int PlotAndControlShipV2(int mydelay){
  // planet surface 
  int instruction[] = {1, 6, 11, 16, 21};  
  // mycursor is a globally declared variable which stores where in the *instruction array* the ship
  // is currently residing.  it should not be confused with shipLocation which stores the LEDid of the 
  // ship location.  ie shipLocation = instruction[mycursor]
  
   int rawLocation = analogRead(InputPot); int location = map(rawLocation, 0, 1025, 0, 5);
  
   //Serial.print("rawLocation = "); Serial.print(rawLocation);  Serial.print(" location = "); Serial.println(location);

   lightled(instruction[location],mydelay);  
  
   return instruction[location];
}

void sound(int soundeffect) {
   switch( soundeffect ) {
      case 1: {//grunt
         tone(C_SpeakerPin, 17, 250);
      } break;
      case 2: {//shoot
         for (int z = 0; z < 1; z++){
            tone(C_SpeakerPin, 600, 80);      delay(40);
            tone(C_SpeakerPin, 100, 40);       delay(10);
         } 
      } break;
      case 3: {//explode
         //basically random-ish noise descending in frequency
         int freqbase=480; // where to start 
         for(int i=0; i < 15; i++){ // how many individual noises in the effect
            freqbase = freqbase - 20; // how much lower should each noise go?
            int frequency = random(freqbase-30,freqbase); //what exact freqency to plot
            int mydelay = random(30,150); // how long should each noise go for
            tone(C_SpeakerPin, frequency, mydelay); 
            Serial.print("frequency = "); Serial.print(frequency);  Serial.print(" mydelay = "); Serial.println(mydelay);
         } 
      } break;
      case 4: {// end round
         tone(C_SpeakerPin, 300, 300);delay(150);
         tone(C_SpeakerPin, 600, 100);delay(300);
         tone(C_SpeakerPin, 300, 300);delay(150);
         tone(C_SpeakerPin, 600, 800);delay(300);
      } break;
      case 5: {//you loose
         for (int z = 0; z < 1; z++){
            tone(C_SpeakerPin, 300, 80);      delay(800);
            tone(C_SpeakerPin, 275, 80);      delay(800);
            tone(C_SpeakerPin, 260, 80);      delay(800);
            for (int i = 0; i < 30; i++) {
               tone(C_SpeakerPin, 245, 15);     delay(15);  
               tone(C_SpeakerPin, 255, 15);     delay(15);                  
            }
            tone(C_SpeakerPin, 100, 40);       delay(10);
         } 
      } break;
      case 6: {//you win!
         // the song to play at the truimphant conclusion :)
         char *song = "20thCenFoxShort:d=16,o=5,b=140:b,8p,b,b,2b,p,c6,32p,b,32p,c6,32p,b,32p,c6,32p,b,8p,b,b,b,32p,b,32p,b,32p,b,32p,b,32p,b,32p,b,32p,g#,32p,a,32p,b,8p,b,b,2b,4p";
         play_rtttl(song);   
      } break;
      case 7: {// game start
         //basically random-ish noise ascending in frequency
         int freqbase=180; // where to start 
         for(int i=0; i < 30; i++){ // how many individual noises in the effect
            freqbase = freqbase + 15; // how much lower should each noise go?
            int frequency = random(freqbase-30,freqbase); //what exact freqency to plot
            int mydelay = random(30,150); // how long should each noise go for
            tone(C_SpeakerPin, frequency, mydelay); 
            Serial.print("frequency = "); Serial.print(frequency);  Serial.print(" mydelay = "); Serial.println(mydelay);  
         }
         tone(C_SpeakerPin, 100, 15);     delay(250);                  
         for (int i = 0; i < 2; i++) {
            for (int i = 0; i < 4; i++) {
               tone(C_SpeakerPin, 690, 100);     delay(20);  
               tone(C_SpeakerPin, 640, 100);     delay(20);  
            }
            tone(C_SpeakerPin, 100, 15);     delay(100);                  
         }
      } break;
   }
}

void lightled(int LEDid, int ondelay) {
  //Serial.print(" led = "); Serial.print(LEDid); Serial.print("  ondelay = "); Serial.println(ondelay);
  switch (LEDid) {
    case 0:
      //all off
      digitalWrite(cathodes0, LOW); digitalWrite(annodes0, LOW); digitalWrite(cathodes1, LOW); digitalWrite(annodes1, LOW);
      digitalWrite(cathodes2, LOW); digitalWrite(annodes2, LOW); digitalWrite(cathodes3, LOW); digitalWrite(annodes3, LOW);
      digitalWrite(cathodes4, LOW); digitalWrite(annodes4, LOW); break;
    case 1: //c0a0
      digitalWrite(cathodes0, HIGH); digitalWrite(annodes0, HIGH); break;
    case 2: //c0a1
      digitalWrite(cathodes0, HIGH); digitalWrite(annodes1, HIGH); break;
    case 3: //c0a2
      digitalWrite(cathodes0, HIGH); digitalWrite(annodes2, HIGH); break;
    case 4: //c0a3
      digitalWrite(cathodes0, HIGH); digitalWrite(annodes3, HIGH); break;      
    case 5: //c0a4
      digitalWrite(cathodes0, HIGH); digitalWrite(annodes4, HIGH); break;
    case 6: //c1a0
      digitalWrite(cathodes1, HIGH); digitalWrite(annodes0, HIGH); break;
    case 7: //c1a1
      digitalWrite(cathodes1, HIGH); digitalWrite(annodes1, HIGH); break;
    case 8: //c1a2
      digitalWrite(cathodes1, HIGH); digitalWrite(annodes2, HIGH); break;
    case 9: //c1a3
      digitalWrite(cathodes1, HIGH); digitalWrite(annodes3, HIGH); break;
    case 10: //c1a4
      digitalWrite(cathodes1, HIGH); digitalWrite(annodes4, HIGH); break;
    case 11: //c2a0
      digitalWrite(cathodes2, HIGH); digitalWrite(annodes0, HIGH); break;
    case 12: //c2a1
      digitalWrite(cathodes2, HIGH); digitalWrite(annodes1, HIGH); break;
    case 13: //c2a2
      digitalWrite(cathodes2, HIGH); digitalWrite(annodes2, HIGH); break;
    case 14: //c2a3
      digitalWrite(cathodes2, HIGH); digitalWrite(annodes3, HIGH); break;
    case 15: //c2a4
      digitalWrite(cathodes2, HIGH); digitalWrite(annodes4, HIGH); break;
    case 16: //c3a0
      digitalWrite(cathodes3, HIGH); digitalWrite(annodes0, HIGH); break;
    case 17: //c3a1
      digitalWrite(cathodes3, HIGH); digitalWrite(annodes1, HIGH); break;
    case 18: //c3a2
      digitalWrite(cathodes3, HIGH); digitalWrite(annodes2, HIGH); break;
    case 19: //c3a3
      digitalWrite(cathodes3, HIGH); digitalWrite(annodes3, HIGH); break;
    case 20: //c3a4
      digitalWrite(cathodes3, HIGH); digitalWrite(annodes4, HIGH); break;
    case 21: //c4a0
      digitalWrite(cathodes4, HIGH); digitalWrite(annodes0, HIGH); break;
    case 22: //c4a1
      digitalWrite(cathodes4, HIGH); digitalWrite(annodes1, HIGH); break;
    case 23: //c4a2
      digitalWrite(cathodes4, HIGH); digitalWrite(annodes2, HIGH); break;
    case 24: //c4a3
      digitalWrite(cathodes4, HIGH); digitalWrite(annodes3, HIGH); break;
    case 25: //c4a3
      digitalWrite(cathodes4, HIGH); digitalWrite(annodes4, HIGH); break;
    case 88: //do nothing.
      break;  
    case 100:
      //all on
      digitalWrite(cathodes0, HIGH); digitalWrite(annodes0, HIGH); digitalWrite(cathodes1, HIGH); digitalWrite(annodes1, HIGH);
      digitalWrite(cathodes2, HIGH); digitalWrite(annodes2, HIGH); digitalWrite(cathodes3, HIGH); digitalWrite(annodes3, HIGH);
      digitalWrite(cathodes4, HIGH); digitalWrite(annodes4, HIGH);    
      break;      
    default: // if nothing else matches, do the default
      //Serial.print("ERROR LEDid = "); Serial.println(LEDid); alloff(); allon(); delay(1500);
      break;
  }
  //delay a sec before resuming
  delay(ondelay);  alloff(); 
}

void alloff(){
  // turn everything off for contrast
  digitalWrite(cathodes0, LOW);  digitalWrite(cathodes1, LOW);  digitalWrite(cathodes2, LOW);  digitalWrite(cathodes3, LOW);  digitalWrite(cathodes4, LOW);
  digitalWrite(annodes0, LOW);  digitalWrite(annodes1, LOW);  digitalWrite(annodes2, LOW);  digitalWrite(annodes3, LOW);  digitalWrite(annodes4, LOW); 
  digitalWrite(IndicatorLED, LOW);
}

void allon(){
  // turn everything off for contrast
  digitalWrite(cathodes0, HIGH);  digitalWrite(cathodes1, HIGH);  digitalWrite(cathodes2, HIGH);  digitalWrite(cathodes3, HIGH);  digitalWrite(cathodes4, HIGH);
  digitalWrite(annodes0, HIGH);  digitalWrite(annodes1, HIGH);  digitalWrite(annodes2, HIGH);  digitalWrite(annodes3, HIGH);  digitalWrite(annodes4, HIGH); 
  digitalWrite(IndicatorLED, HIGH);
}

void SoundEffectGenViaPot(){
 //generate sounds using input pot
  int buttonState = digitalRead(C_ButtonPin);
  int frequencyX = analogRead(InputPot); int frequency = map(frequencyX, 0, 1023, 1, 6000);
   if (buttonState == HIGH) { 
      //tone(C_SpeakerPin, frequency, 200);
      tone(C_SpeakerPin, frequency, 40);      //delay(10);
      tone(C_SpeakerPin, frequency-200, 40);      //delay(10);
      tone(C_SpeakerPin, frequency, 40);      //delay(10);
      //tone(C_SpeakerPin, 100, 40);       delay(5);
  }
  Serial.print("frequency = "); Serial.println(frequency);
}

void  basicMultiplexing(){
  // for our matrix 24 is a good delay, but more leds means more flicker.  simple.
  int ondelayX = analogRead(InputPot); int ondelay = map(ondelayX, 0, 1023, 0, 100);

  for (int i = 1; i <= 25; i++){lightled(i,ondelay);}
  Serial.print("ondelay = "); Serial.println(ondelay);
}

int PlotAndControlShipVelocity(int mydelay){
  // planet surface 
  int instruction[] = {1, 6, 11, 16, 21};  
  // mycursor is a globally declared variable which stores where in the *instruction array* the ship
  // is currently residing.  it should not be confused with shipLocation which stores the LEDid of the 
  // ship location.  ie shipLocation = instruction[mycursor]
  
  // note the last element in the array is arraylen -1 !!!!
  int arraylen = sizeof(instruction)/sizeof(int); int lastarrayelement = arraylen - 1;
  // determine the position of the pot and assign a velocity to it
  int velocityX= analogRead(InputPot); int velocity = map(velocityX, 0, 1023, -150, 150);
  // set the cursor to increment or decrement depending on the velocity, note the deadzone
  if (velocity > 15) { mycursor++;} 
  if (velocity < -15) {mycursor--;}
  
  // what do we do when they reach the edge of the play area?  to wrap or not to wrap?
  // wrap:
  // if (mycursor < 0) { mycursor = lastarrayelement; } if (mycursor > lastarrayelement) { mycursor = 0; }
  // not wrap:
  if (mycursor < 0) { mycursor = 0; } if (mycursor > lastarrayelement) { mycursor = lastarrayelement; }
  
  //Serial.print("mycursor = "); Serial.print(mycursor);  Serial.print(" mydelay = "); Serial.print(mydelay);  Serial.print(" velocity = "); Serial.print(velocity); 
       
  // blink the leds according to the instructions
  lightled(instruction[mycursor],mydelay);
  
  //ie shipLocation = instruction[mycursor]  
  return instruction[mycursor];
}

int SimpleVelocityLoop(){
   // define the instructions for the array and the delay for led illumination
   //int instruction[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,0,100,0,100}; //test
   //int instruction[] = {1, 6, 11, 16, 21, 22, 23, 24, 25, 20, 15, 10, 5, 4, 3, 2, 7, 12, 17, 18, 19, 14, 9, 8, 13, 7}; // spiral and exit
   int instruction[] = {1, 6, 11, 16, 21, 22, 23, 24, 25, 20, 15, 10, 5, 4, 3, 2}; // outside loop
   //int instruction[] = {1, 6, 7, 11, 16, 12, 21, 22, 17, 23, 24, 18, 25, 20, 19, 15, 10, 14, 5, 4, 9, 3, 2, 8}; // 2 square loop

   // and then simply iterate through the instruction array
   int i; int arraylen = sizeof(instruction)/sizeof(int);
   
   int mydelayX = 0;
   int buttonState = 0;
   
   while(buttonState != HIGH){ 
      for (i = 0; i < arraylen; i = i + 1) {
         buttonState = digitalRead(C_ButtonPin); if (buttonState) { break; }
         // delay is used for setting both the ON and OFF durations
         mydelayX= analogRead(InputPot); int mydelay = map(mydelayX, 0, 1023, 4, 200);  
         // blink the leds according to the instructions
         // sound(1);
         lightled(instruction[i],mydelay);
         // turn everything off and wait a sec (before re-reading the instructinos and starting again etc)
         alloff(); delay(mydelay/2);
         //Serial.print("mydelayX = "); Serial.print(mydelayX);
      }   
   }
   return mydelayX;
}

void play_rtttl(char *p) {
  // this function based on the LCA 2012 leo stick tunes collaborative effort at https://gist.github.com/1800871
    
  //char *song = "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";
  //char *song = "Looney:d=4,o=5,b=140:32p,c6,8f6,8e6,8d6,8c6,a.,8c6,8f6,8e6,8d6,8d#6,e.6,8e6,8e6,8c6,8d6,8c6,8e6,8c6,8d6,8a,8c6,8g,8a#,8a,8f";
  //char *song = "20thCenFox:d=16,o=5,b=140:b,8p,b,b,2b,p,c6,32p,b,32p,c6,32p,b,32p,c6,32p,b,8p,b,b,b,32p,b,32p,b,32p,b,32p,b,32p,b,32p,b,32p,g#,32p,a,32p,b,8p,b,b,2b,4p,8e,8g#,8b,1c#6,8f#,8a,8c#6,1e6,8a,8c#6,8e6,1e6,8b,8g#,8a,2b";
  //char *song = "Canon:d=4,o=5,b=80:8d,8f#,8a,8d6,8c#,8e,8a,8c#6,8d,8f#,8b,8d6,8a,8c#,8f#,8a,8b,8d,8g,8b,8a,8d,8f#,8a,8b,8f#,8g,8b,8c#,8e,8a,8c#6,f#6,8f#,8a,e6,8e,8a,d6,8f#,8a,c#6,8c#,8e,b,8d,8g,a,8f#,8d,b,8d,8g,c#.6";
  //char *song = "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";
  //char *song = "Gadget:d=16,o=5,b=50: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#";
  //char *song = "Smurfs:d=32,o=5,b=200:4c#6,16p,4f#6,p,16c#6,p,8d#6,p,8b,p,4g#,16p,4c#6,p,16a#,p,8f#,p,8a#,p,4g#,4p,g#,p,a#,p,b,p,c6,p,4c#6,16p,4f#6,p,16c#6,p,8d#6,p,8b,p,4g#,16p,4c#6,p,16a#,p,8b,p,8f,p,4f#";
  //char *song = "LeisureSuit:d=16,o=6,b=56:f.5,f#.5,g.5,g#5,32a#5,f5,g#.5,a#.5,32f5,g#5,32a#5,g#5,8c#.,a#5,32c#,a5,a#.5,c#.,32a5,a#5,32c#,d#,8e,c#.,f.,f.,f.,f.,f,32e,d#,8d,a#.5,e,32f,e,32f,c#,d#.,c#";
    
  #define isdigit(n) (n >= '0' && n <= '9')
   
  #define NOTE_B0 31
  #define NOTE_C1 33
  #define NOTE_CS1 35
  #define NOTE_D1 37
  #define NOTE_DS1 39
  #define NOTE_E1 41
  #define NOTE_F1 44
  #define NOTE_FS1 46
  #define NOTE_G1 49
  #define NOTE_GS1 52
  #define NOTE_A1 55
  #define NOTE_AS1 58
  #define NOTE_B1 62
  #define NOTE_C2 65
  #define NOTE_CS2 69
  #define NOTE_D2 73
  #define NOTE_DS2 78
  #define NOTE_E2 82
  #define NOTE_F2 87
  #define NOTE_FS2 93
  #define NOTE_G2 98
  #define NOTE_GS2 104
  #define NOTE_A2 110
  #define NOTE_AS2 117
  #define NOTE_B2 123
  #define NOTE_C3 131
  #define NOTE_CS3 139
  #define NOTE_D3 147
  #define NOTE_DS3 156
  #define NOTE_E3 165
  #define NOTE_F3 175
  #define NOTE_FS3 185
  #define NOTE_G3 196
  #define NOTE_GS3 208
  #define NOTE_A3 220
  #define NOTE_AS3 233
  #define NOTE_B3 247
  #define NOTE_C4 262
  #define NOTE_CS4 277
  #define NOTE_D4 294
  #define NOTE_DS4 311
  #define NOTE_E4 330
  #define NOTE_F4 349
  #define NOTE_FS4 370
  #define NOTE_G4 392
  #define NOTE_GS4 415
  #define NOTE_A4 440
  #define NOTE_AS4 466
  #define NOTE_B4 494
  #define NOTE_C5 523
  #define NOTE_CS5 554
  #define NOTE_D5 587
  #define NOTE_DS5 622
  #define NOTE_E5 659
  #define NOTE_F5 698
  #define NOTE_FS5 740
  #define NOTE_G5 784
  #define NOTE_GS5 831
  #define NOTE_A5 880
  #define NOTE_AS5 932
  #define NOTE_B5 988
  #define NOTE_C6 1047
  #define NOTE_CS6 1109
  #define NOTE_D6 1175
  #define NOTE_DS6 1245
  #define NOTE_E6 1319
  #define NOTE_F6 1397
  #define NOTE_FS6 1480
  #define NOTE_G6 1568
  #define NOTE_GS6 1661
  #define NOTE_A6 1760
  #define NOTE_AS6 1865
  #define NOTE_B6 1976
  #define NOTE_C7 2093
  #define NOTE_CS7 2217
  #define NOTE_D7 2349
  #define NOTE_DS7 2489
  #define NOTE_E7 2637
  #define NOTE_F7 2794
  #define NOTE_FS7 2960
  #define NOTE_G7 3136
  #define NOTE_GS7 3322
  #define NOTE_A7 3520
  #define NOTE_AS7 3729
  #define NOTE_B7 3951
  #define NOTE_C8 4186
  #define NOTE_CS8 4435
  #define NOTE_D8 4699
  #define NOTE_DS8 4978  
  #define OCTAVE_OFFSET 0
  
  int notes[] = { 0,
  NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
  NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
  NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
  NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7
  };
    // Absolutely no error checking in here
  
    byte default_dur = 4;
    byte default_oct = 6;
    int bpm = 63;
    int num;
    long wholenote;
    long duration;
    byte note;
    byte scale;
  
    // format: d=N,o=N,b=NNN:
    // find the start (skip name, etc)
  
    while(*p != ':') p++; // ignore name
    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
    }
    
    // BPM usually expresses the number of quarter notes per minute
    wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole note (in milliseconds)
    
    // 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++;
      }
    
      // now, get scale
      if(isdigit(*p)){
        scale = *p - '0';
        p++;
      }
      else{
        scale = default_oct;
      }
  
      scale += OCTAVE_OFFSET;
  
      if(*p == ',')
        p++; // skip comma for next note (or we may be at the end)
  
      // now play the note
  
      if(note){
        digitalWrite(C_SpeakerPin, HIGH);
        int danFreq;
        float danDur;
        danFreq = notes[(scale - 4) * 12 + note];
        danDur = 1000000 / danFreq;
        unsigned long start = millis();
        while (millis() - start <= duration) {
            digitalWrite(C_SpeakerPin, HIGH);
            delayMicroseconds(danDur);
            digitalWrite(C_SpeakerPin, LOW);
            delayMicroseconds(danDur);
        }
        digitalWrite(C_SpeakerPin, LOW);
      }
      else{
        delay(duration);
      }
    }
}


10 comments:

Just Build Stuff said...

Thanks for posting this, TINY is a great project and much more than Yet Another Arduino Blinken Light Thing. This will be my next Arduino project, I hope to have it done by the end of the year. The kids should think it is fun.

daler said...

very nice! good job.

Anonymous said...

good job! very nice.

daler said...

very nice! good job.

BikeRiderTV.com said...

Mate, that is definitely the coolest blinkin' thing I have seen done with an Arduino. Well done!

Lars Jansen said...

Mate, that is definitely the coolest blinkin' thing I have seen done with an Arduino. Well done!

Hades Spaniel said...

I think it's awesome. Challenge was offered, you accepted, and delivered. Greets from Adelaide Hills

Anonymous said...

That is so cool....I want an arduino...

B said...

So cool... :)

Hoolio said...

Thanks everyone for your appreciation and encouragement, it means alot :)