On a traditional Indian classical harmonium, sound is not just a collection of static audio samples. Sound is life and breath. You pump a leather bellows with your left hand to force pressurized air into an internal reservoir, while your right hand presses keys that open valves over tuned brass reeds. If you stop pumping, the reservoir bleeds air and the notes choke into silence. If you pump vigorously, the air pressure swells the tone with rich, warm, buzzy acoustic overtones. I wanted to recreate that living physical interaction digitally on my laptop without buying an expensive MIDI breath controller or specialized pressure sensors.
The solution was turning my standard laptop webcam into an optical air bellows pump. The Python codebase (centered in Applications/VSCode/Harmonium_LidSensor/finale.py) polls the webcam via OpenCV at 100 Hz (cv2.VideoCapture(0)). In every frame, it converts the image to grayscale and calculates the average scene luminance: curr = np.mean(gray). By tracking the delta between consecutive frames (diff = abs(curr - last_brightness)), waving your hand in front of the camera or tilting the laptop lid up and down acts as an optical pump:
if diff > 0.3:
reservoir += (diff / 255) * sens_mult * 8.0 # Boosted air filling
last_brightness = curr
reservoir = max(0.0, min(1.0, reservoir - 0.008)) # Continuous air leak
The continuous subtraction of 0.008 per frame is crucial: it simulates the physical air leakage through open reed chambers. If you don't keep pumping rhythmically, the reservoir depletes and the audio smoothly chokes off, exactly like an acoustic harmonium.
For the audio DSP engine, standard sine or square waves sound like harsh 8-bit chip tunes. Real brass reeds have rich, asymmetric harmonic spectra. Using NumPy and sounddevice running at 44,100 Hz with 512-sample buffer blocks, I synthesized reed oscillations by summing seven calibrated harmonic multipliers: [(1, 1.0), (2, 0.45), (3, 0.75), (4, 0.20), (5, 0.55), (6, 0.10), (7, 0.35)]. The summed waveform is scaled by air pressure (p_smooth = math.sin(p * math.pi / 2)) and driven into non-linear tanh saturation:
wave_data = np.tanh(wave_data * 1.4) * 0.4 * p_curve
The hyperbolic tangent function rounds off peak amplitudes, producing the warm, organic acoustic distortion of vibrating metal reeds under pressure. I then piped the signal through a 1-second circular delay buffer (delay_buf = np.zeros(SAMPLE_RATE)) for resonant chamber reverb.
Tuning was another rabbit hole. Western 12-tone equal temperament destroys the microtonal purity of Indian classical ragas. I mapped 22 distinct swaras using ancient just-intonation frequency ratios relative to Middle C (base_root = 261.63 Hz), laid out across keyboard rows z–m, a–k, and q–i.
The interface runs via Python eel serving a custom dark-and-gold HTML/CSS UI (web/index.html, where I cheekily titled the tab <title>potty bum ye mara harmonum</title>). It renders an animated piano keyboard and a real-time golden air pressure gauge bar (#air-bar), complete with sensitivity, reverb, and master volume sliders. It turns any ordinary laptop into an expressive, breathing Indian classical instrument.