So finally, I present to you my magical, whimsical, and bobbing art piece that will let you make your own melody. So here’s the Bobbing melody in action.
To explain the implementation, here is the Arduino circuit.
The potentiometer values are mapped to create a color wheel (the red knob) that will control the color of visualization in real-time. The values of the ultrasonic sensor fitted in the mysterious black box are mapped into the x-axis for visualization. So the hand position in front of the screen is the position of the new balls dropping from the top of the screen. In visualization, these balls bounce at the bottom of the screen and upon hitting the bottom, the color of the just hit ball changes to yellow to eventually later fade back into its original color. The whole screen is divided into twenty-one different notes of a piano and the x position of the hit ball determines the corresponding note to play. The music for piano is implemented using MIDI library and using tools like sforzando and loopMIDI.
While this project was very challenging and frustrating at times to finish, I surely had a great time working on it. I am really glad how it turned out. A lot of the parts, especially the visualization was a trial and error process. I played around with a lot of color schemes and the speed and frame rate, took a lot of time, but finalized on my current settings and I love it. I hope you enjoyed this too. If time permits, I would love to work on this project more to have various different instruments and visualizations to create a whole tune using different musical instruments and different visualization. Surely, this project and the class have inspired me a lot creatively.
Below is the code for Processing and Arduino.
//PROCESSING //Serial connection and sound import processing.serial.*; import themidibus.*; MidiBus myBus; Serial myPort; //variables for color mapping int count = 400; float radToDeg = 180.0 / PI; float countToRad = TWO_PI / float(count); float currentcolor; float Xslider; boolean fade = false; //array list for balls ArrayList<Ball> balls; //sounds 21 int[] pitches = {48,52,53,55,59,60,64,65,67,71,72,76,77,79,83,84,88,89,91,95,96}; void setup() { fullScreen(); //size(800,600); noStroke(); frameRate(54); //port communication String portname=Serial.list()[0]; myPort = new Serial(this,portname,9600); //midibus myBus = new MidiBus(this,-1,"loopMIDI Port"); // Create an empty ArrayList (will store Ball objects) balls = new ArrayList<Ball>(); //color mode colorMode(HSB,360,99,99); } void draw() { background(0,0,0); //adding new balls on screen if(fade == false){ addBalls(1); } //display all balls in action for (int i = balls.size()-1; i >= 0; i--) { Ball ball = balls.get(i); if(ball.colorhue != 0){ ball.move(); } if(ball.colorhue != 0){ ball.display(); } //if no motion or very away from slider, reduce their life if(ball.x <= Xslider-width/6|| ball.x >= Xslider+width/6 || fade==true){ if(ball.life > 100){ ball.life = 100; } } //sound and colorshift on bouncing back from bottom if(ball.y >= height){ ball.colorhue = 60; int numsound = int(map(ball.x,0,width,0,19)); if(numsound<0 || numsound>20){ numsound = abs(numsound%21); } if(ball.life<100){ myBus.sendNoteOn(0,pitches[numsound],50); } else{ myBus.sendNoteOn(0,pitches[numsound],127); } } //set the color back to orignal if(ball.y < height-height/9){ ball.colorhue = ball.originalhue; } //remove the balls from array with finished lives if (ball.finished()) { balls.remove(i); } } } // A new set of x ball objects are added to the ArrayList void addBalls(int count) { for(int i=0; i<count; i++){ float randomS = random(0,10); float ballWidth = random(4,30); float xPos = random(Xslider-width/20, Xslider+width/20); float yPos = random(-20,0); float angle = currentcolor * countToRad; int hue = int(angle * radToDeg); balls.add(new Ball(xPos, yPos, ballWidth,randomS,hue)); } } //Serial Communication void serialEvent(Serial myPort){ String s=myPort.readStringUntil('\n'); s=trim(s); if (s!=null){ int values[]=int(split(s,',')); if (values.length==2){ currentcolor = map(values[0],19,925,380,290); print(values[0]); print(","); println(values[1]); if(values[1]>150 && values[1]<3000){ fade= false; Xslider= map(values[1],150,3000,width, 0); } else{ fade = true; } } } } // Simple bouncing ball class class Ball { float x; float y; float speed; float acceleration; float w; float life = 255; int colorhue; int originalhue; float drag; boolean bounced= false; Ball(float tempX, float tempY, float tempW, float tempS, int hue) { x = tempX; y = tempY; w = tempW; speed = tempS; colorhue = hue; originalhue = hue; acceleration = 0.3; drag = 0.99; } void move() { //add speed speed = speed + acceleration; speed = speed * drag; // Add speed to y location y = y + speed; // If ball reaches the bottom // Reverse speed if (y > height) { // Dampening speed = speed * -0.9; y = height; bounced = true; } if(bounced==true && y<=10){ // Dampening speed = speed * -0.9; y = 10; bounced = false; } } boolean finished() { // Balls fade out life--; if (life < 0) { return true; } else { return false; } } void display() { // Display the circle if(colorhue == 0){ fill(color(colorhue,0,99),life); } else{ fill(color(colorhue,99,99),life); } ellipse(x,y,w,w); } }
//ARDUINO int trigger_pin = 2; int echo_pin = 3; long distance, pulse_duration; int starttime; void setup() { Serial.begin(9600); pinMode(trigger_pin, OUTPUT); pinMode(echo_pin, INPUT); digitalWrite(trigger_pin, LOW); Serial.println("0,0"); } void loop() { int sensor = analogRead(A0); digitalWrite(trigger_pin, HIGH); digitalWrite(trigger_pin, LOW); pulse_duration = pulseIn(echo_pin, HIGH); Serial.print(sensor); Serial.print(','); Serial.println(pulse_duration); }