aboutsummaryrefslogtreecommitdiff
path: root/quantum/audio/audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'quantum/audio/audio.c')
-rw-r--r--quantum/audio/audio.c539
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
67uint8_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
68musical_tone_t tones[AUDIO_TONE_STACKSIZE]; // stack of currently active tones
69
70bool playing_melody = false; // playing a SONG?
71bool playing_note = false; // or (possibly multiple simultaneous) tones
72bool state_changed = false; // global flag, which is set if anything changes with the active_tones
73
74// melody/SONG related state variables
75float (*notes_pointer)[][2]; // SONG, an array of MUSICAL_NOTEs
76uint16_t notes_count; // length of the notes_pointer array
77bool notes_repeat; // PLAY_SONG or PLAY_LOOP?
78uint16_t melody_current_note_duration = 0; // duration of the currently playing note from the active melody, in ms
79uint8_t note_tempo = TEMPO_DEFAULT; // beats-per-minute
80uint16_t current_note = 0; // index into the array at notes_pointer
81bool note_resting = false; // if a short pause was introduced between two notes with the same frequency while playing a melody
82uint16_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
88uint16_t tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT;
89uint8_t tone_multiplexing_index_shift = 0; // offset used on active-tone array access
90#endif
91
92// provided and used by voices.c
93extern uint8_t note_timbre;
94extern bool glissando;
95extern bool vibrato;
96extern 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
107float startup_song[][2] = STARTUP_SONG;
108float audio_on_song[][2] = AUDIO_ON_SONG;
109float audio_off_song[][2] = AUDIO_OFF_SONG;
110
111static bool audio_initialized = false;
112static bool audio_driver_stopped = true;
113audio_config_t audio_config;
114
115void 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
144void audio_startup(void) {
145 if (audio_config.enable) {
146 PLAY_SONG(startup_song);
147 }
148
149 last_timestamp = timer_read();
150}
151
152void 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
163void 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
170void 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
178bool audio_is_on(void) { return (audio_config.enable != 0); }
179
180void 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
201void 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
242void 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
291void audio_play_tone(float pitch) { audio_play_note(pitch, 0xffff); }
292
293void 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
322float click[2][2];
323void 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
344bool audio_is_playing_note(void) { return playing_note; }
345
346bool audio_is_playing_melody(void) { return playing_melody; }
347
348uint8_t audio_get_number_of_active_tones(void) { return active_tones; }
349
350float 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
357float 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
378bool 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
484void audio_set_tone_multiplexing_rate(uint16_t rate) { tone_multiplexing_rate = rate; }
485void audio_enable_tone_multiplexing(void) { tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT; }
486void audio_disable_tone_multiplexing(void) { tone_multiplexing_rate = 0; }
487void audio_increase_tone_multiplexing_rate(uint16_t change) {
488 if ((0xffff - change) > tone_multiplexing_rate) {
489 tone_multiplexing_rate += change;
490 }
491}
492void 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
501void 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
509void 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
516void 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?
524uint16_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}
533uint16_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}