qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

audio.c (21231B)


      1 /* Copyright 2016-2020 Jack Humbert
      2  * Copyright 2020 JohSchneider
      3 
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 2 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 #include "audio.h"
     18 #include "eeconfig.h"
     19 #include "timer.h"
     20 #include "debug.h"
     21 #include "wait.h"
     22 #include "util.h"
     23 #include "gpio.h"
     24 
     25 /* audio system:
     26  *
     27  * audio.[ch] takes care of all overall state, tracking the actively playing
     28  *            notes/tones; the notes a SONG consists of;
     29  *            ...
     30  *            = everything audio-related that is platform agnostic
     31  *
     32  * driver_[avr|chibios]_[dac|pwm] take care of the lower hardware dependent parts,
     33  *            specific to each platform and the used subsystem/driver to drive
     34  *            the output pins/channels with the calculated frequencies for each
     35  *            active tone
     36  *            as part of this, the driver has to trigger regular state updates by
     37  *            calling 'audio_update_state' through some sort of timer - be it a
     38  *            dedicated one or piggybacking on for example the timer used to
     39  *            generate a pwm signal/clock.
     40  *
     41  *
     42  * A Note on terminology:
     43  * tone, pitch and frequency are used somewhat interchangeably, in a strict Wikipedia-sense:
     44  *    "(Musical) tone, a sound characterized by its duration, pitch (=frequency),
     45  *    intensity (=volume), and timbre"
     46  * - intensity/volume is currently not handled at all, although the 'dac_additive' driver could do so
     47  * - timbre is handled globally (TODO: only used with the pwm drivers at the moment)
     48  *
     49  * in musical_note.h a 'note' is the combination of a pitch and a duration
     50  * these are used to create SONG arrays; during playback their frequencies
     51  * are handled as single successive tones, while the durations are
     52  * kept track of in 'audio_update_state'
     53  *
     54  * 'voice' as it is used here, equates to a sort of instrument with its own
     55  * characteristics sound and effects
     56  * the audio system as-is deals only with (possibly multiple) tones of one
     57  * instrument/voice at a time (think: chords). since the number of tones that
     58  * can be reproduced depends on the hardware/driver in use: pwm can only
     59  * reproduce one tone per output/speaker; DACs can reproduce/mix multiple
     60  * when doing additive synthesis.
     61  *
     62  * 'duration' can either be in the beats-per-minute related unit found in
     63  * musical_notes.h, OR in ms; keyboards create SONGs with the former, while
     64  * the internal state of the audio system does its calculations with the later - ms
     65  */
     66 
     67 #ifndef AUDIO_DEFAULT_ON
     68 #    define AUDIO_DEFAULT_ON true
     69 #endif
     70 #ifndef AUDIO_DEFAULT_CLICKY_ON
     71 #    define AUDIO_DEFAULT_CLICKY_ON true
     72 #endif
     73 
     74 #ifndef AUDIO_TONE_STACKSIZE
     75 #    define AUDIO_TONE_STACKSIZE 8
     76 #endif
     77 uint8_t        active_tones = 0;            // number of tones pushed onto the stack by audio_play_tone - might be more than the hardware is able to reproduce at any single time
     78 musical_tone_t tones[AUDIO_TONE_STACKSIZE]; // stack of currently active tones
     79 
     80 bool playing_melody = false; // playing a SONG?
     81 bool playing_note   = false; // or (possibly multiple simultaneous) tones
     82 bool state_changed  = false; // global flag, which is set if anything changes with the active_tones
     83 
     84 // melody/SONG related state variables
     85 float (*notes_pointer)[][2];                           // SONG, an array of MUSICAL_NOTEs
     86 uint16_t notes_count;                                  // length of the notes_pointer array
     87 bool     notes_repeat;                                 // PLAY_SONG or PLAY_LOOP?
     88 uint16_t melody_current_note_duration = 0;             // duration of the currently playing note from the active melody, in ms
     89 uint8_t  note_tempo                   = TEMPO_DEFAULT; // beats-per-minute
     90 uint16_t current_note                 = 0;             // index into the array at notes_pointer
     91 bool     note_resting                 = false;         // if a short pause was introduced between two notes with the same frequency while playing a melody
     92 uint16_t last_timestamp               = 0;
     93 
     94 #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
     95 #    ifndef AUDIO_MAX_SIMULTANEOUS_TONES
     96 #        define AUDIO_MAX_SIMULTANEOUS_TONES 3
     97 #    endif
     98 uint16_t tone_multiplexing_rate        = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT;
     99 uint8_t  tone_multiplexing_index_shift = 0; // offset used on active-tone array access
    100 #endif
    101 
    102 // provided and used by voices.c
    103 extern uint8_t  note_timbre;
    104 extern bool     glissando;
    105 extern bool     vibrato;
    106 extern uint16_t voices_timer;
    107 
    108 #ifndef STARTUP_SONG
    109 #    define STARTUP_SONG SONG(STARTUP_SOUND)
    110 #endif
    111 #ifndef AUDIO_ON_SONG
    112 #    define AUDIO_ON_SONG SONG(AUDIO_ON_SOUND)
    113 #endif
    114 #ifndef AUDIO_OFF_SONG
    115 #    define AUDIO_OFF_SONG SONG(AUDIO_OFF_SOUND)
    116 #endif
    117 float startup_song[][2]   = STARTUP_SONG;
    118 float audio_on_song[][2]  = AUDIO_ON_SONG;
    119 float audio_off_song[][2] = AUDIO_OFF_SONG;
    120 
    121 static bool    audio_initialized    = false;
    122 static bool    audio_driver_stopped = true;
    123 audio_config_t audio_config;
    124 
    125 #ifndef AUDIO_POWER_CONTROL_PIN_ON_STATE
    126 #    define AUDIO_POWER_CONTROL_PIN_ON_STATE 1
    127 #endif
    128 
    129 void audio_driver_initialize(void) {
    130 #ifdef AUDIO_POWER_CONTROL_PIN
    131     gpio_set_pin_output_push_pull(AUDIO_POWER_CONTROL_PIN);
    132     gpio_write_pin(AUDIO_POWER_CONTROL_PIN, !AUDIO_POWER_CONTROL_PIN_ON_STATE);
    133 #endif
    134     audio_driver_initialize_impl();
    135 }
    136 
    137 void audio_driver_stop(void) {
    138     audio_driver_stop_impl();
    139 #ifdef AUDIO_POWER_CONTROL_PIN
    140     gpio_write_pin(AUDIO_POWER_CONTROL_PIN, !AUDIO_POWER_CONTROL_PIN_ON_STATE);
    141 #endif
    142 }
    143 
    144 void audio_driver_start(void) {
    145 #ifdef AUDIO_POWER_CONTROL_PIN
    146     gpio_write_pin(AUDIO_POWER_CONTROL_PIN, AUDIO_POWER_CONTROL_PIN_ON_STATE);
    147 #endif
    148     audio_driver_start_impl();
    149 }
    150 
    151 void eeconfig_update_audio_current(void) {
    152     eeconfig_update_audio(&audio_config);
    153 }
    154 
    155 void eeconfig_update_audio_default(void) {
    156     audio_config.valid         = true;
    157     audio_config.enable        = AUDIO_DEFAULT_ON;
    158     audio_config.clicky_enable = AUDIO_DEFAULT_CLICKY_ON;
    159     eeconfig_update_audio(&audio_config);
    160 }
    161 
    162 void audio_init(void) {
    163     if (audio_initialized) {
    164         return;
    165     }
    166 
    167     eeconfig_read_audio(&audio_config);
    168     if (!audio_config.valid) {
    169         dprintf("audio_init audio_config.valid = 0. Write default values to EEPROM.\n");
    170         eeconfig_update_audio_default();
    171     }
    172 
    173     for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) {
    174         tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0};
    175     }
    176 
    177     audio_driver_initialize();
    178     audio_initialized = true;
    179 
    180     stop_all_notes();
    181 #ifndef AUDIO_INIT_DELAY
    182     audio_startup();
    183 #endif
    184 }
    185 
    186 void audio_task(void) {
    187 #ifdef AUDIO_INIT_DELAY
    188     // startup song potentially needs to be run a little bit
    189     // after keyboard startup, or else they will not work correctly
    190     // because of interaction with the USB device state, which
    191     // may still be in flux...
    192     static bool     delayed_tasks_run  = false;
    193     static uint16_t delayed_task_timer = 0;
    194     if (!delayed_tasks_run) {
    195         if (!delayed_task_timer) {
    196             delayed_task_timer = timer_read();
    197         } else if (timer_elapsed(delayed_task_timer) > 300) {
    198             audio_startup();
    199             delayed_tasks_run = true;
    200         }
    201     }
    202 #endif
    203 }
    204 
    205 void audio_startup(void) {
    206     if (audio_config.enable) {
    207         PLAY_SONG(startup_song);
    208     }
    209 
    210     last_timestamp = timer_read();
    211 }
    212 
    213 void audio_toggle(void) {
    214     if (audio_config.enable) {
    215         stop_all_notes();
    216     }
    217     audio_config.enable ^= 1;
    218     eeconfig_update_audio(&audio_config);
    219     if (audio_config.enable) {
    220         audio_on_user();
    221     } else {
    222         audio_off_user();
    223     }
    224 }
    225 
    226 void audio_on(void) {
    227     audio_config.enable = 1;
    228     eeconfig_update_audio(&audio_config);
    229     audio_on_user();
    230     PLAY_SONG(audio_on_song);
    231 }
    232 
    233 void audio_off(void) {
    234     PLAY_SONG(audio_off_song);
    235     audio_off_user();
    236     wait_ms(100);
    237     audio_stop_all();
    238     audio_config.enable = 0;
    239     eeconfig_update_audio(&audio_config);
    240 }
    241 
    242 bool audio_is_on(void) {
    243     return (audio_config.enable != 0);
    244 }
    245 
    246 void audio_stop_all(void) {
    247     if (audio_driver_stopped) {
    248         return;
    249     }
    250 
    251     active_tones = 0;
    252 
    253     audio_driver_stop();
    254 
    255     playing_melody = false;
    256     playing_note   = false;
    257 
    258     melody_current_note_duration = 0;
    259 
    260     for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) {
    261         tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0};
    262     }
    263 
    264     audio_driver_stopped = true;
    265 }
    266 
    267 void audio_stop_tone(float pitch) {
    268     if (pitch < 0.0f) {
    269         pitch = -1 * pitch;
    270     }
    271 
    272     if (playing_note) {
    273         if (!audio_initialized) {
    274             audio_init();
    275         }
    276         bool found = false;
    277         for (int i = AUDIO_TONE_STACKSIZE - 1; i >= 0; i--) {
    278             found = (tones[i].pitch == pitch);
    279             if (found) {
    280                 for (int j = i; (j < AUDIO_TONE_STACKSIZE - 1); j++) {
    281                     tones[j] = tones[j + 1];
    282                 }
    283                 tones[AUDIO_TONE_STACKSIZE - 1] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0};
    284                 break;
    285             }
    286         }
    287         if (!found) {
    288             return;
    289         }
    290 
    291         state_changed = true;
    292         active_tones--;
    293         if (active_tones < 0) active_tones = 0;
    294 #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
    295         if (tone_multiplexing_index_shift >= active_tones) {
    296             tone_multiplexing_index_shift = 0;
    297         }
    298 #endif
    299         if (active_tones == 0) {
    300             audio_driver_stop();
    301             audio_driver_stopped = true;
    302             playing_note         = false;
    303         }
    304     }
    305 }
    306 
    307 void audio_play_note(float pitch, uint16_t duration) {
    308     if (!audio_config.enable) {
    309         return;
    310     }
    311 
    312     if (!audio_initialized) {
    313         audio_init();
    314     }
    315 
    316     if (pitch < 0.0f) {
    317         pitch = -1 * pitch;
    318     }
    319 
    320     // round-robin: shifting out old tones, keeping only unique ones
    321     // if the new frequency is already amongst the active tones, shift it to the top of the stack
    322     bool found = false;
    323     for (int i = active_tones - 1; i >= 0; i--) {
    324         found = (tones[i].pitch == pitch);
    325         if (found) {
    326             for (int j = i; (j < active_tones - 1); j++) {
    327                 tones[j]     = tones[j + 1];
    328                 tones[j + 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration};
    329             }
    330             return; // since this frequency played already, the hardware was already started
    331         }
    332     }
    333 
    334     // frequency/tone is actually new, so we put it on the top of the stack
    335     active_tones++;
    336     if (active_tones > AUDIO_TONE_STACKSIZE) {
    337         active_tones = AUDIO_TONE_STACKSIZE;
    338         // shift out the oldest tone to make room
    339         for (int i = 0; i < active_tones - 1; i++) {
    340             tones[i] = tones[i + 1];
    341         }
    342     }
    343     state_changed           = true;
    344     playing_note            = true;
    345     tones[active_tones - 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration};
    346 
    347     // TODO: needs to be handled per note/tone -> use its timestamp instead?
    348     voices_timer = timer_read(); // reset to zero, for the effects added by voices.c
    349 
    350     if (audio_driver_stopped) {
    351         audio_driver_start();
    352         audio_driver_stopped = false;
    353     }
    354 }
    355 
    356 void audio_play_tone(float pitch) {
    357     audio_play_note(pitch, 0xffff);
    358 }
    359 
    360 void audio_play_melody(float (*np)[][2], uint16_t n_count, bool n_repeat) {
    361     if (!audio_config.enable) {
    362         audio_stop_all();
    363         return;
    364     }
    365 
    366     if (n_count == 0) {
    367         return;
    368     }
    369 
    370     if (!audio_initialized) {
    371         audio_init();
    372     }
    373 
    374     // Cancel note if a note is playing
    375     if (playing_note) audio_stop_all();
    376 
    377     playing_melody = true;
    378     note_resting   = false;
    379 
    380     notes_pointer = np;
    381     notes_count   = n_count;
    382     notes_repeat  = n_repeat;
    383 
    384     current_note = 0; // note in the melody-array/list at note_pointer
    385 
    386     // start first note manually, which also starts the audio_driver
    387     // all following/remaining notes are played by 'audio_update_state'
    388     audio_play_note((*notes_pointer)[current_note][0], audio_duration_to_ms((*notes_pointer)[current_note][1]));
    389     last_timestamp               = timer_read();
    390     melody_current_note_duration = audio_duration_to_ms((*notes_pointer)[current_note][1]);
    391 }
    392 
    393 void audio_play_click(uint16_t delay, float pitch, uint16_t duration) {
    394     static float click[2][2];
    395 
    396     uint16_t duration_tone  = audio_ms_to_duration(duration);
    397     uint16_t duration_delay = audio_ms_to_duration(delay);
    398 
    399     if (delay <= 0.0f) {
    400         click[0][0] = pitch;
    401         click[0][1] = duration_tone;
    402         click[1][0] = 0.0f;
    403         click[1][1] = 0.0f;
    404         audio_play_melody(&click, 1, false);
    405     } else {
    406         // first note is a rest/pause
    407         click[0][0] = 0.0f;
    408         click[0][1] = duration_delay;
    409         // second note is the actual click
    410         click[1][0] = pitch;
    411         click[1][1] = duration_tone;
    412         audio_play_melody(&click, 2, false);
    413     }
    414 }
    415 
    416 bool audio_is_playing_note(void) {
    417     return playing_note;
    418 }
    419 
    420 bool audio_is_playing_melody(void) {
    421     return playing_melody;
    422 }
    423 
    424 uint8_t audio_get_number_of_active_tones(void) {
    425     return active_tones;
    426 }
    427 
    428 float audio_get_frequency(uint8_t tone_index) {
    429     if (tone_index >= active_tones) {
    430         return 0.0f;
    431     }
    432     return tones[active_tones - tone_index - 1].pitch;
    433 }
    434 
    435 float audio_get_processed_frequency(uint8_t tone_index) {
    436     if (tone_index >= active_tones) {
    437         return 0.0f;
    438     }
    439 
    440     int8_t index = active_tones - tone_index - 1;
    441     // new tones are stacked on top (= appended at the end), so the most recent/current is MAX-1
    442 
    443 #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
    444     index = index - tone_multiplexing_index_shift;
    445     if (index < 0) // wrap around
    446         index += active_tones;
    447 #endif
    448 
    449     if (tones[index].pitch <= 0.0f) {
    450         return 0.0f;
    451     }
    452 
    453     return voice_envelope(tones[index].pitch);
    454 }
    455 
    456 bool audio_update_state(void) {
    457     if (!playing_note && !playing_melody) {
    458         return false;
    459     }
    460 
    461     bool     goto_next_note = false;
    462     uint16_t current_time   = timer_read();
    463 
    464     if (playing_melody) {
    465         goto_next_note = timer_elapsed(last_timestamp) >= melody_current_note_duration;
    466         if (goto_next_note) {
    467             uint16_t delta         = timer_elapsed(last_timestamp) - melody_current_note_duration;
    468             last_timestamp         = current_time;
    469             uint16_t previous_note = current_note;
    470             current_note++;
    471             voices_timer = timer_read(); // reset to zero, for the effects added by voices.c
    472 
    473             if (current_note >= notes_count) {
    474                 if (notes_repeat) {
    475                     current_note = 0;
    476                 } else {
    477                     audio_stop_all();
    478                     return false;
    479                 }
    480             }
    481 
    482             if (!note_resting && (*notes_pointer)[previous_note][0] == (*notes_pointer)[current_note][0]) {
    483                 note_resting = true;
    484 
    485                 // special handling for successive notes of the same frequency:
    486                 // insert a short pause to separate them audibly
    487                 audio_play_note(0.0f, audio_duration_to_ms(2));
    488                 current_note                 = previous_note;
    489                 melody_current_note_duration = audio_duration_to_ms(2);
    490 
    491             } else {
    492                 note_resting = false;
    493 
    494                 // TODO: handle glissando here (or remember previous and current tone)
    495                 /* there would need to be a freq(here we are) -> freq(next note)
    496                  * and do slide/glissando in between problem here is to know which
    497                  * frequency on the stack relates to what other? e.g. a melody starts
    498                  * tones in a sequence, and stops expiring one, so the most recently
    499                  * stopped is the starting point for a glissando to the most recently started?
    500                  * how to detect and preserve this relation?
    501                  * and what about user input, chords, ...?
    502                  */
    503 
    504                 // '- delta': Skip forward in the next note's length if we've over shot
    505                 //            the last, so the overall length of the song is the same
    506                 uint16_t duration = audio_duration_to_ms((*notes_pointer)[current_note][1]);
    507 
    508                 // Skip forward past any completely missed notes
    509                 while (delta > duration && current_note < notes_count - 1) {
    510                     delta -= duration;
    511                     current_note++;
    512                     duration = audio_duration_to_ms((*notes_pointer)[current_note][1]);
    513                 }
    514 
    515                 if (delta < duration) {
    516                     duration -= delta;
    517                 } else {
    518                     // Only way to get here is if it is the last note and
    519                     // we have completely missed it. Play it for 1ms...
    520                     duration = 1;
    521                 }
    522 
    523                 audio_play_note((*notes_pointer)[current_note][0], duration);
    524                 melody_current_note_duration = duration;
    525             }
    526         }
    527     }
    528 
    529     if (playing_note) {
    530 #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
    531         tone_multiplexing_index_shift = (int)(current_time / tone_multiplexing_rate) % MIN(AUDIO_MAX_SIMULTANEOUS_TONES, active_tones);
    532         goto_next_note                = true;
    533 #endif
    534         if (vibrato || glissando) {
    535             // force update on each cycle, since vibrato shifts the frequency slightly
    536             goto_next_note = true;
    537         }
    538 
    539         // housekeeping: stop notes that have no playtime left
    540         for (int i = 0; i < active_tones; i++) {
    541             if ((tones[i].duration != 0xffff) // indefinitely playing notes, started by 'audio_play_tone'
    542                 && (tones[i].duration != 0)   // 'uninitialized'
    543             ) {
    544                 if (timer_elapsed(tones[i].time_started) >= tones[i].duration) {
    545                     audio_stop_tone(tones[i].pitch); // also sets 'state_changed=true'
    546                 }
    547             }
    548         }
    549     }
    550 
    551     // state-changes have a higher priority, always triggering the hardware to update
    552     if (state_changed) {
    553         state_changed = false;
    554         return true;
    555     }
    556 
    557     return goto_next_note;
    558 }
    559 
    560 // Tone-multiplexing functions
    561 #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
    562 void audio_set_tone_multiplexing_rate(uint16_t rate) {
    563     tone_multiplexing_rate = rate;
    564 }
    565 void audio_enable_tone_multiplexing(void) {
    566     tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT;
    567 }
    568 void audio_disable_tone_multiplexing(void) {
    569     tone_multiplexing_rate = 0;
    570 }
    571 void audio_increase_tone_multiplexing_rate(uint16_t change) {
    572     if ((0xffff - change) > tone_multiplexing_rate) {
    573         tone_multiplexing_rate += change;
    574     }
    575 }
    576 void audio_decrease_tone_multiplexing_rate(uint16_t change) {
    577     if (change <= tone_multiplexing_rate) {
    578         tone_multiplexing_rate -= change;
    579     }
    580 }
    581 #endif
    582 
    583 // Tempo functions
    584 
    585 void audio_set_tempo(uint8_t tempo) {
    586     if (tempo < 10) note_tempo = 10;
    587     //  else if (tempo > 250)
    588     //      note_tempo = 250;
    589     else
    590         note_tempo = tempo;
    591 }
    592 
    593 void audio_increase_tempo(uint8_t tempo_change) {
    594     if (tempo_change > 255 - note_tempo)
    595         note_tempo = 255;
    596     else
    597         note_tempo += tempo_change;
    598 }
    599 
    600 void audio_decrease_tempo(uint8_t tempo_change) {
    601     if (tempo_change >= note_tempo - 10)
    602         note_tempo = 10;
    603     else
    604         note_tempo -= tempo_change;
    605 }
    606 
    607 /**
    608  * Converts from units of 1/64ths of a beat to milliseconds.
    609  *
    610  * Round-off error is at most 1 millisecond.
    611  *
    612  * Conversion will never overflow for duration_bpm <= 699, provided that
    613  * note_tempo is at least 10. This is quite a long duration, over ten beats.
    614  *
    615  * Beware that for duration_bpm > 699, the result may overflow uint16_t range
    616  * when duration_bpm is large compared to note_tempo:
    617  *
    618  *    duration_bpm * 60 * 1000 / (64 * note_tempo) > UINT16_MAX
    619  *
    620  *    duration_bpm > (2 * 65535 / 1875) * note_tempo
    621  *                 = 69.904 * note_tempo.
    622  */
    623 uint16_t audio_duration_to_ms(uint16_t duration_bpm) {
    624     return ((uint32_t)duration_bpm * 1875) / ((uint_fast16_t)note_tempo * 2);
    625 }
    626 
    627 /**
    628  * Converts from units of milliseconds to 1/64ths of a beat.
    629  *
    630  * Round-off error is at most 1/64th of a beat.
    631  *
    632  * This conversion never overflows: since duration_ms <= UINT16_MAX = 65535
    633  * and note_tempo <= 255, the result is always in uint16_t range:
    634  *
    635  *     duration_ms * 64 * note_tempo / 60 / 1000
    636  *     <= 65535 * 2 * 255 / 1875
    637  *      = 17825.52
    638  *     <= UINT16_MAX.
    639  */
    640 uint16_t audio_ms_to_duration(uint16_t duration_ms) {
    641     return ((uint32_t)duration_ms * 2 * note_tempo) / 1875;
    642 }
    643 
    644 __attribute__((weak)) void audio_on_user(void) {}
    645 __attribute__((weak)) void audio_off_user(void) {}