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


