Link to p5js code: https://editor.p5js.org/luke.s.ng/sketches/U8Xmnxnwu
Concept:
My final project is a Audio Workstation that can allow users to input music from Arduino using a special glove into p5 and visualize it. I was inspired by this Mimu glove created by MI.MU GLOVES LIMITED, which allows users to make music through movement using only a pair of glove with a lot of physical computing programmed into the device. My project is a simplified version of this, which only utilizes Arduino. I used p5.js to enhance the user’s experience visually, allowing them to see how their music is seen to the eyes.
User testing:
Some pictures / video of project interactions & comments + feedback:
What parts of your project did you feel the need to explain? How could you make these areas more clear to someone that is experiencing your project for the first time?
I got more than 10 people who came and interacted with my device, and a few of them were my classmates and friends from other classes.
Ume passed by, and I also invited her to test it. Afterward, she suggested that for future improvement, I could consider implementing this device in a way that it can trigger many musical instruments at the same time. She commented that this project of mine is already advanced enough, and her suggestion can be considered for a later, more advanced stage of learning interactive media.
Other than Ume, your friend also suggested that I can keep this device as a simple music maker but develop it into one that can incorporate more musical instruments.
Implementation/Description of interaction design:
Users can create music notes using Arduino and trigger them to appear inside p5. The Arduino board makes use of two major components: four buttons representing four fingers and one distance sensor. Each finger is assigned a different music instrument: index finger – piano, middle finger – drum, ring finger – guitar, pinky – synthesizer pulses.
The program currently supports the C major scale, consisting of 7 basic notes: C, D, E, F, G, A, B. Piano notes range from C4 to B5. Drum rhythm ranges from heavy kick to high crash. Guitar notes range from C3 to A3. And synthesizer pulses range from C6 to A6.
The user can control one instrument using two simultaneous actions: pressing the buttons attached to the tip of the glove they wear, users can trigger music notes inside p5.js. Simultaneously, by VERTICALLY controlling a distance sensor placed on the table, they can select/control the notes by changing the frequency of the buttons/their fingertips. Users can press other buttons at the same time to play with other musical instruments.
Additionally, they can press “R” and then play one instrument to record, then press “S” to stop, then press “L” to loop and save the first instrument. They can then input another instrument. The process can be repeated for other instruments. Users can keep doing that until they have generated something either funky, following a stream of consciousness, or a nice melody.
p5js will take the notes users input, put them through an amplitude analyzer and create a visualization on screen.
Arduino code:
// Start serial communication so we can send data
// over the USB connection to our p5js sketch
while (Serial.available() <= 0) {
Serial.println("0,0"); // send a starting message
delay(300); // wait 1/3 second
digitalWrite(LED_BUILTIN, LOW);
// wait for data from p5 before doing something
while (Serial.available()) {
// digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data
if (Serial.read() == '\n') {
// detecting the pressure sensor
int PressureSensor = analogRead(A0);
int PressureSensor2 = analogRead(A2);
int PressureSensor3 = analogRead(A4);
int PressureSensor4 = analogRead(A6);
long duration, inches, cm;
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
digitalWrite(trigPin, LOW);
digitalWrite(trigPin, HIGH);
digitalWrite(trigPin, LOW);
// The same pin is used to read the signal from the PING))): a HIGH pulse
// whose duration is the time (in microseconds) from the sending of the ping
// to the reception of its echo off of an object.
duration = pulseIn(echoPin, HIGH);
// convert the time into a distance
inches = microsecondsToInches(duration);
Serial.print(PressureSensor);
Serial.print(PressureSensor2);
Serial.print(PressureSensor3);
Serial.println(PressureSensor4);
// digitalWrite(LED_BUILTIN, LOW);
long microsecondsToInches(long microseconds) {
// According to Parallax's datasheet for the PING))), there are 73.746
// microseconds per inch (i.e. sound travels at 1130 feet per second).
// This gives the distance travelled by the ping, outbound and return,
// so we divide by 2 to get the distance of the obstacle.
// See: https://www.parallax.com/package/ping-ultrasonic-distance-sensor-downloads/
return microseconds / 74 / 2;
long microsecondsToCentimeters(long microseconds) {
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the object we
// take half of the distance travelled.
return microseconds / 29 / 2;
const int trigPin = 7;
const int echoPin = 8;
void setup() {
// Start serial communication so we can send data
// over the USB connection to our p5js sketch
Serial.begin(9600);
pinMode(trigPin,OUTPUT);
pinMode(echoPin,INPUT);
// start the handshake
while (Serial.available() <= 0) {
Serial.println("0,0"); // send a starting message
delay(300); // wait 1/3 second
digitalWrite(LED_BUILTIN, LOW);
delay(50);
}
}
void loop() {
// wait for data from p5 before doing something
while (Serial.available()) {
// digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data
if (Serial.read() == '\n') {
// detecting the pressure sensor
int PressureSensor = analogRead(A0);
delay(5);
int PressureSensor2 = analogRead(A2);
delay(5);
int PressureSensor3 = analogRead(A4);
delay(5);
int PressureSensor4 = analogRead(A6);
delay(5);
long duration, inches, cm;
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// The same pin is used to read the signal from the PING))): a HIGH pulse
// whose duration is the time (in microseconds) from the sending of the ping
// to the reception of its echo off of an object.
duration = pulseIn(echoPin, HIGH);
// convert the time into a distance
inches = microsecondsToInches(duration);
Serial.print(PressureSensor);
Serial.print(',');
Serial.print(inches);
Serial.print(',');
Serial.print(PressureSensor2);
Serial.print(',');
Serial.print(PressureSensor3);
Serial.print(',');
Serial.println(PressureSensor4);
delay(100);
}
// digitalWrite(LED_BUILTIN, LOW);
}
}
long microsecondsToInches(long microseconds) {
// According to Parallax's datasheet for the PING))), there are 73.746
// microseconds per inch (i.e. sound travels at 1130 feet per second).
// This gives the distance travelled by the ping, outbound and return,
// so we divide by 2 to get the distance of the obstacle.
// See: https://www.parallax.com/package/ping-ultrasonic-distance-sensor-downloads/
return microseconds / 74 / 2;
}
long microsecondsToCentimeters(long microseconds) {
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the object we
// take half of the distance travelled.
return microseconds / 29 / 2;
}
const int trigPin = 7;
const int echoPin = 8;
void setup() {
// Start serial communication so we can send data
// over the USB connection to our p5js sketch
Serial.begin(9600);
pinMode(trigPin,OUTPUT);
pinMode(echoPin,INPUT);
// start the handshake
while (Serial.available() <= 0) {
Serial.println("0,0"); // send a starting message
delay(300); // wait 1/3 second
digitalWrite(LED_BUILTIN, LOW);
delay(50);
}
}
void loop() {
// wait for data from p5 before doing something
while (Serial.available()) {
// digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data
if (Serial.read() == '\n') {
// detecting the pressure sensor
int PressureSensor = analogRead(A0);
delay(5);
int PressureSensor2 = analogRead(A2);
delay(5);
int PressureSensor3 = analogRead(A4);
delay(5);
int PressureSensor4 = analogRead(A6);
delay(5);
long duration, inches, cm;
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// The same pin is used to read the signal from the PING))): a HIGH pulse
// whose duration is the time (in microseconds) from the sending of the ping
// to the reception of its echo off of an object.
duration = pulseIn(echoPin, HIGH);
// convert the time into a distance
inches = microsecondsToInches(duration);
Serial.print(PressureSensor);
Serial.print(',');
Serial.print(inches);
Serial.print(',');
Serial.print(PressureSensor2);
Serial.print(',');
Serial.print(PressureSensor3);
Serial.print(',');
Serial.println(PressureSensor4);
delay(100);
}
// digitalWrite(LED_BUILTIN, LOW);
}
}
long microsecondsToInches(long microseconds) {
// According to Parallax's datasheet for the PING))), there are 73.746
// microseconds per inch (i.e. sound travels at 1130 feet per second).
// This gives the distance travelled by the ping, outbound and return,
// so we divide by 2 to get the distance of the obstacle.
// See: https://www.parallax.com/package/ping-ultrasonic-distance-sensor-downloads/
return microseconds / 74 / 2;
}
long microsecondsToCentimeters(long microseconds) {
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the object we
// take half of the distance travelled.
return microseconds / 29 / 2;
}
p5.js code:
The p5js code is too long to post here, so I’ll include aspects of code that I’m really proud of.
function startPlayingGame() {
// remember to calibrate the note accordingly
if (freq_piano >= 1 && freq_piano <= 2){
c4_piano.play(); c4_piano.setVolume(0.1);
else if (freq_piano >= 3 && freq_piano <= 4){
d4_piano.play(); d4_piano.setVolume(0.1);
else if (freq_piano >= 5 && freq_piano <= 6){
e4_piano.play(); e4_piano.setVolume(0.1);
else if (freq_piano >= 7 && freq_piano <= 8){
f4_piano.play(); f4_piano.setVolume(0.1);
else if (freq_piano >= 9 && freq_piano <= 10){
g4_piano.play(); g4_piano.setVolume(0.1);
else if (freq_piano >= 11 && freq_piano <= 12){
a4_piano.play(); a4_piano.setVolume(0.1);
else if (freq_piano >= 13 && freq_piano <= 14){
b4_piano.play(); b4_piano.setVolume(0.1);
else if (freq_piano >= 15 && freq_piano <= 16){
c5_piano.play(); c5_piano.setVolume(0.1);
else if (freq_piano >= 17 && freq_piano <= 18){
d5_piano.play(); d5_piano.setVolume(0.1);
else if (freq_piano >= 19 && freq_piano <= 20){
e5_piano.play(); e5_piano.setVolume(0.1);
else if (freq_piano >= 21 && freq_piano <= 22){
f5_piano.play(); f5_piano.setVolume(0.1);
else if (freq_piano >= 23 && freq_piano <= 24){
g5_piano.play(); g5_piano.setVolume(0.1);
else if (freq_piano >= 25 && freq_piano <= 26){
a5_piano.play(); a5_piano.setVolume(0.1);
else if (freq_piano >= 27 && freq_piano <= 28){
b5_piano.play(); b5_piano.setVolume(0.1);
if (freq_piano >= 1 && freq_piano <= 3){
heavykick.play(); heavykick.setVolume(0.1);
else if (freq_piano >= 4 && freq_piano <= 6){
lightkick.setVolume(0.1);}
else if (freq_piano >= 7 && freq_piano <= 9){
snaresidekick.setVolume(0.1);}
else if (freq_piano >= 10 && freq_piano <= 12){
else if (freq_piano >= 13 && freq_piano <= 15){
snarecenter.setVolume(0.1);}
else if (freq_piano >= 16 && freq_piano <= 18){
hihatopen.setVolume(0.1);}
else if (freq_piano >= 19 && freq_piano <= 21){
else if (freq_piano >= 22 && freq_piano <= 24){
if (freq_piano >= 1 && freq_piano <= 3){
c3_guitar.setVolume(0.1);
else if (freq_piano >= 4 && freq_piano <= 6){
d3_guitar.setVolume(0.1);
else if (freq_piano >= 7 && freq_piano <= 9){
e3_guitar.setVolume(0.1);
else if (freq_piano >= 10 && freq_piano <= 12){
f3_guitar.setVolume(0.1);
else if (freq_piano >= 13 && freq_piano <= 15){
g3_guitar.setVolume(0.1);
else if (freq_piano >= 16 && freq_piano <= 18){
a3_guitar.setVolume(0.1);
else if (freq_piano >= 19 && freq_piano <= 21){
b3_guitar.setVolume(0.1);
if (freq_piano >= 1 && freq_piano <= 3){
else if (freq_piano >= 4 && freq_piano <= 6){
else if (freq_piano >= 7 && freq_piano <= 9){
else if (freq_piano >= 10 && freq_piano <= 12){
else if (freq_piano >= 13 && freq_piano <= 15){
else if (freq_piano >= 16 && freq_piano <= 18){
else if (freq_piano >= 19 && freq_piano <= 21){
function startPlayingGame() {
// remember to calibrate the note accordingly
background(0);
console.log(note_piano);
if (note_piano > 200){
if (freq_piano >= 1 && freq_piano <= 2){
c4_piano.play(); c4_piano.setVolume(0.1);
}
else if (freq_piano >= 3 && freq_piano <= 4){
d4_piano.play(); d4_piano.setVolume(0.1);
}
else if (freq_piano >= 5 && freq_piano <= 6){
e4_piano.play(); e4_piano.setVolume(0.1);
}
else if (freq_piano >= 7 && freq_piano <= 8){
f4_piano.play(); f4_piano.setVolume(0.1);
}
else if (freq_piano >= 9 && freq_piano <= 10){
g4_piano.play(); g4_piano.setVolume(0.1);
}
else if (freq_piano >= 11 && freq_piano <= 12){
a4_piano.play(); a4_piano.setVolume(0.1);
}
else if (freq_piano >= 13 && freq_piano <= 14){
b4_piano.play(); b4_piano.setVolume(0.1);
}
else if (freq_piano >= 15 && freq_piano <= 16){
c5_piano.play(); c5_piano.setVolume(0.1);
}
else if (freq_piano >= 17 && freq_piano <= 18){
d5_piano.play(); d5_piano.setVolume(0.1);
}
else if (freq_piano >= 19 && freq_piano <= 20){
e5_piano.play(); e5_piano.setVolume(0.1);
}
else if (freq_piano >= 21 && freq_piano <= 22){
f5_piano.play(); f5_piano.setVolume(0.1);
}
else if (freq_piano >= 23 && freq_piano <= 24){
g5_piano.play(); g5_piano.setVolume(0.1);
}
else if (freq_piano >= 25 && freq_piano <= 26){
a5_piano.play(); a5_piano.setVolume(0.1);
}
else if (freq_piano >= 27 && freq_piano <= 28){
b5_piano.play(); b5_piano.setVolume(0.1);
}
}
if (drum > 200){
if (freq_piano >= 1 && freq_piano <= 3){
heavykick.play(); heavykick.setVolume(0.1);
}
else if (freq_piano >= 4 && freq_piano <= 6){
lightkick.play();
lightkick.setVolume(0.1);}
else if (freq_piano >= 7 && freq_piano <= 9){
snaresidekick.play();
snaresidekick.setVolume(0.1);}
else if (freq_piano >= 10 && freq_piano <= 12){
lowtom.play();
lowtom.setVolume(0.1);}
else if (freq_piano >= 13 && freq_piano <= 15){
snarecenter.play();
snarecenter.setVolume(0.1);}
else if (freq_piano >= 16 && freq_piano <= 18){
hihatopen.play();
hihatopen.setVolume(0.1);}
else if (freq_piano >= 19 && freq_piano <= 21){
hitom.play();
hitom.setVolume(0.1);}
else if (freq_piano >= 22 && freq_piano <= 24){
crash.play();
crash.setVolume(0.1);}
}
if (guitar > 200){
if (freq_piano >= 1 && freq_piano <= 3){
c3_guitar.play();
c3_guitar.setVolume(0.1);
}
else if (freq_piano >= 4 && freq_piano <= 6){
d3_guitar.play();
d3_guitar.setVolume(0.1);
}
else if (freq_piano >= 7 && freq_piano <= 9){
e3_guitar.play();
e3_guitar.setVolume(0.1);
}
else if (freq_piano >= 10 && freq_piano <= 12){
f3_guitar.play();
f3_guitar.setVolume(0.1);
}
else if (freq_piano >= 13 && freq_piano <= 15){
g3_guitar.play();
g3_guitar.setVolume(0.1);
}
else if (freq_piano >= 16 && freq_piano <= 18){
a3_guitar.play();
a3_guitar.setVolume(0.1);
}
else if (freq_piano >= 19 && freq_piano <= 21){
b3_guitar.play();
b3_guitar.setVolume(0.1);
}
}
if (synth > 200){
if (freq_piano >= 1 && freq_piano <= 3){
c6_synth.play();
c6_synth.setVolume(0.1);
}
else if (freq_piano >= 4 && freq_piano <= 6){
d6_synth.play();
d6_synth.setVolume(0.1);
}
else if (freq_piano >= 7 && freq_piano <= 9){
e6_synth.play();
e6_synth.setVolume(0.1);
}
else if (freq_piano >= 10 && freq_piano <= 12){
f6_synth.play();
f6_synth.setVolume(0.1);
}
else if (freq_piano >= 13 && freq_piano <= 15){
g6_synth.play();
g6_synth.setVolume(0.1);
}
else if (freq_piano >= 16 && freq_piano <= 18){
a6_synth.play();
a6_synth.setVolume(0.1);
}
else if (freq_piano >= 19 && freq_piano <= 21){
b6_synth.play();
b6_synth.setVolume(0.1);
}
}
function startPlayingGame() {
// remember to calibrate the note accordingly
background(0);
console.log(note_piano);
if (note_piano > 200){
if (freq_piano >= 1 && freq_piano <= 2){
c4_piano.play(); c4_piano.setVolume(0.1);
}
else if (freq_piano >= 3 && freq_piano <= 4){
d4_piano.play(); d4_piano.setVolume(0.1);
}
else if (freq_piano >= 5 && freq_piano <= 6){
e4_piano.play(); e4_piano.setVolume(0.1);
}
else if (freq_piano >= 7 && freq_piano <= 8){
f4_piano.play(); f4_piano.setVolume(0.1);
}
else if (freq_piano >= 9 && freq_piano <= 10){
g4_piano.play(); g4_piano.setVolume(0.1);
}
else if (freq_piano >= 11 && freq_piano <= 12){
a4_piano.play(); a4_piano.setVolume(0.1);
}
else if (freq_piano >= 13 && freq_piano <= 14){
b4_piano.play(); b4_piano.setVolume(0.1);
}
else if (freq_piano >= 15 && freq_piano <= 16){
c5_piano.play(); c5_piano.setVolume(0.1);
}
else if (freq_piano >= 17 && freq_piano <= 18){
d5_piano.play(); d5_piano.setVolume(0.1);
}
else if (freq_piano >= 19 && freq_piano <= 20){
e5_piano.play(); e5_piano.setVolume(0.1);
}
else if (freq_piano >= 21 && freq_piano <= 22){
f5_piano.play(); f5_piano.setVolume(0.1);
}
else if (freq_piano >= 23 && freq_piano <= 24){
g5_piano.play(); g5_piano.setVolume(0.1);
}
else if (freq_piano >= 25 && freq_piano <= 26){
a5_piano.play(); a5_piano.setVolume(0.1);
}
else if (freq_piano >= 27 && freq_piano <= 28){
b5_piano.play(); b5_piano.setVolume(0.1);
}
}
if (drum > 200){
if (freq_piano >= 1 && freq_piano <= 3){
heavykick.play(); heavykick.setVolume(0.1);
}
else if (freq_piano >= 4 && freq_piano <= 6){
lightkick.play();
lightkick.setVolume(0.1);}
else if (freq_piano >= 7 && freq_piano <= 9){
snaresidekick.play();
snaresidekick.setVolume(0.1);}
else if (freq_piano >= 10 && freq_piano <= 12){
lowtom.play();
lowtom.setVolume(0.1);}
else if (freq_piano >= 13 && freq_piano <= 15){
snarecenter.play();
snarecenter.setVolume(0.1);}
else if (freq_piano >= 16 && freq_piano <= 18){
hihatopen.play();
hihatopen.setVolume(0.1);}
else if (freq_piano >= 19 && freq_piano <= 21){
hitom.play();
hitom.setVolume(0.1);}
else if (freq_piano >= 22 && freq_piano <= 24){
crash.play();
crash.setVolume(0.1);}
}
if (guitar > 200){
if (freq_piano >= 1 && freq_piano <= 3){
c3_guitar.play();
c3_guitar.setVolume(0.1);
}
else if (freq_piano >= 4 && freq_piano <= 6){
d3_guitar.play();
d3_guitar.setVolume(0.1);
}
else if (freq_piano >= 7 && freq_piano <= 9){
e3_guitar.play();
e3_guitar.setVolume(0.1);
}
else if (freq_piano >= 10 && freq_piano <= 12){
f3_guitar.play();
f3_guitar.setVolume(0.1);
}
else if (freq_piano >= 13 && freq_piano <= 15){
g3_guitar.play();
g3_guitar.setVolume(0.1);
}
else if (freq_piano >= 16 && freq_piano <= 18){
a3_guitar.play();
a3_guitar.setVolume(0.1);
}
else if (freq_piano >= 19 && freq_piano <= 21){
b3_guitar.play();
b3_guitar.setVolume(0.1);
}
}
if (synth > 200){
if (freq_piano >= 1 && freq_piano <= 3){
c6_synth.play();
c6_synth.setVolume(0.1);
}
else if (freq_piano >= 4 && freq_piano <= 6){
d6_synth.play();
d6_synth.setVolume(0.1);
}
else if (freq_piano >= 7 && freq_piano <= 9){
e6_synth.play();
e6_synth.setVolume(0.1);
}
else if (freq_piano >= 10 && freq_piano <= 12){
f6_synth.play();
f6_synth.setVolume(0.1);
}
else if (freq_piano >= 13 && freq_piano <= 15){
g6_synth.play();
g6_synth.setVolume(0.1);
}
else if (freq_piano >= 16 && freq_piano <= 18){
a6_synth.play();
a6_synth.setVolume(0.1);
}
else if (freq_piano >= 19 && freq_piano <= 21){
b6_synth.play();
b6_synth.setVolume(0.1);
}
}
// Visualization for the drum
spectrum2 = fft2.analyze();
translate(windowWidth / 2, windowHeight / 2);
for (let i = 0; i < spectrum2.length; i++) {
let angle = map(i, 0, spectrum2.length, 0, 360);
//change the shape of the visualizer
let r2 = map(amp, 0, 512, 50, 500);
let x2 = (r2 + 50) * cos(angle);
let y2 = (r2 + 50) * sin(angle);
// Visualization for the piano
for (let i = 0; i < n; i++) {
theta.push(random(0, 2 * PI));
c.push(createVector(windowWidth/2, windowHeight/2));
rect(0, 0, windowWidth, windowHeight);
let spectrum = fft.analyze();
// Calculate average amplitude to detect beats
let level = amplitude.getLevel();
// Adjust animation based on music intensity or beats
for (let i = 0; i < n; i++) {
theta[i] = theta[i] + (PI / 100) * dir[i];
rdir[i] = checkr(rdir[i], r[i]);
x = c[i].x + r[i] * cos(theta[i]);
y = c[i].y + r[i] * sin(theta[i]);
// modify animation based on beat detection
let bass = fft.getEnergy("bass");
let treble = fft.getEnergy("treble");
// adjust wave parameters based on music energy
r[i] = map(bass, 0, 255, 30, 380);
dir[i] = map(treble, 0, 255, -1, 1);
// waveform visualization
stroke('rgb(255,182,222)');
translate(windowWidth / 2, windowHeight / 2);
let wave = fft_waveform.waveform();
for (let k = -1; k <= 1; k += 2) {
for (let i = 0; i <= 180; i+= 0.7) {
let j = floor(map(i, 0, windowWidth, 0, wave.length - 1));
let r3 = map(wave[j], -1, 1, 100, 250);
let x3 = r3 * sin(i) * k;
// waveform of the particles
translate(windowWidth/2, windowHeight/2);
let spectrum4 = fft_particles.analyze(); // Analyze frequency spectrum
// console.log(spectrum);
let currentHasFrequency = spectrum4.some(freq => freq > 0);
// If frequency detected and no particle created for this frequency yet
if (currentHasFrequency && !particleCreated && spectrum4[0]) {
particleCreated = true; // Set flag to true to prevent continuous particle creation for this frequency
// Update and display particles
for (let i = particles.length - 1; i >= 0; i--) {
// Reset flag after particle creation
if (particleCreated && particles.length > 0) {
// Visualization for the drum
push()
angleMode(DEGREES);
colorMode(HSB);
spectrum2 = fft2.analyze();
// background(0);
noStroke();
translate(windowWidth / 2, windowHeight / 2);
//beginShape();
for (let i = 0; i < spectrum2.length; i++) {
let angle = map(i, 0, spectrum2.length, 0, 360);
let amp = spectrum2[i];
//change the shape of the visualizer
let r2 = map(amp, 0, 512, 50, 500);
//create the circle
let x2 = (r2 + 50) * cos(angle);
let y2 = (r2 + 50) * sin(angle);
//color the bar
stroke(i, 100, 100);
line(0, 0, x2, y2);
}
pop()
angleMode(RADIANS)
// Visualization for the piano
push()
fill(0, 0, 0, 5);
stroke(0, 255, 255);
angleMode(RADIANS)
for (let i = 0; i < n; i++) {
theta.push(random(0, 2 * PI));
dir.push(1);
r.push(random(30, 380));
rdir.push(1);
c.push(createVector(windowWidth/2, windowHeight/2));
}
rect(0, 0, windowWidth, windowHeight);
let spectrum = fft.analyze();
// Calculate average amplitude to detect beats
let level = amplitude.getLevel();
// Adjust animation based on music intensity or beats
for (let i = 0; i < n; i++) {
theta[i] = theta[i] + (PI / 100) * dir[i];
rdir[i] = checkr(rdir[i], r[i]);
r[i] = r[i] + rdir[i];
x = c[i].x + r[i] * cos(theta[i]);
y = c[i].y + r[i] * sin(theta[i]);
point(x, y);
// modify animation based on beat detection
let bass = fft.getEnergy("bass");
let treble = fft.getEnergy("treble");
// adjust wave parameters based on music energy
r[i] = map(bass, 0, 255, 30, 380);
dir[i] = map(treble, 0, 255, -1, 1);
}
pop()
// waveform visualization
push()
angleMode(DEGREES);
stroke('rgb(255,182,222)');
noFill();
translate(windowWidth / 2, windowHeight / 2);
let wave = fft_waveform.waveform();
for (let k = -1; k <= 1; k += 2) {
beginShape();
for (let i = 0; i <= 180; i+= 0.7) {
let j = floor(map(i, 0, windowWidth, 0, wave.length - 1));
let r3 = map(wave[j], -1, 1, 100, 250);
let x3 = r3 * sin(i) * k;
let y3 = r3 * cos(i);
vertex(x3, y3);
}
endShape();
}
pop()
// waveform of the particles
push()
translate(windowWidth/2, windowHeight/2);
let spectrum4 = fft_particles.analyze(); // Analyze frequency spectrum
// console.log(spectrum);
let currentHasFrequency = spectrum4.some(freq => freq > 0);
// If frequency detected and no particle created for this frequency yet
if (currentHasFrequency && !particleCreated && spectrum4[0]) {
let p = new Particle();
particles.push(p);
particleCreated = true; // Set flag to true to prevent continuous particle creation for this frequency
}
// Update and display particles
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].show();
particles[i].update();
}
// Reset flag after particle creation
if (particleCreated && particles.length > 0) {
particleCreated = false;
}
pop()
// Visualization for the drum
push()
angleMode(DEGREES);
colorMode(HSB);
spectrum2 = fft2.analyze();
// background(0);
noStroke();
translate(windowWidth / 2, windowHeight / 2);
//beginShape();
for (let i = 0; i < spectrum2.length; i++) {
let angle = map(i, 0, spectrum2.length, 0, 360);
let amp = spectrum2[i];
//change the shape of the visualizer
let r2 = map(amp, 0, 512, 50, 500);
//create the circle
let x2 = (r2 + 50) * cos(angle);
let y2 = (r2 + 50) * sin(angle);
//color the bar
stroke(i, 100, 100);
line(0, 0, x2, y2);
}
pop()
angleMode(RADIANS)
// Visualization for the piano
push()
fill(0, 0, 0, 5);
stroke(0, 255, 255);
angleMode(RADIANS)
for (let i = 0; i < n; i++) {
theta.push(random(0, 2 * PI));
dir.push(1);
r.push(random(30, 380));
rdir.push(1);
c.push(createVector(windowWidth/2, windowHeight/2));
}
rect(0, 0, windowWidth, windowHeight);
let spectrum = fft.analyze();
// Calculate average amplitude to detect beats
let level = amplitude.getLevel();
// Adjust animation based on music intensity or beats
for (let i = 0; i < n; i++) {
theta[i] = theta[i] + (PI / 100) * dir[i];
rdir[i] = checkr(rdir[i], r[i]);
r[i] = r[i] + rdir[i];
x = c[i].x + r[i] * cos(theta[i]);
y = c[i].y + r[i] * sin(theta[i]);
point(x, y);
// modify animation based on beat detection
let bass = fft.getEnergy("bass");
let treble = fft.getEnergy("treble");
// adjust wave parameters based on music energy
r[i] = map(bass, 0, 255, 30, 380);
dir[i] = map(treble, 0, 255, -1, 1);
}
pop()
// waveform visualization
push()
angleMode(DEGREES);
stroke('rgb(255,182,222)');
noFill();
translate(windowWidth / 2, windowHeight / 2);
let wave = fft_waveform.waveform();
for (let k = -1; k <= 1; k += 2) {
beginShape();
for (let i = 0; i <= 180; i+= 0.7) {
let j = floor(map(i, 0, windowWidth, 0, wave.length - 1));
let r3 = map(wave[j], -1, 1, 100, 250);
let x3 = r3 * sin(i) * k;
let y3 = r3 * cos(i);
vertex(x3, y3);
}
endShape();
}
pop()
// waveform of the particles
push()
translate(windowWidth/2, windowHeight/2);
let spectrum4 = fft_particles.analyze(); // Analyze frequency spectrum
// console.log(spectrum);
let currentHasFrequency = spectrum4.some(freq => freq > 0);
// If frequency detected and no particle created for this frequency yet
if (currentHasFrequency && !particleCreated && spectrum4[0]) {
let p = new Particle();
particles.push(p);
particleCreated = true; // Set flag to true to prevent continuous particle creation for this frequency
}
// Update and display particles
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].show();
particles[i].update();
}
// Reset flag after particle creation
if (particleCreated && particles.length > 0) {
particleCreated = false;
}
pop()
// function to initiate commands.
// functions to initiate music-making options
function startRecording() {
recorder.record(soundFile);
console.log('Recording started...');
function stopRecording() {
console.log('Recording stopped.');
function playRecording() {
console.log('Playback started...');
function loopRecording(){
// function to initiate commands.
function keyPressed() {
if (key == " ") {
setUpSerial();
}
if (key == 'r') {
startRecording();
} else if (key == 's') {
stopRecording();
} else if (key == 'p') {
playRecording();
} else if (key == 'l') {
loopRecording();
}
}
// functions to initiate music-making options
function startRecording() {
recorder.record(soundFile);
console.log('Recording started...');
recordingStarted = true;
}
function stopRecording() {
recorder.stop();
console.log('Recording stopped.');
}
function playRecording() {
soundFile.play();
console.log('Playback started...');
}
function loopRecording(){
soundFile.loop();
isLooping = true;
}
// function to initiate commands.
function keyPressed() {
if (key == " ") {
setUpSerial();
}
if (key == 'r') {
startRecording();
} else if (key == 's') {
stopRecording();
} else if (key == 'p') {
playRecording();
} else if (key == 'l') {
loopRecording();
}
}
// functions to initiate music-making options
function startRecording() {
recorder.record(soundFile);
console.log('Recording started...');
recordingStarted = true;
}
function stopRecording() {
recorder.stop();
console.log('Recording stopped.');
}
function playRecording() {
soundFile.play();
console.log('Playback started...');
}
function loopRecording(){
soundFile.loop();
isLooping = true;
}
Communication between Arduino and p5.js:
From p5js side:
function readSerial(data) {
////////////////////////////////////
////////////////////////////////////
// make sure there is actually a message
let fromArduino = split(trim(data), ",");
// if the right length, then proceed
if (fromArduino.length == 5) {
// only store values here
// do everything with those values in the main draw loop
// We take the string we get from Arduino and explicitly
// convert it to a number by using int()
// e.g. "103" becomes 103
note_piano = int(fromArduino[0]);
drum = int(fromArduino[2]);
guitar = int(fromArduino[3]);
synth = int(fromArduino[4]);
// get frequency for all types of sound
freq_piano = int(fromArduino[1]);
if (!recordingStarted && freq_piano != 0) {
startRecording(); // Start recording
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake) - MUST have these lines for Arduino to send to p5
//////////////////////////////////
let sendToArduino = fromArduino + "\n";
writeSerial(sendToArduino);
function readSerial(data) {
////////////////////////////////////
//READ FROM ARDUINO HERE
////////////////////////////////////
if (data != null) {
// make sure there is actually a message
// split the message
let fromArduino = split(trim(data), ",");
print(fromArduino);
// if the right length, then proceed
if (fromArduino.length == 5) {
// only store values here
// do everything with those values in the main draw loop
// We take the string we get from Arduino and explicitly
// convert it to a number by using int()
// e.g. "103" becomes 103
if (isLooping == false){
note_piano = int(fromArduino[0]);
drum = int(fromArduino[2]);
guitar = int(fromArduino[3]);
synth = int(fromArduino[4]);
}
// get frequency for all types of sound
freq_piano = int(fromArduino[1]);
if (!recordingStarted && freq_piano != 0) {
startRecording(); // Start recording
}
}
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake) - MUST have these lines for Arduino to send to p5
//////////////////////////////////
let sendToArduino = fromArduino + "\n";
writeSerial(sendToArduino);
}
function readSerial(data) {
////////////////////////////////////
//READ FROM ARDUINO HERE
////////////////////////////////////
if (data != null) {
// make sure there is actually a message
// split the message
let fromArduino = split(trim(data), ",");
print(fromArduino);
// if the right length, then proceed
if (fromArduino.length == 5) {
// only store values here
// do everything with those values in the main draw loop
// We take the string we get from Arduino and explicitly
// convert it to a number by using int()
// e.g. "103" becomes 103
if (isLooping == false){
note_piano = int(fromArduino[0]);
drum = int(fromArduino[2]);
guitar = int(fromArduino[3]);
synth = int(fromArduino[4]);
}
// get frequency for all types of sound
freq_piano = int(fromArduino[1]);
if (!recordingStarted && freq_piano != 0) {
startRecording(); // Start recording
}
}
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake) - MUST have these lines for Arduino to send to p5
//////////////////////////////////
let sendToArduino = fromArduino + "\n";
writeSerial(sendToArduino);
}
From Arduino side:
Serial.print(PressureSensor);
Serial.print(PressureSensor2);
Serial.print(PressureSensor3);
Serial.println(PressureSensor4);
Serial.print(PressureSensor);
Serial.print(',');
Serial.print(inches);
Serial.print(',');
Serial.print(PressureSensor2);
Serial.print(',');
Serial.print(PressureSensor3);
Serial.print(',');
Serial.println(PressureSensor4);
Serial.print(PressureSensor);
Serial.print(',');
Serial.print(inches);
Serial.print(',');
Serial.print(PressureSensor2);
Serial.print(',');
Serial.print(PressureSensor3);
Serial.print(',');
Serial.println(PressureSensor4);
Schematics:

Some issues and aspects of the project I’m particularly proud of:
I was particularly proud of creating the visualizer. I took some time to learn how to use the p5.FFT function inside p5js. Initially, I watched this Sound Visualization: Frequency Analysis with FFT – p5 tutorial from Daniel Shiffman: https://www.youtube.com/watch?v=2O3nm0Nvbi4. But the tutorial is about syncing the visualizer with a played sound. I had a lot of trouble making the visualizer sync to every time the music note is triggered. I also had some issues combining the visualizers onto one plane and arranging their positions. I had to be really careful with controlling the variables.
I also wanted the users to have the option to record what they play, and figuring out how to record took me a lot of time. I kept failing at this part as I initially approached the code from the perspective of adding each note and each sound byte to an array and playing it back later.
BUT the aspect that I was struggling with the most was about using a pressure sensor. Its variable range varied way too much. The threshold for triggering the music notes when I pressed kept changing from time to time. The music generated inside p5 was not good at all. I was so fixated on this idea as I thought it was the only possible option. Until I discovered the button. It provided a much more stable parameter range of extremely high values and extremely low values for controlling the threshold as a digital device. The buttons I found are really soft to the touch, and they can be attached to the tip of the glove.
Another issue I ran into was about establishing a connection between Arduino and p5js:
I kept running into this error:

and
Pressure sensor doesn’t run when adding while (Serial.available() <= 0) {} and while (Serial.available()) {}
Future improvement:
I approached the project hoping to have only one visualizer for each instrument, but it seems that the object p5.FFT is a universal object that can’t take the frequency of any individual musical instrument. Therefore, I have to think of ways to make that happen for the future.
Also, I hope to be able to incorporate the accelerometer or gyrometer to monitor the motion of wrists and use that to trigger drum pattern/rhythm.
Observations from IM Showcase interactions for future improvements:
Everyone intuitively followed the screen instructions, but almost everyone seems to not know how to proceed at the connecting p5js with Arduino part.
There are two aspects where users are mostly confused about: how to use the distance sensor and how to connect p5js to Arduino. In terms of the distance sensor, it doesn’t come to them intuitively that they can hover their left hand up and down above the distance sensor to trigger different frequencies, hence different musical notes. For a few users, I had to explain to them how they should operate the project, and for them, I had to provide very detailed instructions. I’ve implemented the text “Press Space Bar to connect to the glove,” but it seems that only those who know how to set up serial communication understand it; those who don’t seem not. So for an area of improvement, I’m thinking of having it run as soon as the user presses the “ROCK ON” button to start the visualizer.