diff options
Diffstat (limited to 'quantum/audio/audio.c')
| -rw-r--r-- | quantum/audio/audio.c | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/quantum/audio/audio.c b/quantum/audio/audio.c new file mode 100644 index 000000000..46277dd70 --- /dev/null +++ b/quantum/audio/audio.c | |||
| @@ -0,0 +1,539 @@ | |||
| 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 "wait.h" | ||
| 21 | |||
| 22 | /* audio system: | ||
| 23 | * | ||
| 24 | * audio.[ch] takes care of all overall state, tracking the actively playing | ||
| 25 | * notes/tones; the notes a SONG consists of; | ||
| 26 | * ... | ||
| 27 | * = everything audio-related that is platform agnostic | ||
| 28 | * | ||
| 29 | * driver_[avr|chibios]_[dac|pwm] take care of the lower hardware dependent parts, | ||
| 30 | * specific to each platform and the used subsystem/driver to drive | ||
| 31 | * the output pins/channels with the calculated frequencies for each | ||
| 32 | * active tone | ||
| 33 | * as part of this, the driver has to trigger regular state updates by | ||
| 34 | * calling 'audio_update_state' through some sort of timer - be it a | ||
| 35 | * dedicated one or piggybacking on for example the timer used to | ||
| 36 | * generate a pwm signal/clock. | ||
| 37 | * | ||
| 38 | * | ||
| 39 | * A Note on terminology: | ||
| 40 | * tone, pitch and frequency are used somewhat interchangeably, in a strict Wikipedia-sense: | ||
| 41 | * "(Musical) tone, a sound characterized by its duration, pitch (=frequency), | ||
| 42 | * intensity (=volume), and timbre" | ||
| 43 | * - intensity/volume is currently not handled at all, although the 'dac_additive' driver could do so | ||
| 44 | * - timbre is handled globally (TODO: only used with the pwm drivers at the moment) | ||
| 45 | * | ||
| 46 | * in musical_note.h a 'note' is the combination of a pitch and a duration | ||
| 47 | * these are used to create SONG arrays; during playback their frequencies | ||
| 48 | * are handled as single successive tones, while the durations are | ||
| 49 | * kept track of in 'audio_update_state' | ||
| 50 | * | ||
| 51 | * 'voice' as it is used here, equates to a sort of instrument with its own | ||
| 52 | * characteristics sound and effects | ||
| 53 | * the audio system as-is deals only with (possibly multiple) tones of one | ||
| 54 | * instrument/voice at a time (think: chords). since the number of tones that | ||
| 55 | * can be reproduced depends on the hardware/driver in use: pwm can only | ||
| 56 | * reproduce one tone per output/speaker; DACs can reproduce/mix multiple | ||
| 57 | * when doing additive synthesis. | ||
| 58 | * | ||
| 59 | * 'duration' can either be in the beats-per-minute related unit found in | ||
| 60 | * musical_notes.h, OR in ms; keyboards create SONGs with the former, while | ||
| 61 | * the internal state of the audio system does its calculations with the later - ms | ||
| 62 | */ | ||
| 63 | |||
| 64 | #ifndef AUDIO_TONE_STACKSIZE | ||
| 65 | # define AUDIO_TONE_STACKSIZE 8 | ||
| 66 | #endif | ||
| 67 | 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 | ||
| 68 | musical_tone_t tones[AUDIO_TONE_STACKSIZE]; // stack of currently active tones | ||
| 69 | |||
| 70 | bool playing_melody = false; // playing a SONG? | ||
| 71 | bool playing_note = false; // or (possibly multiple simultaneous) tones | ||
| 72 | bool state_changed = false; // global flag, which is set if anything changes with the active_tones | ||
| 73 | |||
| 74 | // melody/SONG related state variables | ||
| 75 | float (*notes_pointer)[][2]; // SONG, an array of MUSICAL_NOTEs | ||
| 76 | uint16_t notes_count; // length of the notes_pointer array | ||
| 77 | bool notes_repeat; // PLAY_SONG or PLAY_LOOP? | ||
| 78 | uint16_t melody_current_note_duration = 0; // duration of the currently playing note from the active melody, in ms | ||
| 79 | uint8_t note_tempo = TEMPO_DEFAULT; // beats-per-minute | ||
| 80 | uint16_t current_note = 0; // index into the array at notes_pointer | ||
| 81 | bool note_resting = false; // if a short pause was introduced between two notes with the same frequency while playing a melody | ||
| 82 | uint16_t last_timestamp = 0; | ||
| 83 | |||
| 84 | #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING | ||
| 85 | # ifndef AUDIO_MAX_SIMULTANEOUS_TONES | ||
| 86 | # define AUDIO_MAX_SIMULTANEOUS_TONES 3 | ||
| 87 | # endif | ||
| 88 | uint16_t tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT; | ||
| 89 | uint8_t tone_multiplexing_index_shift = 0; // offset used on active-tone array access | ||
| 90 | #endif | ||
| 91 | |||
| 92 | // provided and used by voices.c | ||
| 93 | extern uint8_t note_timbre; | ||
| 94 | extern bool glissando; | ||
| 95 | extern bool vibrato; | ||
| 96 | extern uint16_t voices_timer; | ||
| 97 | |||
| 98 | #ifndef STARTUP_SONG | ||
| 99 | # define STARTUP_SONG SONG(STARTUP_SOUND) | ||
| 100 | #endif | ||
| 101 | #ifndef AUDIO_ON_SONG | ||
| 102 | # define AUDIO_ON_SONG SONG(AUDIO_ON_SOUND) | ||
| 103 | #endif | ||
| 104 | #ifndef AUDIO_OFF_SONG | ||
| 105 | # define AUDIO_OFF_SONG SONG(AUDIO_OFF_SOUND) | ||
| 106 | #endif | ||
| 107 | float startup_song[][2] = STARTUP_SONG; | ||
| 108 | float audio_on_song[][2] = AUDIO_ON_SONG; | ||
| 109 | float audio_off_song[][2] = AUDIO_OFF_SONG; | ||
| 110 | |||
| 111 | static bool audio_initialized = false; | ||
| 112 | static bool audio_driver_stopped = true; | ||
| 113 | audio_config_t audio_config; | ||
| 114 | |||
| 115 | void audio_init() { | ||
| 116 | if (audio_initialized) { | ||
| 117 | return; | ||
| 118 | } | ||
| 119 | |||
| 120 | // Check EEPROM | ||
| 121 | #ifdef EEPROM_ENABLE | ||
| 122 | if (!eeconfig_is_enabled()) { | ||
| 123 | eeconfig_init(); | ||
| 124 | } | ||
| 125 | audio_config.raw = eeconfig_read_audio(); | ||
| 126 | #else // EEPROM settings | ||
| 127 | audio_config.enable = true; | ||
| 128 | # ifdef AUDIO_CLICKY_ON | ||
| 129 | audio_config.clicky_enable = true; | ||
| 130 | # endif | ||
| 131 | #endif // EEPROM settings | ||
| 132 | |||
| 133 | for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) { | ||
| 134 | tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; | ||
| 135 | } | ||
| 136 | |||
| 137 | if (!audio_initialized) { | ||
| 138 | audio_driver_initialize(); | ||
| 139 | audio_initialized = true; | ||
| 140 | } | ||
| 141 | stop_all_notes(); | ||
| 142 | } | ||
| 143 | |||
| 144 | void audio_startup(void) { | ||
| 145 | if (audio_config.enable) { | ||
| 146 | PLAY_SONG(startup_song); | ||
| 147 | } | ||
| 148 | |||
| 149 | last_timestamp = timer_read(); | ||
| 150 | } | ||
| 151 | |||
| 152 | void audio_toggle(void) { | ||
| 153 | if (audio_config.enable) { | ||
| 154 | stop_all_notes(); | ||
| 155 | } | ||
| 156 | audio_config.enable ^= 1; | ||
| 157 | eeconfig_update_audio(audio_config.raw); | ||
| 158 | if (audio_config.enable) { | ||
| 159 | audio_on_user(); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | void audio_on(void) { | ||
| 164 | audio_config.enable = 1; | ||
| 165 | eeconfig_update_audio(audio_config.raw); | ||
| 166 | audio_on_user(); | ||
| 167 | PLAY_SONG(audio_on_song); | ||
| 168 | } | ||
| 169 | |||
| 170 | void audio_off(void) { | ||
| 171 | PLAY_SONG(audio_off_song); | ||
| 172 | wait_ms(100); | ||
| 173 | audio_stop_all(); | ||
| 174 | audio_config.enable = 0; | ||
| 175 | eeconfig_update_audio(audio_config.raw); | ||
| 176 | } | ||
| 177 | |||
| 178 | bool audio_is_on(void) { return (audio_config.enable != 0); } | ||
| 179 | |||
| 180 | void audio_stop_all() { | ||
| 181 | if (audio_driver_stopped) { | ||
| 182 | return; | ||
| 183 | } | ||
| 184 | |||
| 185 | active_tones = 0; | ||
| 186 | |||
| 187 | audio_driver_stop(); | ||
| 188 | |||
| 189 | playing_melody = false; | ||
| 190 | playing_note = false; | ||
| 191 | |||
| 192 | melody_current_note_duration = 0; | ||
| 193 | |||
| 194 | for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) { | ||
| 195 | tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; | ||
| 196 | } | ||
| 197 | |||
| 198 | audio_driver_stopped = true; | ||
| 199 | } | ||
| 200 | |||
| 201 | void audio_stop_tone(float pitch) { | ||
| 202 | if (pitch < 0.0f) { | ||
| 203 | pitch = -1 * pitch; | ||
| 204 | } | ||
| 205 | |||
| 206 | if (playing_note) { | ||
| 207 | if (!audio_initialized) { | ||
| 208 | audio_init(); | ||
| 209 | } | ||
| 210 | bool found = false; | ||
| 211 | for (int i = AUDIO_TONE_STACKSIZE - 1; i >= 0; i--) { | ||
| 212 | found = (tones[i].pitch == pitch); | ||
| 213 | if (found) { | ||
| 214 | tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; | ||
| 215 | for (int j = i; (j < AUDIO_TONE_STACKSIZE - 1); j++) { | ||
| 216 | tones[j] = tones[j + 1]; | ||
| 217 | tones[j + 1] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; | ||
| 218 | } | ||
| 219 | break; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | if (!found) { | ||
| 223 | return; | ||
| 224 | } | ||
| 225 | |||
| 226 | state_changed = true; | ||
| 227 | active_tones--; | ||
| 228 | if (active_tones < 0) active_tones = 0; | ||
| 229 | #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING | ||
| 230 | if (tone_multiplexing_index_shift >= active_tones) { | ||
| 231 | tone_multiplexing_index_shift = 0; | ||
| 232 | } | ||
| 233 | #endif | ||
| 234 | if (active_tones == 0) { | ||
| 235 | audio_driver_stop(); | ||
| 236 | audio_driver_stopped = true; | ||
| 237 | playing_note = false; | ||
| 238 | } | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | void audio_play_note(float pitch, uint16_t duration) { | ||
| 243 | if (!audio_config.enable) { | ||
| 244 | return; | ||
| 245 | } | ||
| 246 | |||
| 247 | if (!audio_initialized) { | ||
| 248 | audio_init(); | ||
| 249 | } | ||
| 250 | |||
| 251 | if (pitch < 0.0f) { | ||
| 252 | pitch = -1 * pitch; | ||
| 253 | } | ||
| 254 | |||
| 255 | // round-robin: shifting out old tones, keeping only unique ones | ||
| 256 | // if the new frequency is already amongst the active tones, shift it to the top of the stack | ||
| 257 | bool found = false; | ||
| 258 | for (int i = active_tones - 1; i >= 0; i--) { | ||
| 259 | found = (tones[i].pitch == pitch); | ||
| 260 | if (found) { | ||
| 261 | for (int j = i; (j < active_tones - 1); j++) { | ||
| 262 | tones[j] = tones[j + 1]; | ||
| 263 | tones[j + 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration}; | ||
| 264 | } | ||
| 265 | return; // since this frequency played already, the hardware was already started | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | // frequency/tone is actually new, so we put it on the top of the stack | ||
| 270 | active_tones++; | ||
| 271 | if (active_tones > AUDIO_TONE_STACKSIZE) { | ||
| 272 | active_tones = AUDIO_TONE_STACKSIZE; | ||
| 273 | // shift out the oldest tone to make room | ||
| 274 | for (int i = 0; i < active_tones - 1; i++) { | ||
| 275 | tones[i] = tones[i + 1]; | ||
| 276 | } | ||
| 277 | } | ||
| 278 | state_changed = true; | ||
| 279 | playing_note = true; | ||
| 280 | tones[active_tones - 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration}; | ||
| 281 | |||
| 282 | // TODO: needs to be handled per note/tone -> use its timestamp instead? | ||
| 283 | voices_timer = timer_read(); // reset to zero, for the effects added by voices.c | ||
| 284 | |||
| 285 | if (audio_driver_stopped) { | ||
| 286 | audio_driver_start(); | ||
| 287 | audio_driver_stopped = false; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | void audio_play_tone(float pitch) { audio_play_note(pitch, 0xffff); } | ||
| 292 | |||
| 293 | void audio_play_melody(float (*np)[][2], uint16_t n_count, bool n_repeat) { | ||
| 294 | if (!audio_config.enable) { | ||
| 295 | audio_stop_all(); | ||
| 296 | return; | ||
| 297 | } | ||
| 298 | |||
| 299 | if (!audio_initialized) { | ||
| 300 | audio_init(); | ||
| 301 | } | ||
| 302 | |||
| 303 | // Cancel note if a note is playing | ||
| 304 | if (playing_note) audio_stop_all(); | ||
| 305 | |||
| 306 | playing_melody = true; | ||
| 307 | note_resting = false; | ||
| 308 | |||
| 309 | notes_pointer = np; | ||
| 310 | notes_count = n_count; | ||
| 311 | notes_repeat = n_repeat; | ||
| 312 | |||
| 313 | current_note = 0; // note in the melody-array/list at note_pointer | ||
| 314 | |||
| 315 | // start first note manually, which also starts the audio_driver | ||
| 316 | // all following/remaining notes are played by 'audio_update_state' | ||
| 317 | audio_play_note((*notes_pointer)[current_note][0], audio_duration_to_ms((*notes_pointer)[current_note][1])); | ||
| 318 | last_timestamp = timer_read(); | ||
| 319 | melody_current_note_duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); | ||
| 320 | } | ||
| 321 | |||
| 322 | float click[2][2]; | ||
| 323 | void audio_play_click(uint16_t delay, float pitch, uint16_t duration) { | ||
| 324 | uint16_t duration_tone = audio_ms_to_duration(duration); | ||
| 325 | uint16_t duration_delay = audio_ms_to_duration(delay); | ||
| 326 | |||
| 327 | if (delay <= 0.0f) { | ||
| 328 | click[0][0] = pitch; | ||
| 329 | click[0][1] = duration_tone; | ||
| 330 | click[1][0] = 0.0f; | ||
| 331 | click[1][1] = 0.0f; | ||
| 332 | audio_play_melody(&click, 1, false); | ||
| 333 | } else { | ||
| 334 | // first note is a rest/pause | ||
| 335 | click[0][0] = 0.0f; | ||
| 336 | click[0][1] = duration_delay; | ||
| 337 | // second note is the actual click | ||
| 338 | click[1][0] = pitch; | ||
| 339 | click[1][1] = duration_tone; | ||
| 340 | audio_play_melody(&click, 2, false); | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | bool audio_is_playing_note(void) { return playing_note; } | ||
| 345 | |||
| 346 | bool audio_is_playing_melody(void) { return playing_melody; } | ||
| 347 | |||
| 348 | uint8_t audio_get_number_of_active_tones(void) { return active_tones; } | ||
| 349 | |||
| 350 | float audio_get_frequency(uint8_t tone_index) { | ||
| 351 | if (tone_index >= active_tones) { | ||
| 352 | return 0.0f; | ||
| 353 | } | ||
| 354 | return tones[active_tones - tone_index - 1].pitch; | ||
| 355 | } | ||
| 356 | |||
| 357 | float audio_get_processed_frequency(uint8_t tone_index) { | ||
| 358 | if (tone_index >= active_tones) { | ||
| 359 | return 0.0f; | ||
| 360 | } | ||
| 361 | |||
| 362 | int8_t index = active_tones - tone_index - 1; | ||
| 363 | // new tones are stacked on top (= appended at the end), so the most recent/current is MAX-1 | ||
| 364 | |||
| 365 | #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING | ||
| 366 | index = index - tone_multiplexing_index_shift; | ||
| 367 | if (index < 0) // wrap around | ||
| 368 | index += active_tones; | ||
| 369 | #endif | ||
| 370 | |||
| 371 | if (tones[index].pitch <= 0.0f) { | ||
| 372 | return 0.0f; | ||
| 373 | } | ||
| 374 | |||
| 375 | return voice_envelope(tones[index].pitch); | ||
| 376 | } | ||
| 377 | |||
| 378 | bool audio_update_state(void) { | ||
| 379 | if (!playing_note && !playing_melody) { | ||
| 380 | return false; | ||
| 381 | } | ||
| 382 | |||
| 383 | bool goto_next_note = false; | ||
| 384 | uint16_t current_time = timer_read(); | ||
| 385 | |||
| 386 | if (playing_melody) { | ||
| 387 | goto_next_note = timer_elapsed(last_timestamp) >= melody_current_note_duration; | ||
| 388 | if (goto_next_note) { | ||
| 389 | uint16_t delta = timer_elapsed(last_timestamp) - melody_current_note_duration; | ||
| 390 | last_timestamp = current_time; | ||
| 391 | uint16_t previous_note = current_note; | ||
| 392 | current_note++; | ||
| 393 | voices_timer = timer_read(); // reset to zero, for the effects added by voices.c | ||
| 394 | |||
| 395 | if (current_note >= notes_count) { | ||
| 396 | if (notes_repeat) { | ||
| 397 | current_note = 0; | ||
| 398 | } else { | ||
| 399 | audio_stop_all(); | ||
| 400 | return false; | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | if (!note_resting && (*notes_pointer)[previous_note][0] == (*notes_pointer)[current_note][0]) { | ||
| 405 | note_resting = true; | ||
| 406 | |||
| 407 | // special handling for successive notes of the same frequency: | ||
| 408 | // insert a short pause to separate them audibly | ||
| 409 | audio_play_note(0.0f, audio_duration_to_ms(2)); | ||
| 410 | current_note = previous_note; | ||
| 411 | melody_current_note_duration = audio_duration_to_ms(2); | ||
| 412 | |||
| 413 | } else { | ||
| 414 | note_resting = false; | ||
| 415 | |||
| 416 | // TODO: handle glissando here (or remember previous and current tone) | ||
| 417 | /* there would need to be a freq(here we are) -> freq(next note) | ||
| 418 | * and do slide/glissando in between problem here is to know which | ||
| 419 | * frequency on the stack relates to what other? e.g. a melody starts | ||
| 420 | * tones in a sequence, and stops expiring one, so the most recently | ||
| 421 | * stopped is the starting point for a glissando to the most recently started? | ||
| 422 | * how to detect and preserve this relation? | ||
| 423 | * and what about user input, chords, ...? | ||
| 424 | */ | ||
| 425 | |||
| 426 | // '- delta': Skip forward in the next note's length if we've over shot | ||
| 427 | // the last, so the overall length of the song is the same | ||
| 428 | uint16_t duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); | ||
| 429 | |||
| 430 | // Skip forward past any completely missed notes | ||
| 431 | while (delta > duration && current_note < notes_count - 1) { | ||
| 432 | delta -= duration; | ||
| 433 | current_note++; | ||
| 434 | duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); | ||
| 435 | } | ||
| 436 | |||
| 437 | if (delta < duration) { | ||
| 438 | duration -= delta; | ||
| 439 | } else { | ||
| 440 | // Only way to get here is if it is the last note and | ||
| 441 | // we have completely missed it. Play it for 1ms... | ||
| 442 | duration = 1; | ||
| 443 | } | ||
| 444 | |||
| 445 | audio_play_note((*notes_pointer)[current_note][0], duration); | ||
| 446 | melody_current_note_duration = duration; | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | if (playing_note) { | ||
| 452 | #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING | ||
| 453 | tone_multiplexing_index_shift = (int)(current_time / tone_multiplexing_rate) % MIN(AUDIO_MAX_SIMULTANEOUS_TONES, active_tones); | ||
| 454 | goto_next_note = true; | ||
| 455 | #endif | ||
| 456 | if (vibrato || glissando) { | ||
| 457 | // force update on each cycle, since vibrato shifts the frequency slightly | ||
| 458 | goto_next_note = true; | ||
| 459 | } | ||
| 460 | |||
| 461 | // housekeeping: stop notes that have no playtime left | ||
| 462 | for (int i = 0; i < active_tones; i++) { | ||
| 463 | if ((tones[i].duration != 0xffff) // indefinitely playing notes, started by 'audio_play_tone' | ||
| 464 | && (tones[i].duration != 0) // 'uninitialized' | ||
| 465 | ) { | ||
| 466 | if (timer_elapsed(tones[i].time_started) >= tones[i].duration) { | ||
| 467 | audio_stop_tone(tones[i].pitch); // also sets 'state_changed=true' | ||
| 468 | } | ||
| 469 | } | ||
| 470 | } | ||
| 471 | } | ||
| 472 | |||
| 473 | // state-changes have a higher priority, always triggering the hardware to update | ||
| 474 | if (state_changed) { | ||
| 475 | state_changed = false; | ||
| 476 | return true; | ||
| 477 | } | ||
| 478 | |||
| 479 | return goto_next_note; | ||
| 480 | } | ||
| 481 | |||
| 482 | // Tone-multiplexing functions | ||
| 483 | #ifdef AUDIO_ENABLE_TONE_MULTIPLEXING | ||
| 484 | void audio_set_tone_multiplexing_rate(uint16_t rate) { tone_multiplexing_rate = rate; } | ||
| 485 | void audio_enable_tone_multiplexing(void) { tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT; } | ||
| 486 | void audio_disable_tone_multiplexing(void) { tone_multiplexing_rate = 0; } | ||
| 487 | void audio_increase_tone_multiplexing_rate(uint16_t change) { | ||
| 488 | if ((0xffff - change) > tone_multiplexing_rate) { | ||
| 489 | tone_multiplexing_rate += change; | ||
| 490 | } | ||
| 491 | } | ||
| 492 | void audio_decrease_tone_multiplexing_rate(uint16_t change) { | ||
| 493 | if (change <= tone_multiplexing_rate) { | ||
| 494 | tone_multiplexing_rate -= change; | ||
| 495 | } | ||
| 496 | } | ||
| 497 | #endif | ||
| 498 | |||
| 499 | // Tempo functions | ||
| 500 | |||
| 501 | void audio_set_tempo(uint8_t tempo) { | ||
| 502 | if (tempo < 10) note_tempo = 10; | ||
| 503 | // else if (tempo > 250) | ||
| 504 | // note_tempo = 250; | ||
| 505 | else | ||
| 506 | note_tempo = tempo; | ||
| 507 | } | ||
| 508 | |||
| 509 | void audio_increase_tempo(uint8_t tempo_change) { | ||
| 510 | if (tempo_change > 255 - note_tempo) | ||
| 511 | note_tempo = 255; | ||
| 512 | else | ||
| 513 | note_tempo += tempo_change; | ||
| 514 | } | ||
| 515 | |||
| 516 | void audio_decrease_tempo(uint8_t tempo_change) { | ||
| 517 | if (tempo_change >= note_tempo - 10) | ||
| 518 | note_tempo = 10; | ||
| 519 | else | ||
| 520 | note_tempo -= tempo_change; | ||
| 521 | } | ||
| 522 | |||
| 523 | // TODO in the int-math version are some bugs; songs sometimes abruptly end - maybe an issue with the timer/system-tick wrapping around? | ||
| 524 | uint16_t audio_duration_to_ms(uint16_t duration_bpm) { | ||
| 525 | #if defined(__AVR__) | ||
| 526 | // doing int-math saves us some bytes in the overall firmware size, but the intermediate result is less accurate before being cast to/returned as uint | ||
| 527 | return ((uint32_t)duration_bpm * 60 * 1000) / (64 * note_tempo); | ||
| 528 | // NOTE: beware of uint16_t overflows when note_tempo is low and/or the duration is long | ||
| 529 | #else | ||
| 530 | return ((float)duration_bpm * 60) / (64 * note_tempo) * 1000; | ||
| 531 | #endif | ||
| 532 | } | ||
| 533 | uint16_t audio_ms_to_duration(uint16_t duration_ms) { | ||
| 534 | #if defined(__AVR__) | ||
| 535 | return ((uint32_t)duration_ms * 64 * note_tempo) / 60 / 1000; | ||
| 536 | #else | ||
| 537 | return ((float)duration_ms * 64 * note_tempo) / 60 / 1000; | ||
| 538 | #endif | ||
| 539 | } | ||
