1
0
Fork 0
forked from forks/qmk_firmware

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>
This commit is contained in:
Drashna Jaelre 2021-02-14 14:40:38 -08:00 committed by GitHub
parent f53e41ac81
commit c80e5f9f88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
113 changed files with 9738 additions and 2463 deletions

View file

@ -42,12 +42,31 @@ ifeq ($(strip $(COMMAND_ENABLE)), yes)
OPT_DEFS += -DCOMMAND_ENABLE
endif
AUDIO_ENABLE ?= no
ifeq ($(strip $(AUDIO_ENABLE)), yes)
ifeq ($(PLATFORM),CHIBIOS)
AUDIO_DRIVER ?= dac_basic
ifeq ($(strip $(AUDIO_DRIVER)), dac_basic)
OPT_DEFS += -DAUDIO_DRIVER_DAC
else ifeq ($(strip $(AUDIO_DRIVER)), dac_additive)
OPT_DEFS += -DAUDIO_DRIVER_DAC
## stm32f2 and above have a usable DAC unit, f1 do not, and need to use pwm instead
else ifeq ($(strip $(AUDIO_DRIVER)), pwm_software)
OPT_DEFS += -DAUDIO_DRIVER_PWM
else ifeq ($(strip $(AUDIO_DRIVER)), pwm_hardware)
OPT_DEFS += -DAUDIO_DRIVER_PWM
endif
else
# fallback for all other platforms is pwm
AUDIO_DRIVER ?= pwm_hardware
OPT_DEFS += -DAUDIO_DRIVER_PWM
endif
OPT_DEFS += -DAUDIO_ENABLE
MUSIC_ENABLE = yes
SRC += $(QUANTUM_DIR)/process_keycode/process_audio.c
SRC += $(QUANTUM_DIR)/process_keycode/process_clicky.c
SRC += $(QUANTUM_DIR)/audio/audio_$(PLATFORM_KEY).c
SRC += $(QUANTUM_DIR)/audio/audio.c ## common audio code, hardware agnostic
SRC += $(QUANTUM_DIR)/audio/driver_$(PLATFORM_KEY)_$(strip $(AUDIO_DRIVER)).c
SRC += $(QUANTUM_DIR)/audio/voices.c
SRC += $(QUANTUM_DIR)/audio/luts.c
endif

View file

@ -133,6 +133,7 @@
* [Compatible Microcontrollers](compatible_microcontrollers.md)
* [Drivers](hardware_drivers.md)
* [ADC Driver](adc_driver.md)
* [Audio Driver](audio_driver.md)
* [I2C Driver](i2c_driver.md)
* [SPI Driver](spi_driver.md)
* [WS2812 Driver](ws2812_driver.md)

221
docs/audio_driver.md Normal file
View file

@ -0,0 +1,221 @@
# Audio Driver :id=audio-driver
The [Audio feature](feature_audio.md) breaks the hardware specifics out into separate, exchangeable driver units, with a common interface to the audio-"core" - which itself handles playing songs and notes while tracking their progress in an internal state, initializing/starting/stopping the driver as needed.
Not all MCUs support every available driver, either the platform-support is not there (yet?) or the MCU simply does not have the required hardware peripheral.
## AVR :id=avr
Boards built around an Atmega32U4 can use two sets of PWM capable pins, each driving a separate speaker.
The possible configurations are:
| | Timer3 | Timer1 |
|--------------|-------------|--------------|
| one speaker | C4,C5 or C6 | |
| one speaker | | B4, B5 or B7 |
| two speakers | C4,C5 or C6 | B4, B5 or B7 |
Currently there is only one/default driver for AVR based boards, which is automatically configured to:
```make
AUDIO_DRIVER = pwm_hardware
```
## ARM :id=arm
For Arm based boards, QMK depends on ChibiOS - hence any MCU supported by the later is likely usable, as long as certain hardware peripherals are available.
Supported wiring configurations, with their ChibiOS/MCU peripheral requirement are listed below;
piezo speakers are marked with :one: for the first/primary and :two: for the secondary.
| driver | GPTD6<br>Tim6 | GPTD7<br>Tim7 | GPTD8<br>Tim8 | PWMD1<sup>1</sup><br>Tim1_Ch1 |
|--------------|------------------------------------------|------------------------|---------------|-------------------------------|
| dac_basic | A4+DACD1 = :one: | A5+DACD2 = :one: | state | |
| | A4+DACD1 = :one: + Gnd | A5+DACD2 = :two: + Gnd | state | |
| | A4+DACD1 = :two: + Gnd | A5+DACD2 = :one: + Gnd | state | |
| | A4+DACD1 = :one: + Gnd | | state | |
| | | A5+DACD2 = :one: + Gnd | state | |
| dac_additive | A4+DACD1 = :one: + Gnd | | | |
| | A5+DACD2 = :one: + Gnd | | | |
| | A4+DACD1 + A5+DACD2 = :one: <sup>2</sup> | | | |
| pwm_software | state-update | | | any = :one: |
| pwm hardware | state-update | | | A8 = :one: <sup>3</sup> |
<sup>1</sup>: the routing and alternate functions for PWM differ sometimes between STM32 MCUs, if in doubt consult the data-sheet
<sup>2</sup>: one piezo connected to A4 and A5, with AUDIO_PIN_ALT_AS_NEGATIVE set
<sup>3</sup>: TIM1_CH1 = A8 on STM32F103C8, other combinations are possible, see Data-sheet. configured with: AUDIO_PWM_DRIVER and AUDIO_PWM_CHANNEL
### DAC basic :id=dac-basic
The default driver for ARM boards, in absence of an overriding configuration.
This driver needs one Timer per enabled/used DAC channel, to trigger conversion; and a third timer to trigger state updates with the audio-core.
Additionally, in the board config, you'll want to make changes to enable the DACs, GPT for Timers 6, 7 and 8:
``` c
//halconf.h:
#define HAL_USE_DAC TRUE
#define HAL_USE_GPT TRUE
#include_next <halconf.h>
```
``` c
// mcuconf.h:
#include_next <mcuconf.h>
#undef STM32_DAC_USE_DAC1_CH1
#define STM32_DAC_USE_DAC1_CH1 TRUE
#undef STM32_DAC_USE_DAC1_CH2
#define STM32_DAC_USE_DAC1_CH2 TRUE
#undef STM32_GPT_USE_TIM6
#define STM32_GPT_USE_TIM6 TRUE
#undef STM32_GPT_USE_TIM7
#define STM32_GPT_USE_TIM7 TRUE
#undef STM32_GPT_USE_TIM8
#define STM32_GPT_USE_TIM8 TRUE
```
?> Note: DAC1 (A4) uses TIM6, DAC2 (A5) uses TIM7, and the audio state timer uses TIM8 (configurable).
You can also change the timer used for the overall audio state by defining the driver. For instance:
```c
#define AUDIO_STATE_TIMER GPTD9
```
### DAC additive :id=dac-additive
only needs one timer (GPTD6, Tim6) to trigger the DAC unit to do a conversion; the audio state updates are in turn triggered during the DAC callback.
Additionally, in the board config, you'll want to make changes to enable the DACs, GPT for Timer 6:
``` c
//halconf.h:
#define HAL_USE_DAC TRUE
#define HAL_USE_GPT TRUE
#include_next <halconf.h>
```
``` c
// mcuconf.h:
#include_next <mcuconf.h>
#undef STM32_DAC_USE_DAC1_CH1
#define STM32_DAC_USE_DAC1_CH1 TRUE
#undef STM32_DAC_USE_DAC1_CH2
#define STM32_DAC_USE_DAC1_CH2 TRUE
#undef STM32_GPT_USE_TIM6
#define STM32_GPT_USE_TIM6 TRUE
```
### DAC Config
| Define | Defaults | Description --------------------------------------------------------------------------------------------- |
| `AUDIO_DAC_SAMPLE_MAX` | `4095U` | Highest value allowed. Lower value means lower volume. And 4095U is the upper limit, since this is limited to a 12 bit value. Only effects non-pregenerated samples. |
| `AUDIO_DAC_OFF_VALUE` | `AUDIO_DAC_SAMPLE_MAX / 2` | The value of the DAC when notplaying anything. Some setups may require a high (`AUDIO_DAC_SAMPLE_MAX`) or low (`0`) value here. |
| `AUDIO_MAX_SIMULTANEOUS_TONES` | __see next table__ | The number of tones that can be played simultaneously. A value that is too high may freeze the controller or glitch out when too many tones are being played. |
| `AUDIO_DAC_SAMPLE_RATE` | __see next table__ | Effective bit rate of the DAC (in hertz), higher limits simultaneous tones, and lower sacrifices quality. |
There are a number of predefined quality settings that you can use, with "sane minimum" being the default. You can use custom values by simply defining the sample rate and number of simultaneous tones, instead of using one of the listed presets.
| Define | Sample Rate | Simultaneous tones |
| `AUDIO_DAC_QUALITY_VERY_LOW` | `11025U` | `8` |
| `AUDIO_DAC_QUALITY_LOW` | `22040U` | `4` |
| `AUDIO_DAC_QUALITY_HIGH` | `44100U` | `2` |
| `AUDIO_DAC_QUALITY_VERY_HIGH` | `88200U` | `1` |
| `AUDIO_DAC_QUALITY_SANE_MINIMUM` | `16384U` | `8` |
```c
/* 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
*/
```
### PWM hardware :id=pwm-hardware
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 MCU's data-sheet for which pin can be driven by what timer - looking for TIMx_CHy and the corresponding alternate function.
A configuration example for the STM32F103C8 would be:
``` c
//halconf.h:
#define HAL_USE_PWM TRUE
#define HAL_USE_PAL TRUE
#define HAL_USE_GPT TRUE
#include_next <halconf.h>
```
``` c
// mcuconf.h:
#include_next <mcuconf.h>
#undef STM32_PWM_USE_TIM1
#define STM32_PWM_USE_TIM1 TRUE
#undef STM32_GPT_USE_TIM4
#define STM32_GPT_USE_TIM4 TRUE
```
If we now target pin A8, looking through the data-sheet of the STM32F103C8, for the timers and alternate functions
- TIM1_CH1 = PA8 <- alternate0
- TIM1_CH2 = PA9
- TIM1_CH3 = PA10
- TIM1_CH4 = PA11
with all this information, the configuration would contain these lines:
``` c
//config.h:
#define AUDIO_PIN A8
#define AUDIO_PWM_DRIVER PWMD1
#define AUDIO_PWM_CHANNEL 1
#define AUDIO_STATE_TIMER GPTD4
```
ChibiOS uses GPIOv1 for the F103, which only knows of one alternate function.
On 'larger' STM32s, GPIOv2 or GPIOv3 are used; with them it is also necessary to configure `AUDIO_PWM_PAL_MODE` to the correct alternate function for the selected pin, timer and timer-channel.
### PWM software :id=pwm-software
This driver uses the PWM callbacks from PWMD1 with TIM1_CH1 to toggle the selected AUDIO_PIN in software.
During the same callback, with AUDIO_PIN_ALT_AS_NEGATIVE set, the AUDIO_PIN_ALT is toggled inversely to AUDIO_PIN. This is useful for setups that drive a piezo from two pins (instead of one and Gnd).
You can also change the timer used for software PWM by defining the driver. For instance:
```c
#define AUDIO_STATE_TIMER GPTD8
```
### Testing Notes :id=testing-notes
While not an exhaustive list, the following table provides the scenarios that have been partially validated:
| | DAC basic | DAC additive | PWM hardware | PWM software |
|--------------------------|--------------------|--------------------|--------------------|--------------------|
| Atmega32U4 | :o: | :o: | :heavy_check_mark: | :o: |
| STM32F103C8 (bluepill) | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |
| STM32F303CCT6 (proton-c) | :heavy_check_mark: | :heavy_check_mark: | ? | :heavy_check_mark: |
| STM32F405VG | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| L0xx | :x: (no Tim8) | ? | ? | ? |
:heavy_check_mark: : works and was tested
:o: : does not apply
:x: : not supported by MCU
*Other supported ChibiOS boards and/or pins may function, it will be highly chip and configuration dependent.*

