From 3caef587df7416108c04f9431a1bddf7f5b5ccd2 Mon Sep 17 00:00:00 2001 From: Alessandro Genova Date: Fri, 15 Aug 2025 00:53:23 -0400 Subject: [PATCH] Play an arbitrary stream on the buzzer without blocking --- watch-library/hardware/watch/watch_tcc.c | 70 ++++++++++++++++++++++- watch-library/shared/watch/watch_tcc.h | 49 ++++++++++++++++ watch-library/simulator/watch/watch_tcc.c | 68 +++++++++++++++++++++- 3 files changed, 183 insertions(+), 4 deletions(-) diff --git a/watch-library/hardware/watch/watch_tcc.c b/watch-library/hardware/watch/watch_tcc.c index 0e1fdd9d..8624ffcc 100644 --- a/watch-library/hardware/watch/watch_tcc.c +++ b/watch-library/hardware/watch/watch_tcc.c @@ -28,11 +28,15 @@ #include "tc.h" void _watch_enable_tcc(void); +static void (*_cb_tc0)(void) = NULL; void cb_watch_buzzer_seq(void); +void cb_watch_buzzer_raw_source(void); static uint16_t _seq_position; static int8_t _tone_ticks, _repeat_counter; static int8_t *_sequence; +static watch_buzzer_raw_source_t _raw_source; +static void* _userdata; static uint8_t _volume; static void (*_cb_finished)(void); static watch_cb_t _cb_start_global = NULL; @@ -94,6 +98,7 @@ void watch_buzzer_play_sequence_with_volume(int8_t *note_sequence, void (*callba _repeat_counter = -1; // prepare buzzer + _cb_tc0 = cb_watch_buzzer_seq; // setup TC0 timer _tc0_initialize(); // start the timer (for the 64 hz callback) @@ -138,6 +143,67 @@ void cb_watch_buzzer_seq(void) { } else _tone_ticks--; } +void watch_buzzer_play_raw_source(watch_buzzer_raw_source_t raw_source, void* userdata, watch_cb_t callback_on_end) { + watch_buzzer_play_raw_source_with_volume(raw_source, userdata, callback_on_end, WATCH_BUZZER_VOLUME_LOUD); +} + +void watch_buzzer_play_raw_source_with_volume(watch_buzzer_raw_source_t raw_source, void* userdata, watch_cb_t callback_on_end, watch_buzzer_volume_t volume) { + // Abort any previous sequence + watch_buzzer_abort_sequence(); + + _buzzer_is_active = true; + + if (_cb_start_global) { + _cb_start_global(); + } + + watch_enable_buzzer_and_leds(); + + watch_set_buzzer_off(); + _raw_source = raw_source; + _userdata = userdata; + _cb_finished = callback_on_end; + _volume = volume == WATCH_BUZZER_VOLUME_SOFT ? 5 : 25; + _seq_position = 0; + _tone_ticks = 0; + // prepare buzzer + + _cb_tc0 = cb_watch_buzzer_raw_source; + // setup TC0 timer + _tc0_initialize(); + // start the timer (for the 64 hz callback) + _tc0_start(); +} + +void cb_watch_buzzer_raw_source(void) { + // callback for reading the note sequence + uint16_t period; + uint16_t duration; + bool done; + + if (_tone_ticks == 0) { + done = _raw_source(_seq_position, _userdata, &period, &duration); + + if (done) { + // end the sequence + watch_buzzer_abort_sequence(); + } else { + if (period == WATCH_BUZZER_PERIOD_REST) { + watch_set_buzzer_off(); + } else { + watch_set_buzzer_period_and_duty_cycle(period, _volume); + watch_set_buzzer_on(); + } + + // set duration ticks and move to next tone + _tone_ticks = duration; + _seq_position += 1; + } + } else { + _tone_ticks--; + } +} + void watch_buzzer_abort_sequence(void) { // ends/aborts the sequence if (!_buzzer_is_active) { @@ -169,7 +235,9 @@ void watch_buzzer_register_global_callbacks(watch_cb_t cb_start, watch_cb_t cb_s void irq_handler_tc0(void) { // interrupt handler for TC0 (globally!) - cb_watch_buzzer_seq(); + if (_cb_tc0) { + _cb_tc0(); + } TC0->COUNT8.INTFLAG.reg |= TC_INTFLAG_OVF; } diff --git a/watch-library/shared/watch/watch_tcc.h b/watch-library/shared/watch/watch_tcc.h index 99c56dcc..e87e53bf 100644 --- a/watch-library/shared/watch/watch_tcc.h +++ b/watch-library/shared/watch/watch_tcc.h @@ -126,6 +126,10 @@ typedef enum { BUZZER_NOTE_REST ///< no sound } watch_buzzer_note_t; +#define WATCH_BUZZER_PERIOD_REST 0 + +typedef bool (*watch_buzzer_raw_source_t)(uint16_t position, void* userdata, uint16_t* period, uint16_t* duration); + /** @brief Returns true if either the buzzer or the LED driver is enabled. * @details Both the buzzer and the LED use the TCC peripheral to drive their behavior. This function returns true if that * peripheral is enabled. You can use this function to determine whether you need to call the watch_enable_leds or @@ -210,6 +214,51 @@ void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(v */ void watch_buzzer_play_sequence_with_volume(int8_t *note_sequence, void (*callback_on_end)(void), watch_buzzer_volume_t volume); +/** @brief Plays the given raw buzzer source function in a non-blocking way. + * + * @details This function plays audio data generated by a raw source callback function, + * allowing for precise control over buzzer timing and frequency. The raw source + * function is called repeatedly to generate audio samples, each containing a + * period and duration for the buzzer tone. + * Useful for applications such as chirpy, so that they won't need to allocate a + * long note sequence, and we will also take care of all the timing logic. + * + * @param raw_source Pointer to the callback function that generates raw buzzer data. + * The function should take a position parameter and return true if + * more data is available, false if end of sequence is reached. + * Parameters: + * - position: Current position in the audio sequence (0-based) + * - userdata: User-provided data passed through to the callback + * - period: Pointer to store the period (in microseconds) for the tone + * - duration: Pointer to store the duration (in microseconds) for the tone + * @param userdata Pointer to user data that will be passed to the raw_source callback + * @param callback_on_end A pointer to a callback function to be invoked when the sequence has finished playing. + */ +void watch_buzzer_play_raw_source(watch_buzzer_raw_source_t raw_source, void* userdata, watch_cb_t callback_on_end); + +/** @brief Plays the given raw buzzer source function in a non-blocking way. + * + * @details This function plays audio data generated by a raw source callback function, + * allowing for precise control over buzzer timing and frequency. The raw source + * function is called repeatedly to generate audio samples, each containing a + * period and duration for the buzzer tone. + * Useful for applications such as chirpy, so that they won't need to allocate a + * long note sequence, and we will also take care of all the timing logic. + * + * @param raw_source Pointer to the callback function that generates raw buzzer data. + * The function should take a position parameter and return true if + * more data is available, false if end of sequence is reached. + * Parameters: + * - position: Current position in the audio sequence (0-based) + * - userdata: User-provided data passed through to the callback + * - period: Pointer to store the period (in microseconds) for the tone + * - duration: Pointer to store the duration (in microseconds) for the tone + * @param userdata Pointer to user data that will be passed to the raw_source callback + * @param callback_on_end A pointer to a callback function to be invoked when the sequence has finished playing. + * @param volume either WATCH_BUZZER_VOLUME_SOFT or WATCH_BUZZER_VOLUME_LOUD + */ +void watch_buzzer_play_raw_source_with_volume(watch_buzzer_raw_source_t raw_source, void* userdata, watch_cb_t callback_on_end, watch_buzzer_volume_t volume); + /** @brief Aborts a playing sequence. */ void watch_buzzer_abort_sequence(void); diff --git a/watch-library/simulator/watch/watch_tcc.c b/watch-library/simulator/watch/watch_tcc.c index c7b1182d..1ca4bbf0 100644 --- a/watch-library/simulator/watch/watch_tcc.c +++ b/watch-library/simulator/watch/watch_tcc.c @@ -32,11 +32,14 @@ static volatile bool buzzer_enabled = false; static uint32_t buzzer_period; void cb_watch_buzzer_seq(void *userData); +void cb_watch_buzzer_raw_source(void *userData); static uint16_t _seq_position; static int8_t _tone_ticks, _repeat_counter; static volatile long _em_interval_id = 0; static int8_t *_sequence; +static watch_buzzer_raw_source_t _raw_source; +static void* _userdata; static uint8_t _volume; static void (*_cb_finished)(void); static watch_cb_t _cb_start_global = NULL; @@ -57,6 +60,10 @@ void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(v void watch_buzzer_play_sequence_with_volume(int8_t *note_sequence, void (*callback_on_end)(void), watch_buzzer_volume_t volume) { watch_buzzer_abort_sequence(); + // prepare buzzer + watch_enable_buzzer(); + watch_set_buzzer_off(); + _buzzer_is_active = true; if (_cb_start_global) { @@ -69,9 +76,6 @@ void watch_buzzer_play_sequence_with_volume(int8_t *note_sequence, void (*callba _seq_position = 0; _tone_ticks = 0; _repeat_counter = -1; - // prepare buzzer - watch_enable_buzzer(); - watch_set_buzzer_off(); // initiate 64 hz callback _em_interval_id = emscripten_set_interval(cb_watch_buzzer_seq, (double)(1000/64), (void *)NULL); } @@ -117,6 +121,64 @@ void cb_watch_buzzer_seq(void *userData) { } else _tone_ticks--; } +void watch_buzzer_play_raw_source(watch_buzzer_raw_source_t raw_source, void* userdata, watch_cb_t callback_on_end) { + watch_buzzer_play_raw_source_with_volume(raw_source, userdata, callback_on_end, WATCH_BUZZER_VOLUME_LOUD); +} + +void watch_buzzer_play_raw_source_with_volume(watch_buzzer_raw_source_t raw_source, void* userdata, watch_cb_t callback_on_end, watch_buzzer_volume_t volume) { + watch_buzzer_abort_sequence(); + + // prepare buzzer + watch_enable_buzzer(); + watch_set_buzzer_off(); + + _buzzer_is_active = true; + + if (_cb_start_global) { + _cb_start_global(); + } + + _raw_source = raw_source; + _userdata = userdata; + _cb_finished = callback_on_end; + _volume = volume == WATCH_BUZZER_VOLUME_SOFT ? 5 : 25; + _seq_position = 0; + _tone_ticks = 0; + + // initiate 64 hz callback + _em_interval_id = emscripten_set_interval(cb_watch_buzzer_raw_source, (double)(1000/64), (void *)NULL); +} + +void cb_watch_buzzer_raw_source(void *userData) { + // callback for reading the note sequence + (void) userData; + uint16_t period; + uint16_t duration; + bool done; + + if (_tone_ticks == 0) { + done = _raw_source(_seq_position, _userdata, &period, &duration); + + if (done) { + // end the sequence + watch_buzzer_abort_sequence(); + } else { + if (period == WATCH_BUZZER_PERIOD_REST) { + watch_set_buzzer_off(); + } else { + watch_set_buzzer_period_and_duty_cycle(period, _volume); + watch_set_buzzer_on(); + } + + // set duration ticks and move to next tone + _tone_ticks = duration; + _seq_position += 1; + } + } else { + _tone_ticks--; + } +} + void watch_buzzer_abort_sequence(void) { // ends/aborts the sequence if (_em_interval_id) _em_interval_stop();