An interactive visual art installation that generates underwater bubble effects synchronized with your voice and facial movements using computer vision and audio processing.
Bubble Breath uses your webcam and microphone to create an immersive underwater experience:
- Detects your face and mouth position in real-time
- Spawns colorful bubbles when you speak, sing, or blow
- Color maps to pitch — low notes produce deep blue bubbles, high notes produce vivid yellow ones
- Bubble trail — bubbles gently follow your face as you move while making sound
- Controls bubble rise speed from glacial drift to racing float
- Shows live frequency (Hz) and volume in the HUD
- Adds animated underwater caustic light effects
- Supports multiple faces simultaneously (up to 4)
- Python 3.7 or higher
Install all required packages using pip:
pip install opencv-python mediapipe numpy sounddeviceOr if you have a requirements.txt:
pip install -r requirements.txt- Webcam: Any USB or built-in camera
- Microphone: Built-in or external microphone
- Recommended: Good lighting for optimal face detection
- Clone or download this repository
git clone https://github.com/oceaneboulais/singBubbles.git
cd singBubbles- Install dependencies
pip install opencv-python mediapipe numpy sounddevice- Run the program
python main.py- Interact with the program
- Position your face in the camera frame
- Speak, sing, blow, or make sounds to generate bubbles
- Press Q to quit
All tunable parameters are located in the main() function under the "Tunable knobs" section (around line 166-169). You can adjust these to customize the bubble effect:
Default: 0.012
Range: 0.001 to 0.1
What it does: Minimum microphone loudness required to spawn bubbles
Examples:
0.005- Very sensitive, bubbles spawn from quiet breath0.012- Medium sensitivity (default), normal speaking volume0.025- Less sensitive, requires louder sounds0.050- Very loud sounds only (shouting, blowing hard)
Effect: Lower values make it spawn bubbles more easily; higher values require louder sounds.
Default: 30
Range: 10 to 200
What it does: Maximum number of bubbles allowed on screen simultaneously
Examples:
15- Sparse, minimal bubbles (better performance on slower computers)30- Moderate bubble density (default)60- Dense bubble field (more dramatic effect)100- Very dense, chaotic bubble storm (may slow down on weaker systems)
Effect: Higher values create more visual density but may impact performance.
Default: True
Options: True or False
What it does: Makes bubbles gently follow your face as you move while making sound
Examples:
True- Bubbles trail behind your face, creating flowing patterns when you moveFalse- Bubbles float straight up independently of face movement
Effect: When enabled, bubbles have a subtle attraction to the mouth position, creating a flowing trail effect when you move your head while speaking or singing.
Default: 0.50
Range: 0.1 to 2.0
What it does: Scales how fast every bubble floats upward each frame
How it works (pseudocode):
base_vy = random(−3.2, −1.2) ← pixels/frame; negative = upward in screen coords
vy = base_vy × BUBBLE_RISE_SPEED
each frame: bubble.y += bubble.vy ← more-negative vy = moves up faster
Examples:
| Value | Effect |
|---|---|
0.10 |
Glacial — barely moves, meditative |
0.30 |
Slow, gentle drift |
0.50 |
Default — dreamy underwater float |
1.00 |
Original full speed |
1.50 |
Noticeably energetic |
2.00 |
Racing upward — chaotic energy |
Effect: At lower values bubbles linger on screen longer and feel weightless. Higher values create a more energetic, fizzy look.
Default: True
Options: True or False
What it does: Shows the detected dominant frequency (Hz) in the bottom-left HUD
Effect: When enabled, displays the live pitch of your voice in cyan. Useful for understanding the color-to-pitch mapping and for tuning your performance.
You can modify these properties in the Bubble class (__init__ method, around line 30-44):
self.radius = random.randint(8, 28) + int(amplitude * 180)random.randint(8, 28)- Base size range (8-28 pixels)int(amplitude * 180)- Loudness bonus (louder = bigger)
Examples:
- Small bubbles:
random.randint(5, 15) + int(amplitude * 100) - Large bubbles:
random.randint(15, 40) + int(amplitude * 250) - Uniform size:
20(no randomness)
Note: Prefer adjusting
BUBBLE_RISE_SPEEDin the tunable knobs section rather than editing the raw velocity values here — it's the intended high-level control.
The raw base velocities (before rise-speed scaling) are:
self.vx = random.uniform(-1.2, 1.2) # Horizontal drift
self.vy = random.uniform(-3.2, -1.2) # Upward velocity (scaled by BUBBLE_RISE_SPEED)Examples:
- No horizontal drift:
self.vx = 0 - More horizontal movement:
self.vx = random.uniform(-3.0, 3.0)
self.alpha = random.uniform(0.25, 0.55)Default: 0.25 to 0.55 (moderately transparent)
Examples:
- More transparent:
random.uniform(0.15, 0.40) - More opaque:
random.uniform(0.40, 0.75) - Fully opaque:
random.uniform(0.75, 0.95)
self.lifetime = random.randint(90, 180) # frames (at ~30 fps = 3-6 seconds)Examples:
- Short-lived bubbles:
random.randint(45, 90)(~1.5-3 seconds) - Long-lived bubbles:
random.randint(150, 300)(~5-10 seconds) - Very persistent:
random.randint(300, 600)(~10-20 seconds)
Bubble color is automatically driven by pitch. You don't need to set a hue manually; it's computed from the detected microphone frequency:
pseudocode:
t = clamp((frequency − 60) / (1200 − 60), 0, 1) ← normalise Hz to 0-1
hue = 0.62 − t × 0.48 ← blue (0.62) → yellow (0.14)
hue += random jitter ±0.03 ← keeps individual bubbles unique
sat = 0.35 + t × 0.30 ← pastels at low pitch, vivid at high
Frequency → color table:
| Pitch (Hz) | Color |
|---|---|
| ~60 Hz — very low / bass | Deep blue |
| ~300 Hz — speaking voice | Blue-cyan |
| ~600 Hz — mid singing | Cyan-green |
| ~900 Hz — high singing | Green-gold |
| ~1200 Hz — soprano / whistle | Vivid yellow |
To override and use a fixed palette instead, replace the frequency-mapping block in Bubble.__init__ with:
h = random.uniform(0.50, 0.72) # fixed blue-violet palette
r, g, b = colorsys.hsv_to_rgb(h, 0.35, 1.0)
self.color = (int(b*255), int(g*255), int(r*255))Hue reference (0.0 → 1.0):
0.00–0.10Red → orange0.10–0.17Yellow0.17–0.33Green0.33–0.50Cyan0.50–0.72Blue → violet0.72–0.83Magenta0.83–1.00Pink → red
self.wobble_speed = random.uniform(0.025, 0.06) # Oscillation speed
# Applied in update():
self.x += self.vx + math.sin(self.wobble_phase) * 0.7 # 0.7 = wobble amountExamples:
- Gentle wobble:
0.7→0.3 - Extreme wobble:
0.7→2.0 - No wobble:
0.7→0.0 - Fast oscillation:
random.uniform(0.08, 0.15)
trail_strength = 0.08 # In update() method, around line 56Examples:
- Subtle follow:
0.03- Bubbles barely track the face - Strong follow:
0.15- Bubbles stick closely to mouth - Default:
0.08- Moderate trailing effect
In the main() function:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)Common resolutions:
640 x 480- Low quality, best performance1280 x 720- HD (default)1920 x 1080- Full HD (may reduce frame rate)
face_mesh = mp_fm.FaceMesh(
max_num_faces=4, # Number of faces to track
min_detection_confidence=0.5, # Detection threshold
min_tracking_confidence=0.5, # Tracking threshold
)Examples:
- Single person:
max_num_faces=1 - Group performance:
max_num_faces=6 - More sensitive detection:
min_detection_confidence=0.3 - More strict detection:
min_detection_confidence=0.7
In draw_water_overlay() function (~line 117):
tint = np.full_like(frame, (52, 28, 6), dtype=np.uint8) # BGR: blue=52, green=28, red=6Examples:
- Deep ocean blue:
(80, 40, 10) - Turquoise pool:
(60, 60, 30) - Green swamp:
(30, 60, 20) - Purple twilight:
(70, 30, 50)
caus_bgr = np.stack([
(caustic * 58).astype(np.uint8), # Blue channel
(caustic * 46).astype(np.uint8), # Green channel
(caustic * 20).astype(np.uint8), # Red channel
], axis=2)Examples:
- Brighter caustics: Multiply by
90, 70, 30 - Dimmer caustics: Multiply by
30, 20, 10 - Warmer caustics: Multiply by
30, 50, 80(more red)
SPAWN_THRESHOLD = 0.008
MAX_BUBBLES = 20
BUBBLE_TRAIL = True
BUBBLE_RISE_SPEED = 0.30 # slow dream-like drift
# In Bubble class:
self.alpha = random.uniform(0.15, 0.35)SPAWN_THRESHOLD = 0.015
MAX_BUBBLES = 50
BUBBLE_TRAIL = True
BUBBLE_RISE_SPEED = 0.70 # energetic but not frantic
# In Bubble class:
self.radius = random.randint(12, 35) + int(amplitude * 220)
self.alpha = random.uniform(0.35, 0.65)SPAWN_THRESHOLD = 0.005
MAX_BUBBLES = 100
BUBBLE_TRAIL = False
BUBBLE_RISE_SPEED = 1.60 # racing upward
# In Bubble class:
self.lifetime = random.randint(40, 100)
self.vx = random.uniform(-3.0, 3.0)SPAWN_THRESHOLD = 0.010
MAX_BUBBLES = 15
BUBBLE_TRAIL = True
BUBBLE_RISE_SPEED = 0.15 # glacial float
# In Bubble class:
self.alpha = random.uniform(0.20, 0.40)
# note: low frequencies = blue, which suits the peaceful mood- Check your microphone is connected and working
- Lower
SPAWN_THRESHOLDto make it more sensitive - Speak louder or blow into the microphone
- Check microphone permissions on your system
- Ensure good lighting on your face
- Center yourself in the camera frame
- Try adjusting
min_detection_confidenceto a lower value (e.g.,0.3)
- Reduce
MAX_BUBBLESto a lower number (e.g.,15) - Lower camera resolution to
640x480 - Close other applications
- Set
max_num_faces=1if tracking only yourself
- Increase opacity range:
self.alpha = random.uniform(0.40, 0.75)
- Adjust
self.vyin the Bubble class - Faster:
random.uniform(-5.0, -2.5) - Slower:
random.uniform(-1.5, -0.5)
- Face Detection: MediaPipe FaceMesh detects facial landmarks in real-time
- Mouth Tracking: Specific landmarks around the mouth are averaged to find the bubble spawn point
- Audio Analysis: Microphone input is analyzed for RMS (root mean square) amplitude
- Bubble Spawning: When audio exceeds threshold, bubbles are created near the mouth
- Physics Simulation: Each bubble has velocity, acceleration, wobble, and fade-out behavior
- Trail Effect: If enabled, bubbles gently accelerate toward the current mouth position
- Underwater Overlay: Sine wave caustics and color tinting create an underwater atmosphere
- Target: ~30 FPS
- Actual FPS depends on your hardware and parameter settings
- Sample Rate: 44.1 kHz (CD quality)
- Block Size: 512 samples (~12ms latency)
- Threading: Audio runs in background thread for smooth performance
Feel free to use, modify, and share this project!
Built with:
- OpenCV - Computer vision and image processing
- MediaPipe - Face mesh detection by Google
- NumPy - Numerical computing
- SoundDevice - Real-time audio I/O
Feel free to fork this project and experiment! Some ideas:
- Add different bubble shapes (hearts, stars)
- Implement particle emitters for different sounds (low vs high pitch)
- Add recording/export functionality
- Create beat detection for music synchronization
- Implement gesture controls
Enjoy creating bubble art! 🫧✨