Skip to content

Commit 5ab80bf

Browse files
Kaanoz-enKaan OzenCharly-sketch
authored
feat(apds9960): Add pentatonic light theremin example. (#367)
* feat(apds9960): Add pentatonic light theremin example. * fix(apds9960): fix 'dividing by zero' comment. --------- Co-authored-by: Kaan Ozen <kaanozen@MacBook-Air-de-Kaan.local> Co-authored-by: Charly-sketch <glic.per@gmail.com>
1 parent aee671f commit 5ab80bf

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

lib/apds9960/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ if sensor.is_gesture_available():
153153
| `ambient_light.py` | Read ambient light and RGB values |
154154
| `proximity.py` | Detect nearby objects |
155155
| `gesture.py` | Detect directional gestures |
156+
| `light_thermein.py`| Theremin controlled by light using the buzzer |
157+
156158

157159
```bash
158160
mpremote mount lib/apds9960 run lib/apds9960/examples/ambient_light.py
@@ -163,3 +165,4 @@ mpremote mount lib/apds9960 run lib/apds9960/examples/ambient_light.py
163165
* Ambient light and proximity reads automatically enable their respective sensors when needed (lazy activation).
164166
* Gesture detection must be explicitly enabled before polling.
165167
* Both `APDS9960` and `uAPDS9960` (MicroPython-optimized) classes are exported.
168+
* The light theremin example requires the buzzer connected on the PWM port, pin "SPEAKER"
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""Light theremin example using APDS9960 and the board buzzer.
2+
3+
Changes the buzzer pitch based on ambient light level.
4+
Uses a pentatonic scale for a musical, "auto-tuned" sound.
5+
Updates hardware only when the note changes to prevent jitter.
6+
"""
7+
8+
from time import sleep_ms
9+
10+
from apds9960 import uAPDS9960 as APDS9960
11+
from machine import I2C, Pin
12+
from pyb import Timer
13+
14+
# Calibration Constants
15+
# You may need to adjust MAX_LIGHT depending on your room's ambient lighting
16+
MIN_LIGHT = 45
17+
MAX_LIGHT = 570
18+
19+
# Musical Frequency Range (Hz) - Auto-Tuned Pentatonic Scale
20+
PENTATONIC_NOTES = [
21+
131, 147, 165, 196, 220, # Octave 3
22+
262, 294, 330, 392, 440, # Octave 4
23+
523, 587, 659, 784, 880 # Octave 5
24+
]
25+
TOTAL_NOTES = len(PENTATONIC_NOTES)
26+
27+
# Hardware Initialization
28+
i2c = I2C(1)
29+
apds = APDS9960(i2c)
30+
apds.enable_light_sensor()
31+
32+
# Hardware PWM on SPEAKER pin
33+
buzzer_tim = Timer(1, freq=1000)
34+
buzzer_ch = buzzer_tim.channel(4, Timer.PWM, pin=Pin("SPEAKER"))
35+
buzzer_ch.pulse_width_percent(0)
36+
37+
print("=======================")
38+
print(" Light Theremin ")
39+
print("=======================")
40+
print("Move your hand over the sensor.")
41+
print("Cover it completely to mute.")
42+
print("Press Ctrl+C to exit.")
43+
44+
# STATE CACHE: Track the last played frequency
45+
last_freq = 0
46+
47+
try:
48+
while True:
49+
light_level = apds.ambient_light()
50+
51+
if light_level < MIN_LIGHT:
52+
# Only update hardware/console if it wasn't already muted
53+
if last_freq != 0:
54+
buzzer_ch.pulse_width_percent(0)
55+
print("Muted")
56+
last_freq = 0
57+
else:
58+
# Clamp the light reading to the expected range to avoid out-of-bounds errors
59+
clamped_light = max(MIN_LIGHT, min(light_level, MAX_LIGHT))
60+
61+
# Avoid dividing by zero if MAX_LIGHT is not greater than MIN_LIGHT
62+
range_light = MAX_LIGHT - MIN_LIGHT
63+
if range_light <= 0:
64+
range_light = 1
65+
66+
# Map the light range to an index in our note array (0 to 14)
67+
note_index = (clamped_light - MIN_LIGHT) * (TOTAL_NOTES - 1) // range_light
68+
69+
70+
# Fetch the perfect harmonic frequency
71+
freq = PENTATONIC_NOTES[note_index]
72+
73+
# Update the buzzer tone
74+
if freq != last_freq:
75+
buzzer_tim.freq(freq)
76+
buzzer_ch.pulse_width_percent(50)
77+
print("Light: {} | Note: {} | Freq: {} Hz".format(light_level, note_index, freq), end="\r")
78+
last_freq = freq
79+
80+
81+
sleep_ms(20)
82+
83+
except KeyboardInterrupt:
84+
print("\nTheremin stopped.")
85+
finally:
86+
buzzer_ch.pulse_width_percent(0)

0 commit comments

Comments
 (0)