Super Mario Ball

Assignment: Input / Output Coincidence Lab Assignment

Collaborators: ryan

Description

One of the joys of youth is navigating through hostile lands as an unassuming plumber, rescuing a princess and killing koopa troopas as you go. One of the most fun, or most aggravating, aspects of mastering Mario is the precise timing required to speed through levels. Among the cues given to players is the music of Mario, which can elate and terrify. This project was an attempt to create a game-like experience for users in which their timing, memory of Mario, and physical skills were combined. The project allows users to replay the Mario theme song, one note at a time, by hitting a tennis ball. The ball is both the input device and the speaker.

Materials

  • tennis ball
  • cardboard
  • FSR
  • Piezo speaker
  • (2) arduino board

Programs

Processing

import processing.serial.*;
import java.util.*;

// Change this to the portname your Arduino board
String portname = "/dev/tty.usbserial-A7006RZH"; // or "COM5"
String portname2 = "/dev/tty.usbserial-A7006SoU";
//String tty.usbserial-A7006SoU
Serial port;
Serial port2;
Serial sensor_port;
Serial speaker_port;
String buf="";
int cr = 13;  // ASCII return   == 13
int lf = 10;  // ASCII linefeed == 10

// Awesome screenshot
PImage screenshot = loadImage("http://dl.getdropbox.com/u/172008/mario.png");

// Inputs from Arduino
int lightVal;
int forceVal;

// Used to determine the adjusted bounds for light sensitivity
// I would like these to be set (by prompting the user to make it bright or dark)
// when the program is run, but for now I'm just setting the values here.
int maxLightThreshold = 250; // Photosensor values above the maxLightThreshold are considered "maximum brightness"
int minLightThreshold = 80; // Photosensor values below the minLightThreshold are considered "minimum brightness"


// Used to determine when to process a particular force value
// Activity is only detected if reading from FSR passes threshold value
int forceThreshold = 450; // Starts at -1. First reading from FSR is used to calibrate initial threshold.
int forceMax = 1023; // Theoretical maximum force. Used to determine relative size of circle based on force.
boolean forceEvent = true; // Used to track if an event can occur, based on previous readings

int k = 0;

// Classes for music
// Class to represent the notes to be played for a given song
class Song {
  ArrayList notes;
  int currentNote;

  Song(String s) {
    notes = new ArrayList();
    currentNote = 0;

    String[] n = s.split(","); // Split song string into notes
    for (int i = 0; i < n.length; i++) {
      notes.add(n[i]);
    } 
    println("Created song that is " + notes.size() + " notes long");
  }

  void listNotes() {
    for (int i = 0; i < notes.size(); i++) {
      println("Next note is: " + notes.get(i));
    }
  }

  Integer nextNote() {
    // println("currentNote is " + currentNote);
    if (currentNote >= notes.size()) {
      // Restart song
      // And return value indicating that song has ended
      currentNote = 0;
      return 1915;
    }
    String nextNote = (String) notes.get(currentNote++);
    println("Playing " + nextNote);
    return (Integer) notePeriods.get(nextNote);
  }
}

Song song;

static class Notes {
  static String noteNames[] = {
    "c", "d", "e", "f", "g", "a", "b", "C", "D", "F", "ab", "a#", "bb", "c#", "db", "d#", "eb", "f#", "gb", "g#", "a2"    };
  static int notePeriods[] = { 
    1915, 1700, 1519, 1432, 1275, 1136, 1014, 956, 851, 716, 1204, 1072, 1072, 1805, 1805, 1607, 1607, 1355, 1355, 1204, 2272    };

  static HashMap namesPeriodsMap() {
    HashMap h = new HashMap();
    // Create mapping between note names and corresponding frequencies
    for (int i = 0; i < noteNames.length; i++) {
      h.put(noteNames[i], notePeriods[i]);
    }
    return h;
  }
}

void playNote() {
  // println("Playing next note...");
  int n = song.nextNote();
  speaker_port.write(n + "*25%");
}

HashMap notePeriods = Notes.namesPeriodsMap();   // Create mapping between note names and corresponding frequencies

