1
0
Fork 0

Move Audio drivers from quantum to platform drivers folder (#14308)

* Move Audio drivers from quantum to platform drivers folder

* fix path for audio drivers

Co-authored-by: Ryan <fauxpark@gmail.com>

Co-authored-by: Ryan <fauxpark@gmail.com>
This commit is contained in:
Drashna Jael're 2021-10-05 18:01:45 -07:00 committed by GitHub
parent 9f0e74802a
commit ba8f1454f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 5 additions and 10 deletions

View file

@ -26,17 +26,12 @@
#if defined(__AVR__)
# include <avr/io.h>
# if defined(AUDIO_DRIVER_PWM)
# include "driver_avr_pwm.h"
# endif
#endif
#if defined(PROTOCOL_CHIBIOS)
# if defined(AUDIO_DRIVER_PWM)
# include "driver_chibios_pwm.h"
# elif defined(AUDIO_DRIVER_DAC)
# include "driver_chibios_dac.h"
# endif
#if defined(AUDIO_DRIVER_PWM)
# include "audio_pwm.h"
#elif defined(AUDIO_DRIVER_DAC)
# include "audio_dac.h"
#endif
typedef union {

View file

@ -1,17 +0,0 @@
/* Copyright 2020 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once

View file

@ -1,332 +0,0 @@
/* Copyright 2016 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(__AVR__)
# include <avr/pgmspace.h>
# include <avr/interrupt.h>
# include <avr/io.h>
#endif
#include "audio.h"
extern bool playing_note;
extern bool playing_melody;
extern uint8_t note_timbre;
#define CPU_PRESCALER 8
/*
Audio Driver: PWM
drive up to two speakers through the AVR PWM hardware-peripheral, using timer1 and/or timer3 on Atmega32U4.
the primary channel_1 can be connected to either pin PC4 PC5 or PC6 (the later being used by most AVR based keyboards) with a PMW signal generated by timer3
and an optional secondary channel_2 on either pin PB5, PB6 or PB7, with a PWM signal from timer1
alternatively, the PWM pins on PORTB can be used as only/primary speaker
*/
#if defined(AUDIO_PIN) && (AUDIO_PIN != C4) && (AUDIO_PIN != C5) && (AUDIO_PIN != C6) && (AUDIO_PIN != B5) && (AUDIO_PIN != B6) && (AUDIO_PIN != B7) && (AUDIO_PIN != D5)
# error "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under the AVR settings for available options."
#endif
#if (AUDIO_PIN == C4) || (AUDIO_PIN == C5) || (AUDIO_PIN == C6)
# define AUDIO1_PIN_SET
# define AUDIO1_TIMSKx TIMSK3
# define AUDIO1_TCCRxA TCCR3A
# define AUDIO1_TCCRxB TCCR3B
# define AUDIO1_ICRx ICR3
# define AUDIO1_WGMx0 WGM30
# define AUDIO1_WGMx1 WGM31
# define AUDIO1_WGMx2 WGM32
# define AUDIO1_WGMx3 WGM33
# define AUDIO1_CSx0 CS30
# define AUDIO1_CSx1 CS31
# define AUDIO1_CSx2 CS32
# if (AUDIO_PIN == C6)
# define AUDIO1_COMxy0 COM3A0
# define AUDIO1_COMxy1 COM3A1
# define AUDIO1_OCIExy OCIE3A
# define AUDIO1_OCRxy OCR3A
# define AUDIO1_PIN C6
# define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPA_vect
# elif (AUDIO_PIN == C5)
# define AUDIO1_COMxy0 COM3B0
# define AUDIO1_COMxy1 COM3B1
# define AUDIO1_OCIExy OCIE3B
# define AUDIO1_OCRxy OCR3B
# define AUDIO1_PIN C5
# define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPB_vect
# elif (AUDIO_PIN == C4)
# define AUDIO1_COMxy0 COM3C0
# define AUDIO1_COMxy1 COM3C1
# define AUDIO1_OCIExy OCIE3C
# define AUDIO1_OCRxy OCR3C
# define AUDIO1_PIN C4
# define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPC_vect
# endif
#endif
#if defined(AUDIO_PIN) && defined(AUDIO_PIN_ALT) && (AUDIO_PIN == AUDIO_PIN_ALT)
# error "Audio feature: AUDIO_PIN and AUDIO_PIN_ALT on the same pin makes no sense."
#endif
#if ((AUDIO_PIN == B5) && ((AUDIO_PIN_ALT == B6) || (AUDIO_PIN_ALT == B7))) || ((AUDIO_PIN == B6) && ((AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B7))) || ((AUDIO_PIN == B7) && ((AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B6)))
# error "Audio feature: PORTB as AUDIO_PIN and AUDIO_PIN_ALT at the same time is not supported."
#endif
#if defined(AUDIO_PIN_ALT) && (AUDIO_PIN_ALT != B5) && (AUDIO_PIN_ALT != B6) && (AUDIO_PIN_ALT != B7)
# error "Audio feature: the pin selected as AUDIO_PIN_ALT is not supported."
#endif
#if (AUDIO_PIN == B5) || (AUDIO_PIN == B6) || (AUDIO_PIN == B7) || (AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B6) || (AUDIO_PIN_ALT == B7) || (AUDIO_PIN == D5)
# define AUDIO2_PIN_SET
# define AUDIO2_TIMSKx TIMSK1
# define AUDIO2_TCCRxA TCCR1A
# define AUDIO2_TCCRxB TCCR1B
# define AUDIO2_ICRx ICR1
# define AUDIO2_WGMx0 WGM10
# define AUDIO2_WGMx1 WGM11
# define AUDIO2_WGMx2 WGM12
# define AUDIO2_WGMx3 WGM13
# define AUDIO2_CSx0 CS10
# define AUDIO2_CSx1 CS11
# define AUDIO2_CSx2 CS12
# if (AUDIO_PIN == B5) || (AUDIO_PIN_ALT == B5)
# define AUDIO2_COMxy0 COM1A0
# define AUDIO2_COMxy1 COM1A1
# define AUDIO2_OCIExy OCIE1A
# define AUDIO2_OCRxy OCR1A
# define AUDIO2_PIN B5
# define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPA_vect
# elif (AUDIO_PIN == B6) || (AUDIO_PIN_ALT == B6)
# define AUDIO2_COMxy0 COM1B0
# define AUDIO2_COMxy1 COM1B1
# define AUDIO2_OCIExy OCIE1B
# define AUDIO2_OCRxy OCR1B
# define AUDIO2_PIN B6
# define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPB_vect
# elif (AUDIO_PIN == B7) || (AUDIO_PIN_ALT == B7)
# define AUDIO2_COMxy0 COM1C0
# define AUDIO2_COMxy1 COM1C1
# define AUDIO2_OCIExy OCIE1C
# define AUDIO2_OCRxy OCR1C
# define AUDIO2_PIN B7
# define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPC_vect
# elif (AUDIO_PIN == D5) && defined(__AVR_ATmega32A__)
# pragma message "Audio support for ATmega32A is experimental and can cause crashes."
# undef AUDIO2_TIMSKx
# define AUDIO2_TIMSKx TIMSK
# define AUDIO2_COMxy0 COM1A0
# define AUDIO2_COMxy1 COM1A1
# define AUDIO2_OCIExy OCIE1A
# define AUDIO2_OCRxy OCR1A
# define AUDIO2_PIN D5
# define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPA_vect
# endif
#endif
// C6 seems to be the assumed default by many existing keyboard - but sill warn the user
#if !defined(AUDIO1_PIN_SET) && !defined(AUDIO2_PIN_SET)
# pragma message "Audio feature enabled, but no suitable pin selected - see docs/feature_audio under the AVR settings for available options. Don't expect to hear anything... :-)"
// TODO: make this an error - go through the breaking-change-process and change all keyboards to the new define
#endif
// -----------------------------------------------------------------------------
#ifdef AUDIO1_PIN_SET
static float channel_1_frequency = 0.0f;
void channel_1_set_frequency(float freq) {
if (freq == 0.0f) // a pause/rest is a valid "note" with freq=0
{
// disable the output, but keep the pwm-ISR going (with the previous
// frequency) so the audio-state keeps getting updated
// Note: setting the duty-cycle 0 is not possible on non-inverting PWM mode - see the AVR data-sheet
AUDIO1_TCCRxA &= ~(_BV(AUDIO1_COMxy1) | _BV(AUDIO1_COMxy0));
return;
} else {
AUDIO1_TCCRxA |= _BV(AUDIO1_COMxy1); // enable output, PWM mode
}
channel_1_frequency = freq;
// set pwm period
AUDIO1_ICRx = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER));
// and duty cycle
AUDIO1_OCRxy = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre / 100);
}
void channel_1_start(void) {
// enable timer-counter ISR
AUDIO1_TIMSKx |= _BV(AUDIO1_OCIExy);
// enable timer-counter output
AUDIO1_TCCRxA |= _BV(AUDIO1_COMxy1);
}
void channel_1_stop(void) {
// disable timer-counter ISR
AUDIO1_TIMSKx &= ~_BV(AUDIO1_OCIExy);
// disable timer-counter output
AUDIO1_TCCRxA &= ~(_BV(AUDIO1_COMxy1) | _BV(AUDIO1_COMxy0));
}
#endif
#ifdef AUDIO2_PIN_SET
static float channel_2_frequency = 0.0f;
void channel_2_set_frequency(float freq) {
if (freq == 0.0f) {
AUDIO2_TCCRxA &= ~(_BV(AUDIO2_COMxy1) | _BV(AUDIO2_COMxy0));
return;
} else {
AUDIO2_TCCRxA |= _BV(AUDIO2_COMxy1);
}
channel_2_frequency = freq;
AUDIO2_ICRx = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER));
AUDIO2_OCRxy = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre / 100);
}
float channel_2_get_frequency(void) { return channel_2_frequency; }
void channel_2_start(void) {
AUDIO2_TIMSKx |= _BV(AUDIO2_OCIExy);
AUDIO2_TCCRxA |= _BV(AUDIO2_COMxy1);
}
void channel_2_stop(void) {
AUDIO2_TIMSKx &= ~_BV(AUDIO2_OCIExy);
AUDIO2_TCCRxA &= ~(_BV(AUDIO2_COMxy1) | _BV(AUDIO2_COMxy0));
}
#endif
void audio_driver_initialize() {
#ifdef AUDIO1_PIN_SET
channel_1_stop();
setPinOutput(AUDIO1_PIN);
#endif
#ifdef AUDIO2_PIN_SET
channel_2_stop();
setPinOutput(AUDIO2_PIN);
#endif
// TCCR3A / TCCR3B: Timer/Counter #3 Control Registers TCCR3A/TCCR3B, TCCR1A/TCCR1B
// Compare Output Mode (COM3An and COM1An) = 0b00 = Normal port operation
// OC3A -- PC6
// OC3B -- PC5
// OC3C -- PC4
// OC1A -- PB5
// OC1B -- PB6
// OC1C -- PB7
// Waveform Generation Mode (WGM3n) = 0b1110 = Fast PWM Mode 14. Period = ICR3, Duty Cycle OCR3A)
// OCR3A - PC6
// OCR3B - PC5
// OCR3C - PC4
// OCR1A - PB5
// OCR1B - PB6
// OCR1C - PB7
// Clock Select (CS3n) = 0b010 = Clock / 8
#ifdef AUDIO1_PIN_SET
// initialize timer-counter
AUDIO1_TCCRxA = (0 << AUDIO1_COMxy1) | (0 << AUDIO1_COMxy0) | (1 << AUDIO1_WGMx1) | (0 << AUDIO1_WGMx0);
AUDIO1_TCCRxB = (1 << AUDIO1_WGMx3) | (1 << AUDIO1_WGMx2) | (0 << AUDIO1_CSx2) | (1 << AUDIO1_CSx1) | (0 << AUDIO1_CSx0);
#endif
#ifdef AUDIO2_PIN_SET
AUDIO2_TCCRxA = (0 << AUDIO2_COMxy1) | (0 << AUDIO2_COMxy0) | (1 << AUDIO2_WGMx1) | (0 << AUDIO2_WGMx0);
AUDIO2_TCCRxB = (1 << AUDIO2_WGMx3) | (1 << AUDIO2_WGMx2) | (0 << AUDIO2_CSx2) | (1 << AUDIO2_CSx1) | (0 << AUDIO2_CSx0);
#endif
}
void audio_driver_stop() {
#ifdef AUDIO1_PIN_SET
channel_1_stop();
#endif
#ifdef AUDIO2_PIN_SET
channel_2_stop();
#endif
}
void audio_driver_start(void) {
#ifdef AUDIO1_PIN_SET
channel_1_start();
if (playing_note) {
channel_1_set_frequency(audio_get_processed_frequency(0));
}
#endif
#if !defined(AUDIO1_PIN_SET) && defined(AUDIO2_PIN_SET)
channel_2_start();
if (playing_note) {
channel_2_set_frequency(audio_get_processed_frequency(0));
}
#endif
}
static volatile uint32_t isr_counter = 0;
#ifdef AUDIO1_PIN_SET
ISR(AUDIO1_TIMERx_COMPy_vect) {
isr_counter++;
if (isr_counter < channel_1_frequency / (CPU_PRESCALER * 8)) return;
isr_counter = 0;
bool state_changed = audio_update_state();
if (!playing_note && !playing_melody) {
channel_1_stop();
# ifdef AUDIO2_PIN_SET
channel_2_stop();
# endif
return;
}
if (state_changed) {
channel_1_set_frequency(audio_get_processed_frequency(0));
# ifdef AUDIO2_PIN_SET
if (audio_get_number_of_active_tones() > 1) {
channel_2_set_frequency(audio_get_processed_frequency(1));
} else {
channel_2_stop();
}
# endif
}
}
#endif
#if !defined(AUDIO1_PIN_SET) && defined(AUDIO2_PIN_SET)
ISR(AUDIO2_TIMERx_COMPy_vect) {
isr_counter++;
if (isr_counter < channel_2_frequency / (CPU_PRESCALER * 8)) return;
isr_counter = 0;
bool state_changed = audio_update_state();
if (!playing_note && !playing_melody) {
channel_2_stop();
return;
}
if (state_changed) {
channel_2_set_frequency(audio_get_processed_frequency(0));
}
}
#endif