View file

@ -67,16 +67,22 @@ This is a C header file that is one of the first things included, and will persi
* turns on the alternate audio voices (to cycle through)
* `#define C4_AUDIO`
* enables audio on pin C4
* Deprecated. Use `#define AUDIO_PIN C4`
* `#define C5_AUDIO`
* enables audio on pin C5
* Deprecated. Use `#define AUDIO_PIN C5`
* `#define C6_AUDIO`
* enables audio on pin C6
* Deprecated. Use `#define AUDIO_PIN C6`
* `#define B5_AUDIO`
* enables audio on pin B5 (duophony is enables if one of B[5-7]\_AUDIO is enabled along with one of C[4-6]\_AUDIO)
* enables audio on pin B5 (duophony is enabled if one of B pins is enabled along with one of C pins)
* Deprecated. Use `#define AUDIO_PIN B5`, or use `#define AUDIO_PIN_ALT B5` if a `C` pin is enabled with `AUDIO_PIN`
* `#define B6_AUDIO`
* enables audio on pin B6 (duophony is enables if one of B[5-7]\_AUDIO is enabled along with one of C[4-6]\_AUDIO)
* enables audio on pin B5 (duophony is enabled if one of B pins is enabled along with one of C pins)
* Deprecated. Use `#define AUDIO_PIN B6`, or use `#define AUDIO_PIN_ALT B6` if a `C` pin is enabled with `AUDIO_PIN`
* `#define B7_AUDIO`
* enables audio on pin B7 (duophony is enables if one of B[5-7]\_AUDIO is enabled along with one of C[4-6]\_AUDIO)
* enables audio on pin B5 (duophony is enabled if one of B pins is enabled along with one of C pins)
* Deprecated. Use `#define AUDIO_PIN B7`, or use `#define AUDIO_PIN_ALT B7` if a `C` pin is enabled with `AUDIO_PIN`
* `#define BACKLIGHT_PIN B7`
* pin of the backlight
* `#define BACKLIGHT_LEVELS 3`

View file