//String songText = "E,E,E,C,E,G,G,C,G,E,A,B,A#,G,E,G,A,F,G,E,C,D,B,C,G,E,A,B,A#,G,E,G,A,F,G,E,C,D,B,G,F#,F,D#,E,G#,A,C,A,C,D,G,F#,F,
//,C,C,C,G,F#,F,D#,E,G#,A,C,A,C,D,D#,D,C,C,C,C,C,D,E,C,A,G,C,C,C,C,D,E,C,C,C,C,D,E,C,A,G,E,E,E,C,E,G,G,E,C,G,G#,A,F,F,A,B,A,A,A,G,F,E,C,A,G,E,C,G,G#,A,F,F,A,B,F,F,F,E,D,C";
String songText = "a,a,a,f,a,C,c,f,c,a2,d,e,d#,d,c,a,C,D,a#,C,a,f,g,e,f,c,a2,d,e,d#,d,c,a,C,D,a#,C,a,f,g,e,C,b,a#,g#,a,c#,d,f,d,f,g,C,b,a#,g#,a,F,F,F,C,b,a#,g#,a,c#,d,f,d,f,g,C,b,a#,g#,a,c#,d,f,d,f,g,g#,g,f";
boolean speakerReady = false;
boolean sensorReady = false;

void checkForForceEvent(int val) {
  // This is the first force reading
  // Set the initial threshold
  if (forceThreshold == -1) {
    setForceThreshold(val);
    return;
  }

  // There is no pressure currently. Reset the flag so that a forceEvent occurs
  // the next time pressure exceeds the threshold
  if (forceEvent == false && val <= forceThreshold) {
    forceEvent = true;
    println("Reset for next force event");
    return;
  }

  // If forceEvent is false, that means that we have already surpassed the threshold
  // and acted on the force once. We don't want to act again until we have dropped below
  // the threshold. This is so that any force event only triggers one action.
  //
  // We also don't want to do anything if the current reading is below the threshold.
  if (forceEvent == false || val <= forceThreshold) {
    // println("Nothing");
    return;
  }

  // Schedule the circle to be drawn next time draw() is run
  // The use of this flag is to get around a bizarre bug in OpenGL rendering.
  // If drawCircle() is called directly from here the program crashes.
  println("Force event occured with force = " + val);
      playNote();
  forceEvent = false;
}

// Set threshold for detecting activity to 105% of initial start value
void setForceThreshold(int force) {
  println("Current force at " + force);
  forceThreshold = round((force + 1) * 1.25); // Plus 1 in case the initial value is 0
  println("Set initial force threshold to " + forceThreshold);
}

void setup() {
  // Set up serial communication
  sensor_port = new Serial(this, portname, 9600);
  speaker_port = new Serial(this, portname2, 9600);

  song = new Song(songText);
  song.listNotes();

  size(510, 480);
  image(screenshot, 0, 0);

  delay(5000);
  println("Done with setup...");
}

void draw() {
}

// called whenever serial data arrives
void serialEvent(Serial p) {

  int c = sensor_port.read();

  // Add string to buffer if not end of line
  if (c != lf && c != cr) {
    buf += char(c);
  }
  if (c == lf) {
    if (buf.charAt(0) == 'F') {
      // println("Got value for force...");
      forceVal = int(buf.substring(1));
      checkForForceEvent(forceVal);
    } 
    else if (buf.charAt(0) == 'L') {
      // println("Got value for light...");
      lightVal = int(buf.substring(1));
    } 

    buf = ""; // Clear buffer
  }
}

Arduino - Speaker