View file

@ -1,126 +0,0 @@
/* Copyright 2019 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef A4
# define A4 PAL_LINE(GPIOA, 4)
#endif
#ifndef A5
# define A5 PAL_LINE(GPIOA, 5)
#endif
/**
* Size of the dac_buffer arrays. All must be the same size.
*/
#define AUDIO_DAC_BUFFER_SIZE 256U
/**
* Highest value allowed sample value.
* since the DAC is limited to 12 bit, the absolute max is 0xfff = 4095U;
* lower values adjust the peak-voltage aka volume down.
* adjusting this value has only an effect on a sample-buffer whose values are
* are NOT pregenerated - see square-wave
*/
#ifndef AUDIO_DAC_SAMPLE_MAX
# define AUDIO_DAC_SAMPLE_MAX 4095U
#endif
#if !defined(AUDIO_DAC_SAMPLE_RATE) && !defined(AUDIO_MAX_SIMULTANEOUS_TONES) && !defined(AUDIO_DAC_QUALITY_VERY_LOW) && !defined(AUDIO_DAC_QUALITY_LOW) && !defined(AUDIO_DAC_QUALITY_HIGH) && !defined(AUDIO_DAC_QUALITY_VERY_HIGH)
# define AUDIO_DAC_QUALITY_SANE_MINIMUM
#endif
/**
* These presets allow you to quickly switch between quality settings for
* the DAC. The sample rate and maximum number of simultaneous tones roughly
* has an inverse relationship - slightly higher sample rates may be possible.
*
* NOTE: a high sample-rate results in a higher cpu-load, which might lead to
* (audible) discontinuities and/or starve other processes of cpu-time
* (like RGB-led back-lighting, ...)
*/
#ifdef AUDIO_DAC_QUALITY_VERY_LOW
# define AUDIO_DAC_SAMPLE_RATE 11025U
# define AUDIO_MAX_SIMULTANEOUS_TONES 8
#endif
#ifdef AUDIO_DAC_QUALITY_LOW
# define AUDIO_DAC_SAMPLE_RATE 22050U
# define AUDIO_MAX_SIMULTANEOUS_TONES 4
#endif
#ifdef AUDIO_DAC_QUALITY_HIGH
# define AUDIO_DAC_SAMPLE_RATE 44100U
# define AUDIO_MAX_SIMULTANEOUS_TONES 2
#endif
#ifdef AUDIO_DAC_QUALITY_VERY_HIGH
# define AUDIO_DAC_SAMPLE_RATE 88200U
# define AUDIO_MAX_SIMULTANEOUS_TONES 1
#endif
#ifdef AUDIO_DAC_QUALITY_SANE_MINIMUM
/* a sane-minimum config: with a trade-off between cpu-load and tone-range
*
* the (currently) highest defined note is NOTE_B8 with 7902Hz; if we now
* aim for an even even multiple of the buffer-size, we end up with:
* ( roundUptoPow2(highest note / AUDIO_DAC_BUFFER_SIZE) * nyquist-rate * AUDIO_DAC_BUFFER_SIZE)
* 7902/256 = 30.867 * 2 * 256 ~= 16384
* which works out (but the 'scope shows some sampling artifacts with lower harmonics :-P)
*/
# define AUDIO_DAC_SAMPLE_RATE 16384U
# define AUDIO_MAX_SIMULTANEOUS_TONES 8
#endif
/**
* Effective bit-rate of the DAC. 44.1khz is the standard for most audio - any
* lower will sacrifice perceptible audio quality. Any higher will limit the
* number of simultaneous tones. In most situations, a tenth (1/10) of the
* sample rate is where notes become unbearable.
*/
#ifndef AUDIO_DAC_SAMPLE_RATE
# define AUDIO_DAC_SAMPLE_RATE 44100U
#endif
/**
* The number of tones that can be played simultaneously. If too high a value
* is used here, the keyboard will freeze and glitch-out when that many tones
* are being played.
*/
#ifndef AUDIO_MAX_SIMULTANEOUS_TONES
# define AUDIO_MAX_SIMULTANEOUS_TONES 2
#endif
/**
* The default value of the DAC when not playing anything. Certain hardware
* setups may require a high (AUDIO_DAC_SAMPLE_MAX) or low (0) value here.
* Since multiple added sine waves tend to oscillate around the midpoint,
* and possibly never/rarely reach either 0 of MAX, 1/2 MAX can be a
* reasonable default value.
*/
#ifndef AUDIO_DAC_OFF_VALUE
# define AUDIO_DAC_OFF_VALUE AUDIO_DAC_SAMPLE_MAX / 2
#endif
#if AUDIO_DAC_OFF_VALUE > AUDIO_DAC_SAMPLE_MAX
# error "AUDIO_DAC: OFF_VALUE may not be larger than SAMPLE_MAX"
#endif
/**
*user overridable sample generation/processing
*/
uint16_t dac_value_generate(void);

