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