feat(apds9960): Add pentatonic light theremin example.#367
feat(apds9960): Add pentatonic light theremin example.#367
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new APDS9960 example that turns ambient light readings into discrete pitches on a C Major pentatonic scale, driving the board buzzer via hardware PWM (a “light theremin”).
Changes:
- Added
light_theremin.pyexample wiring up APDS9960 ALS readings to buzzer PWM frequency. - Implemented MIN/MAX light calibration, note quantization, and muting when covered.
- Ensured buzzer is silenced in a
finallyblock on exit.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| note_index = (clamped_light - MIN_LIGHT) * (TOTAL_NOTES - 1) // (MAX_LIGHT - MIN_LIGHT) | ||
| print("Light Level: {}, Note Index: {}".format(light_level, note_index)) | ||
|
|
There was a problem hiding this comment.
The loop prints on every 20ms iteration, which will spam the serial console (~50 lines/sec) and can noticeably slow the example on MicroPython targets. Consider printing only when the note changes or at a much slower interval (or guard behind a debug flag).
There was a problem hiding this comment.
You're absolutely right, 50 lines per second is way too much and could definitely cause performance bottlenecks over serial on the MicroPython board. I've updated the loop to include a state cache (last_freq) so the print() statement is now guarded and only triggers when the note actually changes or when the device is muted.
| # Update the buzzer tone | ||
| buzzer_tim.freq(freq) | ||
| buzzer_ch.pulse_width_percent(50) |
There was a problem hiding this comment.
The buzzer timer frequency is reconfigured every loop iteration even when the mapped note hasn’t changed; this adds overhead and can introduce audible jitter. Cache the last note index/frequency and only call buzzer_tim.freq() (and duty updates) when it changes.
There was a problem hiding this comment.
Re-initializing the PWM timer constantly was indeed causing unnecessary hardware overhead and slight audible jitter. I've implemented a last_freq state cache to track the current note. Now, we only call buzzer_tim.freq() and update the duty cycle when the mapped frequency actually changes. The audio output is much cleaner and smoother now!
22351a2 to
4bbbc80
Compare
ReviewTrès bon travail sur cet exemple. 1. Mapping lumière → note (division)note_index = (clamped_light - MIN_LIGHT) * (TOTAL_NOTES - 1) // (MAX_LIGHT - MIN_LIGHT)Ici il manque une protection si Suggestion : range_light = MAX_LIGHT - MIN_LIGHT
if range_light <= 0:
range_light = 1
note_index = (clamped_light - MIN_LIGHT) * (TOTAL_NOTES - 1) // range_light2. Clamp incompletclamped_light = min(light_level, MAX_LIGHT)Tu clamps uniquement le max, mais pas le min. Du coup la formule peut produire des valeurs inattendues si Suggestion : clamped_light = max(MIN_LIGHT, min(light_level, MAX_LIGHT))3. Prints très verbeuxprint("Light Level: {}, Note Index: {}".format(light_level, note_index))
print("Playing: {} Hz".format(freq))Ça spam beaucoup la console et ralentit un peu la boucle. Suggestion : print("Light: {} | Note: {} | Freq: {} Hz".format(light_level, note_index, freq), end="\r")4. README non mis à jourL’exemple n’est pas ajouté dans le README du driver. À faire :
5. Description de la PRIl manque la référence à l’issue dans la description. À ajouter : ConclusionTrès bon exemple, original et agréable à utiliser. À corriger principalement :
Avec ces ajustements, ce sera un excellent exemple |
Summary
Adds a
light_theremin.pyexample using the APDS9960 ambient light sensor and the board's buzzer.Changes
buzzer_ch.pulse_width_percent(0)) in thefinallyblock.Checklist
ruff checkpassespython -m pytest tests/ -k mock -vpasses (no mock test broken)<scope>: <Description.>format