Skip to content

Commit 3e30d97

Browse files
committed
pybricks.common.Speaker: Use new awaitable.
1 parent 113b2f3 commit 3e30d97

File tree

1 file changed

+61
-79
lines changed

1 file changed

+61
-79
lines changed

pybricks/common/pb_type_speaker.c

Lines changed: 61 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
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
}
6262
static 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-
7264
static 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

10593
static 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
}
133120
static 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

330313
static 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
}
349331
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Speaker_play_notes_obj, 1, pb_type_Speaker_play_notes);
350332

0 commit comments

Comments
 (0)