View file

@ -1,335 +0,0 @@
/* Copyright 2016-2019 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "audio.h"
#include <ch.h>
#include <hal.h>
/*
Audio Driver: DAC
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
it is also possible to have a custom sample-LUT by implementing/overriding 'dac_value_generate'
this driver allows for multiple simultaneous tones to be played through one single channel by doing additive wave-synthesis
*/
#if !defined(AUDIO_PIN)
# error "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under 'ARM (DAC additive)' for available options."
#endif
#if defined(AUDIO_PIN_ALT) && !defined(AUDIO_PIN_ALT_AS_NEGATIVE)
# 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."
#endif
#if !defined(AUDIO_PIN_ALT)
// no ALT pin defined is valid, but the c-ifs below need some value set
# define AUDIO_PIN_ALT PAL_NOLINE
#endif
#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)
# define AUDIO_DAC_SAMPLE_WAVEFORM_SINE
#endif
#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_SINE
/* one full sine wave over [0,2*pi], but shifted up one amplitude and left pi/4; for the samples to start at 0
*/
static const dacsample_t dac_buffer_sine[AUDIO_DAC_BUFFER_SIZE] = {
// 256 values, max 4095
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,
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};
#endif // AUDIO_DAC_SAMPLE_WAVEFORM_SINE
#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE
static const dacsample_t dac_buffer_triangle[AUDIO_DAC_BUFFER_SIZE] = {
// 256 values, max 4095
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,
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};
#endif // AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE
#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE
static const dacsample_t dac_buffer_square[AUDIO_DAC_BUFFER_SIZE] = {
[0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = 0, // first and
[AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = AUDIO_DAC_SAMPLE_MAX, // second half
};
#endif // AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE
/*
// four steps: 0, 1/3, 2/3 and 1
static const dacsample_t dac_buffer_staircase[AUDIO_DAC_BUFFER_SIZE] = {
[0 ... AUDIO_DAC_BUFFER_SIZE/3 -1 ] = 0,
[AUDIO_DAC_BUFFER_SIZE / 4 ... AUDIO_DAC_BUFFER_SIZE / 2 -1 ] = AUDIO_DAC_SAMPLE_MAX / 3,
[AUDIO_DAC_BUFFER_SIZE / 2 ... 3 * AUDIO_DAC_BUFFER_SIZE / 4 -1 ] = 2 * AUDIO_DAC_SAMPLE_MAX / 3,
[3 * AUDIO_DAC_BUFFER_SIZE / 4 ... AUDIO_DAC_BUFFER_SIZE -1 ] = AUDIO_DAC_SAMPLE_MAX,
}
*/
#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID
static const dacsample_t dac_buffer_trapezoid[AUDIO_DAC_BUFFER_SIZE] = {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,
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};
#endif // AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID
static dacsample_t dac_buffer_empty[AUDIO_DAC_BUFFER_SIZE] = {AUDIO_DAC_OFF_VALUE};
/* keep track of the sample position for for each frequency */
static float dac_if[AUDIO_MAX_SIMULTANEOUS_TONES] = {0.0};
static float active_tones_snapshot[AUDIO_MAX_SIMULTANEOUS_TONES] = {0, 0};
static uint8_t active_tones_snapshot_length = 0;
typedef enum {
OUTPUT_SHOULD_START,
OUTPUT_RUN_NORMALLY,
// path 1: wait for zero, then change/update active tones
OUTPUT_TONES_CHANGED,
OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE,
// path 2: hardware should stop, wait for zero then turn output off = stop the timer
OUTPUT_SHOULD_STOP,
OUTPUT_REACHED_ZERO_BEFORE_OFF,
OUTPUT_OFF,
OUTPUT_OFF_1,
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
number_of_output_states
} output_states_t;
output_states_t state = OUTPUT_OFF_2;
/**
* Generation of the waveform being passed to the callback. Declared weak so users
* can override it with their own wave-forms/noises.
*/
__attribute__((weak)) uint16_t dac_value_generate(void) {
// DAC is running/asking for values but snapshot length is zero -> must be playing a pause
if (active_tones_snapshot_length == 0) {
return AUDIO_DAC_OFF_VALUE;
}
/* doing additive wave synthesis over all currently playing tones = adding up
* sine-wave-samples for each frequency, scaled by the number of active tones
*/
uint16_t value = 0;
float frequency = 0.0f;
for (uint8_t i = 0; i < active_tones_snapshot_length; i++) {
/* Note: a user implementation does not have to rely on the active_tones_snapshot, but
* could directly query the active frequencies through audio_get_processed_frequency */
frequency = active_tones_snapshot[i];
dac_if[i] = dac_if[i] + ((frequency * AUDIO_DAC_BUFFER_SIZE) / AUDIO_DAC_SAMPLE_RATE) * 2 / 3;
/*Note: the 2/3 are necessary to get the correct frequencies on the
* DAC output (as measured with an oscilloscope), since the gpt
* timer runs with 3*AUDIO_DAC_SAMPLE_RATE; and the DAC callback
* is called twice per conversion.*/
dac_if[i] = fmod(dac_if[i], AUDIO_DAC_BUFFER_SIZE);
// Wavetable generation/lookup
uint16_t dac_i = (uint16_t)dac_if[i];
#if defined(AUDIO_DAC_SAMPLE_WAVEFORM_SINE)
value += dac_buffer_sine[dac_i] / active_tones_snapshot_length;
#elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE)
value += dac_buffer_triangle[dac_i] / active_tones_snapshot_length;
#elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID)
value += dac_buffer_trapezoid[dac_i] / active_tones_snapshot_length;
#elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE)
value += dac_buffer_square[dac_i] / active_tones_snapshot_length;
#endif
/*
// SINE
value += dac_buffer_sine[dac_i] / active_tones_snapshot_length / 3;
// TRIANGLE
value += dac_buffer_triangle[dac_i] / active_tones_snapshot_length / 3;
// SQUARE
value += dac_buffer_square[dac_i] / active_tones_snapshot_length / 3;
//NOTE: combination of these three wave-forms is more exemplary - and doesn't sound particularly good :-P
*/
// STAIRS (mostly usefully as test-pattern)
// value_avg = dac_buffer_staircase[dac_i] / active_tones_snapshot_length;
}
return value;
}
/**
* DAC streaming callback. Does all of the main computing for playing songs.
*
* Note: chibios calls this CB twice: during the 'half buffer event', and the 'full buffer event'.
*/
static void dac_end(DACDriver *dacp) {
dacsample_t *sample_p = (dacp)->samples;
// work on the other half of the buffer
if (dacIsBufferComplete(dacp)) {
sample_p += AUDIO_DAC_BUFFER_SIZE / 2; // 'half_index'
}
for (uint8_t s = 0; s < AUDIO_DAC_BUFFER_SIZE / 2; s++) {
if (OUTPUT_OFF <= state) {
sample_p[s] = AUDIO_DAC_OFF_VALUE;
continue;
} else {
sample_p[s] = dac_value_generate();
}
/* zero crossing (or approach, whereas zero == DAC_OFF_VALUE, which can be configured to anything from 0 to DAC_SAMPLE_MAX)
* ============================*=*========================== AUDIO_DAC_SAMPLE_MAX
* * *
* * *
* ---------------------------------------------------------
* * * } AUDIO_DAC_SAMPLE_MAX/100
* --------------------------------------------------------- AUDIO_DAC_OFF_VALUE
* * * } AUDIO_DAC_SAMPLE_MAX/100
* ---------------------------------------------------------
* *
* * *
* * *
* =====*=*================================================= 0x0
*/
if (((sample_p[s] + (AUDIO_DAC_SAMPLE_MAX / 100)) > AUDIO_DAC_OFF_VALUE) && // value approaches from below
(sample_p[s] < (AUDIO_DAC_OFF_VALUE + (AUDIO_DAC_SAMPLE_MAX / 100))) // or above
) {
if ((OUTPUT_SHOULD_START == state) && (active_tones_snapshot_length > 0)) {
state = OUTPUT_RUN_NORMALLY;
} else if (OUTPUT_TONES_CHANGED == state) {
state = OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE;
} else if (OUTPUT_SHOULD_STOP == state) {
state = OUTPUT_REACHED_ZERO_BEFORE_OFF;
}
}
// still 'ramping up', reset the output to OFF_VALUE until the generated values reach that value, to do a smooth handover
if (OUTPUT_SHOULD_START == state) {
sample_p[s] = AUDIO_DAC_OFF_VALUE;
}
if ((OUTPUT_SHOULD_START == state) || (OUTPUT_REACHED_ZERO_BEFORE_OFF == state) || (OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE == state)) {
uint8_t active_tones = MIN(AUDIO_MAX_SIMULTANEOUS_TONES, audio_get_number_of_active_tones());
active_tones_snapshot_length = 0;
// update the snapshot - once, and only on occasion that something changed;
// -> saves cpu cycles (?)
for (uint8_t i = 0; i < active_tones; i++) {
float freq = audio_get_processed_frequency(i);
if (freq > 0) { // disregard 'rest' notes, with valid frequency 0.0f; which would only lower the resulting waveform volume during the additive synthesis step
active_tones_snapshot[active_tones_snapshot_length++] = freq;
}
}
if ((0 == active_tones_snapshot_length) && (OUTPUT_REACHED_ZERO_BEFORE_OFF == state)) {
state = OUTPUT_OFF;
}
if (OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE == state) {
state = OUTPUT_RUN_NORMALLY;
}
}
}
// update audio internal state (note position, current_note, ...)
if (audio_update_state()) {
if (OUTPUT_SHOULD_STOP != state) {
state = OUTPUT_TONES_CHANGED;
}
}
if (OUTPUT_OFF <= state) {
if (OUTPUT_OFF_2 == state) {
// stopping timer6 = stopping the DAC at whatever value it is currently pushing to the output = AUDIO_DAC_OFF_VALUE
gptStopTimer(&GPTD6);
} else {
state++;
}
}
}
static void dac_error(DACDriver *dacp, dacerror_t err) {
(void)dacp;
(void)err;
chSysHalt("DAC failure. halp");
}
static const GPTConfig gpt6cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE * 3,
.callback = NULL,
.cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */
.dier = 0U};
static const DACConfig dac_conf = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT};
/**
* @note The DAC_TRG(0) here selects the Timer 6 TRGO event, which is triggered
* on the rising edge after 3 APB1 clock cycles, causing our gpt6cfg1.frequency
* to be a third of what we expect.
*
* Here are all the values for DAC_TRG (TSEL in the ref manual)
* TIM15_TRGO 0b011
* TIM2_TRGO 0b100
* TIM3_TRGO 0b001
* TIM6_TRGO 0b000
* TIM7_TRGO 0b010
* EXTI9 0b110
* SWTRIG 0b111
*/
static const DACConversionGroup dac_conv_cfg = {.num_channels = 1U, .end_cb = dac_end, .error_cb = dac_error, .trigger = DAC_TRG(0b000)};
void audio_driver_initialize() {
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
palSetLineMode(A4, PAL_MODE_INPUT_ANALOG);
dacStart(&DACD1, &dac_conf);
}
if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) {
palSetLineMode(A5, PAL_MODE_INPUT_ANALOG);
dacStart(&DACD2, &dac_conf);
}
/* enable the output buffer, to directly drive external loads with no additional circuitry
*
* see: AN4566 Application note: Extending the DAC performance of STM32 microcontrollers
* Note: Buffer-Off bit -> has to be set 0 to enable the output buffer
* Note: enabling the output buffer imparts an additional dc-offset of a couple mV
*
* this is done here, reaching directly into the stm32 registers since chibios has not implemented BOFF handling yet
* (see: chibios/os/hal/ports/STM32/todo.txt '- BOFF handling in DACv1.'
*/
DACD1.params->dac->CR &= ~DAC_CR_BOFF1;
DACD2.params->dac->CR &= ~DAC_CR_BOFF2;
if (AUDIO_PIN == A4) {
dacStartConversion(&DACD1, &dac_conv_cfg, dac_buffer_empty, AUDIO_DAC_BUFFER_SIZE);
} else if (AUDIO_PIN == A5) {
dacStartConversion(&DACD2, &dac_conv_cfg, dac_buffer_empty, AUDIO_DAC_BUFFER_SIZE);
}
// no inverted/out-of-phase waveform (yet?), only pulling AUDIO_PIN_ALT to AUDIO_DAC_OFF_VALUE
#if defined(AUDIO_PIN_ALT_AS_NEGATIVE)
if (AUDIO_PIN_ALT == A4) {
dacPutChannelX(&DACD1, 0, AUDIO_DAC_OFF_VALUE);
} else if (AUDIO_PIN_ALT == A5) {
dacPutChannelX(&DACD2, 0, AUDIO_DAC_OFF_VALUE);
}
#endif
gptStart(&GPTD6, &gpt6cfg1);
}
void audio_driver_stop(void) { state = OUTPUT_SHOULD_STOP; }
void audio_driver_start(void) {
gptStartContinuous(&GPTD6, 2U);
for (uint8_t i = 0; i < AUDIO_MAX_SIMULTANEOUS_TONES; i++) {
dac_if[i] = 0.0f;
active_tones_snapshot[i] = 0.0f;
}
active_tones_snapshot_length = 0;
state = OUTPUT_SHOULD_START;
}

