diff options
author | Drashna Jaelre <drashna@live.com> | 2021-02-14 14:40:38 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-15 09:40:38 +1100 |
commit | c80e5f9f8868ccaa8cb990be6f4da3f1011c2b78 (patch) | |
tree | 146b27153da876b0d068c512691536e8aa9a3769 /quantum/audio | |
parent | f53e41ac81662a560a299a23c7863dd2f618a1f8 (diff) |
Audio system overhaul (#11820)
* Redo Arm DAC implementation for additive, wavetable synthesis, sample playback
changes by Jack Humbert on an implementation for DAC audio on arm/chibios platforms
this commits bundles the changes from the arm-dac-work branch focused on audio/audio_arm.* into one commit (leaving out the test-keyboard)
f52faeb5d (origin/arm-dac-work) add sample and wavetable examples, parsers for both
-> only the changes on audio_arm_.*, the keyboard related parts are split off to a separate commit
bfe468ef1 start morphing wavetable
474d100b5 refined a bit
208bee10f play_notes working
3e6478b0b start in-place documentation of dac settings
3e1826a33 fixed blip (rounding error), other waves, added key selection (left/right)
73853d651 5 voices at 44.1khz
dfb401b95 limit voices to working number
9632b3379 configuration for the ez
6241f3f3b notes working in a new way
* Redo Arm DAC implementation for additive, wavetable synthesis, sample playback
changes by Jack Humbert on an implementation for DAC audio on arm/chibios platforms
this commit splits off the plank example keymap from commit
f52faeb5d (origin/arm-dac-work) add sample and wavetable examples, parsers for both
* refactoring: rename audio_ to reflect their supported hardware-platform and audio-generation method: avr vs arm, and pwm vs dac
* refactoring: deducplicate ISR code to update the pwm duty-cycle and period in the avr-pwm-implementation
pulls three copies of the same code into one function
which should improve readability and maintainability :-)
* refactoring: move common code of arm and avr implementation into a separate/new file
* refactoring: audio_avr_pwm, renaming defines to decouple them from actually used timers, registers and ISRs
* refactoring: audio_avr_pwm - replacing function defines with plain register defines
aligns better with other existing qmk code (and the new audio_arm_pwm) doing similar pwm thing
* add audio-arm-pwm
since not all STM32 have a DAC onboard (STM32F2xx and STM32F3xx), pwm-audio is an alternative (STM32F1xx)
this code works on a "BluePill" clone, with an STM32F103C8B
* clang-format changes on quantum/audio/* only
* audio_arm_dac: stopping the notes caused screeching when using the DAC audio paths
* audio_arm_pwm: use pushpull on the pin; so that a piezzo can be hooked up direclty without additional components (opendrain would require an external pullup)
* refactoring: remove unused file from/for atmel-avr chips
* refactoring: remove unused (avr) wavetable file
* audio_arm_dac: adapt dac_end callback to changed chibios DAC api
the previous chibios (17.6.0) passed along a pointer into the buffer plus a sample_count (which are/already where included in the DACDrivre object) - the current chibios (19.1.0) only passes the driver object.
this patch ports more or less exactly what the previous chibios ISR code did: either have the user-callback work the first or second half of the buffer (dacsample_t pointer, with half the DAC_BUFFER_SIZE samples) by adjusting the pointer and sample count
* audio-arm-dac: show a compile-warning on undefined audio-pins
Co-Authored-By: Drashna Jaelre <drashna@live.com>
* audio_arm_dac: switch from exemplary wavetable generation to sine only
sine+triangle+squrare is exemplary, and not realy fit for "production" use
'stairs' are usefull for debugging (hardware, with an oscilloscope)
* audio_arm_dac: enable output buffers in the STM32
to drive external loads without any additional ciruitry - external opamps and such
* audio: prevent out-of-bounds array access
* audio_arm_dac: add output-frequency correcting factor
* audio_arm_pwm: get both the alternate-function and pm-callback variants back into working condition
and do some code-cleanup, refine documentation, ...
* audio_arm_pwm: increase pwm frequency for "higher fidelity"
on the previous .frequency=100000 higher frequency musical notes came out wrong
(frequency measured on a Tektronix TDS2014B)
note | freq | arm-pwm
C2 | 65.4 | 65.491
C5 | 523.25 | 523.93
C6 | 1046.5 | 1053.38
C7 | 2093 | 2129
C8 | 4186 | 4350.91
with .frequency = 500000
C8 | 4186 | 4204.6
* audio refactoring: remove unused variables
* audio_arm_dac: calibrate note tempo: with a tempo of 60beats-per-second a whole-note should last for exactly one second
* audio: allow feature selection in rules.mk
so the user can switch the audio driver between DAC and PWM on STM32 boards which support both (STM32F2 and up)
or select the "pin alternate" pwm mode, for example on STM32F103
* audio-refactoring: move codeblocks in audio.[ch] into more coherent groups
and add some inline documentation
* audio-refactoring: cleanup and streamline common code between audio_arm_[dac|pwm]
untangeling the relation between audio.c and the two drivers
and adding more documenting comments :-)
* audio_avr_pwm: getting it back into working condition, and cleanup+refactor
* audio-refactoring: documentation and typo fixes
Co-Authored-By: Nick Brassel <nick@tzarc.org>
* audio-refactoring: cleanup defines, inludes and remove debug-prints
* audio_chibios_dac: define&use a minimal sampling rate, based on the available tone-range
to ease up on the cpu-load, while still rendering the higher notes/tones sufficiently
also reenable the lower tones, since with the new implementation there is no evidence of them still beeing 'bugged'
* audio-refactoring: one common AUDIO_MAX_VOICES define for all audio-drivers
* audio-chibios-pwm: pwm-pin-allternate: make the the timer, timer-channel and alternate function user-#definable
* audio_chibios_dac: math.h has fmod for this
* Redo Arm DAC implementation for additive, wavetable synthesis, sample playback
update Jack Humberts dac-example keymaps for the slight changes in the audio-dac interface
* audio-refactoring: use a common AUDIO_PIN configuration switch instead of defines
have the user select a pin by configuration in rules.mk instead of a define in config.h
has the advantage of beeing in a common form/pattern across all audio-driver implementations
* audio-refactoring: switch backlight_avr.c to the new AUDIO_PIN defines
* audio-common: have advance_note return a boolean if the note changed, to the next one in the melody beeing played
* audio-chibios-pwm: fix issue with ~130ms silence between note/frequency changes while playing a SONG
through trial,error and a scope/logic analyzer figured out Chibios-PWMDriver (at least in the current version) misbehaves if the initial period is set to zero (or one; two seems to work); when thats the case subsequent calls to 'pwmChhangePeriod' + pwmEnableChannel took ~135ms of silence, before the PWM continued with the new frequency...
* audio-refactoring: get 'play_note' working again
with a limited number of available voices (say AUDIO_VOICES_MAX=1) allow new frequencies to be played, by discarding the oldest one in the 'frequencies' queue
* audio: set the fallback driver to DAC for chibios and PWM for all others (==avr at the moment)
* audio-refactoring: moore documentation
and some cleanup
* audio-avr-pwm: no fallback on unset AUDIO_PIN
this seems to be the expected behaviour by some keyboards (looking at ckeys/handwire_101:default) which otherwise fail to build because the firmware-image ends up beeing too large for the atmega... so we fail silently instead to keep travis happy
* audio-refactoring: untangling terminology: voice->tone
the code actually was working on tones (combination of pitch/frequency, duration, timbre, intensity/volume) and not voices (characteristic sound of an instrument; think piano vs guitar, which can be played together, each having its own "track" = voice on a music sheet)
* audio-pwm: allow freq=0 aka a pause/rest in a SONG
continue processing, but do not enable pwm units, since freq=0 wouldn't produce any sound anyway (and lead to division by zero on that occasion)
* audio-refactoring: audio_advance_note -> audio_advance_state
since it does not only affect 'one note', but the internally kept state as a whole
* audio-refactoring: untangling terminology: polyphony
the feature om the "inherited" avr code has little to do with polyphony (see wikipedia), but is more a time-multiplexing feature, to work around hardware limitations - like only having one pwm channel, that could on its own only reproduce one voice/instrument at a time
* audio-chibios-dac: add zero-crossing feature
have tones only change/stop when the waveform approaches zero - to avoid audible clicks
note that this also requires the samples to start at zero, since the internally kept index into the samples is reset to zero too
* audio-refactoring: feature: time-multiplexing of tones on a single output channel
this feature was in the original avr-pwm implementation misnomed as "polyphony"
with polyphony_rate and so on; did the same thing though: time-multiplexing multiple active notes so that a single output channel could reproduce more than one note at a time (which is not the same as a polyphony - see wikipedia :-) )
* audio-avr-pwm: get music-mode working (again) on AVRs
with both pwm channels, or either one of the two :-)
play_notes worked already - but music_mode uses play_note
* audio-refactoring: split define MAX_SIMULTANEOUS_TONES -> TONE_STACKSIZE
since the two cases are independant from one another, the hardware might impose limitations on the number of simultaneously reproducable tones, but the audio state should be able to track an unrelated number of notes recently started by play_note
* audio-arm-dac: per define selectable sample-luts
plus generation script in ./util
* audio-refactoring: heh, avr has a MIN...
* audio-refactoring: add basic dac audio-driver based on the current/master implementation
whereas current=d96380e65496912e0f68e6531565f4b45efd1623
which is the state of things before this whole audio-refactoring branch
boiled down to interface with the refactored audio system = removing all
redundant state-managing and frequency calculation
* audio-refactoring: rename audio-drivers to driver_$PLATFORM_$DRIVER
* audio-arm-pwm: split the software/hardware implementations into separate files
which saves us partially from a 'define hell', with the tradeoff that now two somewhat similar chibios_pwm implementations have to be maintained
* audio-refactoring: update documentation
* audio-arm-dac: apply AUDIO_PIN defines to driver_chibios_dac_basic
* audio-arm-dac: dac_additive: stop the hardware when the last sample completed
the audio system calls for a driver_stop, which is delayed until the current sample conversion finishes
* audio-refactoring: make function-namespace consistent
- all (public) audio functions start with audio_
- also refactoring play*_notes/tones to play*_melody, to visually distance it a bit from play*_tone/_note
* audio-refactoring: consistent define namespace: DAC_ -> AUDIO_DAC_
* audio-arm-dac: update (inline) documentation regarding MAX for sample values
* audio-chibios-dac: remove zero-crossing feature
didn't quite work as intended anyway, and stopping the hardware on close-to-zero seems to be enought anyway
* audio-arm-dac: dac_basic: respect the configured sample-rate
* audio-arm-pwm: have 'note_timbre' influence the pwm-duty cycle
like it already does in the avr implementation
* audio-refactoring: get VIBRATO working (again)
with all drivers (verified with chibios_[dac|pwm])
* audio-arm-dac: zero-crossing feature (Mk II)
wait for the generated waveform to approach 'zero' before either turning off the output+timer or switching to the current set of active_tones
* audio-refactoring: re-add note-resting -> introduce short_rest inbetween
- introduce a short pause/rest between two notes of the same frequency, to separate them audibly
- also updating the refactoring comments
* audio-refactoring: cleanup refactoring remnants
remove the former avr-isr code block - since all its features are now refactored into the different parts of the current system
also updates the TODOS
* audio-refactoring: reserve negative numbers as unitialized frequencies
to allow the valid tone/frequency f=0Hz == rest/pause
* audio-refactoring: FIX: first note of melody was missing
the first note was missing because 'goto_next_note'=false overrode a state_change=true of the initial play_tone
and some code-indentations/cleanup of related parts
* audio-arm-dac: fix hardware init-click
due to wron .init= value
* audio-refactoring: new conveniance function: audio_play_click
which can be used to further refactor/remove fauxclicky (avr only) and/or the 'clicky' features
* audio-refactoring: clang-format on quantum/audio/*
* audio-avr-pwm: consecutive notes of the same frequency get a pause inserted inbetween by audio.c
* audio-refactoring: use milliseconds instead of seconds for 'click' parameters
clicks are supposed to be short, seconds make little sense
* audio-refactoring: use timer ticks instead of counters
local counters were used in the original (avr)ISR to advance an index into the lookup tables (for vibrato), and something similar was used for the tone-multiplexing feature
decoupling these from the (possibly irregular) calls to advance_state made sesne, since those counters/lookups need to be in relation to a wall-time anyway
* audio-refactoring: voices.c: drop 'envelope_index' counter in favour of timer ticks
* audio-refactoring: move vibrato and timbre related parts from audio.c to voices.c
also drops the now (globally) unused AUDIO_VIBRATO/AUDIO_ENABLE_VIBRATO defines
* audio.c: use system-ticks instead of counters the drivers have to take care of for the internal state posision
since there already is a system-tick with ms resolution, keeping count separatly with each driver implementation makes little sense; especially since they had to take special care to call audio_advance_state with the correct step/end parameters for the audio state to advance regularly and with the correct pace
* audio.c: stop notes after new ones have been started
avoids brief states of with no notes playing that would otherwise stop the hardware and might lead to clicks
* audio.c: bugfix: actually play a pause
instead of just idling/stopping which lead the pwm drivers to stop entirely...
* audio-arm-pwm: pwm-software: add inverted output
new define AUDIO_PIN_ALT_AS_NEGATIVE will generate an inverted signal on the alternate pin, which boosts the volume if a piezo is connected to both AUDIO_PIN and AUDIO_PIN_ALT
* audio-arm-dac: basic: handle piezo configured&wired to both audio pins
* audio-refactoring: docs: update for AUDIO_PIN_ALT_AS_NEGATIVE and piezo wiring
* audio.c: bugfix: use timer_elapsed32 instad of keeping timestamps
avoids running into issues when the uint32 of the timer overflows
* audio-refactoring: add 'pragma once' and remove deprecated NOTE_REST
* audio_arm_dac: basic: add missing bracket
* audio.c: fix delta calculation
was in the wrong place, needs to use the 'last_timestamp' before it was reset
* audio-refactoring: buildfix: wrong legacy macro for set_timbre
* audio.c: 16bit timerstamps suffice
* audio-refactoring: separate includes for AVR and chibios
* audio-refactoring: timbre: use uint8 instead of float
* audio-refactoring: duration: use uint16 for internal per-tone/note state
* audio-refactoring: tonemultiplexing: use uint16 instead of float
* audio-arm-dac: additive: set second pin output-low
used when a piezo is connected to AUDIO_PIN and AUDIO_PIN_ALT, with PIN_ALT_AS_NEGATIVE
* audio-refactoring: move AUDIO_PIN selection from rules.mk to config.h
to be consistent with how other features are handled in QMK
* audio-refactoring: buildfix: wrong legacy macro for set_tempo
* audio-arm-dac: additive: set second pin output-low -- FIXUP
* audio.c: do duration<>ms conversion in uint instead of float
on AVR, to save a couple of bytes in the firmware size
* audio-refactoring: cleanup eeprom defines/usage
for ARM, avr is handled automagically through the avr libc and common_features.mk
Co-Authored-By: Drashna Jaelre <drashna@live.com>
* audio.h: throw an error if OFF is larger than MAX
* audio-arm-dac: basic: actually stop the dac-conversion on a audio_driver_stop
to put the output pin in a known state == AUDIO_DAC_OFF_VALUE, instead of just leaving them where the last conversion was... with AUDIO_PIN_ALT_AS_NEGATIVE this meant one output was left HIGH while the other was left LOW
one CAVEAT: due to this change the opposing squarewave when using both A4 and A5 with AUDIO_PIN_ALT_AS_NEGATIVE
show extra pulses at the beginning/end on one of the outputs, the two waveforms are in sync otherwise.
the extra pusles probably matter little, since this is no high-fidelity sound generation :P
* audio-arm-dac: additive: move zero-crossing code out of dac_value_generate
which is/should be user-overridable == simple, and doing one thing: providing sample values
state-transitions necessary for the zero crossing are better handled in the surrounding loop in the dac_end callback
* audio-arm-dac: dac-additive: zero-crossing: ramping up or down
after a start trigger ramp up: generate values until zero=OFF_VALUE is reached, then continue normally
same in reverse for strop trigger: output values until zero is reached/crossed, then keep OFF_VALUE on the output
* audio-arm-dac: dac-additive: BUGFIX: return OFF_VALUE when a pause is playing
fixes a bug during SONG playback, which suddenly stopped when it encoutnered a pause
* audio-arm-dac: set a sensible default for AUDIO_DAC_VALUE_OFF
1/2 MAX was probably exemplary, can't think of a setup where that would make sense :-P
* audio-arm-dac: update synth_sample/_wavetable for new pin-defines
* audio-arm-dac: default for AUDIO_DAC_VALUE_OFF
turned out that zero or max are bad default choices:
when multiple tones are played (>>5) and released at the same time (!), due to the complex waveform never reaching 'zero' the output can take quite a while to reach zero, and hence the zero-crossing code only "releases" the output waaay to late
* audio-arm-dac: additive: use DAC for negative pin
instead of PAL, which only allows the pin to be configured as output; LOW or HIGH
* audio-arm-dac: more compile-time configuration checks
* audio-refactoring: typo fixed
* audio-refactoring: clang-format on quantum/audio/*
* audio-avr-pwm: add defines for B-pin as primary/only speaker
also updates documentation.
* audio-refactoring: update documentation with proton-c config.h example
* audio-refactoring: move glissando (TODO) to voices.c
refactored/saved from the original glissando implementation in then upstream-master:audio_avr.c
still needs some work though, as it is now the calculation *should* work, but the start-frequency needs to be tracked somewhere/somehow; not only during a SONG playback but also with user input?
* audio-refactoring: cleanup: one round of aspell -c
* audio-avr-pwm: back to AUDIO_PIN
since config_common.h expands them to plain integers, the AUDIO_PIN define can directly be compared to e.g. B5
so there is no need to deal with separate defines like AUDIO_PIN_B5
* audio-refactoring: add technical documentation audio_driver.md
which moves some in-code documentation there
* audio-arm-dac: move AUDIO_PIN checks into c-code
instead of doing everything with the preprocessor, since A4/A5 do not expand to simple integers, preprocessor int-comparison is not possible. but necessary to get a consistent configuration scheme going throughout the audio-code... solution: let c-code handle the different AUDIO_PIN configurations instead (and leave code/size optimizations to the compiler)
* audio-arm-dac: compile-fix: set AUDIO_PIN if unset
workaround to get the build going again, and be backwarts compatible to arm-keyboards which not yet set the AUDIO_PIN define. until the define is enforced through an '#error"
* audio-refactoring: document tone-multiplexing feature
* audio-refactoring: Apply suggestions from documentation review
Co-authored-by: James Young <18669334+noroadsleft@users.noreply.github.com>
* audio-refactoring: Update docs/audio_driver.md
* audio-refactoring: docs: fix markdown newlines
Terminating a line in Markdown with <space>-<space>-<linebreak> creates an HTML single-line break (<br>).
Co-authored-by: James Young <18669334+noroadsleft@users.noreply.github.com>
* audio-arm-dac: additive: fix AUDIO_PIN_ALT handling
* audio-arm-pwm: align define naming with other drivers
Co-authored-by: Joel Challis <git@zvecr.com>
* audio-refactoring: set detault tempo to 120
and add documentation for the override
* audio-refactoring: update backlight define checks to new AUDIO_PIN names
* audio-refactoring: reworking PWM related defines
to be more consistent with other QMK code
Co-authored-by: Joel Challis <git@zvecr.com>
* audio-arm: have the state-update-timer user configurable
defaulting to GPTD6 or GPTD8 for stm32f2+ (=proton-c)
stm32f1 might need to set this to GPTD4, since 6 and 8 are not available
* audio-refactoring: PLAY_NOTE_ARRAY was already removed in master
* Add prototype for startup
* Update chibiOS dac basic to disable pins on stop
* Add defaults for Proton C
* avoid hanging audio if note is completely missed
* Don't redefine pins if they're already defined
* Define A4 and A5 for CTPC support
* Add license headers to keymap files
* Remove figlet? comments
* Add DAC config to audio driver docs
* Apply suggestions from code review
Co-authored-by: Jack Humbert <jack.humb@gmail.com>
* Add license header to py files
* correct license header
* Add JohSchneider's name to modified files
AKA credit where credit's due
* Set executable permission and change interpeter
* Add 'wave' to pip requirements
* Improve documentation
* Add some settings I missed
* Strip AUDIO_DRIVER to parse the name correctly
* fix depreciated
* Update util/audio_generate_dac_lut.py
Co-authored-by: Jack Humbert <jack.humb@gmail.com>
* Fix type in clueboard config
* Apply suggestions from tzarc
Co-authored-by: Nick Brassel <nick@tzarc.org>
Co-authored-by: Johannes <you@example.com>
Co-authored-by: JohSchneider <JohSchneider@googlemail.com>
Co-authored-by: Nick Brassel <nick@tzarc.org>
Co-authored-by: James Young <18669334+noroadsleft@users.noreply.github.com>
Co-authored-by: Joel Challis <git@zvecr.com>
Co-authored-by: Joshua Diamond <josh@windowoffire.com>
Co-authored-by: Jack Humbert <jack.humb@gmail.com>
Diffstat (limited to 'quantum/audio')
-rw-r--r-- | quantum/audio/audio.c | 539 | ||||
-rw-r--r-- | quantum/audio/audio.h | 281 | ||||
-rw-r--r-- | quantum/audio/audio_avr.c | 812 | ||||
-rw-r--r-- | quantum/audio/audio_chibios.c | 721 | ||||
-rw-r--r-- | quantum/audio/audio_pwm.c | 606 | ||||
-rw-r--r-- | quantum/audio/driver_avr_pwm.h | 17 | ||||
-rw-r--r-- | quantum/audio/driver_avr_pwm_hardware.c | 322 | ||||
-rw-r--r-- | quantum/audio/driver_chibios_dac.h | 126 | ||||
-rw-r--r-- | quantum/audio/driver_chibios_dac_additive.c | 335 | ||||
-rw-r--r-- | quantum/audio/driver_chibios_dac_basic.c | 245 | ||||
-rw-r--r-- | quantum/audio/driver_chibios_pwm.h | 40 | ||||
-rw-r--r-- | quantum/audio/driver_chibios_pwm_hardware.c | 144 | ||||
-rw-r--r-- | quantum/audio/driver_chibios_pwm_software.c | 164 | ||||
-rw-r--r-- | quantum/audio/musical_notes.h | 77 | ||||
-rw-r--r-- | quantum/audio/voices.c | 170 | ||||
-rw-r--r-- | quantum/audio/voices.h | 21 | ||||
-rw-r--r-- | quantum/audio/wave.h | 36 |
17 files changed, 2337 insertions, 2319 deletions
diff --git a/quantum/audio/audio.c b/quantum/audio/audio.c new file mode 100644 index 0000000000..46277dd70b --- /dev/null +++ b/quantum/audio/audio.c @@ -0,0 +1,539 @@ +/* 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 "eeconfig.h" +#include "timer.h" +#include "wait.h" + +/* audio system: + * + * audio.[ch] takes care of all overall state, tracking the actively playing + * notes/tones; the notes a SONG consists of; + * ... + * = everything audio-related that is platform agnostic + * + * driver_[avr|chibios]_[dac|pwm] take care of the lower hardware dependent parts, + * specific to each platform and the used subsystem/driver to drive + * the output pins/channels with the calculated frequencies for each + * active tone + * as part of this, the driver has to trigger regular state updates by + * calling 'audio_update_state' through some sort of timer - be it a + * dedicated one or piggybacking on for example the timer used to + * generate a pwm signal/clock. + * + * + * A Note on terminology: + * tone, pitch and frequency are used somewhat interchangeably, in a strict Wikipedia-sense: + * "(Musical) tone, a sound characterized by its duration, pitch (=frequency), + * intensity (=volume), and timbre" + * - intensity/volume is currently not handled at all, although the 'dac_additive' driver could do so + * - timbre is handled globally (TODO: only used with the pwm drivers at the moment) + * + * in musical_note.h a 'note' is the combination of a pitch and a duration + * these are used to create SONG arrays; during playback their frequencies + * are handled as single successive tones, while the durations are + * kept track of in 'audio_update_state' + * + * 'voice' as it is used here, equates to a sort of instrument with its own + * characteristics sound and effects + * the audio system as-is deals only with (possibly multiple) tones of one + * instrument/voice at a time (think: chords). since the number of tones that + * can be reproduced depends on the hardware/driver in use: pwm can only + * reproduce one tone per output/speaker; DACs can reproduce/mix multiple + * when doing additive synthesis. + * + * 'duration' can either be in the beats-per-minute related unit found in + * musical_notes.h, OR in ms; keyboards create SONGs with the former, while + * the internal state of the audio system does its calculations with the later - ms + */ + +#ifndef AUDIO_TONE_STACKSIZE +# define AUDIO_TONE_STACKSIZE 8 +#endif +uint8_t active_tones = 0; // number of tones pushed onto the stack by audio_play_tone - might be more than the hardware is able to reproduce at any single time +musical_tone_t tones[AUDIO_TONE_STACKSIZE]; // stack of currently active tones + +bool playing_melody = false; // playing a SONG? +bool playing_note = false; // or (possibly multiple simultaneous) tones +bool state_changed = false; // global flag, which is set if anything changes with the active_tones + +// melody/SONG related state variables +float (*notes_pointer)[][2]; // SONG, an array of MUSICAL_NOTEs +uint16_t notes_count; // length of the notes_pointer array +bool notes_repeat; // PLAY_SONG or PLAY_LOOP? +uint16_t melody_current_note_duration = 0; // duration of the currently playing note from the active melody, in ms +uint8_t note_tempo = TEMPO_DEFAULT; // beats-per-minute +uint16_t current_note = 0; // index into the array at notes_pointer +bool note_resting = false; // if a short pause was introduced between two notes with the same frequency while playing a melody +uint16_t last_timestamp = 0; + +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING +# ifndef AUDIO_MAX_SIMULTANEOUS_TONES +# define AUDIO_MAX_SIMULTANEOUS_TONES 3 +# endif +uint16_t tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT; +uint8_t tone_multiplexing_index_shift = 0; // offset used on active-tone array access +#endif + +// provided and used by voices.c +extern uint8_t note_timbre; +extern bool glissando; +extern bool vibrato; +extern uint16_t voices_timer; + +#ifndef STARTUP_SONG +# define STARTUP_SONG SONG(STARTUP_SOUND) +#endif +#ifndef AUDIO_ON_SONG +# define AUDIO_ON_SONG SONG(AUDIO_ON_SOUND) +#endif +#ifndef AUDIO_OFF_SONG +# define AUDIO_OFF_SONG SONG(AUDIO_OFF_SOUND) +#endif +float startup_song[][2] = STARTUP_SONG; +float audio_on_song[][2] = AUDIO_ON_SONG; +float audio_off_song[][2] = AUDIO_OFF_SONG; + +static bool audio_initialized = false; +static bool audio_driver_stopped = true; +audio_config_t audio_config; + +void audio_init() { + if (audio_initialized) { + return; + } + + // Check EEPROM +#ifdef EEPROM_ENABLE + if (!eeconfig_is_enabled()) { + eeconfig_init(); + } + audio_config.raw = eeconfig_read_audio(); +#else // EEPROM settings + audio_config.enable = true; +# ifdef AUDIO_CLICKY_ON + audio_config.clicky_enable = true; +# endif +#endif // EEPROM settings + + for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) { + tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + } + + if (!audio_initialized) { + audio_driver_initialize(); + audio_initialized = true; + } + stop_all_notes(); +} + +void audio_startup(void) { + if (audio_config.enable) { + PLAY_SONG(startup_song); + } + + last_timestamp = timer_read(); +} + +void audio_toggle(void) { + if (audio_config.enable) { + stop_all_notes(); + } + audio_config.enable ^= 1; + eeconfig_update_audio(audio_config.raw); + if (audio_config.enable) { + audio_on_user(); + } +} + +void audio_on(void) { + audio_config.enable = 1; + eeconfig_update_audio(audio_config.raw); + audio_on_user(); + PLAY_SONG(audio_on_song); +} + +void audio_off(void) { + PLAY_SONG(audio_off_song); + wait_ms(100); + audio_stop_all(); + audio_config.enable = 0; + eeconfig_update_audio(audio_config.raw); +} + +bool audio_is_on(void) { return (audio_config.enable != 0); } + +void audio_stop_all() { + if (audio_driver_stopped) { + return; + } + + active_tones = 0; + + audio_driver_stop(); + + playing_melody = false; + playing_note = false; + + melody_current_note_duration = 0; + + for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) { + tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + } + + audio_driver_stopped = true; +} + +void audio_stop_tone(float pitch) { + if (pitch < 0.0f) { + pitch = -1 * pitch; + } + + if (playing_note) { + if (!audio_initialized) { + audio_init(); + } + bool found = false; + for (int i = AUDIO_TONE_STACKSIZE - 1; i >= 0; i--) { + found = (tones[i].pitch == pitch); + if (found) { + tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + for (int j = i; (j < AUDIO_TONE_STACKSIZE - 1); j++) { + tones[j] = tones[j + 1]; + tones[j + 1] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + } + break; + } + } + if (!found) { + return; + } + + state_changed = true; + active_tones--; + if (active_tones < 0) active_tones = 0; +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING + if (tone_multiplexing_index_shift >= active_tones) { + tone_multiplexing_index_shift = 0; + } +#endif + if (active_tones == 0) { + audio_driver_stop(); + audio_driver_stopped = true; + playing_note = false; + } + } +} + +void audio_play_note(float pitch, uint16_t duration) { + if (!audio_config.enable) { + return; + } + + if (!audio_initialized) { + audio_init(); + } + + if (pitch < 0.0f) { + pitch = -1 * pitch; + } + + // round-robin: shifting out old tones, keeping only unique ones + // if the new frequency is already amongst the active tones, shift it to the top of the stack + bool found = false; + for (int i = active_tones - 1; i >= 0; i--) { + found = (tones[i].pitch == pitch); + if (found) { + for (int j = i; (j < active_tones - 1); j++) { + tones[j] = tones[j + 1]; + tones[j + 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration}; + } + return; // since this frequency played already, the hardware was already started + } + } + + // frequency/tone is actually new, so we put it on the top of the stack + active_tones++; + if (active_tones > AUDIO_TONE_STACKSIZE) { + active_tones = AUDIO_TONE_STACKSIZE; + // shift out the oldest tone to make room + for (int i = 0; i < active_tones - 1; i++) { + tones[i] = tones[i + 1]; + } + } + state_changed = true; + playing_note = true; + tones[active_tones - 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration}; + + // TODO: needs to be handled per note/tone -> use its timestamp instead? + voices_timer = timer_read(); // reset to zero, for the effects added by voices.c + + if (audio_driver_stopped) { + audio_driver_start(); + audio_driver_stopped = false; + } +} + +void audio_play_tone(float pitch) { audio_play_note(pitch, 0xffff); } + +void audio_play_melody(float (*np)[][2], uint16_t n_count, bool n_repeat) { + if (!audio_config.enable) { + audio_stop_all(); + return; + } + + if (!audio_initialized) { + audio_init(); + } + + // Cancel note if a note is playing + if (playing_note) audio_stop_all(); + + playing_melody = true; + note_resting = false; + + notes_pointer = np; + notes_count = n_count; + notes_repeat = n_repeat; + + current_note = 0; // note in the melody-array/list at note_pointer + + // start first note manually, which also starts the audio_driver + // all following/remaining notes are played by 'audio_update_state' + audio_play_note((*notes_pointer)[current_note][0], audio_duration_to_ms((*notes_pointer)[current_note][1])); + last_timestamp = timer_read(); + melody_current_note_duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); +} + +float click[2][2]; +void audio_play_click(uint16_t delay, float pitch, uint16_t duration) { + uint16_t duration_tone = audio_ms_to_duration(duration); + uint16_t duration_delay = audio_ms_to_duration(delay); + + if (delay <= 0.0f) { + click[0][0] = pitch; + click[0][1] = duration_tone; + click[1][0] = 0.0f; + click[1][1] = 0.0f; + audio_play_melody(&click, 1, false); + } else { + // first note is a rest/pause + click[0][0] = 0.0f; + click[0][1] = duration_delay; + // second note is the actual click + click[1][0] = pitch; + click[1][1] = duration_tone; + audio_play_melody(&click, 2, false); + } +} + +bool audio_is_playing_note(void) { return playing_note; } + +bool audio_is_playing_melody(void) { return playing_melody; } + +uint8_t audio_get_number_of_active_tones(void) { return active_tones; } + +float audio_get_frequency(uint8_t tone_index) { + if (tone_index >= active_tones) { + return 0.0f; + } + return tones[active_tones - tone_index - 1].pitch; +} + +float audio_get_processed_frequency(uint8_t tone_index) { + if (tone_index >= active_tones) { + return 0.0f; + } + + int8_t index = active_tones - tone_index - 1; + // new tones are stacked on top (= appended at the end), so the most recent/current is MAX-1 + +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING + index = index - tone_multiplexing_index_shift; + if (index < 0) // wrap around + index += active_tones; +#endif + + if (tones[index].pitch <= 0.0f) { + return 0.0f; + } + + return voice_envelope(tones[index].pitch); +} + +bool audio_update_state(void) { + if (!playing_note && !playing_melody) { + return false; + } + + bool goto_next_note = false; + uint16_t current_time = timer_read(); + + if (playing_melody) { + goto_next_note = timer_elapsed(last_timestamp) >= melody_current_note_duration; + if (goto_next_note) { + uint16_t delta = timer_elapsed(last_timestamp) - melody_current_note_duration; + last_timestamp = current_time; + uint16_t previous_note = current_note; + current_note++; + voices_timer = timer_read(); // reset to zero, for the effects added by voices.c + + if (current_note >= notes_count) { + if (notes_repeat) { + current_note = 0; + } else { + audio_stop_all(); + return false; + } + } + + if (!note_resting && (*notes_pointer)[previous_note][0] == (*notes_pointer)[current_note][0]) { + note_resting = true; + + // special handling for successive notes of the same frequency: + // insert a short pause to separate them audibly + audio_play_note(0.0f, audio_duration_to_ms(2)); + current_note = previous_note; + melody_current_note_duration = audio_duration_to_ms(2); + + } else { + note_resting = false; + + // TODO: handle glissando here (or remember previous and current tone) + /* there would need to be a freq(here we are) -> freq(next note) + * and do slide/glissando in between problem here is to know which + * frequency on the stack relates to what other? e.g. a melody starts + * tones in a sequence, and stops expiring one, so the most recently + * stopped is the starting point for a glissando to the most recently started? + * how to detect and preserve this relation? + * and what about user input, chords, ...? + */ + + // '- delta': Skip forward in the next note's length if we've over shot + // the last, so the overall length of the song is the same + uint16_t duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); + + // Skip forward past any completely missed notes + while (delta > duration && current_note < notes_count - 1) { + delta -= duration; + current_note++; + duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); + } + + if (delta < duration) { + duration -= delta; + } else { + // Only way to get here is if it is the last note and + // we have completely missed it. Play it for 1ms... + duration = 1; + } + + audio_play_note((*notes_pointer)[current_note][0], duration); + melody_current_note_duration = duration; + } + } + } + + if (playing_note) { +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING + tone_multiplexing_index_shift = (int)(current_time / tone_multiplexing_rate) % MIN(AUDIO_MAX_SIMULTANEOUS_TONES, active_tones); + goto_next_note = true; +#endif + if (vibrato || glissando) { + // force update on each cycle, since vibrato shifts the frequency slightly + goto_next_note = true; + } + + // housekeeping: stop notes that have no playtime left + for (int i = 0; i < active_tones; i++) { + if ((tones[i].duration != 0xffff) // indefinitely playing notes, started by 'audio_play_tone' + && (tones[i].duration != 0) // 'uninitialized' + ) { + if (timer_elapsed(tones[i].time_started) >= tones[i].duration) { + audio_stop_tone(tones[i].pitch); // also sets 'state_changed=true' + } + } + } + } + + // state-changes have a higher priority, always triggering the hardware to update + if (state_changed) { + state_changed = false; + return true; + } + + return goto_next_note; +} + +// Tone-multiplexing functions +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING +void audio_set_tone_multiplexing_rate(uint16_t rate) { tone_multiplexing_rate = rate; } +void audio_enable_tone_multiplexing(void) { tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT; } +void audio_disable_tone_multiplexing(void) { tone_multiplexing_rate = 0; } +void audio_increase_tone_multiplexing_rate(uint16_t change) { + if ((0xffff - change) > tone_multiplexing_rate) { + tone_multiplexing_rate += change; + } +} +void audio_decrease_tone_multiplexing_rate(uint16_t change) { + if (change <= tone_multiplexing_rate) { + tone_multiplexing_rate -= change; + } +} +#endif + +// Tempo functions + +void audio_set_tempo(uint8_t tempo) { + if (tempo < 10) note_tempo = 10; + // else if (tempo > 250) + // note_tempo = 250; + else + note_tempo = tempo; +} + +void audio_increase_tempo(uint8_t tempo_change) { + if (tempo_change > 255 - note_tempo) + note_tempo = 255; + else + note_tempo += tempo_change; +} + +void audio_decrease_tempo(uint8_t tempo_change) { + if (tempo_change >= note_tempo - 10) + note_tempo = 10; + else + note_tempo -= tempo_change; +} + +// TODO in the int-math version are some bugs; songs sometimes abruptly end - maybe an issue with the timer/system-tick wrapping around? +uint16_t audio_duration_to_ms(uint16_t duration_bpm) { +#if defined(__AVR__) + // 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 + return ((uint32_t)duration_bpm * 60 * 1000) / (64 * note_tempo); + // NOTE: beware of uint16_t overflows when note_tempo is low and/or the duration is long +#else + return ((float)duration_bpm * 60) / (64 * note_tempo) * 1000; +#endif +} +uint16_t audio_ms_to_duration(uint16_t duration_ms) { +#if defined(__AVR__) + return ((uint32_t)duration_ms * 64 * note_tempo) / 60 / 1000; +#else + return ((float)duration_ms * 64 * note_tempo) / 60 / 1000; +#endif +} diff --git a/quantum/audio/audio.h b/quantum/audio/audio.h index dccf03d5f6..56b9158a1a 100644 --- a/quantum/audio/audio.h +++ b/quantum/audio/audio.h @@ -1,4 +1,5 @@ -/* Copyright 2016 Jack Humbert +/* 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 @@ -13,28 +14,30 @@ * 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 #include <stdint.h> #include <stdbool.h> -#if defined(__AVR__) -# include <avr/io.h> -#endif -#include "wait.h" #include "musical_notes.h" #include "song_list.h" #include "voices.h" #include "quantum.h" #include <math.h> -// Largely untested PWM audio mode (doesn't sound as good) -// #define PWM_AUDIO - -// #define VIBRATO_ENABLE +#if defined(__AVR__) +# include <avr/io.h> +# if defined(AUDIO_DRIVER_PWM) +# include "driver_avr_pwm.h" +# endif +#endif -// Enable vibrato strength/amplitude - slows down ISR too much -// #define VIBRATO_STRENGTH_ENABLE +#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 +#endif typedef union { uint8_t raw; @@ -45,62 +48,238 @@ typedef union { }; } audio_config_t; -bool is_audio_on(void); +// AVR/LUFA has a MIN, arm/chibios does not +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +/* + * a 'musical note' is represented by pitch and duration; a 'musical tone' adds intensity and timbre + * https://en.wikipedia.org/wiki/Musical_tone + * "A musical tone is characterized by its duration, pitch, intensity (or loudness), and timbre (or quality)" + */ +typedef struct { + uint16_t time_started; // timestamp the tone/note was started, system time runs with 1ms resolution -> 16bit timer overflows every ~64 seconds, long enough under normal circumstances; but might be too soon for long-duration notes when the note_tempo is set to a very low value + float pitch; // aka frequency, in Hz + uint16_t duration; // in ms, converted from the musical_notes.h unit which has 64parts to a beat, factoring in the current tempo in beats-per-minute + // float intensity; // aka volume [0,1] TODO: not used at the moment; pwm drivers can't handle it + // uint8_t timbre; // range: [0,100] TODO: this currently kept track of globally, should we do this per tone instead? +} musical_tone_t; + +// public interface + +/** + * @brief one-time initialization called by quantum/quantum.c + * @details usually done lazy, when some tones are to be played + * + * @post audio system (and hardware) initialized and ready to play tones + */ +void audio_init(void); +void audio_startup(void); + +/** + * @brief en-/disable audio output, save this choice to the eeprom + */ void audio_toggle(void); +/** + * @brief enable audio output, save this choice to the eeprom + */ void audio_on(void); +/** + * @brief disable audio output, save this choice to the eeprom + */ void audio_off(void); +/** + * @brief query the if audio output is enabled + */ +bool audio_is_on(void); -// Vibrato rate functions +/** + * @brief start playback of a tone with the given frequency and duration + * + * @details starts the playback of a given note, which is automatically stopped + * at the the end of its duration = fire&forget + * + * @param[in] pitch frequency of the tone be played + * @param[in] duration in milliseconds, use 'audio_duration_to_ms' to convert + * from the musical_notes.h unit to ms + */ +void audio_play_note(float pitch, uint16_t duration); +// TODO: audio_play_note(float pitch, uint16_t duration, float intensity, float timbre); +// audio_play_note_with_instrument ifdef AUDIO_ENABLE_VOICES -#ifdef VIBRATO_ENABLE +/** + * @brief start playback of a tone with the given frequency + * + * @details the 'frequency' is put on-top the internal stack of active tones, + * as a new tone with indefinite duration. this tone is played by + * the hardware until a call to 'audio_stop_tone'. + * should a tone with that frequency already be active, its entry + * is put on the top of said internal stack - so no duplicate + * entries are kept. + * 'hardware_start' is called upon the first note. + * + * @param[in] pitch frequency of the tone be played + */ +void audio_play_tone(float pitch); -void set_vibrato_rate(float rate); -void increase_vibrato_rate(float change); -void decrease_vibrato_rate(float change); +/** + * @brief stop a given tone/frequency + * + * @details removes a tone matching the given frequency from the internal + * playback stack + * the hardware is stopped in case this was the last/only frequency + * being played. + * + * @param[in] pitch tone/frequency to be stopped + */ +void audio_stop_tone(float pitch); -# ifdef VIBRATO_STRENGTH_ENABLE +/** + * @brief play a melody + * + * @details starts playback of a melody passed in from a SONG definition - an + * array of {pitch, duration} float-tuples + * + * @param[in] np note-pointer to the SONG array + * @param[in] n_count number of MUSICAL_NOTES of the SONG + * @param[in] n_repeat false for onetime, true for looped playback + */ +void audio_play_melody(float (*np)[][2], uint16_t n_count, bool n_repeat); -void set_vibrato_strength(float strength); -void increase_vibrato_strength(float change); -void decrease_vibrato_strength(float change); +/** + * @brief play a short tone of a specific frequency to emulate a 'click' + * + * @details constructs a two-note melody (one pause plus a note) and plays it through + * audio_play_melody. very short durations might not quite work due to + * hardware limitations (DAC: added pulses from zero-crossing feature;...) + * + * @param[in] delay in milliseconds, length for the pause before the pulses, can be zero + * @param[in] pitch + * @param[in] duration in milliseconds, length of the 'click' + */ +void audio_play_click(uint16_t delay, float pitch, uint16_t duration); -# endif +/** + * @brief stops all playback + * + * @details stops playback of both a melody as well as single tones, resetting + * the internal state + */ +void audio_stop_all(void); -#endif +/** + * @brief query if one/multiple tones are playing + */ +bool audio_is_playing_note(void); -// Polyphony functions +/** + * @brief query if a melody/SONG is playing + */ +bool audio_is_playing_melody(void); -void set_polyphony_rate(float rate); -void enable_polyphony(void); -void disable_polyphony(void); -void increase_polyphony_rate(float change); -void decrease_polyphony_rate(float change); +// These macros are used to allow audio_play_melody to play an array of indeterminate +// length. This works around the limitation of C's sizeof operation on pointers. +// The global float array for the song must be used here. +#define NOTE_ARRAY_SIZE(x) ((int16_t)(sizeof(x) / (sizeof(x[0])))) -void set_timbre(float timbre); -void set_tempo(uint8_t tempo); +/** + * @brief convenience macro, to play a melody/SONG once + */ +#define PLAY_SONG(note_array) audio_play_melody(¬e_array, NOTE_ARRAY_SIZE((note_array)), false) +// TODO: a 'song' is a melody plus singing/vocals -> PLAY_MELODY +/** + * @brief convenience macro, to play a melody/SONG in a loop, until stopped by 'audio_stop_all' + */ +#define PLAY_LOOP(note_array) audio_play_melody(¬e_array, NOTE_ARRAY_SIZE((note_array)), true) -void increase_tempo(uint8_t tempo_change); -void decrease_tempo(uint8_t tempo_change); +// Tone-Multiplexing functions +// this feature only makes sense for hardware setups which can't do proper +// audio-wave synthesis = have no DAC and need to use PWM for tone generation +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING +# ifndef AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT +# define AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT 0 +// 0=off, good starting value is 4; the lower the value the higher the cpu-load +# endif +void audio_set_tone_multiplexing_rate(uint16_t rate); +void audio_enable_tone_multiplexing(void); +void audio_disable_tone_multiplexing(void); +void audio_increase_tone_multiplexing_rate(uint16_t change); +void audio_decrease_tone_multiplexing_rate(uint16_t change); +#endif + +// Tempo functions + +void audio_set_tempo(uint8_t tempo); +void audio_increase_tempo(uint8_t tempo_change); +void audio_decrease_tempo(uint8_t tempo_change); + +// conversion macros, from 64parts-to-a-beat to milliseconds and back +uint16_t audio_duration_to_ms(uint16_t duration_bpm); +uint16_t audio_ms_to_duration(uint16_t duration_ms); -void audio_init(void); void audio_startup(void); -#ifdef PWM_AUDIO -void play_sample(uint8_t* s, uint16_t l, bool r); -#endif -void play_note(float freq, int vol); -void stop_note(float freq); -void stop_all_notes(void); -void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat); +// hardware interface -#define SCALE \ - (int8_t[]) { 0 + (12 * 0), 2 + (12 * 0), 4 + (12 * 0), 5 + (12 * 0), 7 + (12 * 0), 9 + (12 * 0), 11 + (12 * 0), 0 + (12 * 1), 2 + (12 * 1), 4 + (12 * 1), 5 + (12 * 1), 7 + (12 * 1), 9 + (12 * 1), 11 + (12 * 1), 0 + (12 * 2), 2 + (12 * 2), 4 + (12 * 2), 5 + (12 * 2), 7 + (12 * 2), 9 + (12 * 2), 11 + (12 * 2), 0 + (12 * 3), 2 + (12 * 3), 4 + (12 * 3), 5 + (12 * 3), 7 + (12 * 3), 9 + (12 * 3), 11 + (12 * 3), 0 + (12 * 4), 2 + (12 * 4), 4 + (12 * 4), 5 + (12 * 4), 7 + (12 * 4), 9 + (12 * 4), 11 + (12 * 4), } +// implementation in the driver_avr/arm_* respective parts +void audio_driver_initialize(void); +void audio_driver_start(void); +void audio_driver_stop(void); -// These macros are used to allow play_notes to play an array of indeterminate -// length. This works around the limitation of C's sizeof operation on pointers. -// The global float array for the song must be used here. -#define NOTE_ARRAY_SIZE(x) ((int16_t)(sizeof(x) / (sizeof(x[0])))) -#define PLAY_SONG(note_array) play_notes(¬e_array, NOTE_ARRAY_SIZE((note_array)), false) -#define PLAY_LOOP(note_array) play_notes(¬e_array, NOTE_ARRAY_SIZE((note_array)), true) +/** + * @brief get the number of currently active tones + * @return number, 0=none active + */ +uint8_t audio_get_number_of_active_tones(void); + +/** + * @brief access to the raw/unprocessed frequency for a specific tone + * @details each active tone has a frequency associated with it, which + * the internal state keeps track of, and is usually influenced + * by various effects + * @param[in] tone_index, ranging from 0 to number_of_active_tones-1, with the + * first being the most recent and each increment yielding the next + * older one + * @return a positive frequency, in Hz; or zero if the tone is a pause + */ +float audio_get_frequency(uint8_t tone_index); + +/** + * @brief calculate and return the frequency for the requested tone + * @details effects like glissando, vibrato, ... are post-processed onto the + * each active tones 'base'-frequency; this function returns the + * post-processed result. + * @param[in] tone_index, ranging from 0 to number_of_active_tones-1, with the + * first being the most recent and each increment yielding the next + * older one + * @return a positive frequency, in Hz; or zero if the tone is a pause + */ +float audio_get_processed_frequency(uint8_t tone_index); + +/** + * @brief update audio internal state: currently playing and active tones,... + * @details This function is intended to be called by the audio-hardware + * specific implementation on a somewhat regular basis while a SONG + * or notes (pitch+duration) are playing to 'advance' the internal + * state (current playing notes, position in the melody, ...) + * + * @return true if something changed in the currently active tones, which the + * hardware might need to react to + */ +bool audio_update_state(void); + +// legacy and back-warts compatibility stuff + +#define is_audio_on() audio_is_on() +#define is_playing_notes() audio_is_playing_melody() +#define is_playing_note() audio_is_playing_note() +#define stop_all_notes() audio_stop_all() +#define stop_note(f) audio_stop_tone(f) +#define play_note(f, v) audio_play_tone(f) -bool is_playing_notes(void); +#define set_timbre(t) voice_set_timbre(t) +#define set_tempo(t) audio_set_tempo(t) +#define increase_tempo(t) audio_increase_tempo(t) +#define decrease_tempo(t) audio_decrease_tempo(t) +// vibrato functions are not used in any keyboards diff --git a/quantum/audio/audio_avr.c b/quantum/audio/audio_avr.c deleted file mode 100644 index 1bac43bb43..0000000000 --- a/quantum/audio/audio_avr.c +++ /dev/null @@ -1,812 +0,0 @@ -/* Copyright 2016 Jack Humbert - * - * 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 <stdio.h> -#include <string.h> -//#include <math.h> -#if defined(__AVR__) -# include <avr/pgmspace.h> -# include <avr/interrupt.h> -# include <avr/io.h> -#endif -#include "print.h" -#include "audio.h" -#include "keymap.h" -#include "wait.h" - -#include "eeconfig.h" - -#define CPU_PRESCALER 8 - -// ----------------------------------------------------------------------------- -// Timer Abstractions -// ----------------------------------------------------------------------------- - -// Currently we support timers 1 and 3 used at the sime time, channels A-C, -// pins PB5, PB6, PB7, PC4, PC5, and PC6 -#if defined(C6_AUDIO) -# define CPIN_AUDIO -# define CPIN_SET_DIRECTION DDRC |= _BV(PORTC6); -# define INIT_AUDIO_COUNTER_3 TCCR3A = (0 << COM3A1) | (0 << COM3A0) | (1 << WGM31) | (0 << WGM30); -# define ENABLE_AUDIO_COUNTER_3_ISR TIMSK3 |= _BV(OCIE3A) -# define DISABLE_AUDIO_COUNTER_3_ISR TIMSK3 &= ~_BV(OCIE3A) -# define ENABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A |= _BV(COM3A1); -# define DISABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A &= ~(_BV(COM3A1) | _BV(COM3A0)); -# define TIMER_3_PERIOD ICR3 -# define TIMER_3_DUTY_CYCLE OCR3A -# define TIMER3_AUDIO_vect TIMER3_COMPA_vect -#endif -#if defined(C5_AUDIO) -# define CPIN_AUDIO -# define CPIN_SET_DIRECTION DDRC |= _BV(PORTC5); -# define INIT_AUDIO_COUNTER_3 TCCR3A = (0 << COM3B1) | (0 << COM3B0) | (1 << WGM31) | (0 << WGM30); -# define ENABLE_AUDIO_COUNTER_3_ISR TIMSK3 |= _BV(OCIE3B) -# define DISABLE_AUDIO_COUNTER_3_ISR TIMSK3 &= ~_BV(OCIE3B) -# define ENABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A |= _BV(COM3B1); -# define DISABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A &= ~(_BV(COM3B1) | _BV(COM3B0)); -# define TIMER_3_PERIOD ICR3 -# define TIMER_3_DUTY_CYCLE OCR3B -# define TIMER3_AUDIO_vect TIMER3_COMPB_vect -#endif -#if defined(C4_AUDIO) -# define CPIN_AUDIO -# define CPIN_SET_DIRECTION DDRC |= _BV(PORTC4); -# define INIT_AUDIO_COUNTER_3 TCCR3A = (0 << COM3C1) | (0 << COM3C0) | (1 << WGM31) | (0 << WGM30); -# define ENABLE_AUDIO_COUNTER_3_ISR TIMSK3 |= _BV(OCIE3C) -# define DISABLE_AUDIO_COUNTER_3_ISR TIMSK3 &= ~_BV(OCIE3C) -# define ENABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A |= _BV(COM3C1); -# define DISABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A &= ~(_BV(COM3C1) | _BV(COM3C0)); -# define TIMER_3_PERIOD ICR3 -# define TIMER_3_DUTY_CYCLE OCR3C -# define TIMER3_AUDIO_vect TIMER3_COMPC_vect -#endif - -#if defined(B5_AUDIO) -# define BPIN_AUDIO -# define BPIN_SET_DIRECTION DDRB |= _BV(PORTB5); -# define INIT_AUDIO_COUNTER_1 TCCR1A = (0 << COM1A1) | (0 << COM1A0) | (1 << WGM11) | (0 << WGM10); -# define ENABLE_AUDIO_COUNTER_1_ISR TIMSK1 |= _BV(OCIE1A) -# define DISABLE_AUDIO_COUNTER_1_ISR TIMSK1 &= ~_BV(OCIE1A) -# define ENABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A |= _BV(COM1A1); -# define DISABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A &= ~(_BV(COM1A1) | _BV(COM1A0)); -# define TIMER_1_PERIOD ICR1 -# define TIMER_1_DUTY_CYCLE OCR1A -# define TIMER1_AUDIO_vect TIMER1_COMPA_vect -#endif -#if defined(B6_AUDIO) -# define BPIN_AUDIO -# define BPIN_SET_DIRECTION DDRB |= _BV(PORTB6); -# define INIT_AUDIO_COUNTER_1 TCCR1A = (0 << COM1B1) | (0 << COM1B0) | (1 << WGM11) | (0 << WGM10); -# define ENABLE_AUDIO_COUNTER_1_ISR TIMSK1 |= _BV(OCIE1B) -# define DISABLE_AUDIO_COUNTER_1_ISR TIMSK1 &= ~_BV(OCIE1B) -# define ENABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A |= _BV(COM1B1); -# define DISABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A &= ~(_BV(COM1B1) | _BV(COM1B0)); -# define TIMER_1_PERIOD ICR1 -# define TIMER_1_DUTY_CYCLE OCR1B -# define TIMER1_AUDIO_vect TIMER1_COMPB_vect -#endif -#if defined(B7_AUDIO) -# define BPIN_AUDIO -# define BPIN_SET_DIRECTION DDRB |= _BV(PORTB7); -# define INIT_AUDIO_COUNTER_1 TCCR1A = (0 << COM1C1) | (0 << COM1C0) | (1 << WGM11) | (0 << WGM10); -# define ENABLE_AUDIO_COUNTER_1_ISR TIMSK1 |= _BV(OCIE1C) -# define DISABLE_AUDIO_COUNTER_1_ISR TIMSK1 &= ~_BV(OCIE1C) -# define ENABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A |= _BV(COM1C1); -# define DISABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A &= ~(_BV(COM1C1) | _BV(COM1C0)); -# define TIMER_1_PERIOD ICR1 -# define TIMER_1_DUTY_CYCLE OCR1C -# define TIMER1_AUDIO_vect TIMER1_COMPC_vect -#endif - -#if !defined(BPIN_AUDIO) && !defined(CPIN_AUDIO) -# error "Audio feature enabled, but no suitable pin selected - see docs/feature_audio.md under the AVR settings for available options." -#endif - -// ----------------------------------------------------------------------------- - -int voices = 0; -int voice_place = 0; -float frequency = 0; -float frequency_alt = 0; -int volume = 0; -long position = 0; - -float frequencies[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -int volumes[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -bool sliding = false; - -float place = 0; - -uint8_t* sample; -uint16_t sample_length = 0; - -bool playing_notes = false; -bool playing_note = false; -float note_frequency = 0; -float note_length = 0; -uint8_t note_tempo = TEMPO_DEFAULT; -float note_timbre = TIMBRE_DEFAULT; -uint16_t note_position = 0; -float (*notes_pointer)[][2]; -uint16_t notes_count; -bool notes_repeat; -bool note_resting = false; - -uint16_t current_note = 0; -uint8_t rest_counter = 0; - -#ifdef VIBRATO_ENABLE -float vibrato_counter = 0; -float vibrato_strength = .5; -float vibrato_rate = 0.125; -#endif - -float polyphony_rate = 0; - -static bool audio_initialized = false; - -audio_config_t audio_config; - -uint16_t envelope_index = 0; -bool glissando = true; - -#ifndef STARTUP_SONG -# define STARTUP_SONG SONG(STARTUP_SOUND) -#endif -#ifndef AUDIO_ON_SONG -# define AUDIO_ON_SONG SONG(AUDIO_ON_SOUND) -#endif -#ifndef AUDIO_OFF_SONG -# define AUDIO_OFF_SONG SONG(AUDIO_OFF_SOUND) -#endif -float startup_song[][2] = STARTUP_SONG; -float audio_on_song[][2] = AUDIO_ON_SONG; -float audio_off_song[][2] = AUDIO_OFF_SONG; - -void audio_init() { - // Check EEPROM - if (!eeconfig_is_enabled()) { - eeconfig_init(); - } - audio_config.raw = eeconfig_read_audio(); - - if (!audio_initialized) { -// Set audio ports as output -#ifdef CPIN_AUDIO - CPIN_SET_DIRECTION - DISABLE_AUDIO_COUNTER_3_ISR; -#endif -#ifdef BPIN_AUDIO - BPIN_SET_DIRECTION - DISABLE_AUDIO_COUNTER_1_ISR; -#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 CPIN_AUDIO - INIT_AUDIO_COUNTER_3 - TCCR3B = (1 << WGM33) | (1 << WGM32) | (0 << CS32) | (1 << CS31) | (0 << CS30); - TIMER_3_PERIOD = (uint16_t)(((float)F_CPU) / (440 * CPU_PRESCALER)); - TIMER_3_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (440 * CPU_PRESCALER)) * note_timbre); -#endif -#ifdef BPIN_AUDIO - INIT_AUDIO_COUNTER_1 - TCCR1B = (1 << WGM13) | (1 << WGM12) | (0 << CS12) | (1 << CS11) | (0 << CS10); - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (440 * CPU_PRESCALER)); - TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (440 * CPU_PRESCALER)) * note_timbre); -#endif - - audio_initialized = true; - } -} - -void audio_startup() { - if (audio_config.enable) { - PLAY_SONG(startup_song); - } -} - -void stop_all_notes() { - dprintf("audio stop all notes"); - - if (!audio_initialized) { - audio_init(); - } - voices = 0; - -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; - DISABLE_AUDIO_COUNTER_1_OUTPUT; -#endif - - playing_notes = false; - playing_note = false; - frequency = 0; - frequency_alt = 0; - volume = 0; - - for (uint8_t i = 0; i < 8; i++) { - frequencies[i] = 0; - volumes[i] = 0; - } -} - -void stop_note(float freq) { - dprintf("audio stop note freq=%d", (int)freq); - - if (playing_note) { - if (!audio_initialized) { - audio_init(); - } - for (int i = 7; i >= 0; i--) { - if (frequencies[i] == freq) { - frequencies[i] = 0; - volumes[i] = 0; - for (int j = i; (j < 7); j++) { - frequencies[j] = frequencies[j + 1]; - frequencies[j + 1] = 0; - volumes[j] = volumes[j + 1]; - volumes[j + 1] = 0; - } - break; - } - } - voices--; - if (voices < 0) voices = 0; - if (voice_place >= voices) { - voice_place = 0; - } - if (voices == 0) { -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; - DISABLE_AUDIO_COUNTER_1_OUTPUT; -#endif - frequency = 0; - frequency_alt = 0; - volume = 0; - playing_note = false; - } - } -} - -#ifdef VIBRATO_ENABLE - -float mod(float a, int b) { - float r = fmod(a, b); - return r < 0 ? r + b : r; -} - -float vibrato(float average_freq) { -# ifdef VIBRATO_STRENGTH_ENABLE - float vibrated_freq = average_freq * pow(vibrato_lut[(int)vibrato_counter], vibrato_strength); -# else - float vibrated_freq = average_freq * vibrato_lut[(int)vibrato_counter]; -# endif - vibrato_counter = mod((vibrato_counter + vibrato_rate * (1.0 + 440.0 / average_freq)), VIBRATO_LUT_LENGTH); - return vibrated_freq; -} - -#endif - -#ifdef CPIN_AUDIO -ISR(TIMER3_AUDIO_vect) { - float freq; - - if (playing_note) { - if (voices > 0) { -# ifdef BPIN_AUDIO - float freq_alt = 0; - if (voices > 1) { - if (polyphony_rate == 0) { - if (glissando) { - if (frequency_alt != 0 && frequency_alt < frequencies[voices - 2] && frequency_alt < frequencies[voices - 2] * pow(2, -440 / frequencies[voices - 2] / 12 / 2)) { - frequency_alt = frequency_alt * pow(2, 440 / frequency_alt / 12 / 2); - } else if (frequency_alt != 0 && frequency_alt > frequencies[voices - 2] && frequency_alt > frequencies[voices - 2] * pow(2, 440 / frequencies[voices - 2] / 12 / 2)) { - frequency_alt = frequency_alt * pow(2, -440 / frequency_alt / 12 / 2); - } else { - frequency_alt = frequencies[voices - 2]; - } - } else { - frequency_alt = frequencies[voices - 2]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq_alt = vibrato(frequency_alt); - } else { - freq_alt = frequency_alt; - } -# else - freq_alt = frequency_alt; -# endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq_alt = voice_envelope(freq_alt); - - if (freq_alt < 30.517578125) { - freq_alt = 30.52; - } - - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (freq_alt * CPU_PRESCALER)); - TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq_alt * CPU_PRESCALER)) * note_timbre); - } -# endif - - if (polyphony_rate > 0) { - if (voices > 1) { - voice_place %= voices; - if (place++ > (frequencies[voice_place] / polyphony_rate / CPU_PRESCALER)) { - voice_place = (voice_place + 1) % voices; - place = 0.0; - } - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequencies[voice_place]); - } else { - freq = frequencies[voice_place]; - } -# else - freq = frequencies[voice_place]; -# endif - } else { - if (glissando) { - if (frequency != 0 && frequency < frequencies[voices - 1] && frequency < frequencies[voices - 1] * pow(2, -440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, 440 / frequency / 12 / 2); - } else if (frequency != 0 && frequency > frequencies[voices - 1] && frequency > frequencies[voices - 1] * pow(2, 440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, -440 / frequency / 12 / 2); - } else { - frequency = frequencies[voices - 1]; - } - } else { - frequency = frequencies[voices - 1]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequency); - } else { - freq = frequency; - } -# else - freq = frequency; -# endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq = voice_envelope(freq); - - if (freq < 30.517578125) { - freq = 30.52; - } - - TIMER_3_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_3_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } - } - - if (playing_notes) { - if (note_frequency > 0) { -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(note_frequency); - } else { - freq = note_frequency; - } -# else - freq = note_frequency; -# endif - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - TIMER_3_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_3_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } else { - TIMER_3_PERIOD = 0; - TIMER_3_DUTY_CYCLE = 0; - } - - note_position++; - bool end_of_note = false; - if (TIMER_3_PERIOD > 0) { - if (!note_resting) - end_of_note = (note_position >= (note_length / TIMER_3_PERIOD * 0xFFFF - 1)); - else - end_of_note = (note_position >= (note_length)); - } else { - end_of_note = (note_position >= (note_length)); - } - - if (end_of_note) { - current_note++; - if (current_note >= notes_count) { - if (notes_repeat) { - current_note = 0; - } else { - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; - playing_notes = false; - return; - } - } - if (!note_resting) { - note_resting = true; - current_note--; - if ((*notes_pointer)[current_note][0] == (*notes_pointer)[current_note + 1][0]) { - note_frequency = 0; - note_length = 1; - } else { - note_frequency = (*notes_pointer)[current_note][0]; - note_length = 1; - } - } else { - note_resting = false; - envelope_index = 0; - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - } - - note_position = 0; - } - } - - if (!audio_config.enable) { - playing_notes = false; - playing_note = false; - } -} -#endif - -#ifdef BPIN_AUDIO -ISR(TIMER1_AUDIO_vect) { -# if defined(BPIN_AUDIO) && !defined(CPIN_AUDIO) - float freq = 0; - - if (playing_note) { - if (voices > 0) { - if (polyphony_rate > 0) { - if (voices > 1) { - voice_place %= voices; - if (place++ > (frequencies[voice_place] / polyphony_rate / CPU_PRESCALER)) { - voice_place = (voice_place + 1) % voices; - place = 0.0; - } - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequencies[voice_place]); - } else { - freq = frequencies[voice_place]; - } -# else - freq = frequencies[voice_place]; -# endif - } else { - if (glissando) { - if (frequency != 0 && frequency < frequencies[voices - 1] && frequency < frequencies[voices - 1] * pow(2, -440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, 440 / frequency / 12 / 2); - } else if (frequency != 0 && frequency > frequencies[voices - 1] && frequency > frequencies[voices - 1] * pow(2, 440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, -440 / frequency / 12 / 2); - } else { - frequency = frequencies[voices - 1]; - } - } else { - frequency = frequencies[voices - 1]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequency); - } else { - freq = frequency; - } -# else - freq = frequency; -# endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq = voice_envelope(freq); - - if (freq < 30.517578125) { - freq = 30.52; - } - - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } - } - - if (playing_notes) { - if (note_frequency > 0) { -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(note_frequency); - } else { - freq = note_frequency; - } -# else - freq = note_frequency; -# endif - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } else { - TIMER_1_PERIOD = 0; - TIMER_1_DUTY_CYCLE = 0; - } - - note_position++; - bool end_of_note = false; - if (TIMER_1_PERIOD > 0) { - if (!note_resting) - end_of_note = (note_position >= (note_length / TIMER_1_PERIOD * 0xFFFF - 1)); - else - end_of_note = (note_position >= (note_length)); - } else { - end_of_note = (note_position >= (note_length)); - } - - if (end_of_note) { - current_note++; - if (current_note >= notes_count) { - if (notes_repeat) { - current_note = 0; - } else { - DISABLE_AUDIO_COUNTER_1_ISR; - DISABLE_AUDIO_COUNTER_1_OUTPUT; - playing_notes = false; - return; - } - } - if (!note_resting) { - note_resting = true; - current_note--; - if ((*notes_pointer)[current_note][0] == (*notes_pointer)[current_note + 1][0]) { - note_frequency = 0; - note_length = 1; - } else { - note_frequency = (*notes_pointer)[current_note][0]; - note_length = 1; - } - } else { - note_resting = false; - envelope_index = 0; - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - } - - note_position = 0; - } - } - - if (!audio_config.enable) { - playing_notes = false; - playing_note = false; - } -# endif -} -#endif - -void play_note(float freq, int vol) { - dprintf("audio play note freq=%d vol=%d", (int)freq, vol); - - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable && voices < 8) { -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#endif -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; -#endif - - // Cancel notes if notes are playing - if (playing_notes) stop_all_notes(); - - playing_note = true; - - envelope_index = 0; - - if (freq > 0) { - frequencies[voices] = freq; - volumes[voices] = vol; - voices++; - } - -#ifdef CPIN_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif -#ifdef BPIN_AUDIO -# ifdef CPIN_AUDIO - if (voices > 1) { - ENABLE_AUDIO_COUNTER_1_ISR; - ENABLE_AUDIO_COUNTER_1_OUTPUT; - } -# else - ENABLE_AUDIO_COUNTER_1_ISR; - ENABLE_AUDIO_COUNTER_1_OUTPUT; -# endif -#endif - } -} - -void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable) { -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#endif -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; -#endif - - // Cancel note if a note is playing - if (playing_note) stop_all_notes(); - - playing_notes = true; - - notes_pointer = np; - notes_count = n_count; - notes_repeat = n_repeat; - - place = 0; - current_note = 0; - - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - note_position = 0; - -#ifdef CPIN_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif -#ifdef BPIN_AUDIO -# ifndef CPIN_AUDIO - ENABLE_AUDIO_COUNTER_1_ISR; - ENABLE_AUDIO_COUNTER_1_OUTPUT; -# endif -#endif - } -} - -bool is_playing_notes(void) { return playing_notes; } - -bool is_audio_on(void) { return (audio_config.enable != 0); } - -void audio_toggle(void) { - audio_config.enable ^= 1; - eeconfig_update_audio(audio_config.raw); - if (audio_config.enable) audio_on_user(); -} - -void audio_on(void) { - audio_config.enable = 1; - eeconfig_update_audio(audio_config.raw); - audio_on_user(); - PLAY_SONG(audio_on_song); -} - -void audio_off(void) { - PLAY_SONG(audio_off_song); - wait_ms(100); - stop_all_notes(); - audio_config.enable = 0; - eeconfig_update_audio(audio_config.raw); -} - -#ifdef VIBRATO_ENABLE - -// Vibrato rate functions - -void set_vibrato_rate(float rate) { vibrato_rate = rate; } - -void increase_vibrato_rate(float change) { vibrato_rate *= change; } - -void decrease_vibrato_rate(float change) { vibrato_rate /= change; } - -# ifdef VIBRATO_STRENGTH_ENABLE - -void set_vibrato_strength(float strength) { vibrato_strength = strength; } - -void increase_vibrato_strength(float change) { vibrato_strength *= change; } - -void decrease_vibrato_strength(float change) { vibrato_strength /= change; } - -# endif /* VIBRATO_STRENGTH_ENABLE */ - -#endif /* VIBRATO_ENABLE */ - -// Polyphony functions - -void set_polyphony_rate(float rate) { polyphony_rate = rate; } - -void enable_polyphony() { polyphony_rate = 5; } - -void disable_polyphony() { polyphony_rate = 0; } - -void increase_polyphony_rate(float change) { polyphony_rate *= change; } - -void decrease_polyphony_rate(float change) { polyphony_rate /= change; } - -// Timbre function - -void set_timbre(float timbre) { note_timbre = timbre; } - -// Tempo functions - -void set_tempo(uint8_t tempo) { note_tempo = tempo; } - -void decrease_tempo(uint8_t tempo_change) { note_tempo += tempo_change; } - -void increase_tempo(uint8_t tempo_change) { - if (note_tempo - tempo_change < 10) { - note_tempo = 10; - } else { - note_tempo -= tempo_change; - } -} diff --git a/quantum/audio/audio_chibios.c b/quantum/audio/audio_chibios.c deleted file mode 100644 index b267e57463..0000000000 --- a/quantum/audio/audio_chibios.c +++ /dev/null @@ -1,721 +0,0 @@ -/* Copyright 2016 Jack Humbert - * - * 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> - -#include <string.h> -#include "print.h" -#include "keymap.h" - -#include "eeconfig.h" - -// ----------------------------------------------------------------------------- - -int voices = 0; -int voice_place = 0; -float frequency = 0; -float frequency_alt = 0; -int volume = 0; -long position = 0; - -float frequencies[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -int volumes[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -bool sliding = false; - -float place = 0; - -uint8_t *sample; -uint16_t sample_length = 0; - -bool playing_notes = false; -bool playing_note = false; -float note_frequency = 0; -float note_length = 0; -uint8_t note_tempo = TEMPO_DEFAULT; -float note_timbre = TIMBRE_DEFAULT; -uint16_t note_position = 0; -float (*notes_pointer)[][2]; -uint16_t notes_count; -bool notes_repeat; -bool note_resting = false; - -uint16_t current_note = 0; -uint8_t rest_counter = 0; - -#ifdef VIBRATO_ENABLE -float vibrato_counter = 0; -float vibrato_strength = .5; -float vibrato_rate = 0.125; -#endif - -float polyphony_rate = 0; - -static bool audio_initialized = false; - -audio_config_t audio_config; - -uint16_t envelope_index = 0; -bool glissando = true; - -#ifndef STARTUP_SONG -# define STARTUP_SONG SONG(STARTUP_SOUND) -#endif -float startup_song[][2] = STARTUP_SONG; - -static void gpt_cb8(GPTDriver *gptp); - -#define DAC_BUFFER_SIZE 100 -#ifndef DAC_SAMPLE_MAX -# define DAC_SAMPLE_MAX 65535U -#endif - -#define START_CHANNEL_1() \ - gptStart(&GPTD6, &gpt6cfg1); \ - gptStartContinuous(&GPTD6, 2U); \ - palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG) -#define START_CHANNEL_2() \ - gptStart(&GPTD7, &gpt7cfg1); \ - gptStartContinuous(&GPTD7, 2U); \ - palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG) -#define STOP_CHANNEL_1() \ - gptStopTimer(&GPTD6); \ - palSetPadMode(GPIOA, 4, PAL_MODE_OUTPUT_PUSHPULL); \ - palSetPad(GPIOA, 4) -#define STOP_CHANNEL_2() \ - gptStopTimer(&GPTD7); \ - palSetPadMode(GPIOA, 5, PAL_MODE_OUTPUT_PUSHPULL); \ - palSetPad(GPIOA, 5) -#define RESTART_CHANNEL_1() \ - STOP_CHANNEL_1(); \ - START_CHANNEL_1() -#define RESTART_CHANNEL_2() \ - STOP_CHANNEL_2(); \ - START_CHANNEL_2() -#define UPDATE_CHANNEL_1_FREQ(freq) \ - gpt6cfg1.frequency = freq * DAC_BUFFER_SIZE; \ - RESTART_CHANNEL_1() -#define UPDATE_CHANNEL_2_FREQ(freq) \ - gpt7cfg1.frequency = freq * DAC_BUFFER_SIZE; \ - RESTART_CHANNEL_2() -#define GET_CHANNEL_1_FREQ (uint16_t)(gpt6cfg1.frequency * DAC_BUFFER_SIZE) -#define GET_CHANNEL_2_FREQ (uint16_t)(gpt7cfg1.frequency * DAC_BUFFER_SIZE) - -/* - * GPT6 configuration. - */ -// static const GPTConfig gpt6cfg1 = { -// .frequency = 1000000U, -// .callback = NULL, -// .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ -// .dier = 0U -// }; - -GPTConfig gpt6cfg1 = {.frequency = 440U * DAC_BUFFER_SIZE, - .callback = NULL, - .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ - .dier = 0U}; - -GPTConfig gpt7cfg1 = {.frequency = 440U * DAC_BUFFER_SIZE, - .callback = NULL, - .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ - .dier = 0U}; - -GPTConfig gpt8cfg1 = {.frequency = 10, - .callback = gpt_cb8, - .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ - .dier = 0U}; - -/* - * DAC test buffer (sine wave). - */ -// static const dacsample_t dac_buffer[DAC_BUFFER_SIZE] = { -// 2047, 2082, 2118, 2154, 2189, 2225, 2260, 2296, 2331, 2367, 2402, 2437, -// 2472, 2507, 2542, 2576, 2611, 2645, 2679, 2713, 2747, 2780, 2813, 2846, -// 2879, 2912, 2944, 2976, 3008, 3039, 3070, 3101, 3131, 3161, 3191, 3221, -// 3250, 3278, 3307, 3335, 3362, 3389, 3416, 3443, 3468, 3494, 3519, 3544, -// 3568, 3591, 3615, 3637, 3660, 3681, 3703, 3723, 3744, 3763, 3782, 3801, -// 3819, 3837, 3854, 3870, 3886, 3902, 3917, 3931, 3944, 3958, 3970, 3982, -// 3993, 4004, 4014, 4024, 4033, 4041, 4049, 4056, 4062, 4068, 4074, 4078, -// 4082, 4086, 4089, 4091, 4092, 4093, 4094, 4093, 4092, 4091, 4089, 4086, -// 4082, 4078, 4074, 4068, 4062, 4056, 4049, 4041, 4033, 4024, 4014, 4004, -// 3993, 3982, 3970, 3958, 3944, 3931, 3917, 3902, 3886, 3870, 3854, 3837, -// 3819, 3801, 3782, 3763, 3744, 3723, 3703, 3681, 3660, 3637, 3615, 3591, -// 3568, 3544, 3519, 3494, 3468, 3443, 3416, 3389, 3362, 3335, 3307, 3278, -// 3250, 3221, 3191, 3161, 3131, 3101, 3070, 3039, 3008, 2976, 2944, 2912, -// 2879, 2846, 2813, 2780, 2747, 2713, 2679, 2645, 2611, 2576, 2542, 2507, -// 2472, 2437, 2402, 2367, 2331, 2296, 2260, 2225, 2189, 2154, 2118, 2082, -// 2047, 2012, 1976, 1940, 1905, 1869, 1834, 1798, 1763, 1727, 1692, 1657, -// 1622, 1587, 1552, 1518, 1483, 1449, 1415, 1381, 1347, 1314, 1281, 1248, -// 1215, 1182, 1150, 1118, 1086, 1055, 1024, 993, 963, 933, 903, 873, -// 844, 816, 787, 759, 732, 705, 678, 651, 626, 600, 575, 550, -// 526, 503, 479, 457, 434, 413, 391, 371, 350, 331, 312, 293, -// 275, 257, 240, 224, 208, 192, 177, 163, 150, 136, 124, 112, -// 101, 90, 80, 70, 61, 53, 45, 38, 32, 26, 20, 16, -// 12, 8, 5, 3, 2, 1, 0, 1, 2, 3, 5, 8, -// 12, 16, 20, 26, 32, 38, 45, 53, 61, 70, 80, 90, -// 101, 112, 124, 136, 150, 163, 177, 192, 208, 224, 240, 257, -// 275, 293, 312, 331, 350, 371, 391, 413, 434, 457, 479, 503, -// 526, 550, 575, 600, 626, 651, 678, 705, 732, 759, 787, 816, -// 844, 873, 903, 933, 963, 993, 1024, 1055, 1086, 1118, 1150, 1182, -// 1215, 1248, 1281, 1314, 1347, 1381, 1415, 1449, 1483, 1518, 1552, 1587, -// 1622, 1657, 1692, 1727, 1763, 1798, 1834, 1869, 1905, 1940, 1976, 2012 -// }; - -// static const dacsample_t dac_buffer_2[DAC_BUFFER_SIZE] = { -// 12, 8, 5, 3, 2, 1, 0, 1, 2, 3, 5, 8, -// 12, 16, 20, 26, 32, 38, 45, 53, 61, 70, 80, 90, -// 101, 112, 124, 136, 150, 163, 177, 192, 208, 224, 240, 257, -// 275, 293, 312, 331, 350, 371, 391, 413, 434, 457, 479, 503, -// 526, 550, 575, 600, 626, 651, 678, 705, 732, 759, 787, 816, -// 844, 873, 903, 933, 963, 993, 1024, 1055, 1086, 1118, 1150, 1182, -// 1215, 1248, 1281, 1314, 1347, 1381, 1415, 1449, 1483, 1518, 1552, 1587, -// 1622, 1657, 1692, 1727, 1763, 1798, 1834, 1869, 1905, 1940, 1976, 2012, -// 2047, 2082, 2118, 2154, 2189, 2225, 2260, 2296, 2331, 2367, 2402, 2437, -// 2472, 2507, 2542, 2576, 2611, 2645, 2679, 2713, 2747, 2780, 2813, 2846, -// 2879, 2912, 2944, 2976, 3008, 3039, 3070, 3101, 3131, 3161, 3191, 3221, -// 3250, 3278, 3307, 3335, 3362, 3389, 3416, 3443, 3468, 3494, 3519, 3544, -// 3568, 3591, 3615, 3637, 3660, 3681, 3703, 3723, 3744, 3763, 3782, 3801, -// 3819, 3837, 3854, 3870, 3886, 3902, 3917, 3931, 3944, 3958, 3970, 3982, -// 3993, 4004, 4014, 4024, 4033, 4041, 4049, 4056, 4062, 4068, 4074, 4078, -// 4082, 4086, 4089, 4091, 4092, 4093, 4094, 4093, 4092, 4091, 4089, 4086, -// 4082, 4078, 4074, 4068, 4062, 4056, 4049, 4041, 4033, 4024, 4014, 4004, -// 3993, 3982, 3970, 3958, 3944, 3931, 3917, 3902, 3886, 3870, 3854, 3837, -// 3819, 3801, 3782, 3763, 3744, 3723, 3703, 3681, 3660, 3637, 3615, 3591, -// 3568, 3544, 3519, 3494, 3468, 3443, 3416, 3389, 3362, 3335, 3307, 3278, -// 3250, 3221, 3191, 3161, 3131, 3101, 3070, 3039, 3008, 2976, 2944, 2912, -// 2879, 2846, 2813, 2780, 2747, 2713, 2679, 2645, 2611, 2576, 2542, 2507, -// 2472, 2437, 2402, 2367, 2331, 2296, 2260, 2225, 2189, 2154, 2118, 2082, -// 2047, 2012, 1976, 1940, 1905, 1869, 1834, 1798, 1763, 1727, 1692, 1657, -// 1622, 1587, 1552, 1518, 1483, 1449, 1415, 1381, 1347, 1314, 1281, 1248, -// 1215, 1182, 1150, 1118, 1086, 1055, 1024, 993, 963, 933, 903, 873, -// 844, 816, 787, 759, 732, 705, 678, 651, 626, 600, 575, 550, -// 526, 503, 479, 457, 434, 413, 391, 371, 350, 331, 312, 293, -// 275, 257, 240, 224, 208, 192, 177, 163, 150, 136, 124, 112, -// 101, 90, 80, 70, 61, 53, 45, 38, 32, 26, 20, 16 -// }; - -// squarewave -static const dacsample_t dac_buffer[DAC_BUFFER_SIZE] = { - // First half is max, second half is 0 - [0 ... DAC_BUFFER_SIZE / 2 - 1] = DAC_SAMPLE_MAX, - [DAC_BUFFER_SIZE / 2 ... DAC_BUFFER_SIZE - 1] = 0, -}; - -// squarewave -static const dacsample_t dac_buffer_2[DAC_BUFFER_SIZE] = { - // opposite of dac_buffer above - [0 ... DAC_BUFFER_SIZE / 2 - 1] = 0, - [DAC_BUFFER_SIZE / 2 ... DAC_BUFFER_SIZE - 1] = DAC_SAMPLE_MAX, -}; - -/* - * DAC streaming callback. - */ -size_t nz = 0; -static void end_cb1(DACDriver *dacp) { - (void)dacp; - - nz++; - if ((nz % 1000) == 0) { - // palTogglePad(GPIOD, GPIOD_LED3); - } -} - -/* - * DAC error callback. - */ -static void error_cb1(DACDriver *dacp, dacerror_t err) { - (void)dacp; - (void)err; - - chSysHalt("DAC failure"); -} - -static const DACConfig dac1cfg1 = {.init = DAC_SAMPLE_MAX, .datamode = DAC_DHRM_12BIT_RIGHT}; - -static const DACConversionGroup dacgrpcfg1 = {.num_channels = 1U, .end_cb = end_cb1, .error_cb = error_cb1, .trigger = DAC_TRG(0)}; - -static const DACConfig dac1cfg2 = {.init = DAC_SAMPLE_MAX, .datamode = DAC_DHRM_12BIT_RIGHT}; - -static const DACConversionGroup dacgrpcfg2 = {.num_channels = 1U, .end_cb = end_cb1, .error_cb = error_cb1, .trigger = DAC_TRG(0)}; - -void audio_init() { - if (audio_initialized) { - return; - } - -// Check EEPROM -#ifdef EEPROM_ENABLE - if (!eeconfig_is_enabled()) { - eeconfig_init(); - } - audio_config.raw = eeconfig_read_audio(); -#else // ARM EEPROM - audio_config.enable = true; -# ifdef AUDIO_CLICKY_ON - audio_config.clicky_enable = true; -# endif -#endif // ARM EEPROM - - /* - * Starting DAC1 driver, setting up the output pin as analog as suggested - * by the Reference Manual. - */ - palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG); - palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG); - dacStart(&DACD1, &dac1cfg1); - dacStart(&DACD2, &dac1cfg2); - - /* - * Start the note timer - */ - gptStart(&GPTD8, &gpt8cfg1); - gptStartContinuous(&GPTD8, 2U); - - /* - * Starting GPT6/7 driver, it is used for triggering the DAC. - */ - START_CHANNEL_1(); - START_CHANNEL_2(); - - /* - * Starting a continuous conversion. - */ - dacStartConversion(&DACD1, &dacgrpcfg1, (dacsample_t *)dac_buffer, DAC_BUFFER_SIZE); - dacStartConversion(&DACD2, &dacgrpcfg2, (dacsample_t *)dac_buffer_2, DAC_BUFFER_SIZE); - - audio_initialized = true; - - stop_all_notes(); -} - -void audio_startup() { - if (audio_config.enable) { - PLAY_SONG(startup_song); - } -} - -void stop_all_notes() { - dprintf("audio stop all notes"); - - if (!audio_initialized) { - audio_init(); - } - voices = 0; - - gptStopTimer(&GPTD6); - gptStopTimer(&GPTD7); - gptStopTimer(&GPTD8); - - playing_notes = false; - playing_note = false; - frequency = 0; - frequency_alt = 0; - volume = 0; - - for (uint8_t i = 0; i < 8; i++) { - frequencies[i] = 0; - volumes[i] = 0; - } -} - -void stop_note(float freq) { - dprintf("audio stop note freq=%d", (int)freq); - - if (playing_note) { - if (!audio_initialized) { - audio_init(); - } - for (int i = 7; i >= 0; i--) { - if (frequencies[i] == freq) { - frequencies[i] = 0; - volumes[i] = 0; - for (int j = i; (j < 7); j++) { - frequencies[j] = frequencies[j + 1]; - frequencies[j + 1] = 0; - volumes[j] = volumes[j + 1]; - volumes[j + 1] = 0; - } - break; - } - } - voices--; - if (voices < 0) { - voices = 0; - } - if (voice_place >= voices) { - voice_place = 0; - } - if (voices == 0) { - STOP_CHANNEL_1(); - STOP_CHANNEL_2(); - gptStopTimer(&GPTD8); - frequency = 0; - frequency_alt = 0; - volume = 0; - playing_note = false; - } - } -} - -#ifdef VIBRATO_ENABLE - -float mod(float a, int b) { - float r = fmod(a, b); - return r < 0 ? r + b : r; -} - -float vibrato(float average_freq) { -# ifdef VIBRATO_STRENGTH_ENABLE - float vibrated_freq = average_freq * pow(vibrato_lut[(int)vibrato_counter], vibrato_strength); -# else - float vibrated_freq = average_freq * vibrato_lut[(int)vibrato_counter]; -# endif - vibrato_counter = mod((vibrato_counter + vibrato_rate * (1.0 + 440.0 / average_freq)), VIBRATO_LUT_LENGTH); - return vibrated_freq; -} - -#endif - -static void gpt_cb8(GPTDriver *gptp) { - float freq; - - if (playing_note) { - if (voices > 0) { - float freq_alt = 0; - if (voices > 1) { - if (polyphony_rate == 0) { - if (glissando) { - if (frequency_alt != 0 && frequency_alt < frequencies[voices - 2] && frequency_alt < frequencies[voices - 2] * pow(2, -440 / frequencies[voices - 2] / 12 / 2)) { - frequency_alt = frequency_alt * pow(2, 440 / frequency_alt / 12 / 2); - } else if (frequency_alt != 0 && frequency_alt > frequencies[voices - 2] && frequency_alt > frequencies[voices - 2] * pow(2, 440 / frequencies[voices - 2] / 12 / 2)) { - frequency_alt = frequency_alt * pow(2, -440 / frequency_alt / 12 / 2); - } else { - frequency_alt = frequencies[voices - 2]; - } - } else { - frequency_alt = frequencies[voices - 2]; - } - -#ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq_alt = vibrato(frequency_alt); - } else { - freq_alt = frequency_alt; - } -#else - freq_alt = frequency_alt; -#endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq_alt = voice_envelope(freq_alt); - - if (freq_alt < 30.517578125) { - freq_alt = 30.52; - } - - if (GET_CHANNEL_2_FREQ != (uint16_t)freq_alt) { - UPDATE_CHANNEL_2_FREQ(freq_alt); - } else { - RESTART_CHANNEL_2(); - } - // note_timbre; - } - - if (polyphony_rate > 0) { - if (voices > 1) { - voice_place %= voices; - if (place++ > (frequencies[voice_place] / polyphony_rate)) { - voice_place = (voice_place + 1) % voices; - place = 0.0; - } - } - -#ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequencies[voice_place]); - } else { - freq = frequencies[voice_place]; - } -#else - freq = frequencies[voice_place]; -#endif - } else { - if (glissando) { - if (frequency != 0 && frequency < frequencies[voices - 1] && frequency < frequencies[voices - 1] * pow(2, -440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, 440 / frequency / 12 / 2); - } else if (frequency != 0 && frequency > frequencies[voices - 1] && frequency > frequencies[voices - 1] * pow(2, 440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, -440 / frequency / 12 / 2); - } else { - frequency = frequencies[voices - 1]; - } - } else { - frequency = frequencies[voices - 1]; - } - -#ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequency); - } else { - freq = frequency; - } -#else - freq = frequency; -#endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq = voice_envelope(freq); - - if (freq < 30.517578125) { - freq = 30.52; - } - - if (GET_CHANNEL_1_FREQ != (uint16_t)freq) { - UPDATE_CHANNEL_1_FREQ(freq); - } else { - RESTART_CHANNEL_1(); - } - // note_timbre; - } - } - - if (playing_notes) { - if (note_frequency > 0) { -#ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(note_frequency); - } else { - freq = note_frequency; - } -#else - freq = note_frequency; -#endif - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - if (GET_CHANNEL_1_FREQ != (uint16_t)freq) { - UPDATE_CHANNEL_1_FREQ(freq); - UPDATE_CHANNEL_2_FREQ(freq); - } - // note_timbre; - } else { - // gptStopTimer(&GPTD6); - // gptStopTimer(&GPTD7); - } - - note_position++; - bool end_of_note = false; - if (GET_CHANNEL_1_FREQ > 0) { - if (!note_resting) - end_of_note = (note_position >= (note_length * 8 - 1)); - else - end_of_note = (note_position >= (note_length * 8)); - } else { - end_of_note = (note_position >= (note_length * 8)); - } - - if (end_of_note) { - current_note++; - if (current_note >= notes_count) { - if (notes_repeat) { - current_note = 0; - } else { - STOP_CHANNEL_1(); - STOP_CHANNEL_2(); - // gptStopTimer(&GPTD8); - playing_notes = false; - return; - } - } - if (!note_resting) { - note_resting = true; - current_note--; - if ((*notes_pointer)[current_note][0] == (*notes_pointer)[current_note + 1][0]) { - note_frequency = 0; - note_length = 1; - } else { - note_frequency = (*notes_pointer)[current_note][0]; - note_length = 1; - } - } else { - note_resting = false; - envelope_index = 0; - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - } - - note_position = 0; - } - } - - if (!audio_config.enable) { - playing_notes = false; - playing_note = false; - } -} - -void play_note(float freq, int vol) { - dprintf("audio play note freq=%d vol=%d", (int)freq, vol); - - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable && voices < 8) { - // Cancel notes if notes are playing - if (playing_notes) { - stop_all_notes(); - } - - playing_note = true; - - envelope_index = 0; - - if (freq > 0) { - frequencies[voices] = freq; - volumes[voices] = vol; - voices++; - } - - gptStart(&GPTD8, &gpt8cfg1); - gptStartContinuous(&GPTD8, 2U); - RESTART_CHANNEL_1(); - RESTART_CHANNEL_2(); - } -} - -void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable) { - // Cancel note if a note is playing - if (playing_note) { - stop_all_notes(); - } - - playing_notes = true; - - notes_pointer = np; - notes_count = n_count; - notes_repeat = n_repeat; - - place = 0; - current_note = 0; - - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - note_position = 0; - - gptStart(&GPTD8, &gpt8cfg1); - gptStartContinuous(&GPTD8, 2U); - RESTART_CHANNEL_1(); - RESTART_CHANNEL_2(); - } -} - -bool is_playing_notes(void) { return playing_notes; } - -bool is_audio_on(void) { return (audio_config.enable != 0); } - -void audio_toggle(void) { - if (audio_config.enable) { - stop_all_notes(); - } - audio_config.enable ^= 1; - eeconfig_update_audio(audio_config.raw); - if (audio_config.enable) { - audio_on_user(); - } -} - -void audio_on(void) { - audio_config.enable = 1; - eeconfig_update_audio(audio_config.raw); - audio_on_user(); -} - -void audio_off(void) { - stop_all_notes(); - audio_config.enable = 0; - eeconfig_update_audio(audio_config.raw); -} - -#ifdef VIBRATO_ENABLE - -// Vibrato rate functions - -void set_vibrato_rate(float rate) { vibrato_rate = rate; } - -void increase_vibrato_rate(float change) { vibrato_rate *= change; } - -void decrease_vibrato_rate(float change) { vibrato_rate /= change; } - -# ifdef VIBRATO_STRENGTH_ENABLE - -void set_vibrato_strength(float strength) { vibrato_strength = strength; } - -void increase_vibrato_strength(float change) { vibrato_strength *= change; } - -void decrease_vibrato_strength(float change) { vibrato_strength /= change; } - -# endif /* VIBRATO_STRENGTH_ENABLE */ - -#endif /* VIBRATO_ENABLE */ - -// Polyphony functions - -void set_polyphony_rate(float rate) { polyphony_rate = rate; } - -void enable_polyphony() { polyphony_rate = 5; } - -void disable_polyphony() { polyphony_rate = 0; } - -void increase_polyphony_rate(float change) { polyphony_rate *= change; } - -void decrease_polyphony_rate(float change) { polyphony_rate /= change; } - -// Timbre function - -void set_timbre(float timbre) { note_timbre = timbre; } - -// Tempo functions - -void set_tempo(uint8_t tempo) { note_tempo = tempo; } - -void decrease_tempo(uint8_t tempo_change) { note_tempo += tempo_change; } - -void increase_tempo(uint8_t tempo_change) { - if (note_tempo - tempo_change < 10) { - note_tempo = 10; - } else { - note_tempo -= tempo_change; - } -} diff --git a/quantum/audio/audio_pwm.c b/quantum/audio/audio_pwm.c deleted file mode 100644 index d93ac4bb40..0000000000 --- a/quantum/audio/audio_pwm.c +++ /dev/null @@ -1,606 +0,0 @@ -/* Copyright 2016 Jack Humbert - * - * 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 <stdio.h> -#include <string.h> -//#include <math.h> -#include <avr/pgmspace.h> -#include <avr/interrupt.h> -#include <avr/io.h> -#include "print.h" -#include "audio.h" -#include "keymap.h" - -#include "eeconfig.h" - -#define PI 3.14159265 - -#define CPU_PRESCALER 8 - -#ifndef STARTUP_SONG -# define STARTUP_SONG SONG(STARTUP_SOUND) -#endif -float startup_song[][2] = STARTUP_SONG; - -// Timer Abstractions - -// TIMSK3 - Timer/Counter #3 Interrupt Mask Register -// Turn on/off 3A interputs, stopping/enabling the ISR calls -#define ENABLE_AUDIO_COUNTER_3_ISR TIMSK3 |= _BV(OCIE3A) -#define DISABLE_AUDIO_COUNTER_3_ISR TIMSK3 &= ~_BV(OCIE3A) - -// TCCR3A: Timer/Counter #3 Control Register -// Compare Output Mode (COM3An) = 0b00 = Normal port operation, OC3A disconnected from PC6 -#define ENABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A |= _BV(COM3A1); -#define DISABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A &= ~(_BV(COM3A1) | _BV(COM3A0)); - -#define NOTE_PERIOD ICR3 -#define NOTE_DUTY_CYCLE OCR3A - -#ifdef PWM_AUDIO -# include "wave.h" -# define SAMPLE_DIVIDER 39 -# define SAMPLE_RATE (2000000.0 / SAMPLE_DIVIDER / 2048) -// Resistor value of 1/ (2 * PI * 10nF * (2000000 hertz / SAMPLE_DIVIDER / 10)) for 10nF cap - -float places[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -uint16_t place_int = 0; -bool repeat = true; -#endif - -void delay_us(int count) { - while (count--) { - _delay_us(1); - } -} - -int voices = 0; -int voice_place = 0; -float frequency = 0; -int volume = 0; -long position = 0; - -float frequencies[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -int volumes[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -bool sliding = false; - -float place = 0; - -uint8_t* sample; -uint16_t sample_length = 0; -// float freq = 0; - -bool playing_notes = false; -bool playing_note = false; -float note_frequency = 0; -float note_length = 0; -uint8_t note_tempo = TEMPO_DEFAULT; -float note_timbre = TIMBRE_DEFAULT; -uint16_t note_position = 0; -float (*notes_pointer)[][2]; -uint16_t notes_count; -bool notes_repeat; -float notes_rest; -bool note_resting = false; - -uint16_t current_note = 0; -uint8_t rest_counter = 0; - -#ifdef VIBRATO_ENABLE -float vibrato_counter = 0; -float vibrato_strength = .5; -float vibrato_rate = 0.125; -#endif - -float polyphony_rate = 0; - -static bool audio_initialized = false; - -audio_config_t audio_config; - -uint16_t envelope_index = 0; - -void audio_init() { - // Check EEPROM - if (!eeconfig_is_enabled()) { - eeconfig_init(); - } - audio_config.raw = eeconfig_read_audio(); - -#ifdef PWM_AUDIO - - PLLFRQ = _BV(PDIV2); - PLLCSR = _BV(PLLE); - while (!(PLLCSR & _BV(PLOCK))) - ; - PLLFRQ |= _BV(PLLTM0); /* PCK 48MHz */ - - /* Init a fast PWM on Timer4 */ - TCCR4A = _BV(COM4A0) | _BV(PWM4A); /* Clear OC4A on Compare Match */ - TCCR4B = _BV(CS40); /* No prescaling => f = PCK/256 = 187500Hz */ - OCR4A = 0; - - /* Enable the OC4A output */ - DDRC |= _BV(PORTC6); - - DISABLE_AUDIO_COUNTER_3_ISR; // Turn off 3A interputs - - TCCR3A = 0x0; // Options not needed - TCCR3B = _BV(CS31) | _BV(CS30) | _BV(WGM32); // 64th prescaling and CTC - OCR3A = SAMPLE_DIVIDER - 1; // Correct count/compare, related to sample playback - -#else - - // Set port PC6 (OC3A and /OC4A) as output - DDRC |= _BV(PORTC6); - - DISABLE_AUDIO_COUNTER_3_ISR; - - // TCCR3A / TCCR3B: Timer/Counter #3 Control Registers - // Compare Output Mode (COM3An) = 0b00 = Normal port operation, OC3A disconnected from PC6 - // Waveform Generation Mode (WGM3n) = 0b1110 = Fast PWM Mode 14 (Period = ICR3, Duty Cycle = OCR3A) - // Clock Select (CS3n) = 0b010 = Clock / 8 - TCCR3A = (0 << COM3A1) | (0 << COM3A0) | (1 << WGM31) | (0 << WGM30); - TCCR3B = (1 << WGM33) | (1 << WGM32) | (0 << CS32) | (1 << CS31) | (0 << CS30); - -#endif - - audio_initialized = true; -} - -void audio_startup() { - if (audio_config.enable) { - PLAY_SONG(startup_song); - } -} - -void stop_all_notes() { - if (!audio_initialized) { - audio_init(); - } - voices = 0; -#ifdef PWM_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#else - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - - playing_notes = false; - playing_note = false; - frequency = 0; - volume = 0; - - for (uint8_t i = 0; i < 8; i++) { - frequencies[i] = 0; - volumes[i] = 0; - } -} - -void stop_note(float freq) { - if (playing_note) { - if (!audio_initialized) { - audio_init(); - } -#ifdef PWM_AUDIO - freq = freq / SAMPLE_RATE; -#endif - for (int i = 7; i >= 0; i--) { - if (frequencies[i] == freq) { - frequencies[i] = 0; - volumes[i] = 0; - for (int j = i; (j < 7); j++) { - frequencies[j] = frequencies[j + 1]; - frequencies[j + 1] = 0; - volumes[j] = volumes[j + 1]; - volumes[j + 1] = 0; - } - break; - } - } - voices--; - if (voices < 0) voices = 0; - if (voice_place >= voices) { - voice_place = 0; - } - if (voices == 0) { -#ifdef PWM_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#else - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - frequency = 0; - volume = 0; - playing_note = false; - } - } -} - -#ifdef VIBRATO_ENABLE - -float mod(float a, int b) { - float r = fmod(a, b); - return r < 0 ? r + b : r; -} - -float vibrato(float average_freq) { -# ifdef VIBRATO_STRENGTH_ENABLE - float vibrated_freq = average_freq * pow(vibrato_lut[(int)vibrato_counter], vibrato_strength); -# else - float vibrated_freq = average_freq * vibrato_lut[(int)vibrato_counter]; -# endif - vibrato_counter = mod((vibrato_counter + vibrato_rate * (1.0 + 440.0 / average_freq)), VIBRATO_LUT_LENGTH); - return vibrated_freq; -} - -#endif - -ISR(TIMER3_COMPA_vect) { - if (playing_note) { -#ifdef PWM_AUDIO - if (voices == 1) { - // SINE - OCR4A = pgm_read_byte(&sinewave[(uint16_t)place]) >> 2; - - // SQUARE - // if (((int)place) >= 1024){ - // OCR4A = 0xFF >> 2; - // } else { - // OCR4A = 0x00; - // } - - // SAWTOOTH - // OCR4A = (int)place / 4; - - // TRIANGLE - // if (((int)place) >= 1024) { - // OCR4A = (int)place / 2; - // } else { - // OCR4A = 2048 - (int)place / 2; - // } - - place += frequency; - - if (place >= SINE_LENGTH) place -= SINE_LENGTH; - - } else { - int sum = 0; - for (int i = 0; i < voices; i++) { - // SINE - sum += pgm_read_byte(&sinewave[(uint16_t)places[i]]) >> 2; - - // SQUARE - // if (((int)places[i]) >= 1024){ - // sum += 0xFF >> 2; - // } else { - // sum += 0x00; - // } - - places[i] += frequencies[i]; - - if (places[i] >= SINE_LENGTH) places[i] -= SINE_LENGTH; - } - OCR4A = sum; - } -#else - if (voices > 0) { - float freq; - if (polyphony_rate > 0) { - if (voices > 1) { - voice_place %= voices; - if (place++ > (frequencies[voice_place] / polyphony_rate / CPU_PRESCALER)) { - voice_place = (voice_place + 1) % voices; - place = 0.0; - } - } -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequencies[voice_place]); - } else { -# else - { -# endif - freq = frequencies[voice_place]; - } - } else { - if (frequency != 0 && frequency < frequencies[voices - 1] && frequency < frequencies[voices - 1] * pow(2, -440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, 440 / frequency / 12 / 2); - } else if (frequency != 0 && frequency > frequencies[voices - 1] && frequency > frequencies[voices - 1] * pow(2, 440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, -440 / frequency / 12 / 2); - } else { - frequency = frequencies[voices - 1]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequency); - } else { -# else - { -# endif - freq = frequency; - } - } - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - if (freq < 30.517578125) freq = 30.52; - NOTE_PERIOD = (int)(((double)F_CPU) / (freq * CPU_PRESCALER)); // Set max to the period - NOTE_DUTY_CYCLE = (int)((((double)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); // Set compare to half the period - } -#endif - } - - // SAMPLE - // OCR4A = pgm_read_byte(&sample[(uint16_t)place_int]); - - // place_int++; - - // if (place_int >= sample_length) - // if (repeat) - // place_int -= sample_length; - // else - // DISABLE_AUDIO_COUNTER_3_ISR; - - if (playing_notes) { -#ifdef PWM_AUDIO - OCR4A = pgm_read_byte(&sinewave[(uint16_t)place]) >> 0; - - place += note_frequency; - if (place >= SINE_LENGTH) place -= SINE_LENGTH; -#else - if (note_frequency > 0) { - float freq; - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(note_frequency); - } else { -# else - { -# endif - freq = note_frequency; - } - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - NOTE_PERIOD = (int)(((double)F_CPU) / (freq * CPU_PRESCALER)); // Set max to the period - NOTE_DUTY_CYCLE = (int)((((double)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); // Set compare to half the period - } else { - NOTE_PERIOD = 0; - NOTE_DUTY_CYCLE = 0; - } -#endif - - note_position++; - bool end_of_note = false; - if (NOTE_PERIOD > 0) - end_of_note = (note_position >= (note_length / NOTE_PERIOD * 0xFFFF)); - else - end_of_note = (note_position >= (note_length * 0x7FF)); - if (end_of_note) { - current_note++; - if (current_note >= notes_count) { - if (notes_repeat) { - current_note = 0; - } else { -#ifdef PWM_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#else - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - playing_notes = false; - return; - } - } - if (!note_resting && (notes_rest > 0)) { - note_resting = true; - note_frequency = 0; - note_length = notes_rest; - current_note--; - } else { - note_resting = false; -#ifdef PWM_AUDIO - note_frequency = (*notes_pointer)[current_note][0] / SAMPLE_RATE; - note_length = (*notes_pointer)[current_note][1] * (((float)note_tempo) / 100); -#else - envelope_index = 0; - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); -#endif - } - note_position = 0; - } - } - - if (!audio_config.enable) { - playing_notes = false; - playing_note = false; - } -} - -void play_note(float freq, int vol) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable && voices < 8) { - DISABLE_AUDIO_COUNTER_3_ISR; - - // Cancel notes if notes are playing - if (playing_notes) stop_all_notes(); - - playing_note = true; - - envelope_index = 0; - -#ifdef PWM_AUDIO - freq = freq / SAMPLE_RATE; -#endif - if (freq > 0) { - frequencies[voices] = freq; - volumes[voices] = vol; - voices++; - } - -#ifdef PWM_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; -#else - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - } -} - -void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat, float n_rest) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable) { - DISABLE_AUDIO_COUNTER_3_ISR; - - // Cancel note if a note is playing - if (playing_note) stop_all_notes(); - - playing_notes = true; - - notes_pointer = np; - notes_count = n_count; - notes_repeat = n_repeat; - notes_rest = n_rest; - - place = 0; - current_note = 0; - -#ifdef PWM_AUDIO - note_frequency = (*notes_pointer)[current_note][0] / SAMPLE_RATE; - note_length = (*notes_pointer)[current_note][1] * (((float)note_tempo) / 100); -#else - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); -#endif - note_position = 0; - -#ifdef PWM_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; -#else - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - } -} - -#ifdef PWM_AUDIO -void play_sample(uint8_t* s, uint16_t l, bool r) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable) { - DISABLE_AUDIO_COUNTER_3_ISR; - stop_all_notes(); - place_int = 0; - sample = s; - sample_length = l; - repeat = r; - - ENABLE_AUDIO_COUNTER_3_ISR; - } -} -#endif - -void audio_toggle(void) { - audio_config.enable ^= 1; - eeconfig_update_audio(audio_config.raw); -} - -void audio_on(void) { - audio_config.enable = 1; - eeconfig_update_audio(audio_config.raw); -} - -void audio_off(void) { - audio_config.enable = 0; - eeconfig_update_audio(audio_config.raw); -} - -#ifdef VIBRATO_ENABLE - -// Vibrato rate functions - -void set_vibrato_rate(float rate) { vibrato_rate = rate; } - -void increase_vibrato_rate(float change) { vibrato_rate *= change; } - -void decrease_vibrato_rate(float change) { vibrato_rate /= change; } - -# ifdef VIBRATO_STRENGTH_ENABLE - -void set_vibrato_strength(float strength) { vibrato_strength = strength; } - -void increase_vibrato_strength(float change) { vibrato_strength *= change; } - -void decrease_vibrato_strength(float change) { vibrato_strength /= change; } - -# endif /* VIBRATO_STRENGTH_ENABLE */ - -#endif /* VIBRATO_ENABLE */ - -// Polyphony functions - -void set_polyphony_rate(float rate) { polyphony_rate = rate; } - -void enable_polyphony() { polyphony_rate = 5; } - -void disable_polyphony() { polyphony_rate = 0; } - -void increase_polyphony_rate(float change) { polyphony_rate *= change; } - -void decrease_polyphony_rate(float change) { polyphony_rate /= change; } - -// Timbre function - -void set_timbre(float timbre) { note_timbre = timbre; } - -// Tempo functions - -void set_tempo(uint8_t tempo) { note_tempo = tempo; } - -void decrease_tempo(uint8_t tempo_change) { note_tempo += tempo_change; } - -void increase_tempo(uint8_t tempo_change) { - if (note_tempo - tempo_change < 10) { - note_tempo = 10; - } else { - note_tempo -= tempo_change; - } -} - -//------------------------------------------------------------------------------ -// Override these functions in your keymap file to play different tunes on -// startup and bootloader jump -__attribute__((weak)) void play_startup_tone() {} - -__attribute__((weak)) void play_goodbye_tone() {} -//------------------------------------------------------------------------------ diff --git a/quantum/audio/driver_avr_pwm.h b/quantum/audio/driver_avr_pwm.h new file mode 100644 index 0000000000..d6eb3571da --- /dev/null +++ b/quantum/audio/driver_avr_pwm.h @@ -0,0 +1,17 @@ +/* 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 diff --git a/quantum/audio/driver_avr_pwm_hardware.c b/quantum/audio/driver_avr_pwm_hardware.c new file mode 100644 index 0000000000..492b9bfb04 --- /dev/null +++ b/quantum/audio/driver_avr_pwm_hardware.c @@ -0,0 +1,322 @@ +/* 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) +# 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) +# 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 +# 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 diff --git a/quantum/audio/driver_chibios_dac.h b/quantum/audio/driver_chibios_dac.h new file mode 100644 index 0000000000..07cd622ead --- /dev/null +++ b/quantum/audio/driver_chibios_dac.h @@ -0,0 +1,126 @@ +/* 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); diff --git a/quantum/audio/driver_chibios_dac_additive.c b/quantum/audio/driver_chibios_dac_additive.c new file mode 100644 index 0000000000..db304adb87 --- /dev/null +++ b/quantum/audio/driver_chibios_dac_additive.c @@ -0,0 +1,335 @@ +/* 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; +} diff --git a/quantum/audio/driver_chibios_dac_basic.c b/quantum/audio/driver_chibios_dac_basic.c new file mode 100644 index 0000000000..9a1c9a8c19 --- /dev/null +++ b/quantum/audio/driver_chibios_dac_basic.c @@ -0,0 +1,245 @@ +/* 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, 5, 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); +} diff --git a/quantum/audio/driver_chibios_pwm.h b/quantum/audio/driver_chibios_pwm.h new file mode 100644 index 0000000000..86cab916e1 --- /dev/null +++ b/quantum/audio/driver_chibios_pwm.h @@ -0,0 +1,40 @@ +/* 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 diff --git a/quantum/audio/driver_chibios_pwm_hardware.c b/quantum/audio/driver_chibios_pwm_hardware.c new file mode 100644 index 0000000000..3c7d89b290 --- /dev/null +++ b/quantum/audio/driver_chibios_pwm_hardware.c @@ -0,0 +1,144 @@ +/* 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_STM32_ALTERNATE_PUSHPULL); +#else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command) + palSetLineMode(AUDIO_PIN, PAL_STM32_MODE_ALTERNATE | PAL_STM32_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); + } +} diff --git a/quantum/audio/driver_chibios_pwm_software.c b/quantum/audio/driver_chibios_pwm_software.c new file mode 100644 index 0000000000..15c3e98b6a --- /dev/null +++ b/quantum/audio/driver_chibios_pwm_software.c @@ -0,0 +1,164 @@ +/* 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); + } +} diff --git a/quantum/audio/musical_notes.h b/quantum/audio/musical_notes.h index 0ba572c346..ddd7d374f5 100644 --- a/quantum/audio/musical_notes.h +++ b/quantum/audio/musical_notes.h @@ -1,4 +1,5 @@ /* 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 @@ -13,12 +14,11 @@ * 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 -// Tempo Placeholder #ifndef TEMPO_DEFAULT -# define TEMPO_DEFAULT 100 +# define TEMPO_DEFAULT 120 +// in beats-per-minute #endif #define SONG(notes...) \ @@ -27,12 +27,14 @@ // Note Types #define MUSICAL_NOTE(note, duration) \ { (NOTE##note), duration } + #define BREVE_NOTE(note) MUSICAL_NOTE(note, 128) #define WHOLE_NOTE(note) MUSICAL_NOTE(note, 64) #define HALF_NOTE(note) MUSICAL_NOTE(note, 32) #define QUARTER_NOTE(note) MUSICAL_NOTE(note, 16) #define EIGHTH_NOTE(note) MUSICAL_NOTE(note, 8) #define SIXTEENTH_NOTE(note) MUSICAL_NOTE(note, 4) +#define THIRTYSECOND_NOTE(note) MUSICAL_NOTE(note, 2) #define BREVE_DOT_NOTE(note) MUSICAL_NOTE(note, 128 + 64) #define WHOLE_DOT_NOTE(note) MUSICAL_NOTE(note, 64 + 32) @@ -40,6 +42,9 @@ #define QUARTER_DOT_NOTE(note) MUSICAL_NOTE(note, 16 + 8) #define EIGHTH_DOT_NOTE(note) MUSICAL_NOTE(note, 8 + 4) #define SIXTEENTH_DOT_NOTE(note) MUSICAL_NOTE(note, 4 + 2) +#define THIRTYSECOND_DOT_NOTE(note) MUSICAL_NOTE(note, 2 + 1) +// duration of 64 units == one beat == one whole note +// with a tempo of 60bpm this comes to a length of one second // Note Type Shortcuts #define M__NOTE(note, duration) MUSICAL_NOTE(note, duration) @@ -49,56 +54,52 @@ #define Q__NOTE(n) QUARTER_NOTE(n) #define E__NOTE(n) EIGHTH_NOTE(n) #define S__NOTE(n) SIXTEENTH_NOTE(n) +#define T__NOTE(n) THIRTYSECOND_NOTE(n) #define BD_NOTE(n) BREVE_DOT_NOTE(n) #define WD_NOTE(n) WHOLE_DOT_NOTE(n) #define HD_NOTE(n) HALF_DOT_NOTE(n) #define QD_NOTE(n) QUARTER_DOT_NOTE(n) #define ED_NOTE(n) EIGHTH_DOT_NOTE(n) #define SD_NOTE(n) SIXTEENTH_DOT_NOTE(n) +#define TD_NOTE(n) THIRTYSECOND_DOT_NOTE(n) // Note Timbre // Changes how the notes sound -#define TIMBRE_12 0.125f -#define TIMBRE_25 0.250f -#define TIMBRE_50 0.500f -#define TIMBRE_75 0.750f +#define TIMBRE_12 12 +#define TIMBRE_25 25 +#define TIMBRE_50 50 +#define TIMBRE_75 75 #ifndef TIMBRE_DEFAULT # define TIMBRE_DEFAULT TIMBRE_50 #endif -// Notes - # = Octave -#ifdef __arm__ -# define NOTE_REST 1.00f -#else -# define NOTE_REST 0.00f -#endif +// Notes - # = Octave -/* These notes are currently bugged -#define NOTE_C0 16.35f -#define NOTE_CS0 17.32f -#define NOTE_D0 18.35f -#define NOTE_DS0 19.45f -#define NOTE_E0 20.60f -#define NOTE_F0 21.83f -#define NOTE_FS0 23.12f -#define NOTE_G0 24.50f -#define NOTE_GS0 25.96f -#define NOTE_A0 27.50f -#define NOTE_AS0 29.14f -#define NOTE_B0 30.87f -#define NOTE_C1 32.70f -#define NOTE_CS1 34.65f -#define NOTE_D1 36.71f -#define NOTE_DS1 38.89f -#define NOTE_E1 41.20f -#define NOTE_F1 43.65f -#define NOTE_FS1 46.25f -#define NOTE_G1 49.00f -#define NOTE_GS1 51.91f -#define NOTE_A1 55.00f -#define NOTE_AS1 58.27f -*/ +#define NOTE_REST 0.00f +#define NOTE_C0 16.35f +#define NOTE_CS0 17.32f +#define NOTE_D0 18.35f +#define NOTE_DS0 19.45f +#define NOTE_E0 20.60f +#define NOTE_F0 21.83f +#define NOTE_FS0 23.12f +#define NOTE_G0 24.50f +#define NOTE_GS0 25.96f +#define NOTE_A0 27.50f +#define NOTE_AS0 29.14f +#define NOTE_B0 30.87f +#define NOTE_C1 32.70f +#define NOTE_CS1 34.65f +#define NOTE_D1 36.71f +#define NOTE_DS1 38.89f +#define NOTE_E1 41.20f +#define NOTE_F1 43.65f +#define NOTE_FS1 46.25f +#define NOTE_G1 49.00f +#define NOTE_GS1 51.91f +#define NOTE_A1 55.00f +#define NOTE_AS1 58.27f #define NOTE_B1 61.74f #define NOTE_C2 65.41f #define NOTE_CS2 69.30f diff --git a/quantum/audio/voices.c b/quantum/audio/voices.c index 1592618be4..8988d827e9 100644 --- a/quantum/audio/voices.c +++ b/quantum/audio/voices.c @@ -1,4 +1,5 @@ /* 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 @@ -17,13 +18,19 @@ #include "audio.h" #include <stdlib.h> -// these are imported from audio.c -extern uint16_t envelope_index; -extern float note_timbre; -extern float polyphony_rate; -extern bool glissando; +uint8_t note_timbre = TIMBRE_DEFAULT; +bool glissando = false; +bool vibrato = false; +float vibrato_strength = 0.5; +float vibrato_rate = 0.125; +uint16_t voices_timer = 0; + +#ifdef AUDIO_VOICE_DEFAULT +voice_type voice = AUDIO_VOICE_DEFAULT; +#else voice_type voice = default_voice; +#endif void set_voice(voice_type v) { voice = v; } @@ -31,22 +38,54 @@ void voice_iterate() { voice = (voice + 1) % number_of_voices; } void voice_deiterate() { voice = (voice - 1 + number_of_voices) % number_of_voices; } +#ifdef AUDIO_VOICES +float mod(float a, int b) { + float r = fmod(a, b); + return r < 0 ? r + b : r; +} + +// Effect: 'vibrate' a given target frequency slightly above/below its initial value +float voice_add_vibrato(float average_freq) { + float vibrato_counter = mod(timer_read() / (100 * vibrato_rate), VIBRATO_LUT_LENGTH); + + return average_freq * pow(vibrato_lut[(int)vibrato_counter], vibrato_strength); +} + +// Effect: 'slides' the 'frequency' from the starting-point, to the target frequency +float voice_add_glissando(float from_freq, float to_freq) { + if (to_freq != 0 && from_freq < to_freq && from_freq < to_freq * pow(2, -440 / to_freq / 12 / 2)) { + return from_freq * pow(2, 440 / from_freq / 12 / 2); + } else if (to_freq != 0 && from_freq > to_freq && from_freq > to_freq * pow(2, 440 / to_freq / 12 / 2)) { + return from_freq * pow(2, -440 / from_freq / 12 / 2); + } else { + return to_freq; + } +} +#endif + float voice_envelope(float frequency) { // envelope_index ranges from 0 to 0xFFFF, which is preserved at 880.0 Hz - __attribute__((unused)) uint16_t compensated_index = (uint16_t)((float)envelope_index * (880.0 / frequency)); +// __attribute__((unused)) uint16_t compensated_index = (uint16_t)((float)envelope_index * (880.0 / frequency)); +#ifdef AUDIO_VOICES + uint16_t envelope_index = timer_elapsed(voices_timer); // TODO: multiply in some factor? + uint16_t compensated_index = envelope_index / 100; // TODO: correct factor would be? +#endif switch (voice) { case default_voice: - glissando = false; - note_timbre = TIMBRE_50; - polyphony_rate = 0; + glissando = false; + // note_timbre = TIMBRE_50; //Note: leave the user the possibility to adjust the timbre with 'audio_set_timbre' break; #ifdef AUDIO_VOICES + case vibrating: + glissando = false; + vibrato = true; + break; + case something: - glissando = false; - polyphony_rate = 0; + glissando = false; switch (compensated_index) { case 0 ... 9: note_timbre = TIMBRE_12; @@ -57,24 +96,23 @@ float voice_envelope(float frequency) { break; case 20 ... 200: - note_timbre = .125 + .125; + note_timbre = 12 + 12; break; default: - note_timbre = .125; + note_timbre = 12; break; } break; case drums: - glissando = false; - polyphony_rate = 0; + glissando = false; // switch (compensated_index) { // case 0 ... 10: - // note_timbre = 0.5; + // note_timbre = 50; // break; // case 11 ... 20: - // note_timbre = 0.5 * (21 - compensated_index) / 10; + // note_timbre = 50 * (21 - compensated_index) / 10; // break; // default: // note_timbre = 0; @@ -88,10 +126,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(40)) + 60; switch (envelope_index) { case 0 ... 10: - note_timbre = 0.5; + note_timbre = 50; break; case 11 ... 20: - note_timbre = 0.5 * (21 - envelope_index) / 10; + note_timbre = 50 * (21 - envelope_index) / 10; break; default: note_timbre = 0; @@ -103,10 +141,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(1000)) + 1000; switch (envelope_index) { case 0 ... 5: - note_timbre = 0.5; + note_timbre = 50; break; case 6 ... 20: - note_timbre = 0.5 * (21 - envelope_index) / 15; + note_timbre = 50 * (21 - envelope_index) / 15; break; default: note_timbre = 0; @@ -118,10 +156,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(2000)) + 3000; switch (envelope_index) { case 0 ... 15: - note_timbre = 0.5; + note_timbre = 50; break; case 16 ... 20: - note_timbre = 0.5 * (21 - envelope_index) / 5; + note_timbre = 50 * (21 - envelope_index) / 5; break; default: note_timbre = 0; @@ -133,10 +171,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(2000)) + 3000; switch (envelope_index) { case 0 ... 35: - note_timbre = 0.5; + note_timbre = 50; break; case 36 ... 50: - note_timbre = 0.5 * (51 - envelope_index) / 15; + note_timbre = 50 * (51 - envelope_index) / 15; break; default: note_timbre = 0; @@ -145,8 +183,7 @@ float voice_envelope(float frequency) { } break; case butts_fader: - glissando = true; - polyphony_rate = 0; + glissando = true; switch (compensated_index) { case 0 ... 9: frequency = frequency / 4; @@ -159,7 +196,7 @@ float voice_envelope(float frequency) { break; case 20 ... 200: - note_timbre = .125 - pow(((float)compensated_index - 20) / (200 - 20), 2) * .125; + note_timbre = 12 - (uint8_t)(pow(((float)compensated_index - 20) / (200 - 20), 2) * 12.5); break; default: @@ -169,7 +206,6 @@ float voice_envelope(float frequency) { break; // case octave_crunch: - // polyphony_rate = 0; // switch (compensated_index) { // case 0 ... 9: // case 20 ... 24: @@ -187,14 +223,13 @@ float voice_envelope(float frequency) { // default: // note_timbre = TIMBRE_12; - // break; + // break; // } // break; case duty_osc: // This slows the loop down a substantial amount, so higher notes may freeze - glissando = true; - polyphony_rate = 0; + glissando = true; switch (compensated_index) { default: # define OCS_SPEED 10 @@ -202,38 +237,36 @@ float voice_envelope(float frequency) { // sine wave is slow // note_timbre = (sin((float)compensated_index/10000*OCS_SPEED) * OCS_AMP / 2) + .5; // triangle wave is a bit faster - note_timbre = (float)abs((compensated_index * OCS_SPEED % 3000) - 1500) * (OCS_AMP / 1500) + (1 - OCS_AMP) / 2; + note_timbre = (uint8_t)abs((compensated_index * OCS_SPEED % 3000) - 1500) * (OCS_AMP / 1500) + (1 - OCS_AMP) / 2; break; } break; case duty_octave_down: - glissando = true; - polyphony_rate = 0; - note_timbre = (envelope_index % 2) * .125 + .375 * 2; - if ((envelope_index % 4) == 0) note_timbre = 0.5; + glissando = true; + note_timbre = (uint8_t)(100 * (envelope_index % 2) * .125 + .375 * 2); + if ((envelope_index % 4) == 0) note_timbre = 50; if ((envelope_index % 8) == 0) note_timbre = 0; break; case delayed_vibrato: - glissando = true; - polyphony_rate = 0; - note_timbre = TIMBRE_50; + glissando = true; + note_timbre = TIMBRE_50; # define VOICE_VIBRATO_DELAY 150 # define VOICE_VIBRATO_SPEED 50 switch (compensated_index) { case 0 ... VOICE_VIBRATO_DELAY: break; default: + // TODO: merge/replace with voice_add_vibrato above frequency = frequency * vibrato_lut[(int)fmod((((float)compensated_index - (VOICE_VIBRATO_DELAY + 1)) / 1000 * VOICE_VIBRATO_SPEED), VIBRATO_LUT_LENGTH)]; break; } break; // case delayed_vibrato_octave: - // polyphony_rate = 0; // if ((envelope_index % 2) == 1) { - // note_timbre = 0.55; + // note_timbre = 55; // } else { - // note_timbre = 0.45; + // note_timbre = 45; // } // #define VOICE_VIBRATO_DELAY 150 // #define VOICE_VIBRATO_SPEED 50 @@ -246,35 +279,64 @@ float voice_envelope(float frequency) { // } // break; // case duty_fifth_down: - // note_timbre = 0.5; + // note_timbre = TIMBRE_50; // if ((envelope_index % 3) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // break; // case duty_fourth_down: - // note_timbre = 0.0; + // note_timbre = 0; // if ((envelope_index % 12) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // if (((envelope_index % 12) % 4) != 1) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // break; // case duty_third_down: - // note_timbre = 0.5; + // note_timbre = TIMBRE_50; // if ((envelope_index % 5) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // break; // case duty_fifth_third_down: - // note_timbre = 0.5; + // note_timbre = TIMBRE_50; // if ((envelope_index % 5) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // if ((envelope_index % 3) == 0) - // note_timbre = 0.25; + // note_timbre = TIMBRE_25; // break; -#endif +#endif // AUDIO_VOICES default: break; } +#ifdef AUDIO_VOICES + if (vibrato && (vibrato_strength > 0)) { + frequency = voice_add_vibrato(frequency); + } + + if (glissando) { + // TODO: where to keep track of the start-frequency? + // frequency = voice_add_glissando(??, frequency); + } +#endif // AUDIO_VOICES + return frequency; } + +// Vibrato functions + +void voice_set_vibrato_rate(float rate) { vibrato_rate = rate; } +void voice_increase_vibrato_rate(float change) { vibrato_rate *= change; } +void voice_decrease_vibrato_rate(float change) { vibrato_rate /= change; } +void voice_set_vibrato_strength(float strength) { vibrato_strength = strength; } +void voice_increase_vibrato_strength(float change) { vibrato_strength *= change; } +void voice_decrease_vibrato_strength(float change) { vibrato_strength /= change; } + +// Timbre functions + +void voice_set_timbre(uint8_t timbre) { + if ((timbre > 0) && (timbre < 100)) { + note_timbre = timbre; + } +} +uint8_t voice_get_timbre(void) { return note_timbre; } diff --git a/quantum/audio/voices.h b/quantum/audio/voices.h index abafa2b404..578350d337 100644 --- a/quantum/audio/voices.h +++ b/quantum/audio/voices.h @@ -1,4 +1,5 @@ /* 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 @@ -13,7 +14,6 @@ * 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 #include <stdint.h> @@ -29,6 +29,7 @@ float voice_envelope(float frequency); typedef enum { default_voice, #ifdef AUDIO_VOICES + vibrating, something, drums, butts_fader, @@ -48,3 +49,21 @@ typedef enum { void set_voice(voice_type v); void voice_iterate(void); void voice_deiterate(void); + +// Vibrato functions +void voice_set_vibrato_rate(float rate); +void voice_increase_vibrato_rate(float change); +void voice_decrease_vibrato_rate(float change); +void voice_set_vibrato_strength(float strength); +void voice_increase_vibrato_strength(float change); +void voice_decrease_vibrato_strength(float change); + +// Timbre functions +/** + * @brief set the global timbre for tones to be played + * @note: only applies to pwm implementations - where it adjusts the duty-cycle + * @note: using any instrument from voices.[ch] other than 'default' may override the set value + * @param[in]: timbre: valid range is (0,100) + */ +void voice_set_timbre(uint8_t timbre); +uint8_t voice_get_timbre(void); diff --git a/quantum/audio/wave.h b/quantum/audio/wave.h deleted file mode 100644 index 48210a944e..0000000000 --- a/quantum/audio/wave.h +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2016 Jack Humbert - * - * 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 <avr/io.h> -#include <avr/interrupt.h> -#include <avr/pgmspace.h> - -#define SINE_LENGTH 2048 - -const uint8_t sinewave[] PROGMEM = // 2048 values - {0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x83, 0x83, 0x83, 0x84, 0x84, 0x85, 0x85, 0x85, 0x86, 0x86, 0x87, 0x87, 0x87, 0x88, 0x88, 0x88, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8c, 0x8c, 0x8c, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x90, 0x90, 0x91, 0x91, 0x91, 0x92, 0x92, 0x93, 0x93, 0x93, 0x94, 0x94, 0x95, 0x95, 0x95, 0x96, 0x96, 0x96, 0x97, 0x97, 0x98, 0x98, 0x98, 0x99, 0x99, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9f, 0x9f, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6, 0xa7, 0xa7, 0xa7, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xaa, 0xaa, 0xaa, 0xab, 0xab, 0xac, 0xac, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb4, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xbb, - 0xbb, 0xbb, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc8, 0xc8, 0xc8, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, - 0xe9, 0xe9, 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xea, 0xea, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, - 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, - 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xed, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, - 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9d, - 0x9d, 0x9d, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x98, 0x98, 0x98, 0x97, 0x97, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x94, 0x94, 0x93, 0x93, 0x93, 0x92, 0x92, 0x91, 0x91, 0x91, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x86, 0x86, 0x85, 0x85, 0x85, 0x84, 0x84, 0x83, 0x83, 0x83, 0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, 0x7c, 0x7c, 0x7c, 0x7b, 0x7b, 0x7a, 0x7a, 0x7a, 0x79, 0x79, 0x78, 0x78, 0x78, 0x77, 0x77, 0x77, 0x76, 0x76, 0x75, 0x75, 0x75, 0x74, 0x74, 0x73, 0x73, 0x73, 0x72, 0x72, 0x71, 0x71, 0x71, 0x70, 0x70, 0x70, 0x6f, 0x6f, 0x6e, 0x6e, 0x6e, 0x6d, 0x6d, 0x6c, 0x6c, 0x6c, 0x6b, 0x6b, 0x6a, 0x6a, 0x6a, 0x69, 0x69, 0x69, 0x68, 0x68, 0x67, 0x67, 0x67, 0x66, 0x66, 0x65, 0x65, 0x65, 0x64, 0x64, 0x64, 0x63, 0x63, 0x62, 0x62, 0x62, 0x61, 0x61, 0x61, 0x60, - 0x60, 0x5f, 0x5f, 0x5f, 0x5e, 0x5e, 0x5d, 0x5d, 0x5d, 0x5c, 0x5c, 0x5c, 0x5b, 0x5b, 0x5a, 0x5a, 0x5a, 0x59, 0x59, 0x59, 0x58, 0x58, 0x58, 0x57, 0x57, 0x56, 0x56, 0x56, 0x55, 0x55, 0x55, 0x54, 0x54, 0x53, 0x53, 0x53, 0x52, 0x52, 0x52, 0x51, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, 0x4f, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, 0x4b, 0x4a, 0x4a, 0x4a, 0x49, 0x49, 0x49, 0x48, 0x48, 0x48, 0x47, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, 0x44, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2e, 0x2e, 0x2e, 0x2d, 0x2d, 0x2d, 0x2d, 0x2c, 0x2c, 0x2c, 0x2b, 0x2b, 0x2b, 0x2a, 0x2a, - 0x2a, 0x2a, 0x29, 0x29, 0x29, 0x28, 0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26, 0x26, 0x26, 0x25, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, 0x22, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21, 0x21, 0x20, 0x20, 0x20, 0x1f, 0x1f, 0x1f, 0x1f, 0x1e, 0x1e, 0x1e, 0x1e, 0x1d, 0x1d, 0x1d, 0x1d, 0x1c, 0x1c, 0x1c, 0x1c, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1a, 0x1a, 0x1a, 0x1a, 0x19, 0x19, 0x19, 0x19, 0x18, 0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x17, 0x17, 0x16, 0x16, 0x16, 0x16, 0x15, 0x15, 0x15, 0x15, 0x15, 0x14, 0x14, 0x14, 0x14, 0x14, 0x13, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, 0x12, 0x12, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x10, 0x10, 0x10, 0x10, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xe, 0xe, 0xe, 0xe, 0xe, 0xd, 0xd, 0xd, 0xd, 0xd, 0xd, 0xc, 0xc, 0xc, 0xc, 0xc, 0xc, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x8, 0x8, 0x8, 0x8, 0x8, - 0x8, 0x8, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 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, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, - 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xc, 0xc, 0xc, 0xc, 0xc, 0xc, 0xd, 0xd, 0xd, 0xd, 0xd, 0xd, 0xe, 0xe, 0xe, 0xe, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, - 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x46, - 0x46, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, 0x4f, 0x50, 0x50, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x54, 0x54, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68, 0x68, 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f}; |