]> git.earman.xyz Git - sensor-watch.git/commitdiff
Play an arbitrary stream on the buzzer without blocking
authorAlessandro Genova <ales.genova@gmail.com>
Fri, 15 Aug 2025 04:53:23 +0000 (00:53 -0400)
committerAlessandro Genova <ales.genova@gmail.com>
Sat, 3 Jan 2026 01:07:56 +0000 (20:07 -0500)
watch-library/hardware/watch/watch_tcc.c
watch-library/shared/watch/watch_tcc.h
watch-library/simulator/watch/watch_tcc.c

index 0e1fdd9dad863f4adbc3558656a304138481d630..8624ffccbec54026d5b40518eb6c0437f80db7b0 100644 (file)
 #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;
 }
 
index 99c56dcc475ed06c25d2c2b74692a071444598f6..e87e53bf0e26df8ce5231c7b86aacdc187ecde62 100644 (file)
@@ -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);
index c7b1182def8a9a50f4bd6da2085797593c4092bd..1ca4bbf0e2930e21997abc32e50268f70fef17da 100644 (file)
@@ -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();