@ -1,21 +1,117 @@
# Audio
Your keyboard can make sounds! If you've got a Planck, Preonic, or basically any AVR keyboard that allows access to certain PWM-capable pins, you can hook up a simple speaker and make it beep. You can use those beeps to indicate layer transitions, modifiers, special keys, or just to play some funky 8bit tunes.
Your keyboard can make sounds! If you've got a spare pin you can hook up a simple speaker and make it beep. You can use those beeps to indicate layer transitions, modifiers, special keys, or just to play some funky 8bit tunes.
Up to two simultaneous audio voices are supported, one driven by timer 1 and another driven by timer 3. The following pins can be defined as audio outputs in config.h:
To activate this feature, add `AUDIO_ENABLE = yes` to your `rules.mk`.
Timer 1:
`#define B5_AUDIO`
`#define B6_AUDIO`
`#define B7_AUDIO`
## AVR based boards
On Atmega32U4 based boards, up to two simultaneous tones can be rendered.
With one speaker connected to a PWM capable pin on PORTC driven by timer 3 and the other on one of the PWM pins on PORTB driven by timer 1.
Timer 3:
`#define C4_AUDIO`
`#define C5_AUDIO`
`#define C6_AUDIO`
The following pins can be configured as audio outputs in `config.h` - for one speaker set eiter one out of:
If you add `AUDIO_ENABLE = yes` to your `rules.mk`, there's a couple different sounds that will automatically be enabled without any other configuration:
* `#define AUDIO_PIN C4`
* `#define AUDIO_PIN C5`
* `#define AUDIO_PIN C6`
* `#define AUDIO_PIN B5`
* `#define AUDIO_PIN B6`
* `#define AUDIO_PIN B7`
and *optionally*, for a second speaker, one of:
* `#define AUDIO_PIN_ALT B5`
* `#define AUDIO_PIN_ALT B6`
* `#define AUDIO_PIN_ALT B7`
### Wiring
per speaker is - for example with a piezo buzzer - the black lead to Ground, and the red lead connected to the selected AUDIO_PIN for the primary; and similarly with AUDIO_PIN_ALT for the secondary.
## ARM based boards
for more technical details, see the notes on [Audio driver](audio_driver.md).
<!-- because I'm not sure where to fit this in: https://waveeditonline.com/ -->
### DAC (basic)
Most STM32 MCUs have DAC peripherals, with a notable exception of the STM32F1xx series. Generally, the DAC peripheral drives pins A4 or A5. To enable DAC-based audio output on STM32 devices, add `AUDIO_DRIVER = dac_basic` to `rules.mk` and set in `config.h` either:
`#define AUDIO_PIN A4` or `#define AUDIO_PIN A5`
the other DAC channel can optionally be used with a secondary speaker, just set:
`#define AUDIO_PIN_ALT A4` or `#define AUDIO_PIN_ALT A5`
Do note though that the dac_basic driver is only capable of reproducing one tone per speaker/channel at a time, for more tones simultaneously, try the dac_additive driver.
#### Wiring:
for two piezos, for example configured as `AUDIO_PIN A4` and `AUDIO_PIN_ALT A5` would be: red lead to A4 and black to Ground, and similarly with the second one: A5 = red, and Ground = black
another alternative is to drive *one* piezo with both DAC pins - for an extra "push".
wiring red to A4 and black to A5 (or the other way round) and add `#define AUDIO_PIN_ALT_AS_NEGATIVE` to `config.h`
##### Proton-C Example:
The Proton-C comes (optionally) with one 'builtin' piezo, which is wired to A4+A5.
For this board `config.h` would include these defines:
```c
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
```
### DAC (additive)
Another option, besides dac_basic (which produces sound through a square-wave), is to use the DAC to do additive wave synthesis.
With a number of predefined wave-forms or by providing your own implementation to generate samples on the fly.
To use this feature set `AUDIO_DRIVER = dac_additive` in your `rules.mk`, and select in `config.h` EITHER `#define AUDIO_PIN A4` or `#define AUDIO_PIN A5`.
The used waveform *defaults* to sine, but others can be selected by adding one of the following defines to `config.h`:
* `#define AUDIO_DAC_SAMPLE_WAVEFORM_SINE`
* `#define AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE`
* `#define AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID`
* `#define AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE`
Should you rather choose to generate and use your own sample-table with the DAC unit, implement `uint16_t dac_value_generate(void)` with your keyboard - for an example implementation see keyboards/planck/keymaps/synth_sample or keyboards/planck/keymaps/synth_wavetable
### PWM (software)
if the DAC pins are unavailable (or the MCU has no usable DAC at all, like STM32F1xx); PWM can be an alternative.
Note that there is currently only one speaker/pin supported.
set in `rules.mk`:
`AUDIO_DRIVER = pwm_software` and in `config.h`:
`#define AUDIO_PIN C13` (can be any pin) to have the selected pin output a pwm signal, generated from a timer callback which toggles the pin in software.
#### Wiring
the usual piezo wiring: red goes to the selected AUDIO_PIN, black goes to ground.
OR if you can chose to drive one piezo with two pins, for example `#define AUDIO_PIN B1`, `#define AUDIO_PIN_ALT B2` in `config.h`, with `#define AUDIO_PIN_ALT_AS_NEGATIVE` - then the red lead could go to B1, the black to B2.
### PWM (hardware)
STM32F1xx have to fall back to using PWM, but can do so in hardware; but again on currently only one speaker/pin.
`AUDIO_DRIVER = pwm_hardware` in `rules.mk`, and in `config.h`:
`#define AUDIO_PIN A8`
`#define AUDIO_PWM_DRIVER PWMD1`
`#define AUDIO_PWM_CHANNEL 1`
(as well as `#define AUDIO_PWM_PAL_MODE 42` if you are on STM32F2 or larger)
which will use Timer 1 to directly drive pin PA8 through the PWM hardware (TIM1_CH1 = PA8).
Should you want to use the pwm-hardware on another pin and timer - be ready to dig into the STM32 data-sheet to pick the right TIMx_CHy and pin-alternate function.
## Tone Multiplexing
Since most drivers can only render one tone per speaker at a time (with the one exception: arm dac-additive) there also exists a "workaround-feature" that does time-slicing/multiplexing - which does what the name implies: cycle through a set of active tones (e.g. when playing chords in Music Mode) at a given rate, and put one tone at a time out through the one/few speakers that are available.
To enable this feature, and configure a starting-rate, add the following defines to `config.h`:
```c
#define AUDIO_ENABLE_TONE_MULTIPLEXING
#define AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT 10
```
The audio core offers interface functions to get/set/change the tone multiplexing rate from within `keymap.c`.
## Songs
There's a couple of different sounds that will automatically be enabled without any other configuration:
```
STARTUP_SONG // plays when the keyboard starts up (audio.c)
GOODBYE_SONG // plays when you press the RESET key (quantum.c)
@ -69,13 +165,32 @@ The available keycodes for audio are:
!> These keycodes turn all of the audio functionality on and off. Turning it off means that audio feedback, audio clicky, music mode, etc. are disabled, completely.
## Tempo
the 'speed' at which SONGs are played is dictated by the set Tempo, which is measured in beats-per-minute. Note lenghts are defined relative to that.
The initial/default tempo is set to 120 bpm, but can be configured by setting `TEMPO_DEFAULT` in `config.c`.
There is also a set of functions to modify the tempo from within the user/keymap code:
```c
void audio_set_tempo(uint8_t tempo);
void audio_increase_tempo(uint8_t tempo_change);
void audio_decrease_tempo(uint8_t tempo_change);
```
## ARM Audio Volume
For ARM devices, you can adjust the DAC sample values. If your board is too loud for you or your coworkers, you can set the max using `DAC_SAMPLE_MAX` in your `config.h`:
For ARM devices, you can adjust the DAC sample values. If your board is too loud for you or your coworkers, you can set the max using `AUDIO_DAC_SAMPLE_MAX` in your `config.h`:
```c
#define DAC_SAMPLE_MAX 65535U
#define AUDIO_DAC_SAMPLE_MAX 4095U
```
the DAC usually runs in 12Bit mode, hence a volume of 100% = 4095U
Note: this only adjusts the volume aka 'works' if you stick to WAVEFORM_SQUARE, since its samples are generated on the fly - any other waveform uses a hardcoded/precomputed sample-buffer.
## Voices
Aka "audio effects", different ones can be enabled by setting in `config.h` these defines:
`#define AUDIO_VOICES` to enable the feature, and `#define AUDIO_VOICE_DEFAULT something` to select a specific effect
for details see quantum/audio/voices.h and .c
## Music Mode

View file

@ -18,3 +18,7 @@
#define ENCODERS_PAD_A { A2 }
#define ENCODERS_PAD_B { A1 }
#define ENCODER_RESOLUTION 4
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE

View file

@ -31,7 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BACKLIGHT_PIN B7
#define BACKLIGHT_BREATHING
#define C6_AUDIO
#define AUDIO_PIN C6
/* COL2ROW or ROW2COL */
#define DIODE_DIRECTION COL2ROW

View file

@ -31,7 +31,10 @@
//Audio
#undef AUDIO_VOICES
#undef C6_AUDIO
#undef AUDIO_PIN
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
#ifdef AUDIO_ENABLE
#define STARTUP_SONG SONG(ONE_UP_SOUND)

View file

@ -32,12 +32,3 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# define RGB_DI_PIN D0
# define RGBLED_NUM 4
#endif // !RGBLIGHT_ENABLE
/*
#ifdef AUDIO_ENABLE
# Timer 1: #define B5_AUDIO #define B6_AUDIO #define B7_AUDIO
# Timer 3: #define C4_AUDIO #define C5_AUDIO #define C6_AUDIO
//TODO: only D0 and D1 available
#endif // !AUDIO_ENABLE
*/

View file

@ -154,7 +154,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifdef AUDIO_ENABLE
#define AUDIO_VOICES
#define C6_AUDIO
#define AUDIO_PIN C6
#define STARTUP_SONG SONG(STARTUP_SOUND)
#endif

View file

@ -157,6 +157,9 @@
// NOTE: Must change polyphony_rate to a number higher than 0 in voices.c
#define AUDIO_VOICES
#define PITCH_STANDARD_A 880.0f
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
// Mouse keys
#define MOUSEKEY_DELAY 0

View file

@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "config_common.h"
/* audio support */
#define B7_AUDIO
#define C4_AUDIO
#define AUDIO_PIN_ALT B7
#define AUDIO_PIN C4
#define AUDIO_CLICKY
/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */

View file

@ -27,8 +27,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define ENCODER_RESOLUTION 4
/* audio support */
#define B7_AUDIO
#define C4_AUDIO
#define AUDIO_PIN_ALT B7
#define AUDIO_PIN C4
#define AUDIO_CLICKY
/*

View file

@ -22,3 +22,7 @@
#define RGBLIGHT_EFFECT_KNIGHT_OFFSET 2 // The led to start at
#define RGBLIGHT_EFFECT_KNIGHT_LED_NUM 5 // How many LEDs to travel
#define RGBLIGHT_EFFECT_SNAKE_LENGTH 4 // How many LEDs wide to light up
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE

View file

@ -108,3 +108,7 @@
#define LED_DRIVER_COUNT 1
#define LED_DRIVER_LED_COUNT 71
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE

View file

@ -3,7 +3,7 @@
/* Speaker configuration
*/
#define B7_AUDIO
#define AUDIO_PIN B7
#define NO_MUSIC_MODE
#define AUDIO_CLICKY

View file

@ -1,3 +1,7 @@
#pragma once
#include "config_common.h"
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE

View file

