- BUBBLE CONCERT by Katy Wang & Noelia Yang
- INSTRUCTOR: Gottfried Haider
- CONCEPTION AND DESIGN:
The result: It’s a cooperative game with two users blowing at bubble wands together and bubbles with two different colors will come out from the two sides of the screen accordingly. When bubbles from different sides encounter each other, they will burst and make the sound of a random note in a certain range and certain quality, so with multiple bubbles bumping into each other, music will be created. Moreover, dipping the wand back and pulling it out again will cause a change in sound quality. We eventually had four different sound modes for that. The process: We first decided to make a competitive game where one user creates bubbles while the other breaks them. But eventually, with the help of our instructor, we found that cooperation was the path of interaction we preferred. Also, interaction utilizing mouths is an exciting idea to us, so we were trying to add more layers of “mouth interaction” such as smoking/vaping and discuss the interesting contrast. But the connection was farfetched, so we decided to keep it simple. Thus, the idea behind it is simply about recalling the pure joy from childhood from the visual, auditory and touching senses.
During user testing, one big thing I noticed was that many users found it hard to figure out how to interact based on our unhidden and not yet decorated sensors. So in order to make it more clear, we put some effort to imitate the real bubble wands and tried to design the actions according to what we do with bubble wands in real life. During our presentation, we also gathered some valuable feedback from peers and fellows. Most of them are suggesting more variety in visual decorations, such as background, bubble colors. Following our instructor Gohai’s feedback, we made a big step further: we had trouble figuring out connections between different sound modes so we only inserted one type of sound before Gohai inspired us to utilize the action of “refilling the liquid” to switch the sound. Thus, we added this part of the design for the IMA Show.
- FABRICATION AND PRODUCTION:
1.Temperature sensor: The most unexpected utilization of the sensor in our project is using a temperature sensor to detect the action of blowing because blowing takes heat away and the temperature drops. This brilliant idea was brought up by our instructor gohai, which is much more applicable than using a flex sensor as we previously planned. The appearance of the temperature sensor is two small pieces of iron connecting a little “dot” that detects the temperature with a small circuit board. Thus, in order to fit it into the skinny wands we designed, we decided to lengthen the two pieces of iron by soldering and hiding the circuit board in the knob.2. For the wands and bottles, we adjusted the model we found online to create hollow space for sensors and printed them out with 3D-printing machine.
3. Reed Switch: In order to detect the movement of dipping the wands back, I inserted a reed switch inside the knob and stuck many magnets around the rim so that the direction of putting them back won’t matter. It’s also tricky (annoying) that magnets are hard to separate when they are put inside the bottle within a limited distance so too many magnets are not a good idea and also I needed to glue them one by one so they have time to dry and get fixed.
4.To make cables more organized and hide the Arduino board and breadboard away, I built a box with laser cutting. We first had a small one but to make things easier I built a bigger one which was potentially a problem because users in the IMA show were trying to figure out the function of the box :/
5. The biggest hilarious mistake I remember was after we lengthened the wires in order to make the wands more flexible and movable, we found it encountered a short circuit. We checked all the connections and the sensors themselves but couldn’t find the problem. When we were enormously desperate, it turned out that it was just because a small part of the wires were not wrapped in insulating tapes and touched each other:(
- #particle
- #SinOsc[]
- #*Check overlap
- We have to use millis for background
- Printing of multiple sensors in Arduino has strict formation
- *Randomizing sizes of the images takes longer time, instead, we could alter the sizes of the original images to set different sizes
- *The formula “AverageValue = CurrentValue*0.01 + AverageValue*0.99” makes the changes more consistent to avoid the potential chaos caused by delay or oversensitive changes.
- CONCLUSIONS:
Our goal was to bring pure joy from Childhood and remind people of their forgotten land. During the IMA Show, many of the kids and parents were excited about our project so I guess that sort of counts as achieving the goal. But I also noticed some problems during the interactions:
- The users have to blow very hard to trigger bubbles, which isn’t like the real bubble wands we are used to so we needed to remind the users of this. Moreover, after blowing hard for a long time, many users found it exhausting 🙁
- Professor Rudi pointed out that the delay between blowing and bubbles coming out is still recognizable which makes the process less smooth.
- Also, many users were asking me how the sounds came out, then I realized that maybe it was not obvious enough that bubbles were bumping to make sounds. That might be able to be solved by adding a third picture of bubbles breaking before they disappear.
- When the screen was projected, the color difference wasn’t too obvious, which also caused confusion sometimes.
If I had more time, I would try to think about the questions above and probably also rethink “refilling liquid” by actually designing bubbles that will run out to make more sense. Also, the bubbles change sounds right after the lid is put on instead of new bubbles coming out, which also needs to be fixed.
I learned a lot from this project. Practically, I’m pretty sure my coding ability imporved and I’m a lot more familiar with digital fabrication now. Meanwhile, I also feel strongly about the idea of keeping things simple can be smart.
- DISASSEMBLY:
- APPENDIX
1.Pictures
2. Arduino Code
void setup() { Serial.begin(9600); } void loop() { // to send values to Processing assign the values you want to send // this is an example: int sensor0 = analogRead(A0); int sensor1 = analogRead(A1); int sensorVal0 = digitalRead(2); int sensorVal1 = digitalRead(4); // send the values keeping this format Serial.print(sensor0); Serial.print(","); // put comma between sensor values Serial.print(sensor1); Serial.print(","); // add linefeed after sending the last sensor value Serial.print(sensorVal0); Serial.print(","); // put comma between sensor values Serial.print(sensorVal1); Serial.println(); // too fast communication might cause some latency in Processing // this delay resolves the issue delay(20); // end of example sending values }
3. Processing Code
import processing.serial.*; import processing.sound.*; SoundFile sound1; SinOsc[] sines = new SinOsc[1]; float[] vols = new float[1]; SoundFile [] Drums = new SoundFile [3]; Serial serialPort; int NUM_OF_VALUES_FROM_ARDUINO = 4; /* CHANGE THIS ACCORDING TO YOUR PROJECT */ /* This array stores values from Arduino */ int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; float AverageValueL =510; float AverageValueR =503; PImage [] BlueBubbles = new PImage [6]; PImage [] PurpleBubbles = new PImage [6]; PImage img; PImage img2; PImage bg; int StatusL; int StatusR; float PreValueL; float PreValueR; int CurrentValueL; int CurrentValueR; int lastBubbleTime; int freqMin; int freqMax; int pMagnetValueL = 0; int pMagnetValueR = 0; int SoundMode = 0; //float freqMin = 20; // this value gets changed whenever the user dips //float freqMax = 2000; ArrayList<Particle> particlesR; ArrayList<Particle> particlesL; void setup() { bg = loadImage("Bad.png"); Drums[0]= new SoundFile(this, "kick.wav"); Drums[1]= new SoundFile(this, "snare.wav"); Drums[2]= new SoundFile(this, "hihat.aif"); //m=mills(); BlueBubbles[2] = loadImage("TwoPercentBlueBubble.png"); PurpleBubbles[2] = loadImage("TwoPercentPurpleBubble.png"); BlueBubbles[3] = loadImage("ThreePercentBlueBubble.png"); PurpleBubbles[3] = loadImage("ThreePercentPurpleBubble.png"); BlueBubbles[4] = loadImage("FourPercentBlueBubble.png"); PurpleBubbles[4] = loadImage("FourPercentPurpleBubble.png"); BlueBubbles[5] = loadImage("FivePercentBlueBubble.png"); PurpleBubbles[5] = loadImage("FivePercentPurpleBubble.png"); //sound = new SoundFile(this, "cello-f2.aif"); printArray(Serial.list()); println(); //size(800, 400); fullScreen(); particlesR = new ArrayList<Particle>(); particlesL = new ArrayList<Particle>(); serialPort = new Serial(this, "COM10", 9600); sines[0] = new SinOsc(this); } void draw() { getSerialData(); CurrentValueL = arduino_values[0]; CurrentValueR = arduino_values[1]; AverageValueL = CurrentValueL*0.01 + AverageValueL*0.99; AverageValueR = CurrentValueR*0.01 + AverageValueR*0.99; //println(CurrentValueL-AverageValueL); //println(CurrentValueR-AverageValueR); background(0); if (millis() - lastBubbleTime > 15000) { image(bg, 0, 0, width, height); } if (SoundMode == 0) { freqMin = 200; freqMax = 550; //background(0); } if (SoundMode == 1) { freqMin = 600; freqMax = 1000; //background(67,19,157); } if (SoundMode == 2) { freqMin = 2000; freqMax = 4000; //background(138,19,157); } if (pMagnetValueL == 0 && arduino_values[2] == 1 || pMagnetValueR == 0 && arduino_values[3] == 1) { println("Magnet detected, switching sound"); SoundMode = SoundMode + 1; if (SoundMode == 4) { SoundMode = 0; } //freqMin = random(20, 1000); //freqMax = random(400, 4000); //println(freqMin + " " + freqMax); } //rightside if (AverageValueR< PreValueR-0.05 ) { lastBubbleTime = millis(); for (int i = 0; i < 1; i++) { int a= int(random(2, 6)); Particle pr = new Particle(width, random(-5, 1), color(255, 0, 0), BlueBubbles[a]); particlesR.add(pr); } //print("LNumber of the bubbles:"); //println(particlesL.size()); } //leftside if (AverageValueL< PreValueL-0.05) { lastBubbleTime = millis(); for (int i = 0; i < 1; i++) { int a= int(random(2, 6)); Particle pl = new Particle(0, random(-1, 5), color(0, 0, 255), PurpleBubbles[a]); particlesL.add(pl); } } // right for (int i = particlesR.size() - 1; i >= 0; i--) { Particle particle = particlesR.get(i); particle.update(); // check for overlap first (and mark them for removal) particle.checkOverlap(particlesL); particle.showR(); //if (particle.isOverlapping) { // // remove this particle // particlesR.remove(i); //} } // left for (int i = particlesL.size() - 1; i >= 0; i--) { Particle particle = particlesL.get(i); particle.update(); // check for overlap first (and mark them for removal) particle.checkOverlap(particlesR); particle.showL(); //if (particle.isOverlapping) { // // remove this particle // particlesL.remove(i); //} } // actually do the removal for (int i = particlesR.size() - 1; i >= 0; i--) { Particle particle = particlesR.get(i); if (particle.isOverlapping) { particlesR.remove(i); } } for (int i = particlesL.size() - 1; i >= 0; i--) { Particle particle = particlesL.get(i); if (particle.isOverlapping) { particlesL.remove(i); } } vols[0] = vols[0] - 0.2; if (vols[0] <= 0.0) { sines[0].stop(); } else { sines[0].amp(vols[0]); } PreValueL = AverageValueL; PreValueR = AverageValueR; pMagnetValueL = arduino_values[2]; pMagnetValueR = arduino_values[3]; } class Particle { float x, y; float vx, vy; float alpha; color c; int bubbleType; int Bubblesize; PImage CurrentBubble; boolean isOverlapping; int SoundType = int(random(0, 2)); Particle(float startX, float startVX, color startColor, PImage tempImage) { x = startX; y = 1080; vx = startVX; vy = random(-5, -1); CurrentBubble =tempImage; //alpha = 255; //c = color(random(255), random(255), random(255)); c = startColor; bubbleType = int(random(3)); Bubblesize = int (random(2, 5)); } void update() { x += vx*2; y += vy*2; if (bubbleType==1) { if (x>width) { x=width-1; vx= -vx; } if (y>height) {//you should write width and height instead of the number y=height-1; vy= -vy; } if (x<0) { x=1; vx= -vx; } if (y<0) { y=1; vy= -vy; } } } void checkOverlap(ArrayList<Particle> listToCheckAgainst) { isOverlapping = false; // assume no overlap // go through each item in the list for (int i = listToCheckAgainst.size() - 1; i >= 0; i--) { // get the n-th item from the (other) list Particle otherParticle = listToCheckAgainst.get(i); // calculate the distance between our bubble, and the other bubble float d = dist(x, y, otherParticle.x, otherParticle.y); // if the bubble is less than twice the radius if (d < 80) { // make a note that at least one bubble from the other list // is overlapping with ours isOverlapping = true; } } } void showL() { noStroke(); //stroke(255); if (isOverlapping) { fill(0, 255, 0); } else { // XXX: for speedup: consider loading different bubble sizes and displaying // them at 100% (without resize) image(CurrentBubble, x, y); } //fill(255, alpha); //circle(x, y, 80); } void showR() { noStroke(); //stroke(255); if (isOverlapping) { if (SoundMode == 0 || SoundMode == 1 || SoundMode == 2 ) { sines[0].freq(map(y, 0, height, freqMin, freqMax)); vols[0] = 1.0; sines[0].amp(vols[0]); sines[0].play(); } else if (SoundMode == 3) { Drums[SoundType].play(); } } else { image(CurrentBubble, x, y); } //fill(255, alpha); //circle(x, y, 80); } void vanish () { //image(Burst1, x, y, img.width/Bubblesize, img.height/Bubblesize); } } void getSerialData() { if (serialPort == null) { return; } while (serialPort.available() > 0) { String in = serialPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII if (in != null) { // print("From Arduino: " + in); String[] serialInArray = split(trim(in), ","); if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) { for (int i=0; i<serialInArray.length; i++) { arduino_values[i] = int(serialInArray[i]); } } } } }