View file

@ -1,245 +0,0 @@
/* Copyright 2016-2020 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "audio.h"
#include "ch.h"
#include "hal.h"
/*
Audio Driver: DAC
which utilizes both channels of the DAC unit many STM32 are equipped with to output a modulated square-wave, from precomputed samples stored in a buffer, which is passed to the hardware through DMA
this driver can either be used to drive to separate speakers, wired to A4+Gnd and A5+Gnd, which allows two tones to be played simultaneously
OR
one speaker wired to A4+A5 with the AUDIO_PIN_ALT_AS_NEGATIVE define set - see docs/feature_audio
*/
#if !defined(AUDIO_PIN)
# pragma message "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under 'ARM (DAC basic)' for available options."
// TODO: make this an 'error' instead; go through a breaking change, and add AUDIO_PIN A5 to all keyboards currently using AUDIO on STM32 based boards? - for now: set the define here
# define AUDIO_PIN A5
#endif
// check configuration for ONE speaker, connected to both DAC pins
#if defined(AUDIO_PIN_ALT_AS_NEGATIVE) && !defined(AUDIO_PIN_ALT)
# error "Audio feature: AUDIO_PIN_ALT_AS_NEGATIVE set, but no pin configured as AUDIO_PIN_ALT"
#endif
#ifndef AUDIO_PIN_ALT
// no ALT pin defined is valid, but the c-ifs below need some value set
# define AUDIO_PIN_ALT -1
#endif
#if !defined(AUDIO_STATE_TIMER)
# define AUDIO_STATE_TIMER GPTD8
#endif
// square-wave
static const dacsample_t dac_buffer_1[AUDIO_DAC_BUFFER_SIZE] = {
// First half is max, second half is 0
[0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = AUDIO_DAC_SAMPLE_MAX,
[AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = 0,
};
// square-wave
static const dacsample_t dac_buffer_2[AUDIO_DAC_BUFFER_SIZE] = {
// opposite of dac_buffer above
[0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = 0,
[AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = AUDIO_DAC_SAMPLE_MAX,
};
GPTConfig gpt6cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE,
.callback = NULL,
.cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */
.dier = 0U};
GPTConfig gpt7cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE,
.callback = NULL,
.cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */
.dier = 0U};
static void gpt_audio_state_cb(GPTDriver *gptp);
GPTConfig gptStateUpdateCfg = {.frequency = 10,
.callback = gpt_audio_state_cb,
.cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */
.dier = 0U};
static const DACConfig dac_conf_ch1 = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT};
static const DACConfig dac_conf_ch2 = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT};
/**
* @note The DAC_TRG(0) here selects the Timer 6 TRGO event, which is triggered
* on the rising edge after 3 APB1 clock cycles, causing our gpt6cfg1.frequency
* to be a third of what we expect.
*
* Here are all the values for DAC_TRG (TSEL in the ref manual)
* TIM15_TRGO 0b011
* TIM2_TRGO 0b100
* TIM3_TRGO 0b001
* TIM6_TRGO 0b000
* TIM7_TRGO 0b010
* EXTI9 0b110
* SWTRIG 0b111
*/
static const DACConversionGroup dac_conv_grp_ch1 = {.num_channels = 1U, .trigger = DAC_TRG(0b000)};
static const DACConversionGroup dac_conv_grp_ch2 = {.num_channels = 1U, .trigger = DAC_TRG(0b010)};
void channel_1_start(void) {
gptStart(&GPTD6, &gpt6cfg1);
gptStartContinuous(&GPTD6, 2U);
palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG);
}
void channel_1_stop(void) {
gptStopTimer(&GPTD6);
palSetPadMode(GPIOA, 4, PAL_MODE_OUTPUT_PUSHPULL);
palSetPad(GPIOA, 4);
}
static float channel_1_frequency = 0.0f;
void channel_1_set_frequency(float freq) {
channel_1_frequency = freq;
channel_1_stop();
if (freq <= 0.0) // a pause/rest has freq=0
return;
gpt6cfg1.frequency = 2 * freq * AUDIO_DAC_BUFFER_SIZE;
channel_1_start();
}
float channel_1_get_frequency(void) { return channel_1_frequency; }
void channel_2_start(void) {
gptStart(&GPTD7, &gpt7cfg1);
gptStartContinuous(&GPTD7, 2U);
palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG);
}
void channel_2_stop(void) {
gptStopTimer(&GPTD7);
palSetPadMode(GPIOA, 5, PAL_MODE_OUTPUT_PUSHPULL);
palSetPad(GPIOA, 5);
}
static float channel_2_frequency = 0.0f;
void channel_2_set_frequency(float freq) {
channel_2_frequency = freq;
channel_2_stop();
if (freq <= 0.0) // a pause/rest has freq=0
return;
gpt7cfg1.frequency = 2 * freq * AUDIO_DAC_BUFFER_SIZE;
channel_2_start();
}
float channel_2_get_frequency(void) { return channel_2_frequency; }
static void gpt_audio_state_cb(GPTDriver *gptp) {
if (audio_update_state()) {
#if defined(AUDIO_PIN_ALT_AS_NEGATIVE)
// one piezo/speaker connected to both audio pins, the generated square-waves are inverted
channel_1_set_frequency(audio_get_processed_frequency(0));
channel_2_set_frequency(audio_get_processed_frequency(0));
#else // two separate audio outputs/speakers
// primary speaker on A4, optional secondary on A5
if (AUDIO_PIN == A4) {
channel_1_set_frequency(audio_get_processed_frequency(0));
if (AUDIO_PIN_ALT == A5) {
if (audio_get_number_of_active_tones() > 1) {
channel_2_set_frequency(audio_get_processed_frequency(1));
} else {
channel_2_stop();
}
}
}
// primary speaker on A5, optional secondary on A4
if (AUDIO_PIN == A5) {
channel_2_set_frequency(audio_get_processed_frequency(0));
if (AUDIO_PIN_ALT == A4) {
if (audio_get_number_of_active_tones() > 1) {
channel_1_set_frequency(audio_get_processed_frequency(1));
} else {
channel_1_stop();
}
}
}
#endif
}
}
void audio_driver_initialize() {
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG);
dacStart(&DACD1, &dac_conf_ch1);
// initial setup of the dac-triggering timer is still required, even
// though it gets reconfigured and restarted later on
gptStart(&GPTD6, &gpt6cfg1);
}
if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) {
palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG);
dacStart(&DACD2, &dac_conf_ch2);
gptStart(&GPTD7, &gpt7cfg1);
}
/* enable the output buffer, to directly drive external loads with no additional circuitry
*
* see: AN4566 Application note: Extending the DAC performance of STM32 microcontrollers
* Note: Buffer-Off bit -> has to be set 0 to enable the output buffer
* Note: enabling the output buffer imparts an additional dc-offset of a couple mV
*
* this is done here, reaching directly into the stm32 registers since chibios has not implemented BOFF handling yet
* (see: chibios/os/hal/ports/STM32/todo.txt '- BOFF handling in DACv1.'
*/
DACD1.params->dac->CR &= ~DAC_CR_BOFF1;
DACD2.params->dac->CR &= ~DAC_CR_BOFF2;
// start state-updater
gptStart(&AUDIO_STATE_TIMER, &gptStateUpdateCfg);
}
void audio_driver_stop(void) {
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
gptStopTimer(&GPTD6);
// stop the ongoing conversion and put the output in a known state
dacStopConversion(&DACD1);
dacPutChannelX(&DACD1, 0, AUDIO_DAC_OFF_VALUE);
}
if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) {
gptStopTimer(&GPTD7);
dacStopConversion(&DACD2);
dacPutChannelX(&DACD2, 0, AUDIO_DAC_OFF_VALUE);
}
gptStopTimer(&AUDIO_STATE_TIMER);
}
void audio_driver_start(void) {
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
dacStartConversion(&DACD1, &dac_conv_grp_ch1, (dacsample_t *)dac_buffer_1, AUDIO_DAC_BUFFER_SIZE);
}
if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) {
dacStartConversion(&DACD2, &dac_conv_grp_ch2, (dacsample_t *)dac_buffer_2, AUDIO_DAC_BUFFER_SIZE);
}
gptStartContinuous(&AUDIO_STATE_TIMER, 2U);
}