@ -23,4 +23,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BACKLIGHT_LEVELS 6
// Enable audio
#define C6_AUDIO
#define AUDIO_PIN C6

View file

@ -15,7 +15,7 @@
#define NO_ACTION_ONESHOT
#ifdef AUDIO_ENABLE
# define B5_AUDIO
# define AUDIO_PIN B5
# define NO_MUSIC_MODE
# define AUDIO_CLICKY
#endif

View file

@ -45,7 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* define if matrix has ghost */
//#define MATRIX_HAS_GHOST
#define C6_AUDIO
#define AUDIO_PIN C6
/* number of backlight levels */
#ifdef BACKLIGHT_ENABLE

View file

@ -45,7 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* define if matrix has ghost */
//#define MATRIX_HAS_GHOST
#define C6_AUDIO
#define AUDIO_PIN C6
/* number of backlight levels */
#ifdef BACKLIGHT_ENABLE

View file

@ -1,3 +1,3 @@
#pragma once
#define B7_AUDIO
#define AUDIO_PIN B7

View file

@ -39,4 +39,4 @@
#define QMK_LED B0
// set audio pin
#define C6_AUDIO
#define AUDIO_PIN C6

View file

@ -50,7 +50,11 @@
//Audio
#undef AUDIO_VOICES
#undef C6_AUDIO
#undef AUDIO_PIN
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
#ifdef AUDIO_ENABLE
#define STARTUP_SONG SONG(PLANCK_SOUND)

View file

@ -101,7 +101,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/**
* Aranck-specific definitions
*/
#define B5_AUDIO
#define AUDIO_PIN B5
/**
* Aranck-specific definitions END
*/

View file

@ -61,7 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BOOTMAGIC_LITE_ROW_RIGHT 6
#define BOOTMAGIC_LITE_COLUMN_RIGHT 5
#define C6_AUDIO
#define AUDIO_PIN C6
#define DYNAMIC_KEYMAP_EEPROM_MAX_ADDR 4095
#define DYNAMIC_KEYMAP_LAYER_COUNT 16

View file

@ -101,7 +101,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/**
* Heisenberg-specific definitions
*/
#define B5_AUDIO
#define AUDIO_PIN B5
/**
* Heisenberg-specific definitions END
*/

View file

@ -102,8 +102,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#define B6_AUDIO
#define C6_AUDIO
#define AUDIO_PIN_ALT B6
#define AUDIO_PIN C6
/* control how magic key switches layers */
//#define MAGIC_KEY_SWITCH_LAYER_WITH_FKEYS true

View file

@ -34,10 +34,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifdef AUDIO_ENABLE
#define C6_AUDIO
#define AUDIO_PIN C6
#define STARTUP_SONG SONG(STARTUP_SOUND)
#define NO_MUSIC_MODE
#define TONE_QWERTY SONG(Q__NOTE(_E4));
#define TONE_NUMPAD SONG(Q__NOTE(_D4));
#endif

View file

@ -77,7 +77,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio */
#ifdef AUDIO_ENABLE
#define B5_AUDIO
#define AUDIO_PIN B5
#endif
/* ws2812 RGB LED */

View file

@ -48,5 +48,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define RGBLED_NUM 20
#define RGBLIGHT_ANIMATIONS
#define A4_AUDIO
#define A5_AUDIO
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE

View file

@ -57,8 +57,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio */
#ifdef AUDIO_ENABLE
#define C6_AUDIO
#define B6_AUDIO // 2nd pin for simultaneous audio.
#define AUDIO_PIN C6
#define AUDIO_PIN_ALT B6 // 2nd pin for simultaneous audio.
#define AUDIO_CLICKY
#endif

View file

@ -57,8 +57,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio */
#ifdef AUDIO_ENABLE
#define C6_AUDIO
#define B7_AUDIO // 2nd pin for simultaneous audio.
#define AUDIO_PIN C6
#define AUDIO_PIN_ALT B7 // 2nd pin for simultaneous audio.
#define AUDIO_CLICKY
#endif

View file

@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#define C6_AUDIO
#define AUDIO_PIN C6
#undef RGBLED_NUM
#define RGBLED_NUM 12

View file

@ -32,7 +32,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define RGBLIGHT_HUE_STEP 5
#define RGBLIGHT_SAT_STEP 8
#define RGBLIGHT_VAL_STEP 8
#define C6_AUDIO
#define AUDIO_PIN C6
#define NO_MUSIC_MODE

View file

@ -43,7 +43,7 @@
#endif // RGBLIGHT_ENABLE
#ifdef AUDIO_ENABLE
# define C6_AUDIO
# define AUDIO_PIN C6
# ifdef RGBLIGHT_ENABLE
# ifndef __arm__
# define NO_MUSIC_MODE

View file

@ -34,7 +34,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// #define AUDIO_CLICKY
// #define AUDIO_CLICKY_ON
// #define C6_AUDIO
// #define AUDIO_PIN C6
// #define AUDIO_CLICKY_FREQ_RANDOMNESS 0.1f
// #define AUDIO_CLICKY_FREQ_MAX 100.0f

View file

@ -16,7 +16,7 @@
// Choose pin to use for audio. C6 is the one iris uses.
#ifdef AUDIO_ENABLE
# define C6_AUDIO
# define AUDIO_PIN C6
# define STARTUP_SONG SONG(NO_SOUND) // No startup song.
#endif

View file

@ -29,6 +29,6 @@
#ifdef AUDIO_ENABLE
#define QMK_SPEAKER C6
#define C6_AUDIO
#define AUDIO_PIN C6
#define NO_MUSIC_MODE // Save 2000 bytes
#endif

View file

@ -35,6 +35,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#ifdef AUDIO_ENABLE
#define B7_AUDIO
#define AUDIO_PIN B7
#define AUDIO_CLICKY
#endif

View file

@ -34,7 +34,7 @@
#define PRODUCT Drashnas Viterbi Macro Pad
#ifdef AUDIO_ENABLE
# define C6_AUDIO
# define AUDIO_PIN C6
# define NO_MUSIC_MODE
#endif

View file

@ -59,7 +59,7 @@
#define RGBLIGHT_HUE_STEP 4
#define RGBLIGHT_SAT_STEP 4
#define RGBLIGHT_VAL_STEP 4
#define B6_AUDIO
#define AUDIO_PIN B6
#define AUDIO_CLICKY
#endif

View file

@ -18,8 +18,8 @@
#if defined(AUDIO_ENABLE)
#define AUDIO_CLICKY
#define B5_AUDIO
#define C6_AUDIO
#define AUDIO_PIN_ALT B5
#define AUDIO_PIN C6
#define STARTUP_SONG SONG(ZELDA_PUZZLE)
#define GOODBYE_SONG SONG(COIN_SOUND)
#endif

View file

@ -27,5 +27,5 @@
#define RGBLIGHT_VAL_STEP 17
#define DRIVER_LED_TOTAL RGBLED_NUM
#define B7_AUDIO
#define AUDIO_PIN B7
#define AUDIO_CLICKY

View file

@ -61,7 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio settings */
#ifdef AUDIO_ENABLE
#define C6_AUDIO // Define this to enable the buzzer
#define AUDIO_PIN C6 // Define this to enable the buzzer
#endif
/*

View file

@ -39,7 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TAPPING_TERM 200
#define C6_AUDIO
#define AUDIO_PIN C6
#define AUDIO_VOICES
#define RGB_DI_PIN C7 // Have to set it to something to get the ws2812 code to compile

View file

@ -50,7 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#define AUDIO_VOICES
#define C6_AUDIO
#define AUDIO_PIN C6
#define BACKLIGHT_LEVELS 10
#define BACKLIGHT_PWM_MAP {2, 4, 8, 16, 40, 55, 70, 128, 200, 255}

View file

@ -37,7 +37,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define RGBLED_NUM 26 // Number of LEDs
#define AUDIO_VOICES
#define C6_AUDIO
#define AUDIO_PIN C6
#define BACKLIGHT_LEVELS 10
#define BACKLIGHT_PWM_MAP {2, 4, 8, 16, 40, 55, 70, 128, 200, 255}

View file

@ -43,8 +43,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
//RevB only:
#define AUDIO_VOICES
#define C6_AUDIO
// #define B5_AUDIO
#define AUDIO_PIN C6
#define BACKLIGHT_LEVELS 8
#define BACKLIGHT_PWM_MAP {8, 16, 40, 55, 70, 128, 200, 255}

View file

@ -37,7 +37,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define UNUSED_PINS
#define QMK_SPEAKER B5
#define B5_AUDIO
#define AUDIO_PIN B5
#define AUDIO_VOICES
// #define BACKLIGHT_PIN B7

View file

@ -21,7 +21,7 @@
// place overrides here
#define MUSIC_MASK (keycode != KC_NO)
#define C6_AUDIO
#define AUDIO_PIN C6
#ifdef AUDIO_ENABLE
#define STARTUP_SONG SONG(PLANCK_SOUND)
// #define STARTUP_SONG SONG(NO_SOUND)

View file

@ -36,7 +36,7 @@
}
#define AUDIO_VOICES
#define AUDIO_CLICKY
#define C6_AUDIO
#define AUDIO_PIN C6
#endif
#endif

View file

@ -15,7 +15,7 @@
#endif
#define AUDIO_VOICES
#define C6_AUDIO
#define AUDIO_PIN C6
// fix iPhone power adapter issue
#define USB_MAX_POWER_CONSUMPTION 50

View file

@ -101,3 +101,7 @@
# define DYNAMIC_KEYMAP_EEPROM_MAX_ADDR 16383
# define DYNAMIC_KEYMAP_LAYER_COUNT 32
#endif
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE

View file

@ -87,5 +87,5 @@
#define RGBLED_NUM 3
// disabled, needs PCB patch.
//#define C6_AUDIO
//#define AUDIO_PIN C6
//#define NO_MUSIC_MODE

View file

@ -49,11 +49,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifdef AUDIO_ENABLE
#define STARTUP_SONG SONG(NO_SOUND)
#define AUDIO_PIN A4 // Pin of the left speaker
#define AUDIO_PIN_ALT A5 // Pin of the right speaker
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
/*
* Basically, change this section once pull request 6165 has been merged
* https://github.com/qmk/qmk_firmware/pull/6165
*/
#endif

