2020
2121#include <pybricks/common.h>
2222#include <pybricks/tools.h>
23- #include <pybricks/tools/pb_type_awaitable .h>
23+ #include <pybricks/tools/pb_type_async .h>
2424#include <pybricks/util_mp/pb_kwarg_helper.h>
2525#include <pybricks/util_mp/pb_obj_helper.h>
2626#include <pybricks/util_pb/pb_error.h>
@@ -29,11 +29,11 @@ typedef struct {
2929 mp_obj_base_t base ;
3030
3131 // State of awaitable sound
32+ pb_type_async_t * iter ;
33+ pbio_os_timer_t timer ;
3234 mp_obj_t notes_generator ;
3335 uint32_t note_duration ;
34- uint32_t beep_end_time ;
35- uint32_t release_end_time ;
36- mp_obj_t awaitables ;
36+ uint32_t scaled_duration ;
3737
3838 // volume in 0..100 range
3939 uint8_t volume ;
@@ -61,45 +61,33 @@ static mp_obj_t pb_type_Speaker_volume(size_t n_args, const mp_obj_t *pos_args,
6161}
6262static MP_DEFINE_CONST_FUN_OBJ_KW (pb_type_Speaker_volume_obj , 1 , pb_type_Speaker_volume ) ;
6363
64- static void pb_type_Speaker_start_beep (uint32_t frequency , uint16_t sample_attenuator ) {
65- pbdrv_beep_start (frequency , sample_attenuator );
66- }
67-
68- static void pb_type_Speaker_stop_beep (void ) {
69- pbdrv_sound_stop ();
70- }
71-
7264static mp_obj_t pb_type_Speaker_make_new (const mp_obj_type_t * type , size_t n_args , size_t n_kw , const mp_obj_t * args ) {
7365
7466 pb_type_Speaker_obj_t * self = mp_obj_malloc (pb_type_Speaker_obj_t , type );
7567
76- // List of awaitables associated with speaker. By keeping track,
77- // we can cancel them as needed when a new sound is started.
78- self -> awaitables = mp_obj_new_list (0 , NULL );
79-
8068 // REVISIT: If a user creates two Speaker instances, this will reset the volume settings for both.
8169 // If done only once per singleton, however, altered volume settings would be persisted between program runs.
8270 self -> volume = PBDRV_CONFIG_SOUND_DEFAULT_VOLUME ;
8371 self -> sample_attenuator = INT16_MAX ;
8472
73+ self -> iter = NULL ;
74+
8575 return MP_OBJ_FROM_PTR (self );
8676}
8777
88- static bool pb_type_Speaker_beep_test_completion (mp_obj_t self_in , uint32_t end_time ) {
89- pb_type_Speaker_obj_t * self = MP_OBJ_TO_PTR (self_in );
90- if (mp_hal_ticks_ms () - self -> beep_end_time < (uint32_t )INT32_MAX ) {
91- pb_type_Speaker_stop_beep ();
92- return true;
93- }
94- return false;
78+ static mp_obj_t pb_type_Speaker_close (mp_obj_t self_in ) {
79+ pbdrv_sound_stop ();
80+ return mp_const_none ;
9581}
9682
97- static void pb_type_Speaker_cancel (mp_obj_t self_in ) {
98- pb_type_Speaker_stop_beep ();
99- pb_type_Speaker_obj_t * self = MP_OBJ_TO_PTR (self_in );
100- self -> beep_end_time = mp_hal_ticks_ms ();
101- self -> release_end_time = self -> beep_end_time ;
102- self -> notes_generator = MP_OBJ_NULL ;
83+ static pbio_error_t pb_type_Speaker_beep_iterate_once (pbio_os_state_t * state , mp_obj_t parent_obj ) {
84+ pb_type_Speaker_obj_t * self = MP_OBJ_TO_PTR (parent_obj );
85+ // The beep has already been started. We just need to await the duration
86+ // and then stop.
87+ PBIO_OS_ASYNC_BEGIN (state );
88+ PBIO_OS_AWAIT_UNTIL (state , pbio_os_timer_is_expired (& self -> timer ));
89+ pbdrv_sound_stop ();
90+ PBIO_OS_ASYNC_END (PBIO_SUCCESS );
10391}
10492
10593static mp_obj_t pb_type_Speaker_beep (size_t n_args , const mp_obj_t * pos_args , mp_map_t * kw_args ) {
@@ -111,28 +99,27 @@ static mp_obj_t pb_type_Speaker_beep(size_t n_args, const mp_obj_t *pos_args, mp
11199 mp_int_t frequency = pb_obj_get_int (frequency_in );
112100 mp_int_t duration = pb_obj_get_int (duration_in );
113101
114- pb_type_Speaker_start_beep (frequency , self -> sample_attenuator );
102+ pbdrv_beep_start (frequency , self -> sample_attenuator );
115103
116104 if (duration < 0 ) {
117105 return mp_const_none ;
118106 }
119107
120- self -> beep_end_time = mp_hal_ticks_ms () + (uint32_t )duration ;
121- self -> release_end_time = self -> beep_end_time ;
122- self -> notes_generator = MP_OBJ_NULL ;
123-
124- return pb_type_awaitable_await_or_wait (
125- MP_OBJ_FROM_PTR (self ),
126- self -> awaitables ,
127- pb_type_awaitable_end_time_none ,
128- pb_type_Speaker_beep_test_completion ,
129- pb_type_awaitable_return_none ,
130- pb_type_Speaker_cancel ,
131- PB_TYPE_AWAITABLE_OPT_CANCEL_ALL );
108+ pbio_os_timer_set (& self -> timer , pb_obj_get_int (duration_in ));
109+
110+ pb_type_async_t config = {
111+ .parent_obj = MP_OBJ_FROM_PTR (self ),
112+ .iter_once = pb_type_Speaker_beep_iterate_once ,
113+ .close = pb_type_Speaker_close ,
114+ };
115+ // New operation always wins; ongoing sound awaitable is cancelled.
116+ pb_type_async_schedule_cancel (self -> iter );
117+ return pb_type_async_wait_or_await (& config , & self -> iter );
118+
132119}
133120static MP_DEFINE_CONST_FUN_OBJ_KW (pb_type_Speaker_beep_obj , 1 , pb_type_Speaker_beep ) ;
134121
135- static void pb_type_Speaker_play_note ( pb_type_Speaker_obj_t * self , mp_obj_t obj , int duration ) {
122+ static void pb_type_Speaker_get_note ( mp_obj_t obj , uint32_t note_ms , uint32_t * frequency , uint32_t * total_ms , uint32_t * on_ms ) {
136123 const char * note = mp_obj_str_get_str (obj );
137124 int pos = 0 ;
138125 mp_float_t freq ;
@@ -274,13 +261,13 @@ static void pb_type_Speaker_play_note(pb_type_Speaker_obj_t *self, mp_obj_t obj,
274261 fraction = fraction * 10 + fraction2 ;
275262 }
276263
277- duration /= fraction ;
264+ * total_ms = note_ms / fraction ;
278265
279266 // optional decorations
280267
281268 if (note [pos ++ ] == '.' ) {
282269 // dotted note has length extended by 1/2
283- duration = 3 * duration / 2 ;
270+ * total_ms = 3 * * total_ms / 2 ;
284271 } else {
285272 pos -- ;
286273 }
@@ -292,39 +279,35 @@ static void pb_type_Speaker_play_note(pb_type_Speaker_obj_t *self, mp_obj_t obj,
292279 pos -- ;
293280 }
294281
295- pb_type_Speaker_start_beep ((uint32_t )freq , self -> sample_attenuator );
296-
297- uint32_t time_now = mp_hal_ticks_ms ();
298- self -> release_end_time = time_now + duration ;
299- self -> beep_end_time = release ? time_now + 7 * duration / 8 : time_now + duration ;
282+ * frequency = (uint32_t )freq ;
283+ * on_ms = release ? 7 * (* total_ms ) / 8 : * total_ms ;
300284}
301285
302- static bool pb_type_Speaker_notes_test_completion (mp_obj_t self_in , uint32_t end_time ) {
303- pb_type_Speaker_obj_t * self = MP_OBJ_TO_PTR (self_in );
286+ static pbio_error_t pb_type_Speaker_play_notes_iterate_once (pbio_os_state_t * state , mp_obj_t parent_obj ) {
287+ pb_type_Speaker_obj_t * self = MP_OBJ_TO_PTR (parent_obj );
288+ mp_obj_t item ;
304289
305- bool release_done = mp_hal_ticks_ms () - self -> release_end_time < (uint32_t )INT32_MAX ;
306- bool beep_done = mp_hal_ticks_ms () - self -> beep_end_time < (uint32_t )INT32_MAX ;
290+ PBIO_OS_ASYNC_BEGIN (state );
307291
308- if (self -> notes_generator != MP_OBJ_NULL && release_done && beep_done ) {
309- // Full note done, so get next note.
310- mp_obj_t item = mp_iternext (self -> notes_generator );
292+ while ((item = mp_iternext (self -> notes_generator )) != MP_OBJ_STOP_ITERATION ) {
311293
312- // If there is no next note, generator is done .
313- if ( item == MP_OBJ_STOP_ITERATION ) {
314- return true ;
315- }
294+ // Parse next note.
295+ uint32_t frequency ;
296+ uint32_t beep_time ;
297+ pb_type_Speaker_get_note ( item , self -> note_duration , & frequency , & self -> scaled_duration , & beep_time );
316298
317- // Start the note.
318- pb_type_Speaker_play_note ( self , item , self -> note_duration );
319- return false ;
320- }
299+ // On portion of the note.
300+ pbdrv_beep_start ( frequency , self -> sample_attenuator );
301+ pbio_os_timer_set ( & self -> timer , beep_time ) ;
302+ PBIO_OS_AWAIT_UNTIL ( state , pbio_os_timer_is_expired ( & self -> timer ));
321303
322- if (beep_done ) {
323- // Time to release.
324- pb_type_Speaker_stop_beep ();
304+ // Off portion of the note.
305+ pbdrv_sound_stop ();
306+ self -> timer .duration = self -> scaled_duration ;
307+ PBIO_OS_AWAIT_UNTIL (state , pbio_os_timer_is_expired (& self -> timer ));
325308 }
326309
327- return false ;
310+ PBIO_OS_ASYNC_END ( PBIO_SUCCESS ) ;
328311}
329312
330313static mp_obj_t pb_type_Speaker_play_notes (size_t n_args , const mp_obj_t * pos_args , mp_map_t * kw_args ) {
@@ -335,16 +318,15 @@ static mp_obj_t pb_type_Speaker_play_notes(size_t n_args, const mp_obj_t *pos_ar
335318
336319 self -> notes_generator = mp_getiter (notes_in , NULL );
337320 self -> note_duration = 4 * 60 * 1000 / pb_obj_get_int (tempo_in );
338- self -> beep_end_time = mp_hal_ticks_ms ();
339- self -> release_end_time = self -> beep_end_time ;
340- return pb_type_awaitable_await_or_wait (
341- MP_OBJ_FROM_PTR (self ),
342- self -> awaitables ,
343- pb_type_awaitable_end_time_none ,
344- pb_type_Speaker_notes_test_completion ,
345- pb_type_awaitable_return_none ,
346- pb_type_Speaker_cancel ,
347- PB_TYPE_AWAITABLE_OPT_CANCEL_ALL );
321+
322+ pb_type_async_t config = {
323+ .parent_obj = MP_OBJ_FROM_PTR (self ),
324+ .iter_once = pb_type_Speaker_play_notes_iterate_once ,
325+ .close = pb_type_Speaker_close ,
326+ };
327+ // New operation always wins; ongoing sound awaitable is cancelled.
328+ pb_type_async_schedule_cancel (self -> iter );
329+ return pb_type_async_wait_or_await (& config , & self -> iter );
348330}
349331static MP_DEFINE_CONST_FUN_OBJ_KW (pb_type_Speaker_play_notes_obj , 1 , pb_type_Speaker_play_notes ) ;
350332
0 commit comments