View file

@ -1,40 +0,0 @@
/* Copyright 2020 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#if !defined(AUDIO_PWM_DRIVER)
// NOTE: Timer2 seems to be used otherwise in QMK, otherwise we could default to A5 (= TIM2_CH1, with PWMD2 and alternate-function(1))
# define AUDIO_PWM_DRIVER PWMD1
#endif
#if !defined(AUDIO_PWM_CHANNEL)
// NOTE: sticking to the STM data-sheet numbering: TIMxCH1 to TIMxCH4
// default: STM32F303CC PA8+TIM1_CH1 -> 1
# define AUDIO_PWM_CHANNEL 1
#endif
#if !defined(AUDIO_PWM_PAL_MODE)
// pin-alternate function: see the data-sheet for which pin needs what AF to connect to TIMx_CHy
// default: STM32F303CC PA8+TIM1_CH1 -> 6
# define AUDIO_PWM_PAL_MODE 6
#endif
#if !defined(AUDIO_STATE_TIMER)
// timer used to trigger updates in the audio-system, configured/enabled in chibios mcuconf.
// Tim6 is the default for "larger" STMs, smaller ones might not have this one (enabled) and need to switch to a different one (e.g.: STM32F103 has only Tim1-Tim4)
# define AUDIO_STATE_TIMER GPTD6
#endif

View file

@ -1,144 +0,0 @@
/* Copyright 2020 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Audio Driver: PWM
the duty-cycle is always kept at 50%, and the pwm-period is adjusted to match the frequency of a note to be played back.
this driver uses the chibios-PWM system to produce a square-wave on specific output pins that are connected to the PWM hardware.
The hardware directly toggles the pin via its alternate function. see your MCUs data-sheet for which pin can be driven by what timer - looking for TIMx_CHy and the corresponding alternate function.
*/
#include "audio.h"
#include "ch.h"
#include "hal.h"
#if !defined(AUDIO_PIN)
# error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings"
#endif
extern bool playing_note;
extern bool playing_melody;
extern uint8_t note_timbre;
static PWMConfig pwmCFG = {
.frequency = 100000, /* PWM clock frequency */
// CHIBIOS-BUG? can't set the initial period to <2, or the pwm (hard or software) takes ~130ms with .frequency=500000 for a pwmChangePeriod to take effect; with no output=silence in the meantime
.period = 2, /* initial PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */
.callback = NULL, /* no callback, the hardware directly toggles the pin */
.channels =
{
#if AUDIO_PWM_CHANNEL == 4
{PWM_OUTPUT_DISABLED, NULL}, /* channel 0 -> TIMx_CH1 */
{PWM_OUTPUT_DISABLED, NULL}, /* channel 1 -> TIMx_CH2 */
{PWM_OUTPUT_DISABLED, NULL}, /* channel 2 -> TIMx_CH3 */
{PWM_OUTPUT_ACTIVE_HIGH, NULL} /* channel 3 -> TIMx_CH4 */
#elif AUDIO_PWM_CHANNEL == 3
{PWM_OUTPUT_DISABLED, NULL},
{PWM_OUTPUT_DISABLED, NULL},
{PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH3 */
{PWM_OUTPUT_DISABLED, NULL}
#elif AUDIO_PWM_CHANNEL == 2
{PWM_OUTPUT_DISABLED, NULL},
{PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH2 */
{PWM_OUTPUT_DISABLED, NULL},
{PWM_OUTPUT_DISABLED, NULL}
#else /*fallback to CH1 */
{PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH1 */
{PWM_OUTPUT_DISABLED, NULL},
{PWM_OUTPUT_DISABLED, NULL},
{PWM_OUTPUT_DISABLED, NULL}
#endif
},
};
static float channel_1_frequency = 0.0f;
void channel_1_set_frequency(float freq) {
channel_1_frequency = freq;
if (freq <= 0.0) // a pause/rest has freq=0
return;
pwmcnt_t period = (pwmCFG.frequency / freq);
pwmChangePeriod(&AUDIO_PWM_DRIVER, period);
pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1,
// adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH
PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100));
}
float channel_1_get_frequency(void) { return channel_1_frequency; }
void channel_1_start(void) {
pwmStop(&AUDIO_PWM_DRIVER);
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
}
void channel_1_stop(void) { pwmStop(&AUDIO_PWM_DRIVER); }
static void gpt_callback(GPTDriver *gptp);
GPTConfig gptCFG = {
/* a whole note is one beat, which is - per definition in musical_notes.h - set to 64
the longest note is BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4
the tempo (which might vary!) is in bpm (beats per minute)
therefore: if the timer ticks away at .frequency = (60*64)Hz,
and the .interval counts from 64 downwards - audio_update_state is
called just often enough to not miss any notes
*/
.frequency = 60 * 64,
.callback = gpt_callback,
};
void audio_driver_initialize(void) {
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
// connect the AUDIO_PIN to the PWM hardware
#if defined(USE_GPIOV1) // STM32F103C8
palSetLineMode(AUDIO_PIN, PAL_MODE_ALTERNATE_PUSHPULL);
#else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command)
palSetLineMode(AUDIO_PIN, PAL_MODE_ALTERNATE(AUDIO_PWM_PAL_MODE));
#endif
gptStart(&AUDIO_STATE_TIMER, &gptCFG);
}
void audio_driver_start(void) {
channel_1_stop();
channel_1_start();
if (playing_note || playing_melody) {
gptStartContinuous(&AUDIO_STATE_TIMER, 64);
}
}
void audio_driver_stop(void) {
channel_1_stop();
gptStopTimer(&AUDIO_STATE_TIMER);
}
/* a regular timer task, that checks the note to be currently played
* and updates the pwm to output that frequency
*/
static void gpt_callback(GPTDriver *gptp) {
float freq; // TODO: freq_alt
if (audio_update_state()) {
freq = audio_get_processed_frequency(0); // freq_alt would be index=1
channel_1_set_frequency(freq);
}
}

View file

@ -1,164 +0,0 @@
/* Copyright 2020 Jack Humbert
* Copyright 2020 JohSchneider
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Audio Driver: PWM
the duty-cycle is always kept at 50%, and the pwm-period is adjusted to match the frequency of a note to be played back.
this driver uses the chibios-PWM system to produce a square-wave on any given output pin in software
- a pwm callback is used to set/clear the configured pin.
*/
#include "audio.h"
#include "ch.h"
#include "hal.h"
#if !defined(AUDIO_PIN)
# error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings"
#endif
extern bool playing_note;
extern bool playing_melody;
extern uint8_t note_timbre;
static void pwm_audio_period_callback(PWMDriver *pwmp);
static void pwm_audio_channel_interrupt_callback(PWMDriver *pwmp);
static PWMConfig pwmCFG = {
.frequency = 100000, /* PWM clock frequency */
// CHIBIOS-BUG? can't set the initial period to <2, or the pwm (hard or software) takes ~130ms with .frequency=500000 for a pwmChangePeriod to take effect; with no output=silence in the meantime
.period = 2, /* initial PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */
.callback = pwm_audio_period_callback,
.channels =
{
// software-PWM just needs another callback on any channel
{PWM_OUTPUT_ACTIVE_HIGH, pwm_audio_channel_interrupt_callback}, /* channel 0 -> TIMx_CH1 */
{PWM_OUTPUT_DISABLED, NULL}, /* channel 1 -> TIMx_CH2 */
{PWM_OUTPUT_DISABLED, NULL}, /* channel 2 -> TIMx_CH3 */
{PWM_OUTPUT_DISABLED, NULL} /* channel 3 -> TIMx_CH4 */
},
};
static float channel_1_frequency = 0.0f;
void channel_1_set_frequency(float freq) {
channel_1_frequency = freq;
if (freq <= 0.0) // a pause/rest has freq=0
return;
pwmcnt_t period = (pwmCFG.frequency / freq);
pwmChangePeriod(&AUDIO_PWM_DRIVER, period);
pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1,
// adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH
PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100));
}
float channel_1_get_frequency(void) { return channel_1_frequency; }
void channel_1_start(void) {
pwmStop(&AUDIO_PWM_DRIVER);
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
pwmEnablePeriodicNotification(&AUDIO_PWM_DRIVER);
pwmEnableChannelNotification(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1);
}
void channel_1_stop(void) {
pwmStop(&AUDIO_PWM_DRIVER);
palClearLine(AUDIO_PIN); // leave the line low, after last note was played
#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
palClearLine(AUDIO_PIN_ALT); // leave the line low, after last note was played
#endif
}
// generate a PWM signal on any pin, not necessarily the one connected to the timer
static void pwm_audio_period_callback(PWMDriver *pwmp) {
(void)pwmp;
palClearLine(AUDIO_PIN);
#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
palSetLine(AUDIO_PIN_ALT);
#endif
}
static void pwm_audio_channel_interrupt_callback(PWMDriver *pwmp) {
(void)pwmp;
if (channel_1_frequency > 0) {
palSetLine(AUDIO_PIN); // generate a PWM signal on any pin, not necessarily the one connected to the timer
#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
palClearLine(AUDIO_PIN_ALT);
#endif
}
}
static void gpt_callback(GPTDriver *gptp);
GPTConfig gptCFG = {
/* a whole note is one beat, which is - per definition in musical_notes.h - set to 64
the longest note is BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4
the tempo (which might vary!) is in bpm (beats per minute)
therefore: if the timer ticks away at .frequency = (60*64)Hz,
and the .interval counts from 64 downwards - audio_update_state is
called just often enough to not miss anything
*/
.frequency = 60 * 64,
.callback = gpt_callback,
};
void audio_driver_initialize(void) {
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
palSetLineMode(AUDIO_PIN, PAL_MODE_OUTPUT_PUSHPULL);
palClearLine(AUDIO_PIN);
#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
palSetLineMode(AUDIO_PIN_ALT, PAL_MODE_OUTPUT_PUSHPULL);
palClearLine(AUDIO_PIN_ALT);
#endif
pwmEnablePeriodicNotification(&AUDIO_PWM_DRIVER); // enable pwm callbacks
pwmEnableChannelNotification(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1);
gptStart(&AUDIO_STATE_TIMER, &gptCFG);
}
void audio_driver_start(void) {
channel_1_stop();
channel_1_start();
if (playing_note || playing_melody) {
gptStartContinuous(&AUDIO_STATE_TIMER, 64);
}
}
void audio_driver_stop(void) {
channel_1_stop();
gptStopTimer(&AUDIO_STATE_TIMER);
}
/* a regular timer task, that checks the note to be currently played
* and updates the pwm to output that frequency
*/
static void gpt_callback(GPTDriver *gptp) {
float freq; // TODO: freq_alt
if (audio_update_state()) {
freq = audio_get_processed_frequency(0); // freq_alt would be index=1
channel_1_set_frequency(freq);
}
}