View file

@ -66,7 +66,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio */
#define B7_AUDIO
#define AUDIO_PIN B7
#define AUDIO_CLICKY
@ -80,5 +80,3 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define LOCKING_SUPPORT_ENABLE
/* Locking resynchronize hack */
#define LOCKING_RESYNC_ENABLE

View file

@ -84,7 +84,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// #define RGBLIGHT_EFFECT_BREATHE_MAX 255 // 0 to 255
#endif
#define B7_AUDIO
#define AUDIO_PIN B7
#define AUDIO_CLICKY

View file

@ -66,7 +66,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio */
#define B5_AUDIO
#define AUDIO_PIN B5
#define AUDIO_CLICKY
#define NO_MUSIC_MODE

View file

@ -50,7 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif // RGBLIGHT_ENABLE
#ifdef AUDIO_ENABLE
# define C6_AUDIO
# define AUDIO_PIN C6
# ifdef RGBLIGHT_ENABLE
# define NO_MUSIC_MODE
# endif

View file

@ -41,7 +41,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMK_SPEAKER C6
#define AUDIO_VOICES
#define C6_AUDIO
#define AUDIO_PIN C6
#define BACKLIGHT_PIN B7

View file

@ -56,7 +56,10 @@
#define MUSIC_MAP
#undef AUDIO_VOICES
#undef C6_AUDIO
#undef AUDIO_PIN
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
/* Debounce reduces chatter (unintended double-presses) - set 0 if debouncing is not needed */
// #define DEBOUNCE 6

View file

@ -41,7 +41,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMK_SPEAKER C6
#define AUDIO_VOICES
#define C6_AUDIO
#define AUDIO_PIN C6
#define BACKLIGHT_PIN B7

View file

@ -0,0 +1,56 @@
/*
Copyright 2020 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/>.
*/
#pragma once
#ifdef AUDIO_ENABLE
#define AUDIO_PIN A5
#define STARTUP_SONG SONG(PLANCK_SOUND)
// #define STARTUP_SONG SONG(NO_SOUND)
#define DEFAULT_LAYER_SONGS { SONG(QWERTY_SOUND), \
SONG(COLEMAK_SOUND), \
SONG(DVORAK_SOUND) \
}
#endif
/*
* MIDI options
*/
/* Prevent use of disabled MIDI features in the keymap */
//#define MIDI_ENABLE_STRICT 1
/* enable basic MIDI features:
- MIDI notes can be sent when in Music mode is on
*/
#define MIDI_BASIC
/* enable advanced MIDI features:
- MIDI notes can be added to the keymap
- Octave shift and transpose
- Virtual sustain, portamento, and modulation wheel
- etc.
*/
//#define MIDI_ADVANCED
/* override number of MIDI tone keycodes (each octave adds 12 keycodes and allocates 12 bytes) */
//#define MIDI_TONE_KEYCODE_OCTAVES 2
// Most tactile encoders have detents every 4 stages
#define ENCODER_RESOLUTION 4

View file

