From 47812f462d275ff62e3796d4064cdac66772f6c9 Mon Sep 17 00:00:00 2001 From: TheOnePerson Date: Wed, 11 Jan 2023 21:26:33 +0100 Subject: Play sound sequences asynchronously (#122) * buzzer sequences: first draft, does not work on hardware yet (but in simulator) * buzzer sequences: add changes to movement.c * buzzer sequences: add demo face to Makefile * buzzer sequences: fix problem of interrupted sounds. Add logic for repeating sub sequences. Tidy up (move logic to watch_buzzer files, remove buzzer_demo_face) * buzzer sequences: tidy up even more * buzzer sequences: disable registering a 32 Hz tick callback for watch faces, so it will be used exclusively by the buzzer sequences functionality * buzzer sequences: add callback slot functionality to watch_rtc and make watch_buzzer use it. Switch internal buzzer sequences tick frequency to 64 Hz. Revert changes to movement.c * buzzer sequences: fix parameter sanity check in watch_rtc code * buzzer sequences/watch_rtc: optimize calling tick callbacks in RTC_Handler * buzzer sequences/watch_rtc: fix error in calling callback functions * buzzer sequences: revert changes to watch_rtc logic. Instead, use TC3 as the source for timing the sound sequences. * buzzer sequences: fix frequency of callback * buzzer sequences: integrate changes from PR #162 (set both CCBUF and PERFBUF for correct buzzer tone) --- watch-library/hardware/watch/watch_buzzer.c | 122 ++++++++++++++++++++++++++- watch-library/shared/watch/watch_buzzer.h | 23 +++++ watch-library/simulator/watch/watch_buzzer.c | 76 +++++++++++++++++ 3 files changed, 220 insertions(+), 1 deletion(-) diff --git a/watch-library/hardware/watch/watch_buzzer.c b/watch-library/hardware/watch/watch_buzzer.c index 07303482..b999397a 100644 --- a/watch-library/hardware/watch/watch_buzzer.c +++ b/watch-library/hardware/watch/watch_buzzer.c @@ -23,13 +23,133 @@ */ #include "watch_buzzer.h" +#include "../../../watch-library/hardware/include/saml22j18a.h" +#include "../../../watch-library/hardware/include/component/tc.h" +#include "../../../watch-library/hardware/hri/hri_tc_l22.h" - inline void watch_enable_buzzer(void) { +void cb_watch_buzzer_seq(void); + +static uint16_t _seq_position; +static int8_t _tone_ticks, _repeat_counter; +static bool _callback_running = false; +static int8_t *_sequence; +static void (*_cb_finished)(void); + +static void _tcc_write_RUNSTDBY(bool value) { + // enables or disables RUNSTDBY of the tcc + hri_tcc_clear_CTRLA_ENABLE_bit(TCC0); + hri_tcc_write_CTRLA_RUNSTDBY_bit(TCC0, value); + hri_tcc_set_CTRLA_ENABLE_bit(TCC0); + hri_tcc_wait_for_sync(TCC0, TCC_SYNCBUSY_ENABLE); +} + +static inline void _tc3_start() { + // start the TC3 timer + hri_tc_set_CTRLA_ENABLE_bit(TC3); + _callback_running = true; +} + +static inline void _tc3_stop() { + // stop the TC3 timer + hri_tc_clear_CTRLA_ENABLE_bit(TC3); + hri_tc_wait_for_sync(TC3, TC_SYNCBUSY_ENABLE); + _callback_running = false; +} + +static void _tc3_initialize() { + // setup and initialize TC3 for a 64 Hz interrupt + hri_mclk_set_APBCMASK_TC3_bit(MCLK); + hri_gclk_write_PCHCTRL_reg(GCLK, TC3_GCLK_ID, GCLK_PCHCTRL_GEN_GCLK3 | GCLK_PCHCTRL_CHEN); + _tc3_stop(); + hri_tc_write_CTRLA_reg(TC3, TC_CTRLA_SWRST); + hri_tc_wait_for_sync(TC3, TC_SYNCBUSY_SWRST); + hri_tc_write_CTRLA_reg(TC3, TC_CTRLA_PRESCALER_DIV64 | + TC_CTRLA_MODE_COUNT8 | + TC_CTRLA_RUNSTDBY); + hri_tccount8_write_PER_reg(TC3, 7); // 32 Khz divided by 64 divided by 8 equals 64 Hz + hri_tc_set_INTEN_OVF_bit(TC3); + NVIC_ClearPendingIRQ(TC3_IRQn); + NVIC_EnableIRQ (TC3_IRQn); +} + +void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(void)) { + if (_callback_running) _tc3_stop(); + watch_set_buzzer_off(); + _sequence = note_sequence; + _cb_finished = callback_on_end; + _seq_position = 0; + _tone_ticks = 0; + _repeat_counter = -1; + // prepare buzzer + watch_enable_buzzer(); + // setup TC3 timer + _tc3_initialize(); + // TCC should run in standby mode + _tcc_write_RUNSTDBY(true); + // start the timer (for the 64 hz callback) + _tc3_start(); +} + +void cb_watch_buzzer_seq(void) { + // callback for reading the note sequence + if (_tone_ticks == 0) { + if (_sequence[_seq_position] < 0 && _sequence[_seq_position + 1]) { + // repeat indicator found + if (_repeat_counter == -1) { + // first encounter: load repeat counter + _repeat_counter = _sequence[_seq_position + 1]; + } else _repeat_counter--; + if (_repeat_counter > 0) + // rewind + if (_seq_position > _sequence[_seq_position] * -2) + _seq_position += _sequence[_seq_position] * 2; + else + _seq_position = 0; + else { + // continue + _seq_position += 2; + _repeat_counter = -1; + } + } + if (_sequence[_seq_position] && _sequence[_seq_position + 1]) { + // read note + BuzzerNote note = _sequence[_seq_position]; + if (note != BUZZER_NOTE_REST) { + watch_set_buzzer_period(NotePeriods[note]); + watch_set_buzzer_on(); + } else watch_set_buzzer_off(); + // set duration ticks and move to next tone + _tone_ticks = _sequence[_seq_position + 1]; + _seq_position += 2; + } else { + // end the sequence + watch_buzzer_abort_sequence(); + if (_cb_finished) _cb_finished(); + } + } else _tone_ticks--; +} + +void watch_buzzer_abort_sequence(void) { + // ends/aborts the sequence + if (_callback_running) _tc3_stop(); + watch_set_buzzer_off(); + // disable standby mode for TCC + _tcc_write_RUNSTDBY(false); +} + +void TC3_Handler(void) { + // interrupt handler vor TC3 (globally!) + cb_watch_buzzer_seq(); + TC3->COUNT8.INTFLAG.reg |= TC_INTFLAG_OVF; +} + +inline void watch_enable_buzzer(void) { if (!hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { _watch_enable_tcc(); } gpio_set_pin_direction(BUZZER, GPIO_DIRECTION_OUT); } + inline void watch_set_buzzer_period(uint32_t period) { hri_tcc_write_PERBUF_reg(TCC0, period); hri_tcc_write_CCBUF_reg(TCC0, WATCH_BUZZER_TCC_CHANNEL, period / 2); diff --git a/watch-library/shared/watch/watch_buzzer.h b/watch-library/shared/watch/watch_buzzer.h index 1b5d197c..7ba9a52e 100644 --- a/watch-library/shared/watch/watch_buzzer.h +++ b/watch-library/shared/watch/watch_buzzer.h @@ -160,5 +160,28 @@ void watch_buzzer_play_note(BuzzerNote note, uint16_t duration_ms); /// @brief An array of periods for all the notes on a piano, corresponding to the names in BuzzerNote. extern const uint16_t NotePeriods[108]; +/** @brief Plays the given sequence of notes in a non-blocking way. + * @param note_sequence A pointer to the sequence of buzzer note & duration tuples, ending with a zero. A simple + * RLE logic is implemented: a negative number instead of a buzzer note means that the sequence + * is rewound by the given number of notes. The byte following a negative number determines the number + * of loops. I.e. if you want to repeat the last three notes of the sequence one time, you should provide + * the tuple -3, 1. The repeated notes must not contain any other repeat markers, or you will end up with + * an eternal loop. + * @param callback_on_end A pointer to a callback function to be invoked when the sequence has finished playing. + * @note This function plays the sequence asynchronously, so the UI will not be blocked. + * Hint: It is not possible to play the lowest note BUZZER_NOTE_A1 (55.00 Hz). The note is represented by a + * zero byte, which is used here as the end-of-sequence marker. But hey, a frequency that low cannot be + * played properly by the watch's buzzer, anyway. + */ +void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(void)); + +/** @brief Aborts a playing sequence. + */ +void watch_buzzer_abort_sequence(void); + +#ifndef __EMSCRIPTEN__ +void TC3_Handler(void); +#endif + /// @} #endif diff --git a/watch-library/simulator/watch/watch_buzzer.c b/watch-library/simulator/watch/watch_buzzer.c index 1c95a96d..68d9a139 100644 --- a/watch-library/simulator/watch/watch_buzzer.c +++ b/watch-library/simulator/watch/watch_buzzer.c @@ -26,10 +26,86 @@ #include "watch_main_loop.h" #include +#include static bool buzzer_enabled = false; static uint32_t buzzer_period; +void cb_watch_buzzer_seq(void *userData); + +static uint16_t _seq_position; +static int8_t _tone_ticks, _repeat_counter; +static long _em_interval_id = 0; +static int8_t *_sequence; +static void (*_cb_finished)(void); + +static inline void _em_interval_stop() { + emscripten_clear_interval(_em_interval_id); + _em_interval_id = 0; +} + +void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(void)) { + if (_em_interval_id) _em_interval_stop(); + watch_set_buzzer_off(); + _sequence = note_sequence; + _cb_finished = callback_on_end; + _seq_position = 0; + _tone_ticks = 0; + _repeat_counter = -1; + // prepare buzzer + watch_enable_buzzer(); + // initiate 64 hz callback + _em_interval_id = emscripten_set_interval(cb_watch_buzzer_seq, (double)(1000/64), (void *)NULL); +} + +void cb_watch_buzzer_seq(void *userData) { + // callback for reading the note sequence + (void) userData; + if (_tone_ticks == 0) { + if (_sequence[_seq_position] < 0 && _sequence[_seq_position + 1]) { + // repeat indicator found + if (_repeat_counter == -1) { + // first encounter: load repeat counter + _repeat_counter = _sequence[_seq_position + 1]; + } else _repeat_counter--; + if (_repeat_counter > 0) + // rewind + if (_seq_position > _sequence[_seq_position] * -2) + _seq_position += _sequence[_seq_position] * 2; + else + _seq_position = 0; + else { + // continue + _seq_position += 2; + _repeat_counter = -1; + } + } + if (_sequence[_seq_position] && _sequence[_seq_position + 1]) { + // read note + BuzzerNote note = _sequence[_seq_position]; + if (note == BUZZER_NOTE_REST) { + watch_set_buzzer_off(); + } else { + watch_set_buzzer_period(NotePeriods[note]); + watch_set_buzzer_on(); + } + // set duration ticks and move to next tone + _tone_ticks = _sequence[_seq_position + 1]; + _seq_position += 2; + } else { + // end the sequence + watch_buzzer_abort_sequence(); + if (_cb_finished) _cb_finished(); + } + } else _tone_ticks--; +} + +void watch_buzzer_abort_sequence(void) { + // ends/aborts the sequence + if (_em_interval_id) _em_interval_stop(); + watch_set_buzzer_off(); +} + void watch_enable_buzzer(void) { buzzer_enabled = true; buzzer_period = NotePeriods[BUZZER_NOTE_A4]; -- cgit v1.2.3