/* Sound Serial (aka Keyboard Serial)
* ------------
*
* Program to play tones depending on the data coming from the serial port.
*
* The calculation of the tones is made following the mathematical
* operation:
*
*       timeHigh = 1/(2 * toneFrequency) = period / 2
*
* where the different tones are described as in the table:
*
* note  frequency  period  PW (timeHigh)
* c          261 Hz          3830  1915  
* d          294 Hz          3400  1700  
* e          329 Hz          3038  1519  
* f          349 Hz          2864  1432  
* g          392 Hz          2550  1275  
* a          440 Hz          2272  1136  
* b          493 Hz          2028 1014
* C         523 Hz         1912  956
// a
// b
// c
// d
// e
// f
// g
// h -- a flat
// i -- a sharp
// j -- b flat
// k -- c sharp
// l -- d flat
// m -- d sharp
// n -- e flat
// o -- f sharp
// p -- g flat
// q -- g sharp
*
* (cleft) 2005 D. Cuartielles for K3
*
* Updated by Tod E. Kurt <tod@todbot.com> to use new Serial. commands
* and have a longer cycle time.
*
*/

char serInString[100];  // array that will hold the different bytes of the string
char c;

int speakerPin = 7;
// note names and their corresponding half-periods  
byte names[] ={ 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q'};  
int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956, 1204, 1072, 1072, 1805, 1805, 1607, 1607, 1355, 1355, 1204};

boolean debug = true;

int ledState = LOW;
int count = 0;
int tone = 0;
int duration = 0;
void setup() {
//  pinMode(ledPin, OUTPUT);
  pinMode(speakerPin, OUTPUT);
  Serial.begin(9600);
  Serial.println("ready");
//  establishContact();
}

void loop() {
  if (Serial.available() > 0) {
    long start = millis();
    // Get string
    readSerialString(serInString);

    // Read two numbers from string
    // The read string will be in the format "1234*100"
    // We need to get this into two integers

    int i;

    // First integer
    tone = 0;
    for (i = 0; i < 100; i++) {
      c = serInString[i];

      // Break when "*" is encountered
      if (c == 42) {
        // ASCII * == 42
        break;
      }

      // If char is a number, add it to tone
      if (c >= 48 && c <= 57) {
        // ASCII 0 == 48
        // ASCII 9 == 57
        tone = tone * 10 + (c - 48);
      }
    }

    // Second integer
    duration = 0;
    for (i=i+1; i < 100; i++) { // i=i+1 because we continue reading the same string array
      c = serInString[i];

      // Break when is new line or null char is encountered
      if (c == 0) {
        // The null character, ASCII 0 is used to terminate the string
        break;
      }

      // If char is a number, add it to duration
      if (c >= 48 && c <= 57) {
        // ASCII 0 == 48
        // ASCII 9 == 57
        duration = duration * 10 + (c - 48);
      }
    }
  long stopTime = millis() - start;

  if (debug == true) {
    Serial.println("");
    Serial.print("Time to read: ");
    Serial.print(stopTime);
    Serial.println("ms");

    Serial.print("Received tone value: ");
    Serial.println(tone);

    Serial.print("Received duration value: ");
    Serial.println(duration);
  }

  playSound();

  }
}

// Play Sound
void playSound() {
  Serial.println("Playing note");
      for( int i=0; i<duration; i++ ) {   // play it for 50 cycles
        digitalWrite(speakerPin, HIGH);
        delayMicroseconds(tone);
        digitalWrite(speakerPin, LOW);
        delayMicroseconds(tone);
      }
}

// read a string from the serial and store it in an array
// you must supply the array variable
// readSerialString function by
// Tod E. Kurt <tod@todbot.com>
// Reads from serial until the termination character is received
void readSerialString (char *strArray) {
  int i = 0;
  if(!Serial.available()) {
    return;
  }
  while (strArray[i-1] != 37) { // ASCII 37 = %
    if (Serial.available()) {
      strArray[i] = Serial.read();
      i++;
    }
  }
  strArray[i] = 0; // Terminate string with null
}

// Given a pointer to a sring array,
// set all values in that array to 0 (null char)
void eraseString (char *strArray) {
  int i;
  for (i=0; i < sizeof(strArray); i++) {
    strArray[i] = 0;
  }
}

// http://www.arduino.cc/en/Tutorial/SerialCallResponseASCII
 void establishContact() {
   while (Serial.available() <= 0) {
     Serial.println("\n");   // send an initial string
     delay(300);
   }
 }

Arduino - FSR