@ -0,0 +1,296 @@
/* Copyright 2019 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 QMK_KEYBOARD_H
enum planck_layers {
_QWERTY,
_COLEMAK,
_DVORAK,
_LOWER,
_RAISE,
_PLOVER,
_ADJUST
};
enum planck_keycodes {
QWERTY = SAFE_RANGE,
COLEMAK,
DVORAK,
PLOVER,
BACKLIT,
EXT_PLV
};
#define LOWER MO(_LOWER)
#define RAISE MO(_RAISE)
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
/* Qwerty
* ,-----------------------------------------------------------------------------------.
* | Tab | Q | W | E | R | T | Y | U | I | O | P | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Esc | A | S | D | F | G | H | J | K | L | ; | " |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | Shift| Z | X | C | V | B | N | M | , | . | / |Enter |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Brite| Ctrl | Alt | GUI |Lower | Space |Raise | Left | Down | Up |Right |
* `-----------------------------------------------------------------------------------'
*/
[_QWERTY] = LAYOUT_planck_grid(
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSPC,
KC_ESC, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_ENT ,
BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
),
/* Colemak
* ,-----------------------------------------------------------------------------------.
* | Tab | Q | W | F | P | G | J | L | U | Y | ; | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Esc | A | R | S | T | D | H | N | E | I | O | " |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | Shift| Z | X | C | V | B | K | M | , | . | / |Enter |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Brite| Ctrl | Alt | GUI |Lower | Space |Raise | Left | Down | Up |Right |
* `-----------------------------------------------------------------------------------'
*/
[_COLEMAK] = LAYOUT_planck_grid(
KC_TAB, KC_Q, KC_W, KC_F, KC_P, KC_G, KC_J, KC_L, KC_U, KC_Y, KC_SCLN, KC_BSPC,
KC_ESC, KC_A, KC_R, KC_S, KC_T, KC_D, KC_H, KC_N, KC_E, KC_I, KC_O, KC_QUOT,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_K, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_ENT ,
BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
),
/* Dvorak
* ,-----------------------------------------------------------------------------------.
* | Tab | " | , | . | P | Y | F | G | C | R | L | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Esc | A | O | E | U | I | D | H | T | N | S | / |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | Shift| ; | Q | J | K | X | B | M | W | V | Z |Enter |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Brite| Ctrl | Alt | GUI |Lower | Space |Raise | Left | Down | Up |Right |
* `-----------------------------------------------------------------------------------'
*/
[_DVORAK] = LAYOUT_planck_grid(
KC_TAB, KC_QUOT, KC_COMM, KC_DOT, KC_P, KC_Y, KC_F, KC_G, KC_C, KC_R, KC_L, KC_BSPC,
KC_ESC, KC_A, KC_O, KC_E, KC_U, KC_I, KC_D, KC_H, KC_T, KC_N, KC_S, KC_SLSH,
KC_LSFT, KC_SCLN, KC_Q, KC_J, KC_K, KC_X, KC_B, KC_M, KC_W, KC_V, KC_Z, KC_ENT ,
BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
),
/* Lower
* ,-----------------------------------------------------------------------------------.
* | ~ | ! | @ | # | $ | % | ^ | & | * | ( | ) | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Del | F1 | F2 | F3 | F4 | F5 | F6 | _ | + | { | } | | |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | | F7 | F8 | F9 | F10 | F11 | F12 |ISO ~ |ISO | | Home | End | |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | | | | | | | | Next | Vol- | Vol+ | Play |
* `-----------------------------------------------------------------------------------'
*/
[_LOWER] = LAYOUT_planck_grid(
KC_TILD, KC_EXLM, KC_AT, KC_HASH, KC_DLR, KC_PERC, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_BSPC,
KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, KC_PIPE,
_______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, S(KC_NUHS), S(KC_NUBS), KC_HOME, KC_END, _______,
_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
),
/* Raise
* ,-----------------------------------------------------------------------------------.
* | ` | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Del | F1 | F2 | F3 | F4 | F5 | F6 | - | = | [ | ] | \ |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | | F7 | F8 | F9 | F10 | F11 | F12 |ISO # |ISO / |Pg Up |Pg Dn | |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | | | | | | | | Next | Vol- | Vol+ | Play |
* `-----------------------------------------------------------------------------------'
*/
[_RAISE] = LAYOUT_planck_grid(
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_BSPC,
KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_MINS, KC_EQL, KC_LBRC, KC_RBRC, KC_BSLS,
_______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_NUHS, KC_NUBS, KC_PGUP, KC_PGDN, _______,
_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
),
/* Plover layer (http://opensteno.org)
* ,-----------------------------------------------------------------------------------.
* | # | # | # | # | # | # | # | # | # | # | # | # |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | | S | T | P | H | * | * | F | P | L | T | D |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | | S | K | W | R | * | * | R | B | G | S | Z |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Exit | | | A | O | | E | U | | | |
* `-----------------------------------------------------------------------------------'
*/
[_PLOVER] = LAYOUT_planck_grid(
KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1 ,
XXXXXXX, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC,
XXXXXXX, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,
EXT_PLV, XXXXXXX, XXXXXXX, KC_C, KC_V, XXXXXXX, XXXXXXX, KC_N, KC_M, XXXXXXX, XXXXXXX, XXXXXXX
),
/* Adjust (Lower + Raise)
* ,-----------------------------------------------------------------------------------.
* | | Reset| | | | | | | | | | Del |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | | | |Aud on|Audoff|AGnorm|AGswap|Qwerty|Colemk|Dvorak|Plover| |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | |Voice-|Voice+|Mus on|Musoff|MIDIon|MIDIof| | | | | |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | | | | | | | | | | | |
* `-----------------------------------------------------------------------------------'
*/
[_ADJUST] = LAYOUT_planck_grid(
_______, RESET, DEBUG, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, KC_DEL ,
_______, _______, MU_MOD, AU_ON, AU_OFF, AG_NORM, AG_SWAP, QWERTY, COLEMAK, DVORAK, PLOVER, _______,
_______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, TERM_ON, TERM_OFF, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______
)
};
#ifdef AUDIO_ENABLE
float plover_song[][2] = SONG(PLOVER_SOUND);
float plover_gb_song[][2] = SONG(PLOVER_GOODBYE_SOUND);
#endif
uint32_t layer_state_set_user(uint32_t state) {
return update_tri_layer_state(state, _LOWER, _RAISE, _ADJUST);
}
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case QWERTY:
if (record->event.pressed) {
print("mode just switched to qwerty and this is a huge string\n");
set_single_persistent_default_layer(_QWERTY);
}
return false;
break;
case COLEMAK:
if (record->event.pressed) {
set_single_persistent_default_layer(_COLEMAK);
}
return false;
break;
case DVORAK:
if (record->event.pressed) {
set_single_persistent_default_layer(_DVORAK);
}
return false;
break;
case BACKLIT:
if (record->event.pressed) {
register_code(KC_RSFT);
#ifdef BACKLIGHT_ENABLE
backlight_step();
#endif
#ifdef KEYBOARD_planck_rev5
PORTE &= ~(1<<6);
#endif
} else {
unregister_code(KC_RSFT);
#ifdef KEYBOARD_planck_rev5
PORTE |= (1<<6);
#endif
}
return false;
break;
case PLOVER:
if (record->event.pressed) {
#ifdef AUDIO_ENABLE
stop_all_notes();
PLAY_SONG(plover_song);
#endif
layer_off(_RAISE);
layer_off(_LOWER);
layer_off(_ADJUST);
layer_on(_PLOVER);
if (!eeconfig_is_enabled()) {
eeconfig_init();
}
keymap_config.raw = eeconfig_read_keymap();
keymap_config.nkro = 1;
eeconfig_update_keymap(keymap_config.raw);
}
return false;
break;
case EXT_PLV:
if (record->event.pressed) {
#ifdef AUDIO_ENABLE
PLAY_SONG(plover_gb_song);
#endif
layer_off(_PLOVER);
}
return false;
break;
}
return true;
}
void encoder_update(bool clockwise) {
if (clockwise) {
#ifdef MOUSEKEY_ENABLE
register_code(KC_MS_WH_DOWN);
unregister_code(KC_MS_WH_DOWN);
#else
register_code(KC_PGDN);
unregister_code(KC_PGDN);
#endif
} else {
#ifdef MOUSEKEY_ENABLE
register_code(KC_MS_WH_UP);
unregister_code(KC_MS_WH_UP);
#else
register_code(KC_PGUP);
unregister_code(KC_PGUP);
#endif
}
}
void matrix_scan_user(void) {
}
bool music_mask_user(uint16_t keycode) {
switch (keycode) {
case RAISE:
case LOWER:
return false;
default:
return true;
}
}
#include "sample.h"
uint32_t dac_sample_custom_counter = 0;
uint16_t dac_value_generate(void) {
if (is_playing_note()) {
uint16_t sample = dac_sample_custom[dac_sample_custom_counter];
dac_sample_custom_counter = (dac_sample_custom_counter + 1) % AUDIO_DAC_SAMPLE_CUSTOM_LENGTH;
return sample;
} else {
return AUDIO_DAC_OFF_VALUE;
}
}

View file

@ -0,0 +1,2 @@
AUDIO_ENABLE = yes
AUDIO_DRIVER = dac_additive

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
/*
Copyright 2020 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/>.
*/
#pragma once
#ifdef AUDIO_ENABLE
#define AUDIO_PIN A5
#define STARTUP_SONG SONG(PLANCK_SOUND)
// #define STARTUP_SONG SONG(NO_SOUND)
#define DEFAULT_LAYER_SONGS { SONG(QWERTY_SOUND), \
SONG(COLEMAK_SOUND), \
SONG(DVORAK_SOUND) \
}
#endif
/*
* MIDI options
*/
/* Prevent use of disabled MIDI features in the keymap */
//#define MIDI_ENABLE_STRICT 1
/* enable basic MIDI features:
- MIDI notes can be sent when in Music mode is on
*/
#define MIDI_BASIC
/* enable advanced MIDI features:
- MIDI notes can be added to the keymap
- Octave shift and transpose
- Virtual sustain, portamento, and modulation wheel
- etc.
*/
//#define MIDI_ADVANCED
/* override number of MIDI tone keycodes (each octave adds 12 keycodes and allocates 12 bytes) */
//#define MIDI_TONE_KEYCODE_OCTAVES 2
// Most tactile encoders have detents every 4 stages
#define ENCODER_RESOLUTION 4

View file

