Skip to content

Commit 1f012ac

Browse files
committed
Non-blocking buzzer inter-note gap handling
Implement proper inter-note silence and robust non-blocking playback for the Buzzer. - Add _in_gap and _gap_end_time state to track short gaps between notes instead of sleeping inside the timer callback; refactor _update to advance songs after the gap expires and stop the timer when the song finishes. - Fix note parsing by initializing octave_pos and handling modifier positions to avoid undefined behavior. - When starting single non-blocking notes, clear any active song state and reset gap flags; when starting a non-blocking song, stop any existing timer/playback before beginning. - Ensure _in_gap is cleared on reset. - Update resetbot to call Buzzer.get_default_buzzer().reset_buzzer() (use instance) instead of calling a class method. These changes remove blocking sleeps from timer callbacks, prevent race conditions between single-note and song playback, and make non-blocking behavior deterministic.
1 parent d596014 commit 1f012ac

2 files changed

Lines changed: 38 additions & 28 deletions

File tree

XRPLib/buzzer.py

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def __init__(self, buzzer_pin: int|str = None):
6868
# State variables for non-blocking playback
6969
self._note_end_time = 0
7070
self._tempo = 120 # Default BPM
71+
self._in_gap = False
72+
self._gap_end_time = 0
7173

7274
# Song playback state
7375
self._song = []
@@ -131,15 +133,14 @@ def _parse_note(self, note_str: str):
131133
if note_letter in "ABCDEFG":
132134
# Check for sharp (#) or flat (b) modifier
133135
note_with_modifier = note_letter
136+
octave_pos = 1
134137
if len(note_str) > 1:
135138
if note_str[1] == "#":
136139
note_with_modifier = note_letter + "#"
137140
octave_pos = 2
138141
elif note_str[1] == "B":
139142
note_with_modifier = note_letter + "B"
140143
octave_pos = 2
141-
else:
142-
octave_pos = 1
143144

144145
try:
145146
if octave_pos < len(note_str):
@@ -157,24 +158,25 @@ def _update(self, timer):
157158
Internal callback for non-blocking playback.
158159
Called by the timer when running in non-blocking mode.
159160
"""
160-
161-
# Check if current note is done
162-
if time.ticks_diff(self._note_end_time, time.ticks_ms()) <= 0:
163-
# Note done - stop the tone
161+
now = time.ticks_ms()
162+
163+
if self._in_gap:
164+
# Waiting for the inter-note silence to pass before advancing
165+
if time.ticks_diff(self._gap_end_time, now) <= 0:
166+
self._in_gap = False
167+
self._song_index += 1
168+
if self._song_index < len(self._song):
169+
note, duration = self._song[self._song_index]
170+
self._start_note(note, duration, self._song_tempo)
171+
else:
172+
# Song finished
173+
self._timer.deinit()
174+
self._timer_in_use = False
175+
elif time.ticks_diff(self._note_end_time, now) <= 0:
176+
# Note done - silence and schedule a short gap before next note
164177
self._pwm.duty_u16(0)
165-
166-
# Small gap between notes
167-
time.sleep_ms(20)
168-
169-
# Play next note in song
170-
self._song_index += 1
171-
if self._song_index < len(self._song):
172-
note, duration = self._song[self._song_index]
173-
self._start_note(note, duration, self._song_tempo)
174-
else:
175-
# Song finished
176-
self._timer.deinit()
177-
self._timer_in_use = False
178+
self._in_gap = True
179+
self._gap_end_time = time.ticks_add(now, 20)
178180

179181
def _start_note(self, note: str, duration: str = "quarter", tempo: int = None):
180182
"""Start playing a note (non-blocking helper)."""
@@ -238,14 +240,17 @@ def play_note(self, note: str, duration: str = "quarter", blocking: bool = True,
238240
if blocking:
239241
self._play_tone_blocking(frequency, duration_ms)
240242
else:
241-
# Non-blocking - start the note and set up timer if needed
243+
# Non-blocking - clear any active song so _update won't advance it
244+
self._song = []
245+
self._song_index = 0
246+
self._in_gap = False
247+
242248
if frequency > 0:
243249
self._pwm.freq(frequency)
244250
self._pwm.duty_u16(32768)
245-
251+
246252
self._note_end_time = time.ticks_add(time.ticks_ms(), duration_ms)
247-
248-
# Start timer if not already running
253+
249254
if not self._timer_in_use:
250255
self._timer.init(freq=100, callback=lambda t: self._update(t))
251256
self._timer_in_use = True
@@ -291,17 +296,21 @@ def play_song(self, song: list, tempo: int = None, blocking: bool = True):
291296
duration_ms = self._duration_to_ms(duration, tempo)
292297
self._play_tone_blocking(frequency, duration_ms)
293298
else:
294-
# Non-blocking - store song and start playing with timer
299+
# Non-blocking - stop any existing playback before starting new song
300+
if self._timer_in_use:
301+
self._timer.deinit()
302+
self._timer_in_use = False
303+
self._in_gap = False
304+
295305
self._song = song
296306
self._song_index = 0
297307
self._song_tempo = tempo
298-
self._timer_in_use = False
299-
308+
300309
# Start playing first note
301310
note, duration = song[0]
302311
self._start_note(note, duration, tempo)
303312
self._song_index = 1
304-
313+
305314
# Start the timer for non-blocking playback
306315
self._timer.init(freq=100, callback=lambda t: self._update(t))
307316
self._timer_in_use = True
@@ -322,6 +331,7 @@ def reset_buzzer(self):
322331
# Reset song state
323332
self._song = []
324333
self._song_index = 0
334+
self._in_gap = False
325335

326336
def play_move_it(self, blocking: bool = True):
327337
"""

XRPLib/resetbot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def reset_buzzer():
3333
# Turn off the buzzer
3434
try:
3535
# Turn off the Buzzer if the board has one
36-
Buzzer.reset_buzzer()
36+
Buzzer.get_default_buzzer().reset_buzzer()
3737
except:
3838
pass
3939

0 commit comments

Comments
 (0)