qmk_firmware

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

audio_dac_additive.c (20130B)


      1 /* Copyright 2016-2019 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 
     18 #include "audio.h"
     19 #include "gpio.h"
     20 #include <math.h>
     21 #include "util.h"
     22 
     23 // Need to disable GCC's "tautological-compare" warning for this file, as it causes issues when running `KEEP_INTERMEDIATES=yes`. Corresponding pop at the end of the file.
     24 #pragma GCC diagnostic push
     25 #pragma GCC diagnostic ignored "-Wtautological-compare"
     26 
     27 /*
     28   Audio Driver: DAC
     29 
     30   which utilizes the dac unit many STM32 are equipped with, to output a modulated waveform from samples stored in the dac_buffer_* array who are passed to the hardware through DMA
     31 
     32   it is also possible to have a custom sample-LUT by implementing/overriding 'dac_value_generate'
     33 
     34   this driver allows for multiple simultaneous tones to be played through one single channel by doing additive wave-synthesis
     35 */
     36 
     37 #if !defined(AUDIO_PIN)
     38 #    error "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under 'ARM (DAC additive)' for available options."
     39 #endif
     40 #if defined(AUDIO_PIN_ALT) && !defined(AUDIO_PIN_ALT_AS_NEGATIVE)
     41 #    pragma message "Audio feature: AUDIO_PIN_ALT set, but not AUDIO_PIN_ALT_AS_NEGATIVE - pin will be left unused; audio might still work though."
     42 #endif
     43 
     44 #if !defined(AUDIO_PIN_ALT)
     45 // no ALT pin defined is valid, but the c-ifs below need some value set
     46 #    define AUDIO_PIN_ALT PAL_NOLINE
     47 #endif
     48 
     49 #if !defined(AUDIO_DAC_SAMPLE_WAVEFORM_SINE) && !defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE) && !defined(AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE) && !defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID)
     50 #    define AUDIO_DAC_SAMPLE_WAVEFORM_SINE
     51 #endif
     52 
     53 #ifdef AUDIO_DAC_SAMPLE_WAVEFORM_SINE
     54 /* one full sine wave over [0,2*pi], but shifted up one amplitude and left pi/4; for the samples to start at 0
     55  */
     56 static const dacsample_t dac_buffer_sine[] = {
     57     // 256 values, max 4095
     58     0x0,   0x1,   0x2,   0x6,   0xa,   0xf,   0x16,  0x1e,  0x27,  0x32,  0x3d,  0x4a,  0x58,  0x67,  0x78,  0x89,  0x9c,  0xb0,  0xc5,  0xdb,  0xf2,  0x10a, 0x123, 0x13e, 0x159, 0x175, 0x193, 0x1b1, 0x1d1, 0x1f1, 0x212, 0x235, 0x258, 0x27c, 0x2a0, 0x2c6, 0x2ed, 0x314, 0x33c, 0x365, 0x38e, 0x3b8, 0x3e3, 0x40e, 0x43a, 0x467, 0x494, 0x4c2, 0x4f0, 0x51f, 0x54e, 0x57d, 0x5ad, 0x5dd, 0x60e, 0x63f, 0x670, 0x6a1, 0x6d3, 0x705, 0x737, 0x769, 0x79b, 0x7cd, 0x800, 0x832, 0x864, 0x896, 0x8c8, 0x8fa, 0x92c, 0x95e, 0x98f, 0x9c0, 0x9f1, 0xa22, 0xa52, 0xa82, 0xab1, 0xae0, 0xb0f, 0xb3d, 0xb6b, 0xb98, 0xbc5, 0xbf1, 0xc1c, 0xc47, 0xc71, 0xc9a, 0xcc3, 0xceb, 0xd12, 0xd39, 0xd5f, 0xd83, 0xda7, 0xdca, 0xded, 0xe0e, 0xe2e, 0xe4e, 0xe6c, 0xe8a, 0xea6, 0xec1, 0xedc, 0xef5, 0xf0d, 0xf24, 0xf3a, 0xf4f, 0xf63, 0xf76, 0xf87, 0xf98, 0xfa7, 0xfb5, 0xfc2, 0xfcd, 0xfd8, 0xfe1, 0xfe9, 0xff0, 0xff5, 0xff9, 0xffd, 0xffe,
     59     0xfff, 0xffe, 0xffd, 0xff9, 0xff5, 0xff0, 0xfe9, 0xfe1, 0xfd8, 0xfcd, 0xfc2, 0xfb5, 0xfa7, 0xf98, 0xf87, 0xf76, 0xf63, 0xf4f, 0xf3a, 0xf24, 0xf0d, 0xef5, 0xedc, 0xec1, 0xea6, 0xe8a, 0xe6c, 0xe4e, 0xe2e, 0xe0e, 0xded, 0xdca, 0xda7, 0xd83, 0xd5f, 0xd39, 0xd12, 0xceb, 0xcc3, 0xc9a, 0xc71, 0xc47, 0xc1c, 0xbf1, 0xbc5, 0xb98, 0xb6b, 0xb3d, 0xb0f, 0xae0, 0xab1, 0xa82, 0xa52, 0xa22, 0x9f1, 0x9c0, 0x98f, 0x95e, 0x92c, 0x8fa, 0x8c8, 0x896, 0x864, 0x832, 0x800, 0x7cd, 0x79b, 0x769, 0x737, 0x705, 0x6d3, 0x6a1, 0x670, 0x63f, 0x60e, 0x5dd, 0x5ad, 0x57d, 0x54e, 0x51f, 0x4f0, 0x4c2, 0x494, 0x467, 0x43a, 0x40e, 0x3e3, 0x3b8, 0x38e, 0x365, 0x33c, 0x314, 0x2ed, 0x2c6, 0x2a0, 0x27c, 0x258, 0x235, 0x212, 0x1f1, 0x1d1, 0x1b1, 0x193, 0x175, 0x159, 0x13e, 0x123, 0x10a, 0xf2,  0xdb,  0xc5,  0xb0,  0x9c,  0x89,  0x78,  0x67,  0x58,  0x4a,  0x3d,  0x32,  0x27,  0x1e,  0x16,  0xf,   0xa,   0x6,   0x2,   0x1,
     60 };
     61 #endif // AUDIO_DAC_SAMPLE_WAVEFORM_SINE
     62 #ifdef AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE
     63 static const dacsample_t dac_buffer_triangle[] = {
     64     // 256 values, max 4095
     65     0x0,   0x20,  0x40,  0x60,  0x80,  0xa0,  0xc0,  0xe0,  0x100, 0x120, 0x140, 0x160, 0x180, 0x1a0, 0x1c0, 0x1e0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0x400, 0x420, 0x440, 0x460, 0x480, 0x4a0, 0x4c0, 0x4e0, 0x500, 0x520, 0x540, 0x560, 0x580, 0x5a0, 0x5c0, 0x5e0, 0x600, 0x620, 0x640, 0x660, 0x680, 0x6a0, 0x6c0, 0x6e0, 0x700, 0x720, 0x740, 0x760, 0x780, 0x7a0, 0x7c0, 0x7e0, 0x800, 0x81f, 0x83f, 0x85f, 0x87f, 0x89f, 0x8bf, 0x8df, 0x8ff, 0x91f, 0x93f, 0x95f, 0x97f, 0x99f, 0x9bf, 0x9df, 0x9ff, 0xa1f, 0xa3f, 0xa5f, 0xa7f, 0xa9f, 0xabf, 0xadf, 0xaff, 0xb1f, 0xb3f, 0xb5f, 0xb7f, 0xb9f, 0xbbf, 0xbdf, 0xbff, 0xc1f, 0xc3f, 0xc5f, 0xc7f, 0xc9f, 0xcbf, 0xcdf, 0xcff, 0xd1f, 0xd3f, 0xd5f, 0xd7f, 0xd9f, 0xdbf, 0xddf, 0xdff, 0xe1f, 0xe3f, 0xe5f, 0xe7f, 0xe9f, 0xebf, 0xedf, 0xeff, 0xf1f, 0xf3f, 0xf5f, 0xf7f, 0xf9f, 0xfbf, 0xfdf,
     66     0xfff, 0xfdf, 0xfbf, 0xf9f, 0xf7f, 0xf5f, 0xf3f, 0xf1f, 0xeff, 0xedf, 0xebf, 0xe9f, 0xe7f, 0xe5f, 0xe3f, 0xe1f, 0xdff, 0xddf, 0xdbf, 0xd9f, 0xd7f, 0xd5f, 0xd3f, 0xd1f, 0xcff, 0xcdf, 0xcbf, 0xc9f, 0xc7f, 0xc5f, 0xc3f, 0xc1f, 0xbff, 0xbdf, 0xbbf, 0xb9f, 0xb7f, 0xb5f, 0xb3f, 0xb1f, 0xaff, 0xadf, 0xabf, 0xa9f, 0xa7f, 0xa5f, 0xa3f, 0xa1f, 0x9ff, 0x9df, 0x9bf, 0x99f, 0x97f, 0x95f, 0x93f, 0x91f, 0x8ff, 0x8df, 0x8bf, 0x89f, 0x87f, 0x85f, 0x83f, 0x81f, 0x800, 0x7e0, 0x7c0, 0x7a0, 0x780, 0x760, 0x740, 0x720, 0x700, 0x6e0, 0x6c0, 0x6a0, 0x680, 0x660, 0x640, 0x620, 0x600, 0x5e0, 0x5c0, 0x5a0, 0x580, 0x560, 0x540, 0x520, 0x500, 0x4e0, 0x4c0, 0x4a0, 0x480, 0x460, 0x440, 0x420, 0x400, 0x3e0, 0x3c0, 0x3a0, 0x380, 0x360, 0x340, 0x320, 0x300, 0x2e0, 0x2c0, 0x2a0, 0x280, 0x260, 0x240, 0x220, 0x200, 0x1e0, 0x1c0, 0x1a0, 0x180, 0x160, 0x140, 0x120, 0x100, 0xe0,  0xc0,  0xa0,  0x80,  0x60,  0x40,  0x20,
     67 };
     68 #endif // AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE
     69 #ifdef AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE
     70 static const dacsample_t dac_buffer_square[] = {
     71     AUDIO_DAC_OFF_VALUE,  // first and
     72     AUDIO_DAC_SAMPLE_MAX, // second steps
     73 };
     74 #endif // AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE
     75 /*
     76 // four steps: 0, 1/3, 2/3 and 1
     77 static const dacsample_t dac_buffer_staircase[] = {
     78     0,
     79     AUDIO_DAC_SAMPLE_MAX / 3,
     80     2 * AUDIO_DAC_SAMPLE_MAX / 3,
     81     AUDIO_DAC_SAMPLE_MAX,
     82 }
     83 */
     84 #ifdef AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID
     85 static const dacsample_t dac_buffer_trapezoid[] = {
     86     0x0,   0x1f,  0x7f,  0xdf,  0x13f, 0x19f, 0x1ff, 0x25f, 0x2bf, 0x31f, 0x37f, 0x3df, 0x43f, 0x49f, 0x4ff, 0x55f, 0x5bf, 0x61f, 0x67f, 0x6df, 0x73f, 0x79f, 0x7ff, 0x85f, 0x8bf, 0x91f, 0x97f, 0x9df, 0xa3f, 0xa9f, 0xaff, 0xb5f, 0xbbf, 0xc1f, 0xc7f, 0xcdf, 0xd3f, 0xd9f, 0xdff, 0xe5f, 0xebf, 0xf1f, 0xf7f, 0xfdf, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff,
     87     0xfff, 0xfdf, 0xf7f, 0xf1f, 0xebf, 0xe5f, 0xdff, 0xd9f, 0xd3f, 0xcdf, 0xc7f, 0xc1f, 0xbbf, 0xb5f, 0xaff, 0xa9f, 0xa3f, 0x9df, 0x97f, 0x91f, 0x8bf, 0x85f, 0x7ff, 0x79f, 0x73f, 0x6df, 0x67f, 0x61f, 0x5bf, 0x55f, 0x4ff, 0x49f, 0x43f, 0x3df, 0x37f, 0x31f, 0x2bf, 0x25f, 0x1ff, 0x19f, 0x13f, 0xdf,  0x7f,  0x1f,  0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,   0x0,
     88 };
     89 #endif // AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID
     90 
     91 static dacsample_t dac_buffer[AUDIO_DAC_BUFFER_SIZE];
     92 
     93 /* keep track of the sample position for for each frequency */
     94 static float dac_if[AUDIO_MAX_SIMULTANEOUS_TONES] = {0.0};
     95 
     96 static float   active_tones_snapshot[AUDIO_MAX_SIMULTANEOUS_TONES] = {0};
     97 static uint8_t active_tones_snapshot_length                        = 0;
     98 
     99 typedef enum {
    100     OUTPUT_SHOULD_START,
    101     OUTPUT_RUN_NORMALLY,
    102     // path 1: wait for zero, then change/update active tones
    103     OUTPUT_TONES_CHANGED,
    104     OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE,
    105     // path 2: hardware should stop, wait for zero then turn output off = stop the timer
    106     OUTPUT_SHOULD_STOP,
    107     OUTPUT_REACHED_ZERO_BEFORE_OFF,
    108     OUTPUT_OFF,
    109     OUTPUT_OFF_1,
    110     OUTPUT_OFF_2, // trailing off: giving the DAC two more conversion cycles until the AUDIO_DAC_OFF_VALUE reaches the output, then turn the timer off, which leaves the output at that level
    111     number_of_output_states
    112 } output_states_t;
    113 output_states_t state = OUTPUT_OFF_2;
    114 
    115 /**
    116  * Generation of the waveform being passed to the callback. Declared weak so users
    117  * can override it with their own wave-forms/noises.
    118  */
    119 __attribute__((weak)) uint16_t dac_value_generate(void) {
    120     // DAC is running/asking for values but snapshot length is zero -> must be playing a pause
    121     if (active_tones_snapshot_length == 0) {
    122         return AUDIO_DAC_OFF_VALUE;
    123     }
    124 
    125     /* doing additive wave synthesis over all currently playing tones = adding up
    126      * sine-wave-samples for each frequency, scaled by the number of active tones
    127      */
    128     uint_fast16_t value     = 0;
    129     float         frequency = 0.0f;
    130 
    131 #if defined(AUDIO_DAC_SAMPLE_WAVEFORM_SINE)
    132     const size_t wavetable_length = ARRAY_SIZE(dac_buffer_sine);
    133 #elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE)
    134     const size_t wavetable_length = ARRAY_SIZE(dac_buffer_triangle);
    135 #elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID)
    136     const size_t wavetable_length = ARRAY_SIZE(dac_buffer_trapezoid);
    137 #elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE)
    138     const size_t wavetable_length = ARRAY_SIZE(dac_buffer_square);
    139 #endif
    140 
    141     for (size_t i = 0; i < active_tones_snapshot_length; i++) {
    142         /* Note: a user implementation does not have to rely on the active_tones_snapshot, but
    143          * could directly query the active frequencies through audio_get_processed_frequency */
    144         frequency = active_tones_snapshot[i];
    145 
    146         float new_dac_if = dac_if[i];
    147         new_dac_if += frequency * ((float)wavetable_length / AUDIO_DAC_SAMPLE_RATE * 2.0f / 3.0f);
    148         /*Note: the 2/3 are necessary to get the correct frequencies on the
    149          *      DAC output (as measured with an oscilloscope), since the gpt
    150          *      timer runs with 3*AUDIO_DAC_SAMPLE_RATE; and the DAC callback
    151          *      is called twice per conversion.*/
    152 
    153         while (new_dac_if >= wavetable_length)
    154             new_dac_if -= wavetable_length;
    155         dac_if[i] = new_dac_if;
    156 
    157         // Wavetable generation/lookup
    158         size_t dac_i = (size_t)new_dac_if;
    159 
    160 #if defined(AUDIO_DAC_SAMPLE_WAVEFORM_SINE)
    161         value += dac_buffer_sine[dac_i] / active_tones_snapshot_length;
    162 #elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE)
    163         value += dac_buffer_triangle[dac_i] / active_tones_snapshot_length;
    164 #elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID)
    165         value += dac_buffer_trapezoid[dac_i] / active_tones_snapshot_length;
    166 #elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE)
    167         value += dac_buffer_square[dac_i] / active_tones_snapshot_length;
    168 #endif
    169         /*
    170         // SINE
    171         value += dac_buffer_sine[dac_i] / active_tones_snapshot_length / 3;
    172         // TRIANGLE
    173         value += dac_buffer_triangle[dac_i] / active_tones_snapshot_length / 3;
    174         // SQUARE
    175         value += dac_buffer_square[dac_i] / active_tones_snapshot_length / 3;
    176         //NOTE: combination of these three wave-forms is more exemplary - and doesn't sound particularly good :-P
    177         */
    178 
    179         // STAIRS (mostly usefully as test-pattern)
    180         // value_avg = dac_buffer_staircase[dac_i] / active_tones_snapshot_length;
    181     }
    182 
    183     return value;
    184 }
    185 
    186 /**
    187  * DAC streaming callback. Does all of the main computing for playing songs.
    188  *
    189  * Note: chibios calls this CB twice: during the 'half buffer event', and the 'full buffer event'.
    190  */
    191 static void dac_end(DACDriver *dacp) {
    192     dacsample_t *sample_p = (dacp)->samples;
    193 
    194     // work on the other half of the buffer
    195     if (dacIsBufferComplete(dacp)) {
    196         sample_p += AUDIO_DAC_BUFFER_SIZE / 2; // 'half_index'
    197     }
    198 
    199     for (uint8_t s = 0; s < AUDIO_DAC_BUFFER_SIZE / 2; s++) {
    200         if (OUTPUT_OFF <= state) {
    201             sample_p[s] = AUDIO_DAC_OFF_VALUE;
    202             continue;
    203         } else {
    204             sample_p[s] = dac_value_generate();
    205         }
    206 
    207         /* zero crossing (or approach, whereas zero == DAC_OFF_VALUE, which can be configured to anything from 0 to DAC_SAMPLE_MAX)
    208          * ============================*=*========================== AUDIO_DAC_SAMPLE_MAX
    209          *                          *       *
    210          *                        *           *
    211          * ---------------------------------------------------------
    212          *                     *                 *                  } AUDIO_DAC_SAMPLE_MAX/100
    213          * --------------------------------------------------------- AUDIO_DAC_OFF_VALUE
    214          *                  *                       *               } AUDIO_DAC_SAMPLE_MAX/100
    215          * ---------------------------------------------------------
    216          *               *
    217          * *           *
    218          *   *       *
    219          * =====*=*================================================= 0x0
    220          */
    221         if (((sample_p[s] + (AUDIO_DAC_SAMPLE_MAX / 100)) > AUDIO_DAC_OFF_VALUE) && // value approaches from below
    222             (sample_p[s] < (AUDIO_DAC_OFF_VALUE + (AUDIO_DAC_SAMPLE_MAX / 100)))    // or above
    223         ) {
    224             if ((OUTPUT_SHOULD_START == state) && (active_tones_snapshot_length > 0)) {
    225                 state = OUTPUT_RUN_NORMALLY;
    226             } else if (OUTPUT_TONES_CHANGED == state) {
    227                 state = OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE;
    228             } else if (OUTPUT_SHOULD_STOP == state) {
    229                 state = OUTPUT_REACHED_ZERO_BEFORE_OFF;
    230             }
    231         }
    232 
    233         // still 'ramping up', reset the output to OFF_VALUE until the generated values reach that value, to do a smooth handover
    234         if (OUTPUT_SHOULD_START == state) {
    235             sample_p[s] = AUDIO_DAC_OFF_VALUE;
    236         }
    237 
    238         if ((OUTPUT_SHOULD_START == state) || (OUTPUT_REACHED_ZERO_BEFORE_OFF == state) || (OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE == state)) {
    239             uint8_t active_tones         = MIN(AUDIO_MAX_SIMULTANEOUS_TONES, audio_get_number_of_active_tones());
    240             active_tones_snapshot_length = 0;
    241             // update the snapshot - once, and only on occasion that something changed;
    242             // -> saves cpu cycles (?)
    243             for (uint8_t i = 0; i < active_tones; i++) {
    244                 float freq = audio_get_processed_frequency(i);
    245                 if (freq > 0) { // disregard 'rest' notes, with valid frequency 0.0f; which would only lower the resulting waveform volume during the additive synthesis step
    246                     active_tones_snapshot[active_tones_snapshot_length++] = freq;
    247                 }
    248             }
    249 
    250             if ((0 == active_tones_snapshot_length) && (OUTPUT_REACHED_ZERO_BEFORE_OFF == state)) {
    251                 state = OUTPUT_OFF;
    252             }
    253             if (OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE == state) {
    254                 state = OUTPUT_RUN_NORMALLY;
    255             }
    256         }
    257     }
    258 
    259     // update audio internal state (note position, current_note, ...)
    260     if (audio_update_state()) {
    261         if (OUTPUT_SHOULD_STOP != state) {
    262             state = OUTPUT_TONES_CHANGED;
    263         }
    264     }
    265 
    266     if (OUTPUT_OFF <= state) {
    267         if (OUTPUT_OFF_2 == state) {
    268             // stopping timer6 = stopping the DAC at whatever value it is currently pushing to the output = AUDIO_DAC_OFF_VALUE
    269             gptStopTimer(&GPTD6);
    270         } else {
    271             state++;
    272         }
    273     }
    274 }
    275 
    276 static void dac_error(DACDriver *dacp, dacerror_t err) {
    277     (void)dacp;
    278     (void)err;
    279 
    280     chSysHalt("DAC failure. halp");
    281 }
    282 
    283 static const GPTConfig gpt6cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE * 3,
    284                                    .callback  = NULL,
    285                                    .cr2       = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event.  */
    286                                    .dier      = 0U};
    287 
    288 static const DACConfig dac_conf = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT};
    289 
    290 /**
    291  * @note The DAC_TRG(0) here selects the Timer 6 TRGO event, which is triggered
    292  * on the rising edge after 3 APB1 clock cycles, causing our gpt6cfg1.frequency
    293  * to be a third of what we expect.
    294  *
    295  * Here are all the values for DAC_TRG (TSEL in the ref manual)
    296  * TIM15_TRGO 0b011
    297  * TIM2_TRGO  0b100
    298  * TIM3_TRGO  0b001
    299  * TIM6_TRGO  0b000
    300  * TIM7_TRGO  0b010
    301  * EXTI9      0b110
    302  * SWTRIG     0b111
    303  */
    304 static const DACConversionGroup dac_conv_cfg = {.num_channels = 1U, .end_cb = dac_end, .error_cb = dac_error, .trigger = DAC_TRG(0b000)};
    305 
    306 void audio_driver_initialize_impl(void) {
    307     if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
    308         palSetLineMode(A4, PAL_MODE_INPUT_ANALOG);
    309         dacStart(&DACD1, &dac_conf);
    310     }
    311     if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) {
    312         palSetLineMode(A5, PAL_MODE_INPUT_ANALOG);
    313         dacStart(&DACD2, &dac_conf);
    314     }
    315 
    316     /* enable the output buffer, to directly drive external loads with no additional circuitry
    317      *
    318      * see: AN4566 Application note: Extending the DAC performance of STM32 microcontrollers
    319      * Note: Buffer-Off bit -> has to be set 0 to enable the output buffer
    320      * Note: enabling the output buffer imparts an additional dc-offset of a couple mV
    321      *
    322      * this is done here, reaching directly into the stm32 registers since chibios has not implemented BOFF handling yet
    323      * (see: chibios/os/hal/ports/STM32/todo.txt '- BOFF handling in DACv1.'
    324      */
    325     DACD1.params->dac->CR &= ~DAC_CR_BOFF1;
    326     DACD2.params->dac->CR &= ~DAC_CR_BOFF2;
    327 
    328     /* Start the DAC output with all off values. This buffer will then get fed
    329      * with samples from dac_end, which will play notes.
    330      */
    331     for (size_t i = 0; i < AUDIO_DAC_BUFFER_SIZE; i++) {
    332         dac_buffer[i] = AUDIO_DAC_OFF_VALUE;
    333     }
    334 
    335     if (AUDIO_PIN == A4) {
    336         dacStartConversion(&DACD1, &dac_conv_cfg, dac_buffer, AUDIO_DAC_BUFFER_SIZE);
    337     } else if (AUDIO_PIN == A5) {
    338         dacStartConversion(&DACD2, &dac_conv_cfg, dac_buffer, AUDIO_DAC_BUFFER_SIZE);
    339     }
    340 
    341     // no inverted/out-of-phase waveform (yet?), only pulling AUDIO_PIN_ALT to AUDIO_DAC_OFF_VALUE
    342 #if defined(AUDIO_PIN_ALT_AS_NEGATIVE)
    343     if (AUDIO_PIN_ALT == A4) {
    344         dacPutChannelX(&DACD1, 0, AUDIO_DAC_OFF_VALUE);
    345     } else if (AUDIO_PIN_ALT == A5) {
    346         dacPutChannelX(&DACD2, 0, AUDIO_DAC_OFF_VALUE);
    347     }
    348 #endif
    349 
    350     gptStart(&GPTD6, &gpt6cfg1);
    351 }
    352 
    353 void audio_driver_stop_impl(void) {
    354     state = OUTPUT_SHOULD_STOP;
    355 }
    356 
    357 void audio_driver_start_impl(void) {
    358     gptStartContinuous(&GPTD6, 2U);
    359 
    360     for (uint8_t i = 0; i < AUDIO_MAX_SIMULTANEOUS_TONES; i++) {
    361         dac_if[i]                = 0.0f;
    362         active_tones_snapshot[i] = 0.0f;
    363     }
    364     active_tones_snapshot_length = 0;
    365     state                        = OUTPUT_SHOULD_START;
    366 }
    367 
    368 #pragma GCC diagnostic pop