@ -0,0 +1,320 @@
/* Copyright 2019 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 QMK_KEYBOARD_H
enum planck_layers {
_QWERTY,
_COLEMAK,
_DVORAK,
_LOWER,
_RAISE,
_PLOVER,
_ADJUST
};
enum planck_keycodes {
QWERTY = SAFE_RANGE,
COLEMAK,
DVORAK,
PLOVER,
BACKLIT,
EXT_PLV
};
#define LOWER MO(_LOWER)
#define RAISE MO(_RAISE)
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
/* Qwerty
* ,-----------------------------------------------------------------------------------.
* | Tab | Q | W | E | R | T | Y | U | I | O | P | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Esc | A | S | D | F | G | H | J | K | L | ; | " |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | Shift| Z | X | C | V | B | N | M | , | . | / |Enter |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Brite| Ctrl | Alt | GUI |Lower | Space |Raise | Left | Down | Up |Right |
* `-----------------------------------------------------------------------------------'
*/
[_QWERTY] = LAYOUT_planck_grid(
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSPC,
KC_ESC, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_ENT ,
BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
),
/* Colemak
* ,-----------------------------------------------------------------------------------.
* | Tab | Q | W | F | P | G | J | L | U | Y | ; | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Esc | A | R | S | T | D | H | N | E | I | O | " |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | Shift| Z | X | C | V | B | K | M | , | . | / |Enter |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Brite| Ctrl | Alt | GUI |Lower | Space |Raise | Left | Down | Up |Right |
* `-----------------------------------------------------------------------------------'
*/
[_COLEMAK] = LAYOUT_planck_grid(
KC_TAB, KC_Q, KC_W, KC_F, KC_P, KC_G, KC_J, KC_L, KC_U, KC_Y, KC_SCLN, KC_BSPC,
KC_ESC, KC_A, KC_R, KC_S, KC_T, KC_D, KC_H, KC_N, KC_E, KC_I, KC_O, KC_QUOT,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_K, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_ENT ,
BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
),
/* Dvorak
* ,-----------------------------------------------------------------------------------.
* | Tab | " | , | . | P | Y | F | G | C | R | L | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Esc | A | O | E | U | I | D | H | T | N | S | / |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | Shift| ; | Q | J | K | X | B | M | W | V | Z |Enter |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Brite| Ctrl | Alt | GUI |Lower | Space |Raise | Left | Down | Up |Right |
* `-----------------------------------------------------------------------------------'
*/
[_DVORAK] = LAYOUT_planck_grid(
KC_TAB, KC_QUOT, KC_COMM, KC_DOT, KC_P, KC_Y, KC_F, KC_G, KC_C, KC_R, KC_L, KC_BSPC,
KC_ESC, KC_A, KC_O, KC_E, KC_U, KC_I, KC_D, KC_H, KC_T, KC_N, KC_S, KC_SLSH,
KC_LSFT, KC_SCLN, KC_Q, KC_J, KC_K, KC_X, KC_B, KC_M, KC_W, KC_V, KC_Z, KC_ENT ,
BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
),
/* Lower
* ,-----------------------------------------------------------------------------------.
* | ~ | ! | @ | # | $ | % | ^ | & | * | ( | ) | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Del | F1 | F2 | F3 | F4 | F5 | F6 | _ | + | { | } | | |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | | F7 | F8 | F9 | F10 | F11 | F12 |ISO ~ |ISO | | Home | End | |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | | | | | | | | Next | Vol- | Vol+ | Play |
* `-----------------------------------------------------------------------------------'
*/
[_LOWER] = LAYOUT_planck_grid(
KC_TILD, KC_EXLM, KC_AT, KC_HASH, KC_DLR, KC_PERC, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_BSPC,
KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, KC_PIPE,
_______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, S(KC_NUHS), S(KC_NUBS), KC_HOME, KC_END, _______,
_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
),
/* Raise
* ,-----------------------------------------------------------------------------------.
* | ` | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | Bksp |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | Del | F1 | F2 | F3 | F4 | F5 | F6 | - | = | [ | ] | \ |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | | F7 | F8 | F9 | F10 | F11 | F12 |ISO # |ISO / |Pg Up |Pg Dn | |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | | | | | | | | Next | Vol- | Vol+ | Play |
* `-----------------------------------------------------------------------------------'
*/
[_RAISE] = LAYOUT_planck_grid(
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_BSPC,
KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_MINS, KC_EQL, KC_LBRC, KC_RBRC, KC_BSLS,
_______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_NUHS, KC_NUBS, KC_PGUP, KC_PGDN, _______,
_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
),
/* Plover layer (http://opensteno.org)
* ,-----------------------------------------------------------------------------------.
* | # | # | # | # | # | # | # | # | # | # | # | # |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | | S | T | P | H | * | * | F | P | L | T | D |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | | S | K | W | R | * | * | R | B | G | S | Z |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | Exit | | | A | O | | E | U | | | |
* `-----------------------------------------------------------------------------------'
*/
[_PLOVER] = LAYOUT_planck_grid(
KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1, KC_1 ,
XXXXXXX, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC,
XXXXXXX, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,
EXT_PLV, XXXXXXX, XXXXXXX, KC_C, KC_V, XXXXXXX, XXXXXXX, KC_N, KC_M, XXXXXXX, XXXXXXX, XXXXXXX
),
/* Adjust (Lower + Raise)
* ,-----------------------------------------------------------------------------------.
* | | Reset| | | | | | | | | | Del |
* |------+------+------+------+------+-------------+------+------+------+------+------|
* | | | |Aud on|Audoff|AGnorm|AGswap|Qwerty|Colemk|Dvorak|Plover| |
* |------+------+------+------+------+------|------+------+------+------+------+------|
* | |Voice-|Voice+|Mus on|Musoff|MIDIon|MIDIof| | | | | |
* |------+------+------+------+------+------+------+------+------+------+------+------|
* | | | | | | | | | | | |
* `-----------------------------------------------------------------------------------'
*/
[_ADJUST] = LAYOUT_planck_grid(
_______, RESET, DEBUG, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, KC_DEL ,
_______, _______, MU_MOD, AU_ON, AU_OFF, AG_NORM, AG_SWAP, QWERTY, COLEMAK, DVORAK, PLOVER, _______,
_______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, TERM_ON, TERM_OFF, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______
)
};
#ifdef AUDIO_ENABLE
float plover_song[][2] = SONG(PLOVER_SOUND);
float plover_gb_song[][2] = SONG(PLOVER_GOODBYE_SOUND);
#endif
uint32_t layer_state_set_user(uint32_t state) {
return update_tri_layer_state(state, _LOWER, _RAISE, _ADJUST);
}
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case QWERTY:
if (record->event.pressed) {
print("mode just switched to qwerty and this is a huge string\n");
set_single_persistent_default_layer(_QWERTY);
}
return false;
break;
case COLEMAK:
if (record->event.pressed) {
set_single_persistent_default_layer(_COLEMAK);
}
return false;
break;
case DVORAK:
if (record->event.pressed) {
set_single_persistent_default_layer(_DVORAK);
}
return false;
break;
case BACKLIT:
if (record->event.pressed) {
register_code(KC_RSFT);
#ifdef BACKLIGHT_ENABLE
backlight_step();
#endif
#ifdef KEYBOARD_planck_rev5
PORTE &= ~(1<<6);
#endif
} else {
unregister_code(KC_RSFT);
#ifdef KEYBOARD_planck_rev5
PORTE |= (1<<6);
#endif
}
return false;
break;
case PLOVER:
if (record->event.pressed) {
#ifdef AUDIO_ENABLE
stop_all_notes();
PLAY_SONG(plover_song);
#endif
layer_off(_RAISE);
layer_off(_LOWER);
layer_off(_ADJUST);
layer_on(_PLOVER);
if (!eeconfig_is_enabled()) {
eeconfig_init();
}
keymap_config.raw = eeconfig_read_keymap();
keymap_config.nkro = 1;
eeconfig_update_keymap(keymap_config.raw);
}
return false;
break;
case EXT_PLV:
if (record->event.pressed) {
#ifdef AUDIO_ENABLE
PLAY_SONG(plover_gb_song);
#endif
layer_off(_PLOVER);
}
return false;
break;
}
return true;
}
void matrix_scan_user(void) {
}
bool music_mask_user(uint16_t keycode) {
switch (keycode) {
case RAISE:
case LOWER:
return false;
default:
return true;
}
}
#include "wavetable.h"
float dac_if[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
uint8_t dac_morph = 0;
uint16_t dac_value_generate(void) {
uint16_t value = AUDIO_DAC_OFF_VALUE;
uint8_t active_tones = audio_get_number_of_active_tones();
if (active_tones > AUDIO_MAX_SIMULTANEOUS_TONES)
active_tones = AUDIO_MAX_SIMULTANEOUS_TONES;
if (active_tones > 0) {
uint16_t value_avg = 0;
for (uint8_t i = 0; i < active_tones; i++) {
dac_if[i] = dac_if[i]
+ ((audio_get_frequency(i) * AUDIO_DAC_BUFFER_SIZE) / AUDIO_DAC_SAMPLE_RATE)
* 2/3; // necessary to adjust for the gpt-timer frequency (three times the sample rate) and the dac-conversion beeing called twice per sample
// Needed because % doesn't work with floats
while (dac_if[i] >= (AUDIO_DAC_BUFFER_SIZE))
dac_if[i] = dac_if[i] - AUDIO_DAC_BUFFER_SIZE;
// #define AUDIO_DAC_MORPH_SPEED 372
// #define AUDIO_DAC_MORPH_SPEED_COMPUTED (AUDIO_DAC_SAMPLE_RATE / AUDIO_DAC_WAVETABLE_CUSTOM_LENGTH * (1000 / AUDIO_DAC_MORPH_SPEED))
uint16_t dac_i = (uint16_t)dac_if[i];
// value_avg += dac_buffer_custom[dac_morph_flipped][dac_i] / active_tones / 2 * ((dac_morph >= 63) ? 6400 - dac_morph_counter : dac_morph_counter) / 6400;
// value_avg += dac_buffer_custom[dac_morph_flipped + 1][dac_i] / active_tones / 2 * ((dac_morph >= 63) ? dac_morph_counter : 6400 - dac_morph_counter) / 6400;
// value_avg += dac_wavetable_custom[dac_morph][dac_i] / active_tones / 2 * (AUDIO_DAC_MORPH_SPEED_COMPUTED - dac_morph_counter) / AUDIO_DAC_MORPH_SPEED_COMPUTED;
// value_avg += dac_wavetable_custom[dac_morph + 1][dac_i] / active_tones / 2 * dac_morph_counter / AUDIO_DAC_MORPH_SPEED_COMPUTED;
value_avg += dac_wavetable_custom[dac_morph][dac_i] / active_tones;
}
value = value_avg;
// dac_morph_counter++;
// if (dac_morph_counter >= AUDIO_DAC_MORPH_SPEED_COMPUTED) {
// dac_morph_counter = 0;
// dac_morph = (dac_morph + 1) % 125;
// dac_morph_flipped = ((dac_morph >= 63) ? (125 - dac_morph) : dac_morph);
// dac_morph = (dac_morph + 1) % (AUDIO_DAC_WAVETABLE_CUSTOM_LENGTH - 1);
// }
}
return value;
}
void encoder_update(bool clockwise) {
if (clockwise) {
dac_morph = (dac_morph + 1) % AUDIO_DAC_WAVETABLE_CUSTOM_LENGTH;
} else {
if (dac_morph == 0)
dac_morph = (AUDIO_DAC_WAVETABLE_CUSTOM_LENGTH - 1);
else
dac_morph--;
}
}

View file

@ -0,0 +1,2 @@
AUDIO_ENABLE = yes
AUDIO_DRIVER = dac_additive

File diff suppressed because it is too large Load diff

View file

@ -15,8 +15,8 @@
#define MATRIX_ROW_PINS { B0, E7, F0, F1 }
#define MATRIX_COL_PINS { E6, E3, E4, D3, D4, D5, C0, A7, A6, E1, E0, D7 }
#define C6_AUDIO
#define B5_AUDIO
#define AUDIO_PIN C6
#define AUDIO_PIN_ALT B5
#undef BACKLIGHT_PIN

View file

@ -53,8 +53,10 @@
#define MUSIC_MAP
#undef AUDIO_VOICES
// Note: following undef isn't really necessary on STM32, C6_AUDIO is AVR related
#undef C6_AUDIO
#undef AUDIO_PIN
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
/* Debounce reduces chatter (unintended double-presses) - set 0 if debouncing is not needed */
// #define DEBOUNCE 6

View file

@ -41,7 +41,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMK_SPEAKER C6
#define AUDIO_VOICES
#define C6_AUDIO
#define AUDIO_PIN C6
#define BACKLIGHT_PIN B7

View file

@ -40,8 +40,10 @@
#define MUSIC_MAP
#undef AUDIO_VOICES
// Note: following undef isn't really necessary on STM32, C6_AUDIO is AVR related
#undef C6_AUDIO
#undef AUDIO_PIN
#define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4
#define AUDIO_PIN_ALT_AS_NEGATIVE
/* Debounce reduces chatter (unintended double-presses) - set 0 if debouncing is not needed */
// #define DEBOUNCE 6

View file

@ -45,7 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMK_SPEAKER C6
#ifdef AUDIO_ENABLE
#define C6_AUDIO
#define AUDIO_PIN C6
#define STARTUP_SONG SONG(PREONIC_SOUND)
// Disable music mode to keep the firmware size down
#define NO_MUSIC_MODE

View file

@ -111,7 +111,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio */
#ifdef AUDIO_ENABLE
#define B6_AUDIO
#define AUDIO_PIN B6
#define STARTUP_SONG SONG(STARTUP_SOUND)
#define AUDIO_CLICKY
#define AUDIO_CLICKY_FREQ_RANDOMNESS 1.0f

View file

@ -49,7 +49,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// #define QMK_ESC_INPUT C6
// #define QMK_SPEAKER B6
#define B6_AUDIO
#define AUDIO_PIN B6
#define AUDIO_CLICKY
#define NO_MUSIC_MODE

View file

@ -37,7 +37,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define UNUSED_PINS
// #define AUDIO_VOICES
// #define C6_AUDIO
// #define AUDIO_PIN C6
#define BACKLIGHT_PIN B7

View file

@ -34,7 +34,7 @@
#define NO_ACTION_FUNCTION
#ifdef AUDIO_ENABLE
#define B5_AUDIO
#define AUDIO_PIN B5
#define STARTUP_SONG SONG(ONE_UP_SOUND)
#define NO_MUSIC_MODE
#endif

View file

@ -61,7 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio settings */
#ifdef AUDIO_ENABLE
#define C6_AUDIO // Define this to enable the buzzer
#define AUDIO_PIN C6 // Define this to enable the buzzer
#endif
/*

View file

@ -61,7 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Audio settings */
#ifdef AUDIO_ENABLE
#define C6_AUDIO // Define this to enable the buzzer
#define AUDIO_PIN C6 // Define this to enable the buzzer
#endif
#define QMK_ESC_OUTPUT F1 // usually COL

View file

@ -25,7 +25,7 @@
#define MANUFACTURER wilba.tech
#define PRODUCT wilba.tech WT60-XT
#define C6_AUDIO
#define AUDIO_PIN C6
#define AUDIO_CLICKY
/* key matrix size */

View file

@ -89,7 +89,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
/* Audio Support */
#define C6_AUDIO
#define AUDIO_PIN C6
/* Debounce reduces chatter (unintended double-presses) - set 0 if debouncing is not needed */
#define DEBOUNCE 5

View file

@ -1,7 +1,5 @@
#pragma once
// #define B6_AUDIO
#if defined(RGBLIGHT_ENABLE) && !defined(RGBLED_NUM)
#define RGB_DI_PIN B7
#define RGBLED_NUM 13 // Number of LEDs

View file

@ -16,7 +16,6 @@
#pragma once
// #define B6_AUDIO
#if defined(RGBLIGHT_ENABLE) && !defined(RGBLED_NUM)
# define RGB_DI_PIN B1

View file

@ -16,5 +16,5 @@
#define RGBLIGHT_VAL_STEP 8
#ifdef AUDIO_ENABLE
#define C6_AUDIO
#define AUDIO_PIN C6
#endif

View file

@ -33,6 +33,6 @@
# define RGBLIGHT_EFFECT_KNIGHT_OFFSET 3
# define RGBLIGHT_EFFECT_KNIGHT_LED_NUM 14
# define B7_AUDIO
# define AUDIO_PIN B7
# define NO_MUSIC_MODE
#endif

View file

@ -17,6 +17,6 @@
# define RGBLIGHT_EFFECT_KNIGHT_OFFSET 3
# define RGBLIGHT_EFFECT_KNIGHT_LED_NUM 14
# define B7_AUDIO
# define AUDIO_PIN B7
# define NO_MUSIC_MODE
#endif

View file

@ -74,7 +74,7 @@
#endif
#ifdef AUDIO_ENABLE
# define B6_AUDIO
# define AUDIO_PIN B6
# define NO_MUSIC_MODE
#endif

539
quantum/audio/audio.c Normal file
View file

@ -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
}

View file

@ -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
#if defined(__AVR__)
# include <avr/io.h>
# if defined(AUDIO_DRIVER_PWM)
# include "driver_avr_pwm.h"
# endif
#endif
// #define VIBRATO_ENABLE
// 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);
void audio_toggle(void);
void audio_on(void);
void audio_off(void);
// Vibrato rate functions
#ifdef VIBRATO_ENABLE
void set_vibrato_rate(float rate);
void increase_vibrato_rate(float change);
void decrease_vibrato_rate(float change);
# ifdef VIBRATO_STRENGTH_ENABLE
void set_vibrato_strength(float strength);
void increase_vibrato_strength(float change);
void decrease_vibrato_strength(float change);
// AVR/LUFA has a MIN, arm/chibios does not
#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#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;
// Polyphony functions
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);
void set_timbre(float timbre);
void set_tempo(uint8_t tempo);
void increase_tempo(uint8_t tempo_change);
void decrease_tempo(uint8_t tempo_change);
// 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);
#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);
/**
* @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);
#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), }
/**
* @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
// These macros are used to allow play_notes to play an array of indeterminate
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @brief query if one/multiple tones are playing
*/
bool audio_is_playing_note(void);
/**
* @brief query if a melody/SONG is playing
*/
bool audio_is_playing_melody(void);
// 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]))))
#define PLAY_SONG(note_array) play_notes(&note_array, NOTE_ARRAY_SIZE((note_array)), false)
#define PLAY_LOOP(note_array) play_notes(&note_array, NOTE_ARRAY_SIZE((note_array)), true)
bool is_playing_notes(void);
/**
* @brief convenience macro, to play a melody/SONG once
*/
#define PLAY_SONG(note_array) audio_play_melody(&note_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(&note_array, NOTE_ARRAY_SIZE((note_array)), true)
// 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_startup(void);
// hardware interface
// implementation in the driver_avr/arm_* respective parts
void audio_driver_initialize(void);
void audio_driver_start(void);
void audio_driver_stop(void);
/**
* @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)
#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

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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() {}
//------------------------------------------------------------------------------

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}

Some files were not shown because too many files have changed in this diff Show more