diff options
Diffstat (limited to 'quantum')
48 files changed, 4047 insertions, 2597 deletions
diff --git a/quantum/audio/audio.c b/quantum/audio/audio.c new file mode 100644 index 0000000000..46277dd70b --- /dev/null +++ b/quantum/audio/audio.c @@ -0,0 +1,539 @@ +/* Copyright 2016-2020 Jack Humbert + * Copyright 2020 JohSchneider + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "audio.h" +#include "eeconfig.h" +#include "timer.h" +#include "wait.h" + +/* audio system: + * + * audio.[ch] takes care of all overall state, tracking the actively playing + * notes/tones; the notes a SONG consists of; + * ... + * = everything audio-related that is platform agnostic + * + * driver_[avr|chibios]_[dac|pwm] take care of the lower hardware dependent parts, + * specific to each platform and the used subsystem/driver to drive + * the output pins/channels with the calculated frequencies for each + * active tone + * as part of this, the driver has to trigger regular state updates by + * calling 'audio_update_state' through some sort of timer - be it a + * dedicated one or piggybacking on for example the timer used to + * generate a pwm signal/clock. + * + * + * A Note on terminology: + * tone, pitch and frequency are used somewhat interchangeably, in a strict Wikipedia-sense: + * "(Musical) tone, a sound characterized by its duration, pitch (=frequency), + * intensity (=volume), and timbre" + * - intensity/volume is currently not handled at all, although the 'dac_additive' driver could do so + * - timbre is handled globally (TODO: only used with the pwm drivers at the moment) + * + * in musical_note.h a 'note' is the combination of a pitch and a duration + * these are used to create SONG arrays; during playback their frequencies + * are handled as single successive tones, while the durations are + * kept track of in 'audio_update_state' + * + * 'voice' as it is used here, equates to a sort of instrument with its own + * characteristics sound and effects + * the audio system as-is deals only with (possibly multiple) tones of one + * instrument/voice at a time (think: chords). since the number of tones that + * can be reproduced depends on the hardware/driver in use: pwm can only + * reproduce one tone per output/speaker; DACs can reproduce/mix multiple + * when doing additive synthesis. + * + * 'duration' can either be in the beats-per-minute related unit found in + * musical_notes.h, OR in ms; keyboards create SONGs with the former, while + * the internal state of the audio system does its calculations with the later - ms + */ + +#ifndef AUDIO_TONE_STACKSIZE +# define AUDIO_TONE_STACKSIZE 8 +#endif +uint8_t active_tones = 0; // number of tones pushed onto the stack by audio_play_tone - might be more than the hardware is able to reproduce at any single time +musical_tone_t tones[AUDIO_TONE_STACKSIZE]; // stack of currently active tones + +bool playing_melody = false; // playing a SONG? +bool playing_note = false; // or (possibly multiple simultaneous) tones +bool state_changed = false; // global flag, which is set if anything changes with the active_tones + +// melody/SONG related state variables +float (*notes_pointer)[][2]; // SONG, an array of MUSICAL_NOTEs +uint16_t notes_count; // length of the notes_pointer array +bool notes_repeat; // PLAY_SONG or PLAY_LOOP? +uint16_t melody_current_note_duration = 0; // duration of the currently playing note from the active melody, in ms +uint8_t note_tempo = TEMPO_DEFAULT; // beats-per-minute +uint16_t current_note = 0; // index into the array at notes_pointer +bool note_resting = false; // if a short pause was introduced between two notes with the same frequency while playing a melody +uint16_t last_timestamp = 0; + +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING +# ifndef AUDIO_MAX_SIMULTANEOUS_TONES +# define AUDIO_MAX_SIMULTANEOUS_TONES 3 +# endif +uint16_t tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT; +uint8_t tone_multiplexing_index_shift = 0; // offset used on active-tone array access +#endif + +// provided and used by voices.c +extern uint8_t note_timbre; +extern bool glissando; +extern bool vibrato; +extern uint16_t voices_timer; + +#ifndef STARTUP_SONG +# define STARTUP_SONG SONG(STARTUP_SOUND) +#endif +#ifndef AUDIO_ON_SONG +# define AUDIO_ON_SONG SONG(AUDIO_ON_SOUND) +#endif +#ifndef AUDIO_OFF_SONG +# define AUDIO_OFF_SONG SONG(AUDIO_OFF_SOUND) +#endif +float startup_song[][2] = STARTUP_SONG; +float audio_on_song[][2] = AUDIO_ON_SONG; +float audio_off_song[][2] = AUDIO_OFF_SONG; + +static bool audio_initialized = false; +static bool audio_driver_stopped = true; +audio_config_t audio_config; + +void audio_init() { + if (audio_initialized) { + return; + } + + // Check EEPROM +#ifdef EEPROM_ENABLE + if (!eeconfig_is_enabled()) { + eeconfig_init(); + } + audio_config.raw = eeconfig_read_audio(); +#else // EEPROM settings + audio_config.enable = true; +# ifdef AUDIO_CLICKY_ON + audio_config.clicky_enable = true; +# endif +#endif // EEPROM settings + + for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) { + tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + } + + if (!audio_initialized) { + audio_driver_initialize(); + audio_initialized = true; + } + stop_all_notes(); +} + +void audio_startup(void) { + if (audio_config.enable) { + PLAY_SONG(startup_song); + } + + last_timestamp = timer_read(); +} + +void audio_toggle(void) { + if (audio_config.enable) { + stop_all_notes(); + } + audio_config.enable ^= 1; + eeconfig_update_audio(audio_config.raw); + if (audio_config.enable) { + audio_on_user(); + } +} + +void audio_on(void) { + audio_config.enable = 1; + eeconfig_update_audio(audio_config.raw); + audio_on_user(); + PLAY_SONG(audio_on_song); +} + +void audio_off(void) { + PLAY_SONG(audio_off_song); + wait_ms(100); + audio_stop_all(); + audio_config.enable = 0; + eeconfig_update_audio(audio_config.raw); +} + +bool audio_is_on(void) { return (audio_config.enable != 0); } + +void audio_stop_all() { + if (audio_driver_stopped) { + return; + } + + active_tones = 0; + + audio_driver_stop(); + + playing_melody = false; + playing_note = false; + + melody_current_note_duration = 0; + + for (uint8_t i = 0; i < AUDIO_TONE_STACKSIZE; i++) { + tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + } + + audio_driver_stopped = true; +} + +void audio_stop_tone(float pitch) { + if (pitch < 0.0f) { + pitch = -1 * pitch; + } + + if (playing_note) { + if (!audio_initialized) { + audio_init(); + } + bool found = false; + for (int i = AUDIO_TONE_STACKSIZE - 1; i >= 0; i--) { + found = (tones[i].pitch == pitch); + if (found) { + tones[i] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + for (int j = i; (j < AUDIO_TONE_STACKSIZE - 1); j++) { + tones[j] = tones[j + 1]; + tones[j + 1] = (musical_tone_t){.time_started = 0, .pitch = -1.0f, .duration = 0}; + } + break; + } + } + if (!found) { + return; + } + + state_changed = true; + active_tones--; + if (active_tones < 0) active_tones = 0; +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING + if (tone_multiplexing_index_shift >= active_tones) { + tone_multiplexing_index_shift = 0; + } +#endif + if (active_tones == 0) { + audio_driver_stop(); + audio_driver_stopped = true; + playing_note = false; + } + } +} + +void audio_play_note(float pitch, uint16_t duration) { + if (!audio_config.enable) { + return; + } + + if (!audio_initialized) { + audio_init(); + } + + if (pitch < 0.0f) { + pitch = -1 * pitch; + } + + // round-robin: shifting out old tones, keeping only unique ones + // if the new frequency is already amongst the active tones, shift it to the top of the stack + bool found = false; + for (int i = active_tones - 1; i >= 0; i--) { + found = (tones[i].pitch == pitch); + if (found) { + for (int j = i; (j < active_tones - 1); j++) { + tones[j] = tones[j + 1]; + tones[j + 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration}; + } + return; // since this frequency played already, the hardware was already started + } + } + + // frequency/tone is actually new, so we put it on the top of the stack + active_tones++; + if (active_tones > AUDIO_TONE_STACKSIZE) { + active_tones = AUDIO_TONE_STACKSIZE; + // shift out the oldest tone to make room + for (int i = 0; i < active_tones - 1; i++) { + tones[i] = tones[i + 1]; + } + } + state_changed = true; + playing_note = true; + tones[active_tones - 1] = (musical_tone_t){.time_started = timer_read(), .pitch = pitch, .duration = duration}; + + // TODO: needs to be handled per note/tone -> use its timestamp instead? + voices_timer = timer_read(); // reset to zero, for the effects added by voices.c + + if (audio_driver_stopped) { + audio_driver_start(); + audio_driver_stopped = false; + } +} + +void audio_play_tone(float pitch) { audio_play_note(pitch, 0xffff); } + +void audio_play_melody(float (*np)[][2], uint16_t n_count, bool n_repeat) { + if (!audio_config.enable) { + audio_stop_all(); + return; + } + + if (!audio_initialized) { + audio_init(); + } + + // Cancel note if a note is playing + if (playing_note) audio_stop_all(); + + playing_melody = true; + note_resting = false; + + notes_pointer = np; + notes_count = n_count; + notes_repeat = n_repeat; + + current_note = 0; // note in the melody-array/list at note_pointer + + // start first note manually, which also starts the audio_driver + // all following/remaining notes are played by 'audio_update_state' + audio_play_note((*notes_pointer)[current_note][0], audio_duration_to_ms((*notes_pointer)[current_note][1])); + last_timestamp = timer_read(); + melody_current_note_duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); +} + +float click[2][2]; +void audio_play_click(uint16_t delay, float pitch, uint16_t duration) { + uint16_t duration_tone = audio_ms_to_duration(duration); + uint16_t duration_delay = audio_ms_to_duration(delay); + + if (delay <= 0.0f) { + click[0][0] = pitch; + click[0][1] = duration_tone; + click[1][0] = 0.0f; + click[1][1] = 0.0f; + audio_play_melody(&click, 1, false); + } else { + // first note is a rest/pause + click[0][0] = 0.0f; + click[0][1] = duration_delay; + // second note is the actual click + click[1][0] = pitch; + click[1][1] = duration_tone; + audio_play_melody(&click, 2, false); + } +} + +bool audio_is_playing_note(void) { return playing_note; } + +bool audio_is_playing_melody(void) { return playing_melody; } + +uint8_t audio_get_number_of_active_tones(void) { return active_tones; } + +float audio_get_frequency(uint8_t tone_index) { + if (tone_index >= active_tones) { + return 0.0f; + } + return tones[active_tones - tone_index - 1].pitch; +} + +float audio_get_processed_frequency(uint8_t tone_index) { + if (tone_index >= active_tones) { + return 0.0f; + } + + int8_t index = active_tones - tone_index - 1; + // new tones are stacked on top (= appended at the end), so the most recent/current is MAX-1 + +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING + index = index - tone_multiplexing_index_shift; + if (index < 0) // wrap around + index += active_tones; +#endif + + if (tones[index].pitch <= 0.0f) { + return 0.0f; + } + + return voice_envelope(tones[index].pitch); +} + +bool audio_update_state(void) { + if (!playing_note && !playing_melody) { + return false; + } + + bool goto_next_note = false; + uint16_t current_time = timer_read(); + + if (playing_melody) { + goto_next_note = timer_elapsed(last_timestamp) >= melody_current_note_duration; + if (goto_next_note) { + uint16_t delta = timer_elapsed(last_timestamp) - melody_current_note_duration; + last_timestamp = current_time; + uint16_t previous_note = current_note; + current_note++; + voices_timer = timer_read(); // reset to zero, for the effects added by voices.c + + if (current_note >= notes_count) { + if (notes_repeat) { + current_note = 0; + } else { + audio_stop_all(); + return false; + } + } + + if (!note_resting && (*notes_pointer)[previous_note][0] == (*notes_pointer)[current_note][0]) { + note_resting = true; + + // special handling for successive notes of the same frequency: + // insert a short pause to separate them audibly + audio_play_note(0.0f, audio_duration_to_ms(2)); + current_note = previous_note; + melody_current_note_duration = audio_duration_to_ms(2); + + } else { + note_resting = false; + + // TODO: handle glissando here (or remember previous and current tone) + /* there would need to be a freq(here we are) -> freq(next note) + * and do slide/glissando in between problem here is to know which + * frequency on the stack relates to what other? e.g. a melody starts + * tones in a sequence, and stops expiring one, so the most recently + * stopped is the starting point for a glissando to the most recently started? + * how to detect and preserve this relation? + * and what about user input, chords, ...? + */ + + // '- delta': Skip forward in the next note's length if we've over shot + // the last, so the overall length of the song is the same + uint16_t duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); + + // Skip forward past any completely missed notes + while (delta > duration && current_note < notes_count - 1) { + delta -= duration; + current_note++; + duration = audio_duration_to_ms((*notes_pointer)[current_note][1]); + } + + if (delta < duration) { + duration -= delta; + } else { + // Only way to get here is if it is the last note and + // we have completely missed it. Play it for 1ms... + duration = 1; + } + + audio_play_note((*notes_pointer)[current_note][0], duration); + melody_current_note_duration = duration; + } + } + } + + if (playing_note) { +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING + tone_multiplexing_index_shift = (int)(current_time / tone_multiplexing_rate) % MIN(AUDIO_MAX_SIMULTANEOUS_TONES, active_tones); + goto_next_note = true; +#endif + if (vibrato || glissando) { + // force update on each cycle, since vibrato shifts the frequency slightly + goto_next_note = true; + } + + // housekeeping: stop notes that have no playtime left + for (int i = 0; i < active_tones; i++) { + if ((tones[i].duration != 0xffff) // indefinitely playing notes, started by 'audio_play_tone' + && (tones[i].duration != 0) // 'uninitialized' + ) { + if (timer_elapsed(tones[i].time_started) >= tones[i].duration) { + audio_stop_tone(tones[i].pitch); // also sets 'state_changed=true' + } + } + } + } + + // state-changes have a higher priority, always triggering the hardware to update + if (state_changed) { + state_changed = false; + return true; + } + + return goto_next_note; +} + +// Tone-multiplexing functions +#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING +void audio_set_tone_multiplexing_rate(uint16_t rate) { tone_multiplexing_rate = rate; } +void audio_enable_tone_multiplexing(void) { tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT; } +void audio_disable_tone_multiplexing(void) { tone_multiplexing_rate = 0; } +void audio_increase_tone_multiplexing_rate(uint16_t change) { + if ((0xffff - change) > tone_multiplexing_rate) { + tone_multiplexing_rate += change; + } +} +void audio_decrease_tone_multiplexing_rate(uint16_t change) { + if (change <= tone_multiplexing_rate) { + tone_multiplexing_rate -= change; + } +} +#endif + +// Tempo functions + +void audio_set_tempo(uint8_t tempo) { + if (tempo < 10) note_tempo = 10; + // else if (tempo > 250) + // note_tempo = 250; + else + note_tempo = tempo; +} + +void audio_increase_tempo(uint8_t tempo_change) { + if (tempo_change > 255 - note_tempo) + note_tempo = 255; + else + note_tempo += tempo_change; +} + +void audio_decrease_tempo(uint8_t tempo_change) { + if (tempo_change >= note_tempo - 10) + note_tempo = 10; + else + note_tempo -= tempo_change; +} + +// TODO in the int-math version are some bugs; songs sometimes abruptly end - maybe an issue with the timer/system-tick wrapping around? +uint16_t audio_duration_to_ms(uint16_t duration_bpm) { +#if defined(__AVR__) + // doing int-math saves us some bytes in the overall firmware size, but the intermediate result is less accurate before being cast to/returned as uint + return ((uint32_t)duration_bpm * 60 * 1000) / (64 * note_tempo); + // NOTE: beware of uint16_t overflows when note_tempo is low and/or the duration is long +#else + return ((float)duration_bpm * 60) / (64 * note_tempo) * 1000; +#endif +} +uint16_t audio_ms_to_duration(uint16_t duration_ms) { +#if defined(__AVR__) + return ((uint32_t)duration_ms * 64 * note_tempo) / 60 / 1000; +#else + return ((float)duration_ms * 64 * note_tempo) / 60 / 1000; +#endif +} diff --git a/quantum/audio/audio.h b/quantum/audio/audio.h index bc00cd19e6..56b9158a1a 100644 --- a/quantum/audio/audio.h +++ b/quantum/audio/audio.h @@ -1,4 +1,5 @@ -/* Copyright 2016 Jack Humbert +/* Copyright 2016-2020 Jack Humbert + * Copyright 2020 JohSchneider * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -13,28 +14,30 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - #pragma once #include <stdint.h> #include <stdbool.h> -#if defined(__AVR__) -# include <avr/io.h> -#endif -#include "wait.h" #include "musical_notes.h" #include "song_list.h" #include "voices.h" #include "quantum.h" #include <math.h> -// Largely untested PWM audio mode (doesn't sound as good) -// #define PWM_AUDIO - -// #define VIBRATO_ENABLE +#if defined(__AVR__) +# include <avr/io.h> +# if defined(AUDIO_DRIVER_PWM) +# include "driver_avr_pwm.h" +# endif +#endif -// Enable vibrato strength/amplitude - slows down ISR too much -// #define VIBRATO_STRENGTH_ENABLE +#if defined(PROTOCOL_CHIBIOS) +# if defined(AUDIO_DRIVER_PWM) +# include "driver_chibios_pwm.h" +# elif defined(AUDIO_DRIVER_DAC) +# include "driver_chibios_dac.h" +# endif +#endif typedef union { uint8_t raw; @@ -45,61 +48,238 @@ typedef union { }; } audio_config_t; -bool is_audio_on(void); +// AVR/LUFA has a MIN, arm/chibios does not +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +/* + * a 'musical note' is represented by pitch and duration; a 'musical tone' adds intensity and timbre + * https://en.wikipedia.org/wiki/Musical_tone + * "A musical tone is characterized by its duration, pitch, intensity (or loudness), and timbre (or quality)" + */ +typedef struct { + uint16_t time_started; // timestamp the tone/note was started, system time runs with 1ms resolution -> 16bit timer overflows every ~64 seconds, long enough under normal circumstances; but might be too soon for long-duration notes when the note_tempo is set to a very low value + float pitch; // aka frequency, in Hz + uint16_t duration; // in ms, converted from the musical_notes.h unit which has 64parts to a beat, factoring in the current tempo in beats-per-minute + // float intensity; // aka volume [0,1] TODO: not used at the moment; pwm drivers can't handle it + // uint8_t timbre; // range: [0,100] TODO: this currently kept track of globally, should we do this per tone instead? +} musical_tone_t; + +// public interface + +/** + * @brief one-time initialization called by quantum/quantum.c + * @details usually done lazy, when some tones are to be played + * + * @post audio system (and hardware) initialized and ready to play tones + */ +void audio_init(void); +void audio_startup(void); + +/** + * @brief en-/disable audio output, save this choice to the eeprom + */ void audio_toggle(void); +/** + * @brief enable audio output, save this choice to the eeprom + */ void audio_on(void); +/** + * @brief disable audio output, save this choice to the eeprom + */ void audio_off(void); +/** + * @brief query the if audio output is enabled + */ +bool audio_is_on(void); + +/** + * @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 + +/** + * @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); -// Vibrato rate functions +/** + * @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); -#ifdef VIBRATO_ENABLE +/** + * @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); -void set_vibrato_rate(float rate); -void increase_vibrato_rate(float change); -void decrease_vibrato_rate(float change); +/** + * @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); -# ifdef VIBRATO_STRENGTH_ENABLE +/** + * @brief query if one/multiple tones are playing + */ +bool audio_is_playing_note(void); -void set_vibrato_strength(float strength); -void increase_vibrato_strength(float change); -void decrease_vibrato_strength(float change); +/** + * @brief query if a melody/SONG is playing + */ +bool audio_is_playing_melody(void); -# endif +// 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])))) + +/** + * @brief convenience macro, to play a melody/SONG once + */ +#define PLAY_SONG(note_array) audio_play_melody(¬e_array, NOTE_ARRAY_SIZE((note_array)), false) +// TODO: a 'song' is a melody plus singing/vocals -> PLAY_MELODY +/** + * @brief convenience macro, to play a melody/SONG in a loop, until stopped by 'audio_stop_all' + */ +#define PLAY_LOOP(note_array) audio_play_melody(¬e_array, NOTE_ARRAY_SIZE((note_array)), true) +// 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 -// Polyphony functions +// Tempo 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 audio_set_tempo(uint8_t tempo); +void audio_increase_tempo(uint8_t tempo_change); +void audio_decrease_tempo(uint8_t tempo_change); -void set_timbre(float timbre); -void set_tempo(uint8_t tempo); +// 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 increase_tempo(uint8_t tempo_change); -void decrease_tempo(uint8_t tempo_change); +void audio_startup(void); -void audio_init(void); +// hardware interface -#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); +// implementation in the driver_avr/arm_* respective parts +void audio_driver_initialize(void); +void audio_driver_start(void); +void audio_driver_stop(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 get the number of currently active tones + * @return number, 0=none active + */ +uint8_t audio_get_number_of_active_tones(void); -// These macros are used to allow play_notes to play an array of indeterminate -// length. This works around the limitation of C's sizeof operation on pointers. -// The global float array for the song must be used here. -#define NOTE_ARRAY_SIZE(x) ((int16_t)(sizeof(x) / (sizeof(x[0])))) -#define PLAY_SONG(note_array) play_notes(¬e_array, NOTE_ARRAY_SIZE((note_array)), false) -#define PLAY_LOOP(note_array) play_notes(¬e_array, NOTE_ARRAY_SIZE((note_array)), true) +/** + * @brief access to the raw/unprocessed frequency for a specific tone + * @details each active tone has a frequency associated with it, which + * the internal state keeps track of, and is usually influenced + * by various effects + * @param[in] tone_index, ranging from 0 to number_of_active_tones-1, with the + * first being the most recent and each increment yielding the next + * older one + * @return a positive frequency, in Hz; or zero if the tone is a pause + */ +float audio_get_frequency(uint8_t tone_index); + +/** + * @brief calculate and return the frequency for the requested tone + * @details effects like glissando, vibrato, ... are post-processed onto the + * each active tones 'base'-frequency; this function returns the + * post-processed result. + * @param[in] tone_index, ranging from 0 to number_of_active_tones-1, with the + * first being the most recent and each increment yielding the next + * older one + * @return a positive frequency, in Hz; or zero if the tone is a pause + */ +float audio_get_processed_frequency(uint8_t tone_index); + +/** + * @brief update audio internal state: currently playing and active tones,... + * @details This function is intended to be called by the audio-hardware + * specific implementation on a somewhat regular basis while a SONG + * or notes (pitch+duration) are playing to 'advance' the internal + * state (current playing notes, position in the melody, ...) + * + * @return true if something changed in the currently active tones, which the + * hardware might need to react to + */ +bool audio_update_state(void); + +// legacy and back-warts compatibility stuff + +#define is_audio_on() audio_is_on() +#define is_playing_notes() audio_is_playing_melody() +#define is_playing_note() audio_is_playing_note() +#define stop_all_notes() audio_stop_all() +#define stop_note(f) audio_stop_tone(f) +#define play_note(f, v) audio_play_tone(f) -bool is_playing_notes(void); +#define set_timbre(t) voice_set_timbre(t) +#define set_tempo(t) audio_set_tempo(t) +#define increase_tempo(t) audio_increase_tempo(t) +#define decrease_tempo(t) audio_decrease_tempo(t) +// vibrato functions are not used in any keyboards diff --git a/quantum/audio/audio_avr.c b/quantum/audio/audio_avr.c deleted file mode 100644 index 5a96bf6439..0000000000 --- a/quantum/audio/audio_avr.c +++ /dev/null @@ -1,810 +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; - } - - if (audio_config.enable) { - PLAY_SONG(startup_song); - } -} - -void stop_all_notes() { - dprintf("audio stop all notes"); - - if (!audio_initialized) { - audio_init(); - } - voices = 0; - -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; - DISABLE_AUDIO_COUNTER_1_OUTPUT; -#endif - - playing_notes = false; - playing_note = false; - frequency = 0; - frequency_alt = 0; - volume = 0; - - for (uint8_t i = 0; i < 8; i++) { - frequencies[i] = 0; - volumes[i] = 0; - } -} - -void stop_note(float freq) { - dprintf("audio stop note freq=%d", (int)freq); - - if (playing_note) { - if (!audio_initialized) { - audio_init(); - } - for (int i = 7; i >= 0; i--) { - if (frequencies[i] == freq) { - frequencies[i] = 0; - volumes[i] = 0; - for (int j = i; (j < 7); j++) { - frequencies[j] = frequencies[j + 1]; - frequencies[j + 1] = 0; - volumes[j] = volumes[j + 1]; - volumes[j + 1] = 0; - } - break; - } - } - voices--; - if (voices < 0) voices = 0; - if (voice_place >= voices) { - voice_place = 0; - } - if (voices == 0) { -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; - DISABLE_AUDIO_COUNTER_1_OUTPUT; -#endif - frequency = 0; - frequency_alt = 0; - volume = 0; - playing_note = false; - } - } -} - -#ifdef VIBRATO_ENABLE - -float mod(float a, int b) { - float r = fmod(a, b); - return r < 0 ? r + b : r; -} - -float vibrato(float average_freq) { -# ifdef VIBRATO_STRENGTH_ENABLE - float vibrated_freq = average_freq * pow(vibrato_lut[(int)vibrato_counter], vibrato_strength); -# else - float vibrated_freq = average_freq * vibrato_lut[(int)vibrato_counter]; -# endif - vibrato_counter = mod((vibrato_counter + vibrato_rate * (1.0 + 440.0 / average_freq)), VIBRATO_LUT_LENGTH); - return vibrated_freq; -} - -#endif - -#ifdef CPIN_AUDIO -ISR(TIMER3_AUDIO_vect) { - float freq; - - if (playing_note) { - if (voices > 0) { -# ifdef BPIN_AUDIO - float freq_alt = 0; - if (voices > 1) { - if (polyphony_rate == 0) { - if (glissando) { - if (frequency_alt != 0 && frequency_alt < frequencies[voices - 2] && frequency_alt < frequencies[voices - 2] * pow(2, -440 / frequencies[voices - 2] / 12 / 2)) { - frequency_alt = frequency_alt * pow(2, 440 / frequency_alt / 12 / 2); - } else if (frequency_alt != 0 && frequency_alt > frequencies[voices - 2] && frequency_alt > frequencies[voices - 2] * pow(2, 440 / frequencies[voices - 2] / 12 / 2)) { - frequency_alt = frequency_alt * pow(2, -440 / frequency_alt / 12 / 2); - } else { - frequency_alt = frequencies[voices - 2]; - } - } else { - frequency_alt = frequencies[voices - 2]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq_alt = vibrato(frequency_alt); - } else { - freq_alt = frequency_alt; - } -# else - freq_alt = frequency_alt; -# endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq_alt = voice_envelope(freq_alt); - - if (freq_alt < 30.517578125) { - freq_alt = 30.52; - } - - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (freq_alt * CPU_PRESCALER)); - TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq_alt * CPU_PRESCALER)) * note_timbre); - } -# endif - - if (polyphony_rate > 0) { - if (voices > 1) { - voice_place %= voices; - if (place++ > (frequencies[voice_place] / polyphony_rate / CPU_PRESCALER)) { - voice_place = (voice_place + 1) % voices; - place = 0.0; - } - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequencies[voice_place]); - } else { - freq = frequencies[voice_place]; - } -# else - freq = frequencies[voice_place]; -# endif - } else { - if (glissando) { - if (frequency != 0 && frequency < frequencies[voices - 1] && frequency < frequencies[voices - 1] * pow(2, -440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, 440 / frequency / 12 / 2); - } else if (frequency != 0 && frequency > frequencies[voices - 1] && frequency > frequencies[voices - 1] * pow(2, 440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, -440 / frequency / 12 / 2); - } else { - frequency = frequencies[voices - 1]; - } - } else { - frequency = frequencies[voices - 1]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequency); - } else { - freq = frequency; - } -# else - freq = frequency; -# endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq = voice_envelope(freq); - - if (freq < 30.517578125) { - freq = 30.52; - } - - TIMER_3_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_3_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } - } - - if (playing_notes) { - if (note_frequency > 0) { -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(note_frequency); - } else { - freq = note_frequency; - } -# else - freq = note_frequency; -# endif - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - TIMER_3_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_3_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } else { - TIMER_3_PERIOD = 0; - TIMER_3_DUTY_CYCLE = 0; - } - - note_position++; - bool end_of_note = false; - if (TIMER_3_PERIOD > 0) { - if (!note_resting) - end_of_note = (note_position >= (note_length / TIMER_3_PERIOD * 0xFFFF - 1)); - else - end_of_note = (note_position >= (note_length)); - } else { - end_of_note = (note_position >= (note_length)); - } - - if (end_of_note) { - current_note++; - if (current_note >= notes_count) { - if (notes_repeat) { - current_note = 0; - } else { - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; - playing_notes = false; - return; - } - } - if (!note_resting) { - note_resting = true; - current_note--; - if ((*notes_pointer)[current_note][0] == (*notes_pointer)[current_note + 1][0]) { - note_frequency = 0; - note_length = 1; - } else { - note_frequency = (*notes_pointer)[current_note][0]; - note_length = 1; - } - } else { - note_resting = false; - envelope_index = 0; - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - } - - note_position = 0; - } - } - - if (!audio_config.enable) { - playing_notes = false; - playing_note = false; - } -} -#endif - -#ifdef BPIN_AUDIO -ISR(TIMER1_AUDIO_vect) { -# if defined(BPIN_AUDIO) && !defined(CPIN_AUDIO) - float freq = 0; - - if (playing_note) { - if (voices > 0) { - if (polyphony_rate > 0) { - if (voices > 1) { - voice_place %= voices; - if (place++ > (frequencies[voice_place] / polyphony_rate / CPU_PRESCALER)) { - voice_place = (voice_place + 1) % voices; - place = 0.0; - } - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequencies[voice_place]); - } else { - freq = frequencies[voice_place]; - } -# else - freq = frequencies[voice_place]; -# endif - } else { - if (glissando) { - if (frequency != 0 && frequency < frequencies[voices - 1] && frequency < frequencies[voices - 1] * pow(2, -440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, 440 / frequency / 12 / 2); - } else if (frequency != 0 && frequency > frequencies[voices - 1] && frequency > frequencies[voices - 1] * pow(2, 440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, -440 / frequency / 12 / 2); - } else { - frequency = frequencies[voices - 1]; - } - } else { - frequency = frequencies[voices - 1]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequency); - } else { - freq = frequency; - } -# else - freq = frequency; -# endif - } - - if (envelope_index < 65535) { - envelope_index++; - } - - freq = voice_envelope(freq); - - if (freq < 30.517578125) { - freq = 30.52; - } - - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } - } - - if (playing_notes) { - if (note_frequency > 0) { -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(note_frequency); - } else { - freq = note_frequency; - } -# else - freq = note_frequency; -# endif - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); - TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); - } else { - TIMER_1_PERIOD = 0; - TIMER_1_DUTY_CYCLE = 0; - } - - note_position++; - bool end_of_note = false; - if (TIMER_1_PERIOD > 0) { - if (!note_resting) - end_of_note = (note_position >= (note_length / TIMER_1_PERIOD * 0xFFFF - 1)); - else - end_of_note = (note_position >= (note_length)); - } else { - end_of_note = (note_position >= (note_length)); - } - - if (end_of_note) { - current_note++; - if (current_note >= notes_count) { - if (notes_repeat) { - current_note = 0; - } else { - DISABLE_AUDIO_COUNTER_1_ISR; - DISABLE_AUDIO_COUNTER_1_OUTPUT; - playing_notes = false; - return; - } - } - if (!note_resting) { - note_resting = true; - current_note--; - if ((*notes_pointer)[current_note][0] == (*notes_pointer)[current_note + 1][0]) { - note_frequency = 0; - note_length = 1; - } else { - note_frequency = (*notes_pointer)[current_note][0]; - note_length = 1; - } - } else { - note_resting = false; - envelope_index = 0; - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - } - - note_position = 0; - } - } - - if (!audio_config.enable) { - playing_notes = false; - playing_note = false; - } -# endif -} -#endif - -void play_note(float freq, int vol) { - dprintf("audio play note freq=%d vol=%d", (int)freq, vol); - - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable && voices < 8) { -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#endif -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; -#endif - - // Cancel notes if notes are playing - if (playing_notes) stop_all_notes(); - - playing_note = true; - - envelope_index = 0; - - if (freq > 0) { - frequencies[voices] = freq; - volumes[voices] = vol; - voices++; - } - -#ifdef CPIN_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif -#ifdef BPIN_AUDIO -# ifdef CPIN_AUDIO - if (voices > 1) { - ENABLE_AUDIO_COUNTER_1_ISR; - ENABLE_AUDIO_COUNTER_1_OUTPUT; - } -# else - ENABLE_AUDIO_COUNTER_1_ISR; - ENABLE_AUDIO_COUNTER_1_OUTPUT; -# endif -#endif - } -} - -void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable) { -#ifdef CPIN_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#endif -#ifdef BPIN_AUDIO - DISABLE_AUDIO_COUNTER_1_ISR; -#endif - - // Cancel note if a note is playing - if (playing_note) stop_all_notes(); - - playing_notes = true; - - notes_pointer = np; - notes_count = n_count; - notes_repeat = n_repeat; - - place = 0; - current_note = 0; - - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); - note_position = 0; - -#ifdef CPIN_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif -#ifdef BPIN_AUDIO -# ifndef CPIN_AUDIO - ENABLE_AUDIO_COUNTER_1_ISR; - ENABLE_AUDIO_COUNTER_1_OUTPUT; -# endif -#endif - } -} - -bool is_playing_notes(void) { return playing_notes; } - -bool is_audio_on(void) { return (audio_config.enable != 0); } - -void audio_toggle(void) { - audio_config.enable ^= 1; - eeconfig_update_audio(audio_config.raw); - if (audio_config.enable) audio_on_user(); -} - -void audio_on(void) { - audio_config.enable = 1; - eeconfig_update_audio(audio_config.raw); - audio_on_user(); - PLAY_SONG(audio_on_song); -} - -void audio_off(void) { - PLAY_SONG(audio_off_song); - wait_ms(100); - stop_all_notes(); - audio_config.enable = 0; - eeconfig_update_audio(audio_config.raw); -} - -#ifdef VIBRATO_ENABLE - -// Vibrato rate functions - -void set_vibrato_rate(float rate) { vibrato_rate = rate; } - -void increase_vibrato_rate(float change) { vibrato_rate *= change; } - -void decrease_vibrato_rate(float change) { vibrato_rate /= change; } - -# ifdef VIBRATO_STRENGTH_ENABLE - -void set_vibrato_strength(float strength) { vibrato_strength = strength; } - -void increase_vibrato_strength(float change) { vibrato_strength *= change; } - -void decrease_vibrato_strength(float change) { vibrato_strength /= change; } - -# endif /* VIBRATO_STRENGTH_ENABLE */ - -#endif /* VIBRATO_ENABLE */ - -// Polyphony functions - -void set_polyphony_rate(float rate) { polyphony_rate = rate; } - -void enable_polyphony() { polyphony_rate = 5; } - -void disable_polyphony() { polyphony_rate = 0; } - -void increase_polyphony_rate(float change) { polyphony_rate *= change; } - -void decrease_polyphony_rate(float change) { polyphony_rate /= change; } - -// Timbre function - -void set_timbre(float timbre) { note_timbre = timbre; } - -// Tempo functions - -void set_tempo(uint8_t tempo) { note_tempo = tempo; } - -void decrease_tempo(uint8_t tempo_change) { note_tempo += tempo_change; } - -void increase_tempo(uint8_t tempo_change) { - if (note_tempo - tempo_change < 10) { - note_tempo = 10; - } else { - note_tempo -= tempo_change; - } -} diff --git a/quantum/audio/audio_chibios.c b/quantum/audio/audio_chibios.c deleted file mode 100644 index 1f147f2c92..0000000000 --- a/quantum/audio/audio_chibios.c +++ /dev/null @@ -1,702 +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) -#define START_CHANNEL_2() \ - gptStart(&GPTD7, &gpt7cfg1); \ - gptStartContinuous(&GPTD7, 2U) -#define STOP_CHANNEL_1() gptStopTimer(&GPTD6) -#define STOP_CHANNEL_2() gptStopTimer(&GPTD7) -#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); - - /* - * 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; - - if (audio_config.enable) { - PLAY_SONG(startup_song); - } else { - stop_all_notes(); - } -} - -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) { - audio_config.enable ^= 1; - eeconfig_update_audio(audio_config.raw); - if (audio_config.enable) { - audio_on_user(); - } -} - -void audio_on(void) { - audio_config.enable = 1; - eeconfig_update_audio(audio_config.raw); - audio_on_user(); -} - -void audio_off(void) { - stop_all_notes(); - audio_config.enable = 0; - eeconfig_update_audio(audio_config.raw); -} - -#ifdef VIBRATO_ENABLE - -// Vibrato rate functions - -void set_vibrato_rate(float rate) { vibrato_rate = rate; } - -void increase_vibrato_rate(float change) { vibrato_rate *= change; } - -void decrease_vibrato_rate(float change) { vibrato_rate /= change; } - -# ifdef VIBRATO_STRENGTH_ENABLE - -void set_vibrato_strength(float strength) { vibrato_strength = strength; } - -void increase_vibrato_strength(float change) { vibrato_strength *= change; } - -void decrease_vibrato_strength(float change) { vibrato_strength /= change; } - -# endif /* VIBRATO_STRENGTH_ENABLE */ - -#endif /* VIBRATO_ENABLE */ - -// Polyphony functions - -void set_polyphony_rate(float rate) { polyphony_rate = rate; } - -void enable_polyphony() { polyphony_rate = 5; } - -void disable_polyphony() { polyphony_rate = 0; } - -void increase_polyphony_rate(float change) { polyphony_rate *= change; } - -void decrease_polyphony_rate(float change) { polyphony_rate /= change; } - -// Timbre function - -void set_timbre(float timbre) { note_timbre = timbre; } - -// Tempo functions - -void set_tempo(uint8_t tempo) { note_tempo = tempo; } - -void decrease_tempo(uint8_t tempo_change) { note_tempo += tempo_change; } - -void increase_tempo(uint8_t tempo_change) { - if (note_tempo - tempo_change < 10) { - note_tempo = 10; - } else { - note_tempo -= tempo_change; - } -} diff --git a/quantum/audio/audio_pwm.c b/quantum/audio/audio_pwm.c deleted file mode 100644 index 545aef6dd7..0000000000 --- a/quantum/audio/audio_pwm.c +++ /dev/null @@ -1,595 +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 - -// 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 stop_all_notes() { - if (!audio_initialized) { - audio_init(); - } - voices = 0; -#ifdef PWM_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#else - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - - playing_notes = false; - playing_note = false; - frequency = 0; - volume = 0; - - for (uint8_t i = 0; i < 8; i++) { - frequencies[i] = 0; - volumes[i] = 0; - } -} - -void stop_note(float freq) { - if (playing_note) { - if (!audio_initialized) { - audio_init(); - } -#ifdef PWM_AUDIO - freq = freq / SAMPLE_RATE; -#endif - for (int i = 7; i >= 0; i--) { - if (frequencies[i] == freq) { - frequencies[i] = 0; - volumes[i] = 0; - for (int j = i; (j < 7); j++) { - frequencies[j] = frequencies[j + 1]; - frequencies[j + 1] = 0; - volumes[j] = volumes[j + 1]; - volumes[j + 1] = 0; - } - break; - } - } - voices--; - if (voices < 0) voices = 0; - if (voice_place >= voices) { - voice_place = 0; - } - if (voices == 0) { -#ifdef PWM_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#else - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - frequency = 0; - volume = 0; - playing_note = false; - } - } -} - -#ifdef VIBRATO_ENABLE - -float mod(float a, int b) { - float r = fmod(a, b); - return r < 0 ? r + b : r; -} - -float vibrato(float average_freq) { -# ifdef VIBRATO_STRENGTH_ENABLE - float vibrated_freq = average_freq * pow(vibrato_lut[(int)vibrato_counter], vibrato_strength); -# else - float vibrated_freq = average_freq * vibrato_lut[(int)vibrato_counter]; -# endif - vibrato_counter = mod((vibrato_counter + vibrato_rate * (1.0 + 440.0 / average_freq)), VIBRATO_LUT_LENGTH); - return vibrated_freq; -} - -#endif - -ISR(TIMER3_COMPA_vect) { - if (playing_note) { -#ifdef PWM_AUDIO - if (voices == 1) { - // SINE - OCR4A = pgm_read_byte(&sinewave[(uint16_t)place]) >> 2; - - // SQUARE - // if (((int)place) >= 1024){ - // OCR4A = 0xFF >> 2; - // } else { - // OCR4A = 0x00; - // } - - // SAWTOOTH - // OCR4A = (int)place / 4; - - // TRIANGLE - // if (((int)place) >= 1024) { - // OCR4A = (int)place / 2; - // } else { - // OCR4A = 2048 - (int)place / 2; - // } - - place += frequency; - - if (place >= SINE_LENGTH) place -= SINE_LENGTH; - - } else { - int sum = 0; - for (int i = 0; i < voices; i++) { - // SINE - sum += pgm_read_byte(&sinewave[(uint16_t)places[i]]) >> 2; - - // SQUARE - // if (((int)places[i]) >= 1024){ - // sum += 0xFF >> 2; - // } else { - // sum += 0x00; - // } - - places[i] += frequencies[i]; - - if (places[i] >= SINE_LENGTH) places[i] -= SINE_LENGTH; - } - OCR4A = sum; - } -#else - if (voices > 0) { - float freq; - if (polyphony_rate > 0) { - if (voices > 1) { - voice_place %= voices; - if (place++ > (frequencies[voice_place] / polyphony_rate / CPU_PRESCALER)) { - voice_place = (voice_place + 1) % voices; - place = 0.0; - } - } -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequencies[voice_place]); - } else { -# else - { -# endif - freq = frequencies[voice_place]; - } - } else { - if (frequency != 0 && frequency < frequencies[voices - 1] && frequency < frequencies[voices - 1] * pow(2, -440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, 440 / frequency / 12 / 2); - } else if (frequency != 0 && frequency > frequencies[voices - 1] && frequency > frequencies[voices - 1] * pow(2, 440 / frequencies[voices - 1] / 12 / 2)) { - frequency = frequency * pow(2, -440 / frequency / 12 / 2); - } else { - frequency = frequencies[voices - 1]; - } - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(frequency); - } else { -# else - { -# endif - freq = frequency; - } - } - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - if (freq < 30.517578125) freq = 30.52; - NOTE_PERIOD = (int)(((double)F_CPU) / (freq * CPU_PRESCALER)); // Set max to the period - NOTE_DUTY_CYCLE = (int)((((double)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); // Set compare to half the period - } -#endif - } - - // SAMPLE - // OCR4A = pgm_read_byte(&sample[(uint16_t)place_int]); - - // place_int++; - - // if (place_int >= sample_length) - // if (repeat) - // place_int -= sample_length; - // else - // DISABLE_AUDIO_COUNTER_3_ISR; - - if (playing_notes) { -#ifdef PWM_AUDIO - OCR4A = pgm_read_byte(&sinewave[(uint16_t)place]) >> 0; - - place += note_frequency; - if (place >= SINE_LENGTH) place -= SINE_LENGTH; -#else - if (note_frequency > 0) { - float freq; - -# ifdef VIBRATO_ENABLE - if (vibrato_strength > 0) { - freq = vibrato(note_frequency); - } else { -# else - { -# endif - freq = note_frequency; - } - - if (envelope_index < 65535) { - envelope_index++; - } - freq = voice_envelope(freq); - - NOTE_PERIOD = (int)(((double)F_CPU) / (freq * CPU_PRESCALER)); // Set max to the period - NOTE_DUTY_CYCLE = (int)((((double)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre); // Set compare to half the period - } else { - NOTE_PERIOD = 0; - NOTE_DUTY_CYCLE = 0; - } -#endif - - note_position++; - bool end_of_note = false; - if (NOTE_PERIOD > 0) - end_of_note = (note_position >= (note_length / NOTE_PERIOD * 0xFFFF)); - else - end_of_note = (note_position >= (note_length * 0x7FF)); - if (end_of_note) { - current_note++; - if (current_note >= notes_count) { - if (notes_repeat) { - current_note = 0; - } else { -#ifdef PWM_AUDIO - DISABLE_AUDIO_COUNTER_3_ISR; -#else - DISABLE_AUDIO_COUNTER_3_ISR; - DISABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - playing_notes = false; - return; - } - } - if (!note_resting && (notes_rest > 0)) { - note_resting = true; - note_frequency = 0; - note_length = notes_rest; - current_note--; - } else { - note_resting = false; -#ifdef PWM_AUDIO - note_frequency = (*notes_pointer)[current_note][0] / SAMPLE_RATE; - note_length = (*notes_pointer)[current_note][1] * (((float)note_tempo) / 100); -#else - envelope_index = 0; - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); -#endif - } - note_position = 0; - } - } - - if (!audio_config.enable) { - playing_notes = false; - playing_note = false; - } -} - -void play_note(float freq, int vol) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable && voices < 8) { - DISABLE_AUDIO_COUNTER_3_ISR; - - // Cancel notes if notes are playing - if (playing_notes) stop_all_notes(); - - playing_note = true; - - envelope_index = 0; - -#ifdef PWM_AUDIO - freq = freq / SAMPLE_RATE; -#endif - if (freq > 0) { - frequencies[voices] = freq; - volumes[voices] = vol; - voices++; - } - -#ifdef PWM_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; -#else - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - } -} - -void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat, float n_rest) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable) { - DISABLE_AUDIO_COUNTER_3_ISR; - - // Cancel note if a note is playing - if (playing_note) stop_all_notes(); - - playing_notes = true; - - notes_pointer = np; - notes_count = n_count; - notes_repeat = n_repeat; - notes_rest = n_rest; - - place = 0; - current_note = 0; - -#ifdef PWM_AUDIO - note_frequency = (*notes_pointer)[current_note][0] / SAMPLE_RATE; - note_length = (*notes_pointer)[current_note][1] * (((float)note_tempo) / 100); -#else - note_frequency = (*notes_pointer)[current_note][0]; - note_length = ((*notes_pointer)[current_note][1] / 4) * (((float)note_tempo) / 100); -#endif - note_position = 0; - -#ifdef PWM_AUDIO - ENABLE_AUDIO_COUNTER_3_ISR; -#else - ENABLE_AUDIO_COUNTER_3_ISR; - ENABLE_AUDIO_COUNTER_3_OUTPUT; -#endif - } -} - -#ifdef PWM_AUDIO -void play_sample(uint8_t* s, uint16_t l, bool r) { - if (!audio_initialized) { - audio_init(); - } - - if (audio_config.enable) { - DISABLE_AUDIO_COUNTER_3_ISR; - stop_all_notes(); - place_int = 0; - sample = s; - sample_length = l; - repeat = r; - - ENABLE_AUDIO_COUNTER_3_ISR; - } -} -#endif - -void audio_toggle(void) { - audio_config.enable ^= 1; - eeconfig_update_audio(audio_config.raw); -} - -void audio_on(void) { - audio_config.enable = 1; - eeconfig_update_audio(audio_config.raw); -} - -void audio_off(void) { - audio_config.enable = 0; - eeconfig_update_audio(audio_config.raw); -} - -#ifdef VIBRATO_ENABLE - -// Vibrato rate functions - -void set_vibrato_rate(float rate) { vibrato_rate = rate; } - -void increase_vibrato_rate(float change) { vibrato_rate *= change; } - -void decrease_vibrato_rate(float change) { vibrato_rate /= change; } - -# ifdef VIBRATO_STRENGTH_ENABLE - -void set_vibrato_strength(float strength) { vibrato_strength = strength; } - -void increase_vibrato_strength(float change) { vibrato_strength *= change; } - -void decrease_vibrato_strength(float change) { vibrato_strength /= change; } - -# endif /* VIBRATO_STRENGTH_ENABLE */ - -#endif /* VIBRATO_ENABLE */ - -// Polyphony functions - -void set_polyphony_rate(float rate) { polyphony_rate = rate; } - -void enable_polyphony() { polyphony_rate = 5; } - -void disable_polyphony() { polyphony_rate = 0; } - -void increase_polyphony_rate(float change) { polyphony_rate *= change; } - -void decrease_polyphony_rate(float change) { polyphony_rate /= change; } - -// Timbre function - -void set_timbre(float timbre) { note_timbre = timbre; } - -// Tempo functions - -void set_tempo(uint8_t tempo) { note_tempo = tempo; } - -void decrease_tempo(uint8_t tempo_change) { note_tempo += tempo_change; } - -void increase_tempo(uint8_t tempo_change) { - if (note_tempo - tempo_change < 10) { - note_tempo = 10; - } else { - note_tempo -= tempo_change; - } -} - -//------------------------------------------------------------------------------ -// Override these functions in your keymap file to play different tunes on -// startup and bootloader jump -__attribute__((weak)) void play_startup_tone() {} - -__attribute__((weak)) void play_goodbye_tone() {} -//------------------------------------------------------------------------------ diff --git a/quantum/audio/driver_avr_pwm.h b/quantum/audio/driver_avr_pwm.h new file mode 100644 index 0000000000..d6eb3571da --- /dev/null +++ b/quantum/audio/driver_avr_pwm.h @@ -0,0 +1,17 @@ +/* Copyright 2020 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once diff --git a/quantum/audio/driver_avr_pwm_hardware.c b/quantum/audio/driver_avr_pwm_hardware.c new file mode 100644 index 0000000000..492b9bfb04 --- /dev/null +++ b/quantum/audio/driver_avr_pwm_hardware.c @@ -0,0 +1,322 @@ +/* Copyright 2016 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#if defined(__AVR__) +# include <avr/pgmspace.h> +# include <avr/interrupt.h> +# include <avr/io.h> +#endif + +#include "audio.h" + +extern bool playing_note; +extern bool playing_melody; +extern uint8_t note_timbre; + +#define CPU_PRESCALER 8 + +/* + Audio Driver: PWM + + drive up to two speakers through the AVR PWM hardware-peripheral, using timer1 and/or timer3 on Atmega32U4. + + the primary channel_1 can be connected to either pin PC4 PC5 or PC6 (the later being used by most AVR based keyboards) with a PMW signal generated by timer3 + and an optional secondary channel_2 on either pin PB5, PB6 or PB7, with a PWM signal from timer1 + + alternatively, the PWM pins on PORTB can be used as only/primary speaker +*/ + +#if defined(AUDIO_PIN) && (AUDIO_PIN != C4) && (AUDIO_PIN != C5) && (AUDIO_PIN != C6) && (AUDIO_PIN != B5) && (AUDIO_PIN != B6) && (AUDIO_PIN != B7) +# error "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under the AVR settings for available options." +#endif + +#if (AUDIO_PIN == C4) || (AUDIO_PIN == C5) || (AUDIO_PIN == C6) +# define AUDIO1_PIN_SET +# define AUDIO1_TIMSKx TIMSK3 +# define AUDIO1_TCCRxA TCCR3A +# define AUDIO1_TCCRxB TCCR3B +# define AUDIO1_ICRx ICR3 +# define AUDIO1_WGMx0 WGM30 +# define AUDIO1_WGMx1 WGM31 +# define AUDIO1_WGMx2 WGM32 +# define AUDIO1_WGMx3 WGM33 +# define AUDIO1_CSx0 CS30 +# define AUDIO1_CSx1 CS31 +# define AUDIO1_CSx2 CS32 + +# if (AUDIO_PIN == C6) +# define AUDIO1_COMxy0 COM3A0 +# define AUDIO1_COMxy1 COM3A1 +# define AUDIO1_OCIExy OCIE3A +# define AUDIO1_OCRxy OCR3A +# define AUDIO1_PIN C6 +# define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPA_vect +# elif (AUDIO_PIN == C5) +# define AUDIO1_COMxy0 COM3B0 +# define AUDIO1_COMxy1 COM3B1 +# define AUDIO1_OCIExy OCIE3B +# define AUDIO1_OCRxy OCR3B +# define AUDIO1_PIN C5 +# define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPB_vect +# elif (AUDIO_PIN == C4) +# define AUDIO1_COMxy0 COM3C0 +# define AUDIO1_COMxy1 COM3C1 +# define AUDIO1_OCIExy OCIE3C +# define AUDIO1_OCRxy OCR3C +# define AUDIO1_PIN C4 +# define AUDIO1_TIMERx_COMPy_vect TIMER3_COMPC_vect +# endif +#endif + +#if defined(AUDIO_PIN) && defined(AUDIO_PIN_ALT) && (AUDIO_PIN == AUDIO_PIN_ALT) +# error "Audio feature: AUDIO_PIN and AUDIO_PIN_ALT on the same pin makes no sense." +#endif + +#if ((AUDIO_PIN == B5) && ((AUDIO_PIN_ALT == B6) || (AUDIO_PIN_ALT == B7))) || ((AUDIO_PIN == B6) && ((AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B7))) || ((AUDIO_PIN == B7) && ((AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B6))) +# error "Audio feature: PORTB as AUDIO_PIN and AUDIO_PIN_ALT at the same time is not supported." +#endif + +#if defined(AUDIO_PIN_ALT) && (AUDIO_PIN_ALT != B5) && (AUDIO_PIN_ALT != B6) && (AUDIO_PIN_ALT != B7) +# error "Audio feature: the pin selected as AUDIO_PIN_ALT is not supported." +#endif + +#if (AUDIO_PIN == B5) || (AUDIO_PIN == B6) || (AUDIO_PIN == B7) || (AUDIO_PIN_ALT == B5) || (AUDIO_PIN_ALT == B6) || (AUDIO_PIN_ALT == B7) +# define AUDIO2_PIN_SET +# define AUDIO2_TIMSKx TIMSK1 +# define AUDIO2_TCCRxA TCCR1A +# define AUDIO2_TCCRxB TCCR1B +# define AUDIO2_ICRx ICR1 +# define AUDIO2_WGMx0 WGM10 +# define AUDIO2_WGMx1 WGM11 +# define AUDIO2_WGMx2 WGM12 +# define AUDIO2_WGMx3 WGM13 +# define AUDIO2_CSx0 CS10 +# define AUDIO2_CSx1 CS11 +# define AUDIO2_CSx2 CS12 + +# if (AUDIO_PIN == B5) || (AUDIO_PIN_ALT == B5) +# define AUDIO2_COMxy0 COM1A0 +# define AUDIO2_COMxy1 COM1A1 +# define AUDIO2_OCIExy OCIE1A +# define AUDIO2_OCRxy OCR1A +# define AUDIO2_PIN B5 +# define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPA_vect +# elif (AUDIO_PIN == B6) || (AUDIO_PIN_ALT == B6) +# define AUDIO2_COMxy0 COM1B0 +# define AUDIO2_COMxy1 COM1B1 +# define AUDIO2_OCIExy OCIE1B +# define AUDIO2_OCRxy OCR1B +# define AUDIO2_PIN B6 +# define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPB_vect +# elif (AUDIO_PIN == B7) || (AUDIO_PIN_ALT == B7) +# define AUDIO2_COMxy0 COM1C0 +# define AUDIO2_COMxy1 COM1C1 +# define AUDIO2_OCIExy OCIE1C +# define AUDIO2_OCRxy OCR1C +# define AUDIO2_PIN B7 +# define AUDIO2_TIMERx_COMPy_vect TIMER1_COMPC_vect +# endif +#endif + +// C6 seems to be the assumed default by many existing keyboard - but sill warn the user +#if !defined(AUDIO1_PIN_SET) && !defined(AUDIO2_PIN_SET) +# pragma message "Audio feature enabled, but no suitable pin selected - see docs/feature_audio under the AVR settings for available options. Don't expect to hear anything... :-)" +// TODO: make this an error - go through the breaking-change-process and change all keyboards to the new define +#endif +// ----------------------------------------------------------------------------- + +#ifdef AUDIO1_PIN_SET +static float channel_1_frequency = 0.0f; +void channel_1_set_frequency(float freq) { + if (freq == 0.0f) // a pause/rest is a valid "note" with freq=0 + { + // disable the output, but keep the pwm-ISR going (with the previous + // frequency) so the audio-state keeps getting updated + // Note: setting the duty-cycle 0 is not possible on non-inverting PWM mode - see the AVR data-sheet + AUDIO1_TCCRxA &= ~(_BV(AUDIO1_COMxy1) | _BV(AUDIO1_COMxy0)); + return; + } else { + AUDIO1_TCCRxA |= _BV(AUDIO1_COMxy1); // enable output, PWM mode + } + + channel_1_frequency = freq; + + // set pwm period + AUDIO1_ICRx = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); + // and duty cycle + AUDIO1_OCRxy = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre / 100); +} + +void channel_1_start(void) { + // enable timer-counter ISR + AUDIO1_TIMSKx |= _BV(AUDIO1_OCIExy); + // enable timer-counter output + AUDIO1_TCCRxA |= _BV(AUDIO1_COMxy1); +} + +void channel_1_stop(void) { + // disable timer-counter ISR + AUDIO1_TIMSKx &= ~_BV(AUDIO1_OCIExy); + // disable timer-counter output + AUDIO1_TCCRxA &= ~(_BV(AUDIO1_COMxy1) | _BV(AUDIO1_COMxy0)); +} +#endif + +#ifdef AUDIO2_PIN_SET +static float channel_2_frequency = 0.0f; +void channel_2_set_frequency(float freq) { + if (freq == 0.0f) { + AUDIO2_TCCRxA &= ~(_BV(AUDIO2_COMxy1) | _BV(AUDIO2_COMxy0)); + return; + } else { + AUDIO2_TCCRxA |= _BV(AUDIO2_COMxy1); + } + + channel_2_frequency = freq; + + AUDIO2_ICRx = (uint16_t)(((float)F_CPU) / (freq * CPU_PRESCALER)); + AUDIO2_OCRxy = (uint16_t)((((float)F_CPU) / (freq * CPU_PRESCALER)) * note_timbre / 100); +} + +float channel_2_get_frequency(void) { return channel_2_frequency; } + +void channel_2_start(void) { + AUDIO2_TIMSKx |= _BV(AUDIO2_OCIExy); + AUDIO2_TCCRxA |= _BV(AUDIO2_COMxy1); +} + +void channel_2_stop(void) { + AUDIO2_TIMSKx &= ~_BV(AUDIO2_OCIExy); + AUDIO2_TCCRxA &= ~(_BV(AUDIO2_COMxy1) | _BV(AUDIO2_COMxy0)); +} +#endif + +void audio_driver_initialize() { +#ifdef AUDIO1_PIN_SET + channel_1_stop(); + setPinOutput(AUDIO1_PIN); +#endif + +#ifdef AUDIO2_PIN_SET + channel_2_stop(); + setPinOutput(AUDIO2_PIN); +#endif + + // TCCR3A / TCCR3B: Timer/Counter #3 Control Registers TCCR3A/TCCR3B, TCCR1A/TCCR1B + // Compare Output Mode (COM3An and COM1An) = 0b00 = Normal port operation + // OC3A -- PC6 + // OC3B -- PC5 + // OC3C -- PC4 + // OC1A -- PB5 + // OC1B -- PB6 + // OC1C -- PB7 + + // Waveform Generation Mode (WGM3n) = 0b1110 = Fast PWM Mode 14. Period = ICR3, Duty Cycle OCR3A) + // OCR3A - PC6 + // OCR3B - PC5 + // OCR3C - PC4 + // OCR1A - PB5 + // OCR1B - PB6 + // OCR1C - PB7 + + // Clock Select (CS3n) = 0b010 = Clock / 8 +#ifdef AUDIO1_PIN_SET + // initialize timer-counter + AUDIO1_TCCRxA = (0 << AUDIO1_COMxy1) | (0 << AUDIO1_COMxy0) | (1 << AUDIO1_WGMx1) | (0 << AUDIO1_WGMx0); + AUDIO1_TCCRxB = (1 << AUDIO1_WGMx3) | (1 << AUDIO1_WGMx2) | (0 << AUDIO1_CSx2) | (1 << AUDIO1_CSx1) | (0 << AUDIO1_CSx0); +#endif + +#ifdef AUDIO2_PIN_SET + AUDIO2_TCCRxA = (0 << AUDIO2_COMxy1) | (0 << AUDIO2_COMxy0) | (1 << AUDIO2_WGMx1) | (0 << AUDIO2_WGMx0); + AUDIO2_TCCRxB = (1 << AUDIO2_WGMx3) | (1 << AUDIO2_WGMx2) | (0 << AUDIO2_CSx2) | (1 << AUDIO2_CSx1) | (0 << AUDIO2_CSx0); +#endif +} + +void audio_driver_stop() { +#ifdef AUDIO1_PIN_SET + channel_1_stop(); +#endif + +#ifdef AUDIO2_PIN_SET + channel_2_stop(); +#endif +} + +void audio_driver_start(void) { +#ifdef AUDIO1_PIN_SET + channel_1_start(); + if (playing_note) { + channel_1_set_frequency(audio_get_processed_frequency(0)); + } +#endif + +#if !defined(AUDIO1_PIN_SET) && defined(AUDIO2_PIN_SET) + channel_2_start(); + if (playing_note) { + channel_2_set_frequency(audio_get_processed_frequency(0)); + } +#endif +} + +static volatile uint32_t isr_counter = 0; +#ifdef AUDIO1_PIN_SET +ISR(AUDIO1_TIMERx_COMPy_vect) { + isr_counter++; + if (isr_counter < channel_1_frequency / (CPU_PRESCALER * 8)) return; + + isr_counter = 0; + bool state_changed = audio_update_state(); + + if (!playing_note && !playing_melody) { + channel_1_stop(); +# ifdef AUDIO2_PIN_SET + channel_2_stop(); +# endif + return; + } + + if (state_changed) { + channel_1_set_frequency(audio_get_processed_frequency(0)); +# ifdef AUDIO2_PIN_SET + if (audio_get_number_of_active_tones() > 1) { + channel_2_set_frequency(audio_get_processed_frequency(1)); + } else { + channel_2_stop(); + } +# endif + } +} +#endif + +#if !defined(AUDIO1_PIN_SET) && defined(AUDIO2_PIN_SET) +ISR(AUDIO2_TIMERx_COMPy_vect) { + isr_counter++; + if (isr_counter < channel_2_frequency / (CPU_PRESCALER * 8)) return; + + isr_counter = 0; + bool state_changed = audio_update_state(); + + if (!playing_note && !playing_melody) { + channel_2_stop(); + return; + } + + if (state_changed) { + channel_2_set_frequency(audio_get_processed_frequency(0)); + } +} +#endif diff --git a/quantum/audio/driver_chibios_dac.h b/quantum/audio/driver_chibios_dac.h new file mode 100644 index 0000000000..07cd622ead --- /dev/null +++ b/quantum/audio/driver_chibios_dac.h @@ -0,0 +1,126 @@ +/* Copyright 2019 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#ifndef A4 +# define A4 PAL_LINE(GPIOA, 4) +#endif +#ifndef A5 +# define A5 PAL_LINE(GPIOA, 5) +#endif + +/** + * Size of the dac_buffer arrays. All must be the same size. + */ +#define AUDIO_DAC_BUFFER_SIZE 256U + +/** + * Highest value allowed sample value. + + * since the DAC is limited to 12 bit, the absolute max is 0xfff = 4095U; + * lower values adjust the peak-voltage aka volume down. + * adjusting this value has only an effect on a sample-buffer whose values are + * are NOT pregenerated - see square-wave + */ +#ifndef AUDIO_DAC_SAMPLE_MAX +# define AUDIO_DAC_SAMPLE_MAX 4095U +#endif + +#if !defined(AUDIO_DAC_SAMPLE_RATE) && !defined(AUDIO_MAX_SIMULTANEOUS_TONES) && !defined(AUDIO_DAC_QUALITY_VERY_LOW) && !defined(AUDIO_DAC_QUALITY_LOW) && !defined(AUDIO_DAC_QUALITY_HIGH) && !defined(AUDIO_DAC_QUALITY_VERY_HIGH) +# define AUDIO_DAC_QUALITY_SANE_MINIMUM +#endif + +/** + * These presets allow you to quickly switch between quality settings for + * the DAC. The sample rate and maximum number of simultaneous tones roughly + * has an inverse relationship - slightly higher sample rates may be possible. + * + * NOTE: a high sample-rate results in a higher cpu-load, which might lead to + * (audible) discontinuities and/or starve other processes of cpu-time + * (like RGB-led back-lighting, ...) + */ +#ifdef AUDIO_DAC_QUALITY_VERY_LOW +# define AUDIO_DAC_SAMPLE_RATE 11025U +# define AUDIO_MAX_SIMULTANEOUS_TONES 8 +#endif + +#ifdef AUDIO_DAC_QUALITY_LOW +# define AUDIO_DAC_SAMPLE_RATE 22050U +# define AUDIO_MAX_SIMULTANEOUS_TONES 4 +#endif + +#ifdef AUDIO_DAC_QUALITY_HIGH +# define AUDIO_DAC_SAMPLE_RATE 44100U +# define AUDIO_MAX_SIMULTANEOUS_TONES 2 +#endif + +#ifdef AUDIO_DAC_QUALITY_VERY_HIGH +# define AUDIO_DAC_SAMPLE_RATE 88200U +# define AUDIO_MAX_SIMULTANEOUS_TONES 1 +#endif + +#ifdef AUDIO_DAC_QUALITY_SANE_MINIMUM +/* a sane-minimum config: with a trade-off between cpu-load and tone-range + * + * the (currently) highest defined note is NOTE_B8 with 7902Hz; if we now + * aim for an even even multiple of the buffer-size, we end up with: + * ( roundUptoPow2(highest note / AUDIO_DAC_BUFFER_SIZE) * nyquist-rate * AUDIO_DAC_BUFFER_SIZE) + * 7902/256 = 30.867 * 2 * 256 ~= 16384 + * which works out (but the 'scope shows some sampling artifacts with lower harmonics :-P) + */ +# define AUDIO_DAC_SAMPLE_RATE 16384U +# define AUDIO_MAX_SIMULTANEOUS_TONES 8 +#endif + +/** + * Effective bit-rate of the DAC. 44.1khz is the standard for most audio - any + * lower will sacrifice perceptible audio quality. Any higher will limit the + * number of simultaneous tones. In most situations, a tenth (1/10) of the + * sample rate is where notes become unbearable. + */ +#ifndef AUDIO_DAC_SAMPLE_RATE +# define AUDIO_DAC_SAMPLE_RATE 44100U +#endif + +/** + * The number of tones that can be played simultaneously. If too high a value + * is used here, the keyboard will freeze and glitch-out when that many tones + * are being played. + */ +#ifndef AUDIO_MAX_SIMULTANEOUS_TONES +# define AUDIO_MAX_SIMULTANEOUS_TONES 2 +#endif + +/** + * The default value of the DAC when not playing anything. Certain hardware + * setups may require a high (AUDIO_DAC_SAMPLE_MAX) or low (0) value here. + * Since multiple added sine waves tend to oscillate around the midpoint, + * and possibly never/rarely reach either 0 of MAX, 1/2 MAX can be a + * reasonable default value. + */ +#ifndef AUDIO_DAC_OFF_VALUE +# define AUDIO_DAC_OFF_VALUE AUDIO_DAC_SAMPLE_MAX / 2 +#endif + +#if AUDIO_DAC_OFF_VALUE > AUDIO_DAC_SAMPLE_MAX +# error "AUDIO_DAC: OFF_VALUE may not be larger than SAMPLE_MAX" +#endif + +/** + *user overridable sample generation/processing + */ +uint16_t dac_value_generate(void); diff --git a/quantum/audio/driver_chibios_dac_additive.c b/quantum/audio/driver_chibios_dac_additive.c new file mode 100644 index 0000000000..db304adb87 --- /dev/null +++ b/quantum/audio/driver_chibios_dac_additive.c @@ -0,0 +1,335 @@ +/* Copyright 2016-2019 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "audio.h" +#include <ch.h> +#include <hal.h> + +/* + Audio Driver: DAC + + which utilizes the dac unit many STM32 are equipped with, to output a modulated waveform from samples stored in the dac_buffer_* array who are passed to the hardware through DMA + + it is also possible to have a custom sample-LUT by implementing/overriding 'dac_value_generate' + + this driver allows for multiple simultaneous tones to be played through one single channel by doing additive wave-synthesis +*/ + +#if !defined(AUDIO_PIN) +# error "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under 'ARM (DAC additive)' for available options." +#endif +#if defined(AUDIO_PIN_ALT) && !defined(AUDIO_PIN_ALT_AS_NEGATIVE) +# pragma message "Audio feature: AUDIO_PIN_ALT set, but not AUDIO_PIN_ALT_AS_NEGATIVE - pin will be left unused; audio might still work though." +#endif + +#if !defined(AUDIO_PIN_ALT) +// no ALT pin defined is valid, but the c-ifs below need some value set +# define AUDIO_PIN_ALT PAL_NOLINE +#endif + +#if !defined(AUDIO_DAC_SAMPLE_WAVEFORM_SINE) && !defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE) && !defined(AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE) && !defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID) +# define AUDIO_DAC_SAMPLE_WAVEFORM_SINE +#endif + +#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_SINE +/* one full sine wave over [0,2*pi], but shifted up one amplitude and left pi/4; for the samples to start at 0 + */ +static const dacsample_t dac_buffer_sine[AUDIO_DAC_BUFFER_SIZE] = { + // 256 values, max 4095 + 0x0, 0x1, 0x2, 0x6, 0xa, 0xf, 0x16, 0x1e, 0x27, 0x32, 0x3d, 0x4a, 0x58, 0x67, 0x78, 0x89, 0x9c, 0xb0, 0xc5, 0xdb, 0xf2, 0x10a, 0x123, 0x13e, 0x159, 0x175, 0x193, 0x1b1, 0x1d1, 0x1f1, 0x212, 0x235, 0x258, 0x27c, 0x2a0, 0x2c6, 0x2ed, 0x314, 0x33c, 0x365, 0x38e, 0x3b8, 0x3e3, 0x40e, 0x43a, 0x467, 0x494, 0x4c2, 0x4f0, 0x51f, 0x54e, 0x57d, 0x5ad, 0x5dd, 0x60e, 0x63f, 0x670, 0x6a1, 0x6d3, 0x705, 0x737, 0x769, 0x79b, 0x7cd, 0x800, 0x832, 0x864, 0x896, 0x8c8, 0x8fa, 0x92c, 0x95e, 0x98f, 0x9c0, 0x9f1, 0xa22, 0xa52, 0xa82, 0xab1, 0xae0, 0xb0f, 0xb3d, 0xb6b, 0xb98, 0xbc5, 0xbf1, 0xc1c, 0xc47, 0xc71, 0xc9a, 0xcc3, 0xceb, 0xd12, 0xd39, 0xd5f, 0xd83, 0xda7, 0xdca, 0xded, 0xe0e, 0xe2e, 0xe4e, 0xe6c, 0xe8a, 0xea6, 0xec1, 0xedc, 0xef5, 0xf0d, 0xf24, 0xf3a, 0xf4f, 0xf63, 0xf76, 0xf87, 0xf98, 0xfa7, 0xfb5, 0xfc2, 0xfcd, 0xfd8, 0xfe1, 0xfe9, 0xff0, 0xff5, 0xff9, 0xffd, 0xffe, + 0xfff, 0xffe, 0xffd, 0xff9, 0xff5, 0xff0, 0xfe9, 0xfe1, 0xfd8, 0xfcd, 0xfc2, 0xfb5, 0xfa7, 0xf98, 0xf87, 0xf76, 0xf63, 0xf4f, 0xf3a, 0xf24, 0xf0d, 0xef5, 0xedc, 0xec1, 0xea6, 0xe8a, 0xe6c, 0xe4e, 0xe2e, 0xe0e, 0xded, 0xdca, 0xda7, 0xd83, 0xd5f, 0xd39, 0xd12, 0xceb, 0xcc3, 0xc9a, 0xc71, 0xc47, 0xc1c, 0xbf1, 0xbc5, 0xb98, 0xb6b, 0xb3d, 0xb0f, 0xae0, 0xab1, 0xa82, 0xa52, 0xa22, 0x9f1, 0x9c0, 0x98f, 0x95e, 0x92c, 0x8fa, 0x8c8, 0x896, 0x864, 0x832, 0x800, 0x7cd, 0x79b, 0x769, 0x737, 0x705, 0x6d3, 0x6a1, 0x670, 0x63f, 0x60e, 0x5dd, 0x5ad, 0x57d, 0x54e, 0x51f, 0x4f0, 0x4c2, 0x494, 0x467, 0x43a, 0x40e, 0x3e3, 0x3b8, 0x38e, 0x365, 0x33c, 0x314, 0x2ed, 0x2c6, 0x2a0, 0x27c, 0x258, 0x235, 0x212, 0x1f1, 0x1d1, 0x1b1, 0x193, 0x175, 0x159, 0x13e, 0x123, 0x10a, 0xf2, 0xdb, 0xc5, 0xb0, 0x9c, 0x89, 0x78, 0x67, 0x58, 0x4a, 0x3d, 0x32, 0x27, 0x1e, 0x16, 0xf, 0xa, 0x6, 0x2, 0x1}; +#endif // AUDIO_DAC_SAMPLE_WAVEFORM_SINE +#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE +static const dacsample_t dac_buffer_triangle[AUDIO_DAC_BUFFER_SIZE] = { + // 256 values, max 4095 + 0x0, 0x20, 0x40, 0x60, 0x80, 0xa0, 0xc0, 0xe0, 0x100, 0x120, 0x140, 0x160, 0x180, 0x1a0, 0x1c0, 0x1e0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0x300, 0x320, 0x340, 0x360, 0x380, 0x3a0, 0x3c0, 0x3e0, 0x400, 0x420, 0x440, 0x460, 0x480, 0x4a0, 0x4c0, 0x4e0, 0x500, 0x520, 0x540, 0x560, 0x580, 0x5a0, 0x5c0, 0x5e0, 0x600, 0x620, 0x640, 0x660, 0x680, 0x6a0, 0x6c0, 0x6e0, 0x700, 0x720, 0x740, 0x760, 0x780, 0x7a0, 0x7c0, 0x7e0, 0x800, 0x81f, 0x83f, 0x85f, 0x87f, 0x89f, 0x8bf, 0x8df, 0x8ff, 0x91f, 0x93f, 0x95f, 0x97f, 0x99f, 0x9bf, 0x9df, 0x9ff, 0xa1f, 0xa3f, 0xa5f, 0xa7f, 0xa9f, 0xabf, 0xadf, 0xaff, 0xb1f, 0xb3f, 0xb5f, 0xb7f, 0xb9f, 0xbbf, 0xbdf, 0xbff, 0xc1f, 0xc3f, 0xc5f, 0xc7f, 0xc9f, 0xcbf, 0xcdf, 0xcff, 0xd1f, 0xd3f, 0xd5f, 0xd7f, 0xd9f, 0xdbf, 0xddf, 0xdff, 0xe1f, 0xe3f, 0xe5f, 0xe7f, 0xe9f, 0xebf, 0xedf, 0xeff, 0xf1f, 0xf3f, 0xf5f, 0xf7f, 0xf9f, 0xfbf, 0xfdf, + 0xfff, 0xfdf, 0xfbf, 0xf9f, 0xf7f, 0xf5f, 0xf3f, 0xf1f, 0xeff, 0xedf, 0xebf, 0xe9f, 0xe7f, 0xe5f, 0xe3f, 0xe1f, 0xdff, 0xddf, 0xdbf, 0xd9f, 0xd7f, 0xd5f, 0xd3f, 0xd1f, 0xcff, 0xcdf, 0xcbf, 0xc9f, 0xc7f, 0xc5f, 0xc3f, 0xc1f, 0xbff, 0xbdf, 0xbbf, 0xb9f, 0xb7f, 0xb5f, 0xb3f, 0xb1f, 0xaff, 0xadf, 0xabf, 0xa9f, 0xa7f, 0xa5f, 0xa3f, 0xa1f, 0x9ff, 0x9df, 0x9bf, 0x99f, 0x97f, 0x95f, 0x93f, 0x91f, 0x8ff, 0x8df, 0x8bf, 0x89f, 0x87f, 0x85f, 0x83f, 0x81f, 0x800, 0x7e0, 0x7c0, 0x7a0, 0x780, 0x760, 0x740, 0x720, 0x700, 0x6e0, 0x6c0, 0x6a0, 0x680, 0x660, 0x640, 0x620, 0x600, 0x5e0, 0x5c0, 0x5a0, 0x580, 0x560, 0x540, 0x520, 0x500, 0x4e0, 0x4c0, 0x4a0, 0x480, 0x460, 0x440, 0x420, 0x400, 0x3e0, 0x3c0, 0x3a0, 0x380, 0x360, 0x340, 0x320, 0x300, 0x2e0, 0x2c0, 0x2a0, 0x280, 0x260, 0x240, 0x220, 0x200, 0x1e0, 0x1c0, 0x1a0, 0x180, 0x160, 0x140, 0x120, 0x100, 0xe0, 0xc0, 0xa0, 0x80, 0x60, 0x40, 0x20}; +#endif // AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE +#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE +static const dacsample_t dac_buffer_square[AUDIO_DAC_BUFFER_SIZE] = { + [0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = 0, // first and + [AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = AUDIO_DAC_SAMPLE_MAX, // second half +}; +#endif // AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE +/* +// four steps: 0, 1/3, 2/3 and 1 +static const dacsample_t dac_buffer_staircase[AUDIO_DAC_BUFFER_SIZE] = { + [0 ... AUDIO_DAC_BUFFER_SIZE/3 -1 ] = 0, + [AUDIO_DAC_BUFFER_SIZE / 4 ... AUDIO_DAC_BUFFER_SIZE / 2 -1 ] = AUDIO_DAC_SAMPLE_MAX / 3, + [AUDIO_DAC_BUFFER_SIZE / 2 ... 3 * AUDIO_DAC_BUFFER_SIZE / 4 -1 ] = 2 * AUDIO_DAC_SAMPLE_MAX / 3, + [3 * AUDIO_DAC_BUFFER_SIZE / 4 ... AUDIO_DAC_BUFFER_SIZE -1 ] = AUDIO_DAC_SAMPLE_MAX, +} +*/ +#ifdef AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID +static const dacsample_t dac_buffer_trapezoid[AUDIO_DAC_BUFFER_SIZE] = {0x0, 0x1f, 0x7f, 0xdf, 0x13f, 0x19f, 0x1ff, 0x25f, 0x2bf, 0x31f, 0x37f, 0x3df, 0x43f, 0x49f, 0x4ff, 0x55f, 0x5bf, 0x61f, 0x67f, 0x6df, 0x73f, 0x79f, 0x7ff, 0x85f, 0x8bf, 0x91f, 0x97f, 0x9df, 0xa3f, 0xa9f, 0xaff, 0xb5f, 0xbbf, 0xc1f, 0xc7f, 0xcdf, 0xd3f, 0xd9f, 0xdff, 0xe5f, 0xebf, 0xf1f, 0xf7f, 0xfdf, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, + 0xfff, 0xfdf, 0xf7f, 0xf1f, 0xebf, 0xe5f, 0xdff, 0xd9f, 0xd3f, 0xcdf, 0xc7f, 0xc1f, 0xbbf, 0xb5f, 0xaff, 0xa9f, 0xa3f, 0x9df, 0x97f, 0x91f, 0x8bf, 0x85f, 0x7ff, 0x79f, 0x73f, 0x6df, 0x67f, 0x61f, 0x5bf, 0x55f, 0x4ff, 0x49f, 0x43f, 0x3df, 0x37f, 0x31f, 0x2bf, 0x25f, 0x1ff, 0x19f, 0x13f, 0xdf, 0x7f, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; +#endif // AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID + +static dacsample_t dac_buffer_empty[AUDIO_DAC_BUFFER_SIZE] = {AUDIO_DAC_OFF_VALUE}; + +/* keep track of the sample position for for each frequency */ +static float dac_if[AUDIO_MAX_SIMULTANEOUS_TONES] = {0.0}; + +static float active_tones_snapshot[AUDIO_MAX_SIMULTANEOUS_TONES] = {0, 0}; +static uint8_t active_tones_snapshot_length = 0; + +typedef enum { + OUTPUT_SHOULD_START, + OUTPUT_RUN_NORMALLY, + // path 1: wait for zero, then change/update active tones + OUTPUT_TONES_CHANGED, + OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE, + // path 2: hardware should stop, wait for zero then turn output off = stop the timer + OUTPUT_SHOULD_STOP, + OUTPUT_REACHED_ZERO_BEFORE_OFF, + OUTPUT_OFF, + OUTPUT_OFF_1, + OUTPUT_OFF_2, // trailing off: giving the DAC two more conversion cycles until the AUDIO_DAC_OFF_VALUE reaches the output, then turn the timer off, which leaves the output at that level + number_of_output_states +} output_states_t; +output_states_t state = OUTPUT_OFF_2; + +/** + * Generation of the waveform being passed to the callback. Declared weak so users + * can override it with their own wave-forms/noises. + */ +__attribute__((weak)) uint16_t dac_value_generate(void) { + // DAC is running/asking for values but snapshot length is zero -> must be playing a pause + if (active_tones_snapshot_length == 0) { + return AUDIO_DAC_OFF_VALUE; + } + + /* doing additive wave synthesis over all currently playing tones = adding up + * sine-wave-samples for each frequency, scaled by the number of active tones + */ + uint16_t value = 0; + float frequency = 0.0f; + + for (uint8_t i = 0; i < active_tones_snapshot_length; i++) { + /* Note: a user implementation does not have to rely on the active_tones_snapshot, but + * could directly query the active frequencies through audio_get_processed_frequency */ + frequency = active_tones_snapshot[i]; + + dac_if[i] = dac_if[i] + ((frequency * AUDIO_DAC_BUFFER_SIZE) / AUDIO_DAC_SAMPLE_RATE) * 2 / 3; + /*Note: the 2/3 are necessary to get the correct frequencies on the + * DAC output (as measured with an oscilloscope), since the gpt + * timer runs with 3*AUDIO_DAC_SAMPLE_RATE; and the DAC callback + * is called twice per conversion.*/ + + dac_if[i] = fmod(dac_if[i], AUDIO_DAC_BUFFER_SIZE); + + // Wavetable generation/lookup + uint16_t dac_i = (uint16_t)dac_if[i]; + +#if defined(AUDIO_DAC_SAMPLE_WAVEFORM_SINE) + value += dac_buffer_sine[dac_i] / active_tones_snapshot_length; +#elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE) + value += dac_buffer_triangle[dac_i] / active_tones_snapshot_length; +#elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID) + value += dac_buffer_trapezoid[dac_i] / active_tones_snapshot_length; +#elif defined(AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE) + value += dac_buffer_square[dac_i] / active_tones_snapshot_length; +#endif + /* + // SINE + value += dac_buffer_sine[dac_i] / active_tones_snapshot_length / 3; + // TRIANGLE + value += dac_buffer_triangle[dac_i] / active_tones_snapshot_length / 3; + // SQUARE + value += dac_buffer_square[dac_i] / active_tones_snapshot_length / 3; + //NOTE: combination of these three wave-forms is more exemplary - and doesn't sound particularly good :-P + */ + + // STAIRS (mostly usefully as test-pattern) + // value_avg = dac_buffer_staircase[dac_i] / active_tones_snapshot_length; + } + + return value; +} + +/** + * DAC streaming callback. Does all of the main computing for playing songs. + * + * Note: chibios calls this CB twice: during the 'half buffer event', and the 'full buffer event'. + */ +static void dac_end(DACDriver *dacp) { + dacsample_t *sample_p = (dacp)->samples; + + // work on the other half of the buffer + if (dacIsBufferComplete(dacp)) { + sample_p += AUDIO_DAC_BUFFER_SIZE / 2; // 'half_index' + } + + for (uint8_t s = 0; s < AUDIO_DAC_BUFFER_SIZE / 2; s++) { + if (OUTPUT_OFF <= state) { + sample_p[s] = AUDIO_DAC_OFF_VALUE; + continue; + } else { + sample_p[s] = dac_value_generate(); + } + + /* zero crossing (or approach, whereas zero == DAC_OFF_VALUE, which can be configured to anything from 0 to DAC_SAMPLE_MAX) + * ============================*=*========================== AUDIO_DAC_SAMPLE_MAX + * * * + * * * + * --------------------------------------------------------- + * * * } AUDIO_DAC_SAMPLE_MAX/100 + * --------------------------------------------------------- AUDIO_DAC_OFF_VALUE + * * * } AUDIO_DAC_SAMPLE_MAX/100 + * --------------------------------------------------------- + * * + * * * + * * * + * =====*=*================================================= 0x0 + */ + if (((sample_p[s] + (AUDIO_DAC_SAMPLE_MAX / 100)) > AUDIO_DAC_OFF_VALUE) && // value approaches from below + (sample_p[s] < (AUDIO_DAC_OFF_VALUE + (AUDIO_DAC_SAMPLE_MAX / 100))) // or above + ) { + if ((OUTPUT_SHOULD_START == state) && (active_tones_snapshot_length > 0)) { + state = OUTPUT_RUN_NORMALLY; + } else if (OUTPUT_TONES_CHANGED == state) { + state = OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE; + } else if (OUTPUT_SHOULD_STOP == state) { + state = OUTPUT_REACHED_ZERO_BEFORE_OFF; + } + } + + // still 'ramping up', reset the output to OFF_VALUE until the generated values reach that value, to do a smooth handover + if (OUTPUT_SHOULD_START == state) { + sample_p[s] = AUDIO_DAC_OFF_VALUE; + } + + if ((OUTPUT_SHOULD_START == state) || (OUTPUT_REACHED_ZERO_BEFORE_OFF == state) || (OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE == state)) { + uint8_t active_tones = MIN(AUDIO_MAX_SIMULTANEOUS_TONES, audio_get_number_of_active_tones()); + active_tones_snapshot_length = 0; + // update the snapshot - once, and only on occasion that something changed; + // -> saves cpu cycles (?) + for (uint8_t i = 0; i < active_tones; i++) { + float freq = audio_get_processed_frequency(i); + if (freq > 0) { // disregard 'rest' notes, with valid frequency 0.0f; which would only lower the resulting waveform volume during the additive synthesis step + active_tones_snapshot[active_tones_snapshot_length++] = freq; + } + } + + if ((0 == active_tones_snapshot_length) && (OUTPUT_REACHED_ZERO_BEFORE_OFF == state)) { + state = OUTPUT_OFF; + } + if (OUTPUT_REACHED_ZERO_BEFORE_TONE_CHANGE == state) { + state = OUTPUT_RUN_NORMALLY; + } + } + } + + // update audio internal state (note position, current_note, ...) + if (audio_update_state()) { + if (OUTPUT_SHOULD_STOP != state) { + state = OUTPUT_TONES_CHANGED; + } + } + + if (OUTPUT_OFF <= state) { + if (OUTPUT_OFF_2 == state) { + // stopping timer6 = stopping the DAC at whatever value it is currently pushing to the output = AUDIO_DAC_OFF_VALUE + gptStopTimer(&GPTD6); + } else { + state++; + } + } +} + +static void dac_error(DACDriver *dacp, dacerror_t err) { + (void)dacp; + (void)err; + + chSysHalt("DAC failure. halp"); +} + +static const GPTConfig gpt6cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE * 3, + .callback = NULL, + .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ + .dier = 0U}; + +static const DACConfig dac_conf = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT}; + +/** + * @note The DAC_TRG(0) here selects the Timer 6 TRGO event, which is triggered + * on the rising edge after 3 APB1 clock cycles, causing our gpt6cfg1.frequency + * to be a third of what we expect. + * + * Here are all the values for DAC_TRG (TSEL in the ref manual) + * TIM15_TRGO 0b011 + * TIM2_TRGO 0b100 + * TIM3_TRGO 0b001 + * TIM6_TRGO 0b000 + * TIM7_TRGO 0b010 + * EXTI9 0b110 + * SWTRIG 0b111 + */ +static const DACConversionGroup dac_conv_cfg = {.num_channels = 1U, .end_cb = dac_end, .error_cb = dac_error, .trigger = DAC_TRG(0b000)}; + +void audio_driver_initialize() { + if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) { + palSetLineMode(A4, PAL_MODE_INPUT_ANALOG); + dacStart(&DACD1, &dac_conf); + } + if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) { + palSetLineMode(A5, PAL_MODE_INPUT_ANALOG); + dacStart(&DACD2, &dac_conf); + } + + /* enable the output buffer, to directly drive external loads with no additional circuitry + * + * see: AN4566 Application note: Extending the DAC performance of STM32 microcontrollers + * Note: Buffer-Off bit -> has to be set 0 to enable the output buffer + * Note: enabling the output buffer imparts an additional dc-offset of a couple mV + * + * this is done here, reaching directly into the stm32 registers since chibios has not implemented BOFF handling yet + * (see: chibios/os/hal/ports/STM32/todo.txt '- BOFF handling in DACv1.' + */ + DACD1.params->dac->CR &= ~DAC_CR_BOFF1; + DACD2.params->dac->CR &= ~DAC_CR_BOFF2; + + if (AUDIO_PIN == A4) { + dacStartConversion(&DACD1, &dac_conv_cfg, dac_buffer_empty, AUDIO_DAC_BUFFER_SIZE); + } else if (AUDIO_PIN == A5) { + dacStartConversion(&DACD2, &dac_conv_cfg, dac_buffer_empty, AUDIO_DAC_BUFFER_SIZE); + } + + // no inverted/out-of-phase waveform (yet?), only pulling AUDIO_PIN_ALT to AUDIO_DAC_OFF_VALUE +#if defined(AUDIO_PIN_ALT_AS_NEGATIVE) + if (AUDIO_PIN_ALT == A4) { + dacPutChannelX(&DACD1, 0, AUDIO_DAC_OFF_VALUE); + } else if (AUDIO_PIN_ALT == A5) { + dacPutChannelX(&DACD2, 0, AUDIO_DAC_OFF_VALUE); + } +#endif + + gptStart(&GPTD6, &gpt6cfg1); +} + +void audio_driver_stop(void) { state = OUTPUT_SHOULD_STOP; } + +void audio_driver_start(void) { + gptStartContinuous(&GPTD6, 2U); + + for (uint8_t i = 0; i < AUDIO_MAX_SIMULTANEOUS_TONES; i++) { + dac_if[i] = 0.0f; + active_tones_snapshot[i] = 0.0f; + } + active_tones_snapshot_length = 0; + state = OUTPUT_SHOULD_START; +} diff --git a/quantum/audio/driver_chibios_dac_basic.c b/quantum/audio/driver_chibios_dac_basic.c new file mode 100644 index 0000000000..b8cec5ff1b --- /dev/null +++ b/quantum/audio/driver_chibios_dac_basic.c @@ -0,0 +1,245 @@ +/* Copyright 2016-2020 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "audio.h" +#include "ch.h" +#include "hal.h" + +/* + Audio Driver: DAC + + which utilizes both channels of the DAC unit many STM32 are equipped with to output a modulated square-wave, from precomputed samples stored in a buffer, which is passed to the hardware through DMA + + this driver can either be used to drive to separate speakers, wired to A4+Gnd and A5+Gnd, which allows two tones to be played simultaneously + OR + one speaker wired to A4+A5 with the AUDIO_PIN_ALT_AS_NEGATIVE define set - see docs/feature_audio + +*/ + +#if !defined(AUDIO_PIN) +# pragma message "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under 'ARM (DAC basic)' for available options." +// TODO: make this an 'error' instead; go through a breaking change, and add AUDIO_PIN A5 to all keyboards currently using AUDIO on STM32 based boards? - for now: set the define here +# define AUDIO_PIN A5 +#endif +// check configuration for ONE speaker, connected to both DAC pins +#if defined(AUDIO_PIN_ALT_AS_NEGATIVE) && !defined(AUDIO_PIN_ALT) +# error "Audio feature: AUDIO_PIN_ALT_AS_NEGATIVE set, but no pin configured as AUDIO_PIN_ALT" +#endif + +#ifndef AUDIO_PIN_ALT +// no ALT pin defined is valid, but the c-ifs below need some value set +# define AUDIO_PIN_ALT -1 +#endif + +#if !defined(AUDIO_STATE_TIMER) +# define AUDIO_STATE_TIMER GPTD8 +#endif + +// square-wave +static const dacsample_t dac_buffer_1[AUDIO_DAC_BUFFER_SIZE] = { + // First half is max, second half is 0 + [0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = AUDIO_DAC_SAMPLE_MAX, + [AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = 0, +}; + +// square-wave +static const dacsample_t dac_buffer_2[AUDIO_DAC_BUFFER_SIZE] = { + // opposite of dac_buffer above + [0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = 0, + [AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = AUDIO_DAC_SAMPLE_MAX, +}; + +GPTConfig gpt6cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE, + .callback = NULL, + .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ + .dier = 0U}; +GPTConfig gpt7cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE, + .callback = NULL, + .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ + .dier = 0U}; + +static void gpt_audio_state_cb(GPTDriver *gptp); +GPTConfig gptStateUpdateCfg = {.frequency = 10, + .callback = gpt_audio_state_cb, + .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ + .dier = 0U}; + +static const DACConfig dac_conf_ch1 = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT}; +static const DACConfig dac_conf_ch2 = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT}; + +/** + * @note The DAC_TRG(0) here selects the Timer 6 TRGO event, which is triggered + * on the rising edge after 3 APB1 clock cycles, causing our gpt6cfg1.frequency + * to be a third of what we expect. + * + * Here are all the values for DAC_TRG (TSEL in the ref manual) + * TIM15_TRGO 0b011 + * TIM2_TRGO 0b100 + * TIM3_TRGO 0b001 + * TIM6_TRGO 0b000 + * TIM7_TRGO 0b010 + * EXTI9 0b110 + * SWTRIG 0b111 + */ +static const DACConversionGroup dac_conv_grp_ch1 = {.num_channels = 1U, .trigger = DAC_TRG(0b000)}; +static const DACConversionGroup dac_conv_grp_ch2 = {.num_channels = 1U, .trigger = DAC_TRG(0b010)}; + +void channel_1_start(void) { + gptStart(&GPTD6, &gpt6cfg1); + gptStartContinuous(&GPTD6, 2U); + palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG); +} + +void channel_1_stop(void) { + gptStopTimer(&GPTD6); + palSetPadMode(GPIOA, 4, PAL_MODE_OUTPUT_PUSHPULL); + palSetPad(GPIOA, 4); +} + +static float channel_1_frequency = 0.0f; +void channel_1_set_frequency(float freq) { + channel_1_frequency = freq; + + channel_1_stop(); + if (freq <= 0.0) // a pause/rest has freq=0 + return; + + gpt6cfg1.frequency = 2 * freq * AUDIO_DAC_BUFFER_SIZE; + channel_1_start(); +} +float channel_1_get_frequency(void) { return channel_1_frequency; } + +void channel_2_start(void) { + gptStart(&GPTD7, &gpt7cfg1); + gptStartContinuous(&GPTD7, 2U); + palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG); +} + +void channel_2_stop(void) { + gptStopTimer(&GPTD7); + palSetPadMode(GPIOA, 5, PAL_MODE_OUTPUT_PUSHPULL); + palSetPad(GPIOA, 5); +} + +static float channel_2_frequency = 0.0f; +void channel_2_set_frequency(float freq) { + channel_2_frequency = freq; + + channel_2_stop(); + if (freq <= 0.0) // a pause/rest has freq=0 + return; + + gpt7cfg1.frequency = 2 * freq * AUDIO_DAC_BUFFER_SIZE; + channel_2_start(); +} +float channel_2_get_frequency(void) { return channel_2_frequency; } + +static void gpt_audio_state_cb(GPTDriver *gptp) { + if (audio_update_state()) { +#if defined(AUDIO_PIN_ALT_AS_NEGATIVE) + // one piezo/speaker connected to both audio pins, the generated square-waves are inverted + channel_1_set_frequency(audio_get_processed_frequency(0)); + channel_2_set_frequency(audio_get_processed_frequency(0)); + +#else // two separate audio outputs/speakers + // primary speaker on A4, optional secondary on A5 + if (AUDIO_PIN == A4) { + channel_1_set_frequency(audio_get_processed_frequency(0)); + if (AUDIO_PIN_ALT == A5) { + if (audio_get_number_of_active_tones() > 1) { + channel_2_set_frequency(audio_get_processed_frequency(1)); + } else { + channel_2_stop(); + } + } + } + + // primary speaker on A5, optional secondary on A4 + if (AUDIO_PIN == A5) { + channel_2_set_frequency(audio_get_processed_frequency(0)); + if (AUDIO_PIN_ALT == A4) { + if (audio_get_number_of_active_tones() > 1) { + channel_1_set_frequency(audio_get_processed_frequency(1)); + } else { + channel_1_stop(); + } + } + } +#endif + } +} + +void audio_driver_initialize() { + if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) { + palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG); + dacStart(&DACD1, &dac_conf_ch1); + + // initial setup of the dac-triggering timer is still required, even + // though it gets reconfigured and restarted later on + gptStart(&GPTD6, &gpt6cfg1); + } + + if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) { + palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG); + dacStart(&DACD2, &dac_conf_ch2); + + gptStart(&GPTD7, &gpt7cfg1); + } + + /* enable the output buffer, to directly drive external loads with no additional circuitry + * + * see: AN4566 Application note: Extending the DAC performance of STM32 microcontrollers + * Note: Buffer-Off bit -> has to be set 0 to enable the output buffer + * Note: enabling the output buffer imparts an additional dc-offset of a couple mV + * + * this is done here, reaching directly into the stm32 registers since chibios has not implemented BOFF handling yet + * (see: chibios/os/hal/ports/STM32/todo.txt '- BOFF handling in DACv1.' + */ + DACD1.params->dac->CR &= ~DAC_CR_BOFF1; + DACD2.params->dac->CR &= ~DAC_CR_BOFF2; + + // start state-updater + gptStart(&AUDIO_STATE_TIMER, &gptStateUpdateCfg); +} + +void audio_driver_stop(void) { + if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) { + gptStopTimer(&GPTD6); + + // stop the ongoing conversion and put the output in a known state + dacStopConversion(&DACD1); + dacPutChannelX(&DACD1, 0, AUDIO_DAC_OFF_VALUE); + } + + if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) { + gptStopTimer(&GPTD7); + + dacStopConversion(&DACD2); + dacPutChannelX(&DACD2, 0, AUDIO_DAC_OFF_VALUE); + } + gptStopTimer(&AUDIO_STATE_TIMER); +} + +void audio_driver_start(void) { + if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) { + dacStartConversion(&DACD1, &dac_conv_grp_ch1, (dacsample_t *)dac_buffer_1, AUDIO_DAC_BUFFER_SIZE); + } + if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) { + dacStartConversion(&DACD2, &dac_conv_grp_ch2, (dacsample_t *)dac_buffer_2, AUDIO_DAC_BUFFER_SIZE); + } + gptStartContinuous(&AUDIO_STATE_TIMER, 2U); +} diff --git a/quantum/audio/driver_chibios_pwm.h b/quantum/audio/driver_chibios_pwm.h new file mode 100644 index 0000000000..86cab916e1 --- /dev/null +++ b/quantum/audio/driver_chibios_pwm.h @@ -0,0 +1,40 @@ +/* Copyright 2020 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#if !defined(AUDIO_PWM_DRIVER) +// NOTE: Timer2 seems to be used otherwise in QMK, otherwise we could default to A5 (= TIM2_CH1, with PWMD2 and alternate-function(1)) +# define AUDIO_PWM_DRIVER PWMD1 +#endif + +#if !defined(AUDIO_PWM_CHANNEL) +// NOTE: sticking to the STM data-sheet numbering: TIMxCH1 to TIMxCH4 +// default: STM32F303CC PA8+TIM1_CH1 -> 1 +# define AUDIO_PWM_CHANNEL 1 +#endif + +#if !defined(AUDIO_PWM_PAL_MODE) +// pin-alternate function: see the data-sheet for which pin needs what AF to connect to TIMx_CHy +// default: STM32F303CC PA8+TIM1_CH1 -> 6 +# define AUDIO_PWM_PAL_MODE 6 +#endif + +#if !defined(AUDIO_STATE_TIMER) +// timer used to trigger updates in the audio-system, configured/enabled in chibios mcuconf. +// Tim6 is the default for "larger" STMs, smaller ones might not have this one (enabled) and need to switch to a different one (e.g.: STM32F103 has only Tim1-Tim4) +# define AUDIO_STATE_TIMER GPTD6 +#endif diff --git a/quantum/audio/driver_chibios_pwm_hardware.c b/quantum/audio/driver_chibios_pwm_hardware.c new file mode 100644 index 0000000000..3c7d89b290 --- /dev/null +++ b/quantum/audio/driver_chibios_pwm_hardware.c @@ -0,0 +1,144 @@ +/* Copyright 2020 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* +Audio Driver: PWM + +the duty-cycle is always kept at 50%, and the pwm-period is adjusted to match the frequency of a note to be played back. + +this driver uses the chibios-PWM system to produce a square-wave on specific output pins that are connected to the PWM hardware. +The hardware directly toggles the pin via its alternate function. see your MCUs data-sheet for which pin can be driven by what timer - looking for TIMx_CHy and the corresponding alternate function. + + */ + +#include "audio.h" +#include "ch.h" +#include "hal.h" + +#if !defined(AUDIO_PIN) +# error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings" +#endif + +extern bool playing_note; +extern bool playing_melody; +extern uint8_t note_timbre; + +static PWMConfig pwmCFG = { + .frequency = 100000, /* PWM clock frequency */ + // CHIBIOS-BUG? can't set the initial period to <2, or the pwm (hard or software) takes ~130ms with .frequency=500000 for a pwmChangePeriod to take effect; with no output=silence in the meantime + .period = 2, /* initial PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */ + .callback = NULL, /* no callback, the hardware directly toggles the pin */ + .channels = + { +#if AUDIO_PWM_CHANNEL == 4 + {PWM_OUTPUT_DISABLED, NULL}, /* channel 0 -> TIMx_CH1 */ + {PWM_OUTPUT_DISABLED, NULL}, /* channel 1 -> TIMx_CH2 */ + {PWM_OUTPUT_DISABLED, NULL}, /* channel 2 -> TIMx_CH3 */ + {PWM_OUTPUT_ACTIVE_HIGH, NULL} /* channel 3 -> TIMx_CH4 */ +#elif AUDIO_PWM_CHANNEL == 3 + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH3 */ + {PWM_OUTPUT_DISABLED, NULL} +#elif AUDIO_PWM_CHANNEL == 2 + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH2 */ + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL} +#else /*fallback to CH1 */ + {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH1 */ + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL} +#endif + }, +}; + +static float channel_1_frequency = 0.0f; +void channel_1_set_frequency(float freq) { + channel_1_frequency = freq; + + if (freq <= 0.0) // a pause/rest has freq=0 + return; + + pwmcnt_t period = (pwmCFG.frequency / freq); + pwmChangePeriod(&AUDIO_PWM_DRIVER, period); + pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1, + // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH + PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100)); +} + +float channel_1_get_frequency(void) { return channel_1_frequency; } + +void channel_1_start(void) { + pwmStop(&AUDIO_PWM_DRIVER); + pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); +} + +void channel_1_stop(void) { pwmStop(&AUDIO_PWM_DRIVER); } + +static void gpt_callback(GPTDriver *gptp); +GPTConfig gptCFG = { + /* a whole note is one beat, which is - per definition in musical_notes.h - set to 64 + the longest note is BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4 + the tempo (which might vary!) is in bpm (beats per minute) + therefore: if the timer ticks away at .frequency = (60*64)Hz, + and the .interval counts from 64 downwards - audio_update_state is + called just often enough to not miss any notes + */ + .frequency = 60 * 64, + .callback = gpt_callback, +}; + +void audio_driver_initialize(void) { + pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); + + // connect the AUDIO_PIN to the PWM hardware +#if defined(USE_GPIOV1) // STM32F103C8 + palSetLineMode(AUDIO_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL); +#else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command) + palSetLineMode(AUDIO_PIN, PAL_STM32_MODE_ALTERNATE | PAL_STM32_ALTERNATE(AUDIO_PWM_PAL_MODE)); +#endif + + gptStart(&AUDIO_STATE_TIMER, &gptCFG); +} + +void audio_driver_start(void) { + channel_1_stop(); + channel_1_start(); + + if (playing_note || playing_melody) { + gptStartContinuous(&AUDIO_STATE_TIMER, 64); + } +} + +void audio_driver_stop(void) { + channel_1_stop(); + gptStopTimer(&AUDIO_STATE_TIMER); +} + +/* a regular timer task, that checks the note to be currently played + * and updates the pwm to output that frequency + */ +static void gpt_callback(GPTDriver *gptp) { + float freq; // TODO: freq_alt + + if (audio_update_state()) { + freq = audio_get_processed_frequency(0); // freq_alt would be index=1 + channel_1_set_frequency(freq); + } +} diff --git a/quantum/audio/driver_chibios_pwm_software.c b/quantum/audio/driver_chibios_pwm_software.c new file mode 100644 index 0000000000..15c3e98b6a --- /dev/null +++ b/quantum/audio/driver_chibios_pwm_software.c @@ -0,0 +1,164 @@ +/* Copyright 2020 Jack Humbert + * Copyright 2020 JohSchneider + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* +Audio Driver: PWM + +the duty-cycle is always kept at 50%, and the pwm-period is adjusted to match the frequency of a note to be played back. + +this driver uses the chibios-PWM system to produce a square-wave on any given output pin in software +- a pwm callback is used to set/clear the configured pin. + + */ +#include "audio.h" +#include "ch.h" +#include "hal.h" + +#if !defined(AUDIO_PIN) +# error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings" +#endif +extern bool playing_note; +extern bool playing_melody; +extern uint8_t note_timbre; + +static void pwm_audio_period_callback(PWMDriver *pwmp); +static void pwm_audio_channel_interrupt_callback(PWMDriver *pwmp); + +static PWMConfig pwmCFG = { + .frequency = 100000, /* PWM clock frequency */ + // CHIBIOS-BUG? can't set the initial period to <2, or the pwm (hard or software) takes ~130ms with .frequency=500000 for a pwmChangePeriod to take effect; with no output=silence in the meantime + .period = 2, /* initial PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */ + .callback = pwm_audio_period_callback, + .channels = + { + // software-PWM just needs another callback on any channel + {PWM_OUTPUT_ACTIVE_HIGH, pwm_audio_channel_interrupt_callback}, /* channel 0 -> TIMx_CH1 */ + {PWM_OUTPUT_DISABLED, NULL}, /* channel 1 -> TIMx_CH2 */ + {PWM_OUTPUT_DISABLED, NULL}, /* channel 2 -> TIMx_CH3 */ + {PWM_OUTPUT_DISABLED, NULL} /* channel 3 -> TIMx_CH4 */ + }, +}; + +static float channel_1_frequency = 0.0f; +void channel_1_set_frequency(float freq) { + channel_1_frequency = freq; + + if (freq <= 0.0) // a pause/rest has freq=0 + return; + + pwmcnt_t period = (pwmCFG.frequency / freq); + pwmChangePeriod(&AUDIO_PWM_DRIVER, period); + + pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1, + // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH + PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100)); +} + +float channel_1_get_frequency(void) { return channel_1_frequency; } + +void channel_1_start(void) { + pwmStop(&AUDIO_PWM_DRIVER); + pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); + + pwmEnablePeriodicNotification(&AUDIO_PWM_DRIVER); + pwmEnableChannelNotification(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1); +} + +void channel_1_stop(void) { + pwmStop(&AUDIO_PWM_DRIVER); + + palClearLine(AUDIO_PIN); // leave the line low, after last note was played + +#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE) + palClearLine(AUDIO_PIN_ALT); // leave the line low, after last note was played +#endif +} + +// generate a PWM signal on any pin, not necessarily the one connected to the timer +static void pwm_audio_period_callback(PWMDriver *pwmp) { + (void)pwmp; + palClearLine(AUDIO_PIN); + +#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE) + palSetLine(AUDIO_PIN_ALT); +#endif +} +static void pwm_audio_channel_interrupt_callback(PWMDriver *pwmp) { + (void)pwmp; + if (channel_1_frequency > 0) { + palSetLine(AUDIO_PIN); // generate a PWM signal on any pin, not necessarily the one connected to the timer +#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE) + palClearLine(AUDIO_PIN_ALT); +#endif + } +} + +static void gpt_callback(GPTDriver *gptp); +GPTConfig gptCFG = { + /* a whole note is one beat, which is - per definition in musical_notes.h - set to 64 + the longest note is BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4 + the tempo (which might vary!) is in bpm (beats per minute) + therefore: if the timer ticks away at .frequency = (60*64)Hz, + and the .interval counts from 64 downwards - audio_update_state is + called just often enough to not miss anything + */ + .frequency = 60 * 64, + .callback = gpt_callback, +}; + +void audio_driver_initialize(void) { + pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); + + palSetLineMode(AUDIO_PIN, PAL_MODE_OUTPUT_PUSHPULL); + palClearLine(AUDIO_PIN); + +#if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE) + palSetLineMode(AUDIO_PIN_ALT, PAL_MODE_OUTPUT_PUSHPULL); + palClearLine(AUDIO_PIN_ALT); +#endif + + pwmEnablePeriodicNotification(&AUDIO_PWM_DRIVER); // enable pwm callbacks + pwmEnableChannelNotification(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1); + + gptStart(&AUDIO_STATE_TIMER, &gptCFG); +} + +void audio_driver_start(void) { + channel_1_stop(); + channel_1_start(); + + if (playing_note || playing_melody) { + gptStartContinuous(&AUDIO_STATE_TIMER, 64); + } +} + +void audio_driver_stop(void) { + channel_1_stop(); + gptStopTimer(&AUDIO_STATE_TIMER); +} + +/* a regular timer task, that checks the note to be currently played + * and updates the pwm to output that frequency + */ +static void gpt_callback(GPTDriver *gptp) { + float freq; // TODO: freq_alt + + if (audio_update_state()) { + freq = audio_get_processed_frequency(0); // freq_alt would be index=1 + channel_1_set_frequency(freq); + } +} diff --git a/quantum/audio/musical_notes.h b/quantum/audio/musical_notes.h index 8ac6aafd38..ddd7d374f5 100644 --- a/quantum/audio/musical_notes.h +++ b/quantum/audio/musical_notes.h @@ -1,4 +1,5 @@ /* Copyright 2016 Jack Humbert + * Copyright 2020 JohSchneider * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -13,11 +14,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - #pragma once -// Tempo Placeholder -#define TEMPO_DEFAULT 100 +#ifndef TEMPO_DEFAULT +# define TEMPO_DEFAULT 120 +// in beats-per-minute +#endif #define SONG(notes...) \ { notes } @@ -25,12 +27,14 @@ // Note Types #define MUSICAL_NOTE(note, duration) \ { (NOTE##note), duration } + #define BREVE_NOTE(note) MUSICAL_NOTE(note, 128) #define WHOLE_NOTE(note) MUSICAL_NOTE(note, 64) #define HALF_NOTE(note) MUSICAL_NOTE(note, 32) #define QUARTER_NOTE(note) MUSICAL_NOTE(note, 16) #define EIGHTH_NOTE(note) MUSICAL_NOTE(note, 8) #define SIXTEENTH_NOTE(note) MUSICAL_NOTE(note, 4) +#define THIRTYSECOND_NOTE(note) MUSICAL_NOTE(note, 2) #define BREVE_DOT_NOTE(note) MUSICAL_NOTE(note, 128 + 64) #define WHOLE_DOT_NOTE(note) MUSICAL_NOTE(note, 64 + 32) @@ -38,6 +42,9 @@ #define QUARTER_DOT_NOTE(note) MUSICAL_NOTE(note, 16 + 8) #define EIGHTH_DOT_NOTE(note) MUSICAL_NOTE(note, 8 + 4) #define SIXTEENTH_DOT_NOTE(note) MUSICAL_NOTE(note, 4 + 2) +#define THIRTYSECOND_DOT_NOTE(note) MUSICAL_NOTE(note, 2 + 1) +// duration of 64 units == one beat == one whole note +// with a tempo of 60bpm this comes to a length of one second // Note Type Shortcuts #define M__NOTE(note, duration) MUSICAL_NOTE(note, duration) @@ -47,55 +54,52 @@ #define Q__NOTE(n) QUARTER_NOTE(n) #define E__NOTE(n) EIGHTH_NOTE(n) #define S__NOTE(n) SIXTEENTH_NOTE(n) +#define T__NOTE(n) THIRTYSECOND_NOTE(n) #define BD_NOTE(n) BREVE_DOT_NOTE(n) #define WD_NOTE(n) WHOLE_DOT_NOTE(n) #define HD_NOTE(n) HALF_DOT_NOTE(n) #define QD_NOTE(n) QUARTER_DOT_NOTE(n) #define ED_NOTE(n) EIGHTH_DOT_NOTE(n) #define SD_NOTE(n) SIXTEENTH_DOT_NOTE(n) +#define TD_NOTE(n) THIRTYSECOND_DOT_NOTE(n) // Note Timbre // Changes how the notes sound -#define TIMBRE_12 0.125f -#define TIMBRE_25 0.250f -#define TIMBRE_50 0.500f -#define TIMBRE_75 0.750f -#define TIMBRE_DEFAULT TIMBRE_50 +#define TIMBRE_12 12 +#define TIMBRE_25 25 +#define TIMBRE_50 50 +#define TIMBRE_75 75 +#ifndef TIMBRE_DEFAULT +# define TIMBRE_DEFAULT TIMBRE_50 +#endif // Notes - # = Octave -#ifdef __arm__ -# define NOTE_REST 1.00f -#else -# define NOTE_REST 0.00f -#endif - -/* These notes are currently bugged -#define NOTE_C0 16.35f -#define NOTE_CS0 17.32f -#define NOTE_D0 18.35f -#define NOTE_DS0 19.45f -#define NOTE_E0 20.60f -#define NOTE_F0 21.83f -#define NOTE_FS0 23.12f -#define NOTE_G0 24.50f -#define NOTE_GS0 25.96f -#define NOTE_A0 27.50f -#define NOTE_AS0 29.14f -#define NOTE_B0 30.87f -#define NOTE_C1 32.70f -#define NOTE_CS1 34.65f -#define NOTE_D1 36.71f -#define NOTE_DS1 38.89f -#define NOTE_E1 41.20f -#define NOTE_F1 43.65f -#define NOTE_FS1 46.25f -#define NOTE_G1 49.00f -#define NOTE_GS1 51.91f -#define NOTE_A1 55.00f -#define NOTE_AS1 58.27f -*/ +#define NOTE_REST 0.00f +#define NOTE_C0 16.35f +#define NOTE_CS0 17.32f +#define NOTE_D0 18.35f +#define NOTE_DS0 19.45f +#define NOTE_E0 20.60f +#define NOTE_F0 21.83f +#define NOTE_FS0 23.12f +#define NOTE_G0 24.50f +#define NOTE_GS0 25.96f +#define NOTE_A0 27.50f +#define NOTE_AS0 29.14f +#define NOTE_B0 30.87f +#define NOTE_C1 32.70f +#define NOTE_CS1 34.65f +#define NOTE_D1 36.71f +#define NOTE_DS1 38.89f +#define NOTE_E1 41.20f +#define NOTE_F1 43.65f +#define NOTE_FS1 46.25f +#define NOTE_G1 49.00f +#define NOTE_GS1 51.91f +#define NOTE_A1 55.00f +#define NOTE_AS1 58.27f #define NOTE_B1 61.74f #define NOTE_C2 65.41f #define NOTE_CS2 69.30f diff --git a/quantum/audio/voices.c b/quantum/audio/voices.c index 1592618be4..8988d827e9 100644 --- a/quantum/audio/voices.c +++ b/quantum/audio/voices.c @@ -1,4 +1,5 @@ /* Copyright 2016 Jack Humbert + * Copyright 2020 JohSchneider * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,13 +18,19 @@ #include "audio.h" #include <stdlib.h> -// these are imported from audio.c -extern uint16_t envelope_index; -extern float note_timbre; -extern float polyphony_rate; -extern bool glissando; +uint8_t note_timbre = TIMBRE_DEFAULT; +bool glissando = false; +bool vibrato = false; +float vibrato_strength = 0.5; +float vibrato_rate = 0.125; +uint16_t voices_timer = 0; + +#ifdef AUDIO_VOICE_DEFAULT +voice_type voice = AUDIO_VOICE_DEFAULT; +#else voice_type voice = default_voice; +#endif void set_voice(voice_type v) { voice = v; } @@ -31,22 +38,54 @@ void voice_iterate() { voice = (voice + 1) % number_of_voices; } void voice_deiterate() { voice = (voice - 1 + number_of_voices) % number_of_voices; } +#ifdef AUDIO_VOICES +float mod(float a, int b) { + float r = fmod(a, b); + return r < 0 ? r + b : r; +} + +// Effect: 'vibrate' a given target frequency slightly above/below its initial value +float voice_add_vibrato(float average_freq) { + float vibrato_counter = mod(timer_read() / (100 * vibrato_rate), VIBRATO_LUT_LENGTH); + + return average_freq * pow(vibrato_lut[(int)vibrato_counter], vibrato_strength); +} + +// Effect: 'slides' the 'frequency' from the starting-point, to the target frequency +float voice_add_glissando(float from_freq, float to_freq) { + if (to_freq != 0 && from_freq < to_freq && from_freq < to_freq * pow(2, -440 / to_freq / 12 / 2)) { + return from_freq * pow(2, 440 / from_freq / 12 / 2); + } else if (to_freq != 0 && from_freq > to_freq && from_freq > to_freq * pow(2, 440 / to_freq / 12 / 2)) { + return from_freq * pow(2, -440 / from_freq / 12 / 2); + } else { + return to_freq; + } +} +#endif + float voice_envelope(float frequency) { // envelope_index ranges from 0 to 0xFFFF, which is preserved at 880.0 Hz - __attribute__((unused)) uint16_t compensated_index = (uint16_t)((float)envelope_index * (880.0 / frequency)); +// __attribute__((unused)) uint16_t compensated_index = (uint16_t)((float)envelope_index * (880.0 / frequency)); +#ifdef AUDIO_VOICES + uint16_t envelope_index = timer_elapsed(voices_timer); // TODO: multiply in some factor? + uint16_t compensated_index = envelope_index / 100; // TODO: correct factor would be? +#endif switch (voice) { case default_voice: - glissando = false; - note_timbre = TIMBRE_50; - polyphony_rate = 0; + glissando = false; + // note_timbre = TIMBRE_50; //Note: leave the user the possibility to adjust the timbre with 'audio_set_timbre' break; #ifdef AUDIO_VOICES + case vibrating: + glissando = false; + vibrato = true; + break; + case something: - glissando = false; - polyphony_rate = 0; + glissando = false; switch (compensated_index) { case 0 ... 9: note_timbre = TIMBRE_12; @@ -57,24 +96,23 @@ float voice_envelope(float frequency) { break; case 20 ... 200: - note_timbre = .125 + .125; + note_timbre = 12 + 12; break; default: - note_timbre = .125; + note_timbre = 12; break; } break; case drums: - glissando = false; - polyphony_rate = 0; + glissando = false; // switch (compensated_index) { // case 0 ... 10: - // note_timbre = 0.5; + // note_timbre = 50; // break; // case 11 ... 20: - // note_timbre = 0.5 * (21 - compensated_index) / 10; + // note_timbre = 50 * (21 - compensated_index) / 10; // break; // default: // note_timbre = 0; @@ -88,10 +126,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(40)) + 60; switch (envelope_index) { case 0 ... 10: - note_timbre = 0.5; + note_timbre = 50; break; case 11 ... 20: - note_timbre = 0.5 * (21 - envelope_index) / 10; + note_timbre = 50 * (21 - envelope_index) / 10; break; default: note_timbre = 0; @@ -103,10 +141,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(1000)) + 1000; switch (envelope_index) { case 0 ... 5: - note_timbre = 0.5; + note_timbre = 50; break; case 6 ... 20: - note_timbre = 0.5 * (21 - envelope_index) / 15; + note_timbre = 50 * (21 - envelope_index) / 15; break; default: note_timbre = 0; @@ -118,10 +156,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(2000)) + 3000; switch (envelope_index) { case 0 ... 15: - note_timbre = 0.5; + note_timbre = 50; break; case 16 ... 20: - note_timbre = 0.5 * (21 - envelope_index) / 5; + note_timbre = 50 * (21 - envelope_index) / 5; break; default: note_timbre = 0; @@ -133,10 +171,10 @@ float voice_envelope(float frequency) { frequency = (rand() % (int)(2000)) + 3000; switch (envelope_index) { case 0 ... 35: - note_timbre = 0.5; + note_timbre = 50; break; case 36 ... 50: - note_timbre = 0.5 * (51 - envelope_index) / 15; + note_timbre = 50 * (51 - envelope_index) / 15; break; default: note_timbre = 0; @@ -145,8 +183,7 @@ float voice_envelope(float frequency) { } break; case butts_fader: - glissando = true; - polyphony_rate = 0; + glissando = true; switch (compensated_index) { case 0 ... 9: frequency = frequency / 4; @@ -159,7 +196,7 @@ float voice_envelope(float frequency) { break; case 20 ... 200: - note_timbre = .125 - pow(((float)compensated_index - 20) / (200 - 20), 2) * .125; + note_timbre = 12 - (uint8_t)(pow(((float)compensated_index - 20) / (200 - 20), 2) * 12.5); break; default: @@ -169,7 +206,6 @@ float voice_envelope(float frequency) { break; // case octave_crunch: - // polyphony_rate = 0; // switch (compensated_index) { // case 0 ... 9: // case 20 ... 24: @@ -187,14 +223,13 @@ float voice_envelope(float frequency) { // default: // note_timbre = TIMBRE_12; - // break; + // break; // } // break; case duty_osc: // This slows the loop down a substantial amount, so higher notes may freeze - glissando = true; - polyphony_rate = 0; + glissando = true; switch (compensated_index) { default: # define OCS_SPEED 10 @@ -202,38 +237,36 @@ float voice_envelope(float frequency) { // sine wave is slow // note_timbre = (sin((float)compensated_index/10000*OCS_SPEED) * OCS_AMP / 2) + .5; // triangle wave is a bit faster - note_timbre = (float)abs((compensated_index * OCS_SPEED % 3000) - 1500) * (OCS_AMP / 1500) + (1 - OCS_AMP) / 2; + note_timbre = (uint8_t)abs((compensated_index * OCS_SPEED % 3000) - 1500) * (OCS_AMP / 1500) + (1 - OCS_AMP) / 2; break; } break; case duty_octave_down: - glissando = true; - polyphony_rate = 0; - note_timbre = (envelope_index % 2) * .125 + .375 * 2; - if ((envelope_index % 4) == 0) note_timbre = 0.5; + glissando = true; + note_timbre = (uint8_t)(100 * (envelope_index % 2) * .125 + .375 * 2); + if ((envelope_index % 4) == 0) note_timbre = 50; if ((envelope_index % 8) == 0) note_timbre = 0; break; case delayed_vibrato: - glissando = true; - polyphony_rate = 0; - note_timbre = TIMBRE_50; + glissando = true; + note_timbre = TIMBRE_50; # define VOICE_VIBRATO_DELAY 150 # define VOICE_VIBRATO_SPEED 50 switch (compensated_index) { case 0 ... VOICE_VIBRATO_DELAY: break; default: + // TODO: merge/replace with voice_add_vibrato above frequency = frequency * vibrato_lut[(int)fmod((((float)compensated_index - (VOICE_VIBRATO_DELAY + 1)) / 1000 * VOICE_VIBRATO_SPEED), VIBRATO_LUT_LENGTH)]; break; } break; // case delayed_vibrato_octave: - // polyphony_rate = 0; // if ((envelope_index % 2) == 1) { - // note_timbre = 0.55; + // note_timbre = 55; // } else { - // note_timbre = 0.45; + // note_timbre = 45; // } // #define VOICE_VIBRATO_DELAY 150 // #define VOICE_VIBRATO_SPEED 50 @@ -246,35 +279,64 @@ float voice_envelope(float frequency) { // } // break; // case duty_fifth_down: - // note_timbre = 0.5; + // note_timbre = TIMBRE_50; // if ((envelope_index % 3) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // break; // case duty_fourth_down: - // note_timbre = 0.0; + // note_timbre = 0; // if ((envelope_index % 12) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // if (((envelope_index % 12) % 4) != 1) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // break; // case duty_third_down: - // note_timbre = 0.5; + // note_timbre = TIMBRE_50; // if ((envelope_index % 5) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // break; // case duty_fifth_third_down: - // note_timbre = 0.5; + // note_timbre = TIMBRE_50; // if ((envelope_index % 5) == 0) - // note_timbre = 0.75; + // note_timbre = TIMBRE_75; // if ((envelope_index % 3) == 0) - // note_timbre = 0.25; + // note_timbre = TIMBRE_25; // break; -#endif +#endif // AUDIO_VOICES default: break; } +#ifdef AUDIO_VOICES + if (vibrato && (vibrato_strength > 0)) { + frequency = voice_add_vibrato(frequency); + } + + if (glissando) { + // TODO: where to keep track of the start-frequency? + // frequency = voice_add_glissando(??, frequency); + } +#endif // AUDIO_VOICES + return frequency; } + +// Vibrato functions + +void voice_set_vibrato_rate(float rate) { vibrato_rate = rate; } +void voice_increase_vibrato_rate(float change) { vibrato_rate *= change; } +void voice_decrease_vibrato_rate(float change) { vibrato_rate /= change; } +void voice_set_vibrato_strength(float strength) { vibrato_strength = strength; } +void voice_increase_vibrato_strength(float change) { vibrato_strength *= change; } +void voice_decrease_vibrato_strength(float change) { vibrato_strength /= change; } + +// Timbre functions + +void voice_set_timbre(uint8_t timbre) { + if ((timbre > 0) && (timbre < 100)) { + note_timbre = timbre; + } +} +uint8_t voice_get_timbre(void) { return note_timbre; } diff --git a/quantum/audio/voices.h b/quantum/audio/voices.h index abafa2b404..578350d337 100644 --- a/quantum/audio/voices.h +++ b/quantum/audio/voices.h @@ -1,4 +1,5 @@ /* Copyright 2016 Jack Humbert + * Copyright 2020 JohSchneider * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -13,7 +14,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - #pragma once #include <stdint.h> @@ -29,6 +29,7 @@ float voice_envelope(float frequency); typedef enum { default_voice, #ifdef AUDIO_VOICES + vibrating, something, drums, butts_fader, @@ -48,3 +49,21 @@ typedef enum { void set_voice(voice_type v); void voice_iterate(void); void voice_deiterate(void); + +// Vibrato functions +void voice_set_vibrato_rate(float rate); +void voice_increase_vibrato_rate(float change); +void voice_decrease_vibrato_rate(float change); +void voice_set_vibrato_strength(float strength); +void voice_increase_vibrato_strength(float change); +void voice_decrease_vibrato_strength(float change); + +// Timbre functions +/** + * @brief set the global timbre for tones to be played + * @note: only applies to pwm implementations - where it adjusts the duty-cycle + * @note: using any instrument from voices.[ch] other than 'default' may override the set value + * @param[in]: timbre: valid range is (0,100) + */ +void voice_set_timbre(uint8_t timbre); +uint8_t voice_get_timbre(void); diff --git a/quantum/audio/wave.h b/quantum/audio/wave.h deleted file mode 100644 index 48210a944e..0000000000 --- a/quantum/audio/wave.h +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2016 Jack Humbert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <avr/io.h> -#include <avr/interrupt.h> -#include <avr/pgmspace.h> - -#define SINE_LENGTH 2048 - -const uint8_t sinewave[] PROGMEM = // 2048 values - {0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x83, 0x83, 0x83, 0x84, 0x84, 0x85, 0x85, 0x85, 0x86, 0x86, 0x87, 0x87, 0x87, 0x88, 0x88, 0x88, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8c, 0x8c, 0x8c, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x90, 0x90, 0x91, 0x91, 0x91, 0x92, 0x92, 0x93, 0x93, 0x93, 0x94, 0x94, 0x95, 0x95, 0x95, 0x96, 0x96, 0x96, 0x97, 0x97, 0x98, 0x98, 0x98, 0x99, 0x99, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9f, 0x9f, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6, 0xa7, 0xa7, 0xa7, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xaa, 0xaa, 0xaa, 0xab, 0xab, 0xac, 0xac, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb4, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xbb, - 0xbb, 0xbb, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc8, 0xc8, 0xc8, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, - 0xe9, 0xe9, 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xea, 0xea, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, - 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, - 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xed, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, - 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9d, - 0x9d, 0x9d, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x98, 0x98, 0x98, 0x97, 0x97, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x94, 0x94, 0x93, 0x93, 0x93, 0x92, 0x92, 0x91, 0x91, 0x91, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x86, 0x86, 0x85, 0x85, 0x85, 0x84, 0x84, 0x83, 0x83, 0x83, 0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, 0x7c, 0x7c, 0x7c, 0x7b, 0x7b, 0x7a, 0x7a, 0x7a, 0x79, 0x79, 0x78, 0x78, 0x78, 0x77, 0x77, 0x77, 0x76, 0x76, 0x75, 0x75, 0x75, 0x74, 0x74, 0x73, 0x73, 0x73, 0x72, 0x72, 0x71, 0x71, 0x71, 0x70, 0x70, 0x70, 0x6f, 0x6f, 0x6e, 0x6e, 0x6e, 0x6d, 0x6d, 0x6c, 0x6c, 0x6c, 0x6b, 0x6b, 0x6a, 0x6a, 0x6a, 0x69, 0x69, 0x69, 0x68, 0x68, 0x67, 0x67, 0x67, 0x66, 0x66, 0x65, 0x65, 0x65, 0x64, 0x64, 0x64, 0x63, 0x63, 0x62, 0x62, 0x62, 0x61, 0x61, 0x61, 0x60, - 0x60, 0x5f, 0x5f, 0x5f, 0x5e, 0x5e, 0x5d, 0x5d, 0x5d, 0x5c, 0x5c, 0x5c, 0x5b, 0x5b, 0x5a, 0x5a, 0x5a, 0x59, 0x59, 0x59, 0x58, 0x58, 0x58, 0x57, 0x57, 0x56, 0x56, 0x56, 0x55, 0x55, 0x55, 0x54, 0x54, 0x53, 0x53, 0x53, 0x52, 0x52, 0x52, 0x51, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, 0x4f, 0x4e, 0x4e, 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, 0x4b, 0x4a, 0x4a, 0x4a, 0x49, 0x49, 0x49, 0x48, 0x48, 0x48, 0x47, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, 0x44, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2e, 0x2e, 0x2e, 0x2d, 0x2d, 0x2d, 0x2d, 0x2c, 0x2c, 0x2c, 0x2b, 0x2b, 0x2b, 0x2a, 0x2a, - 0x2a, 0x2a, 0x29, 0x29, 0x29, 0x28, 0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26, 0x26, 0x26, 0x25, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, 0x22, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21, 0x21, 0x20, 0x20, 0x20, 0x1f, 0x1f, 0x1f, 0x1f, 0x1e, 0x1e, 0x1e, 0x1e, 0x1d, 0x1d, 0x1d, 0x1d, 0x1c, 0x1c, 0x1c, 0x1c, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1a, 0x1a, 0x1a, 0x1a, 0x19, 0x19, 0x19, 0x19, 0x18, 0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x17, 0x17, 0x16, 0x16, 0x16, 0x16, 0x15, 0x15, 0x15, 0x15, 0x15, 0x14, 0x14, 0x14, 0x14, 0x14, 0x13, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, 0x12, 0x12, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x10, 0x10, 0x10, 0x10, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xe, 0xe, 0xe, 0xe, 0xe, 0xd, 0xd, 0xd, 0xd, 0xd, 0xd, 0xc, 0xc, 0xc, 0xc, 0xc, 0xc, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x8, 0x8, 0x8, 0x8, 0x8, - 0x8, 0x8, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, - 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xc, 0xc, 0xc, 0xc, 0xc, 0xc, 0xd, 0xd, 0xd, 0xd, 0xd, 0xd, 0xe, 0xe, 0xe, 0xe, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, - 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x46, - 0x46, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, 0x4f, 0x50, 0x50, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x54, 0x54, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68, 0x68, 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f}; diff --git a/quantum/backlight/backlight_avr.c b/quantum/backlight/backlight_avr.c index 4d66da80ba..e47192de34 100644 --- a/quantum/backlight/backlight_avr.c +++ b/quantum/backlight/backlight_avr.c @@ -68,7 +68,7 @@ # define COMxx1 COM3A1 # define OCRxx OCR3A # endif -#elif (defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__)) && (BACKLIGHT_PIN == B7 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6) +#elif (defined(__AVR_AT90USB162__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__)) && (BACKLIGHT_PIN == B7 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6) # define HARDWARE_PWM # define ICRx ICR1 # define TCCRxA TCCR1A @@ -126,7 +126,7 @@ # define COMxx1 COM1B1 # define OCRxx OCR1B # endif -#elif !defined(B5_AUDIO) && !defined(B6_AUDIO) && !defined(B7_AUDIO) +#elif (AUDIO_PIN != B5) && (AUDIO_PIN != B6) && (AUDIO_PIN != B7) && (AUDIO_PIN_ALT != B5) && (AUDIO_PIN_ALT != B6) && (AUDIO_PIN_ALT != B7) // Timer 1 is not in use by Audio feature, Backlight can use it # pragma message "Using hardware timer 1 with software PWM" # define HARDWARE_PWM @@ -145,7 +145,7 @@ # define OCIExA OCIE1A # define OCRxx OCR1A -#elif !defined(C6_AUDIO) && !defined(C5_AUDIO) && !defined(C4_AUDIO) +#elif (AUDIO_PIN != C4) && (AUDIO_PIN != C5) && (AUDIO_PIN != C6) # pragma message "Using hardware timer 3 with software PWM" // Timer 3 is not in use by Audio feature, Backlight can use it # define HARDWARE_PWM diff --git a/quantum/command.c b/quantum/command.c index 59aa4e4d34..34c4b36b1c 100644 --- a/quantum/command.c +++ b/quantum/command.c @@ -550,22 +550,22 @@ static void mousekey_param_print(void) { # if !defined(NO_PRINT) && !defined(USER_PRINT) print("\n\t- Values -\n"); print("1: delay(*10ms): "); - pdec(mk_delay); + print_dec(mk_delay); print("\n"); print("2: interval(ms): "); - pdec(mk_interval); + print_dec(mk_interval); print("\n"); print("3: max_speed: "); - pdec(mk_max_speed); + print_dec(mk_max_speed); print("\n"); print("4: time_to_max: "); - pdec(mk_time_to_max); + print_dec(mk_time_to_max); print("\n"); print("5: wheel_max_speed: "); - pdec(mk_wheel_max_speed); + print_dec(mk_wheel_max_speed); print("\n"); print("6: wheel_time_to_max: "); - pdec(mk_wheel_time_to_max); + print_dec(mk_wheel_time_to_max); print("\n"); # endif /* !NO_PRINT */ } diff --git a/quantum/debounce/sym_defer_pk.c b/quantum/debounce/sym_defer_pk.c index 6c0e3bb071..60513f98e1 100644 --- a/quantum/debounce/sym_defer_pk.c +++ b/quantum/debounce/sym_defer_pk.c @@ -23,6 +23,12 @@ When no state changes have occured for DEBOUNCE milliseconds, we push the state. #include "quantum.h" #include <stdlib.h> +#ifdef PROTOCOL_CHIBIOS +# if CH_CFG_USE_MEMCORE == FALSE +# error ChibiOS is configured without a memory allocator. Your keyboard may have set `#define CH_CFG_USE_MEMCORE FALSE`, which is incompatible with this debounce algorithm. +# endif +#endif + #ifndef DEBOUNCE # define DEBOUNCE 5 #endif diff --git a/quantum/debounce/sym_eager_pk.c b/quantum/debounce/sym_eager_pk.c index 93a40ad441..e66cf92d79 100644 --- a/quantum/debounce/sym_eager_pk.c +++ b/quantum/debounce/sym_eager_pk.c @@ -23,6 +23,12 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred. #include "quantum.h" #include <stdlib.h> +#ifdef PROTOCOL_CHIBIOS +# if CH_CFG_USE_MEMCORE == FALSE +# error ChibiOS is configured without a memory allocator. Your keyboard may have set `#define CH_CFG_USE_MEMCORE FALSE`, which is incompatible with this debounce algorithm. +# endif +#endif + #ifndef DEBOUNCE # define DEBOUNCE 5 #endif diff --git a/quantum/debounce/sym_eager_pr.c b/quantum/debounce/sym_eager_pr.c index d12931fddb..20ccb46f1d 100644 --- a/quantum/debounce/sym_eager_pr.c +++ b/quantum/debounce/sym_eager_pr.c @@ -23,6 +23,12 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred. #include "quantum.h" #include <stdlib.h> +#ifdef PROTOCOL_CHIBIOS +# if CH_CFG_USE_MEMCORE == FALSE +# error ChibiOS is configured without a memory allocator. Your keyboard may have set `#define CH_CFG_USE_MEMCORE FALSE`, which is incompatible with this debounce algorithm. +# endif +#endif + #ifndef DEBOUNCE # define DEBOUNCE 5 #endif diff --git a/quantum/dynamic_keymap.c b/quantum/dynamic_keymap.c index 0608b469c0..a860b94979 100644 --- a/quantum/dynamic_keymap.c +++ b/quantum/dynamic_keymap.c @@ -37,6 +37,8 @@ #ifndef DYNAMIC_KEYMAP_EEPROM_MAX_ADDR # if defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) # define DYNAMIC_KEYMAP_EEPROM_MAX_ADDR 2047 +# elif defined(__AVR_AT90USB162__) +# define DYNAMIC_KEYMAP_EEPROM_MAX_ADDR 511 # else # define DYNAMIC_KEYMAP_EEPROM_MAX_ADDR 1023 # endif diff --git a/quantum/encoder.c b/quantum/encoder.c index 7ca31afedc..2ed64c1e30 100644 --- a/quantum/encoder.c +++ b/quantum/encoder.c @@ -94,8 +94,9 @@ void encoder_init(void) { #endif } -static void encoder_update(int8_t index, uint8_t state) { - uint8_t i = index; +static bool encoder_update(int8_t index, uint8_t state) { + bool changed = false; + uint8_t i = index; #ifdef ENCODER_RESOLUTIONS int8_t resolution = encoder_resolutions[i]; @@ -109,40 +110,53 @@ static void encoder_update(int8_t index, uint8_t state) { encoder_pulses[i] += encoder_LUT[state & 0xF]; if (encoder_pulses[i] >= resolution) { encoder_value[index]++; + changed = true; encoder_update_kb(index, ENCODER_COUNTER_CLOCKWISE); } if (encoder_pulses[i] <= -resolution) { // direction is arbitrary here, but this clockwise encoder_value[index]--; + changed = true; encoder_update_kb(index, ENCODER_CLOCKWISE); } encoder_pulses[i] %= resolution; + return changed; } -void encoder_read(void) { +bool encoder_read(void) { + bool changed = false; for (uint8_t i = 0; i < NUMBER_OF_ENCODERS; i++) { encoder_state[i] <<= 2; encoder_state[i] |= (readPin(encoders_pad_a[i]) << 0) | (readPin(encoders_pad_b[i]) << 1); - encoder_update(i, encoder_state[i]); + changed |= encoder_update(i, encoder_state[i]); } + return changed; } #ifdef SPLIT_KEYBOARD +void last_encoder_activity_trigger(void); + void encoder_state_raw(uint8_t* slave_state) { memcpy(slave_state, &encoder_value[thisHand], sizeof(uint8_t) * NUMBER_OF_ENCODERS); } void encoder_update_raw(uint8_t* slave_state) { + bool changed = false; for (uint8_t i = 0; i < NUMBER_OF_ENCODERS; i++) { uint8_t index = i + thatHand; int8_t delta = slave_state[i] - encoder_value[index]; while (delta > 0) { delta--; encoder_value[index]++; + changed = true; encoder_update_kb(index, ENCODER_COUNTER_CLOCKWISE); } while (delta < 0) { delta++; encoder_value[index]--; + changed = true; encoder_update_kb(index, ENCODER_CLOCKWISE); } } + + // Update the last encoder input time -- handled external to encoder_read() when we're running a split + if (changed) last_encoder_activity_trigger(); } #endif diff --git a/quantum/encoder.h b/quantum/encoder.h index ec09a8cc47..db6f220da4 100644 --- a/quantum/encoder.h +++ b/quantum/encoder.h @@ -20,7 +20,7 @@ #include "quantum.h" void encoder_init(void); -void encoder_read(void); +bool encoder_read(void); void encoder_update_kb(int8_t index, bool clockwise); void encoder_update_user(int8_t index, bool clockwise); diff --git a/quantum/fauxclicky.c b/quantum/fauxclicky.c deleted file mode 100644 index 53499c9c1e..0000000000 --- a/quantum/fauxclicky.c +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2017 Priyadi Iman Nurcahyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include <avr/interrupt.h> -#include <avr/io.h> -#include "timer.h" -#include "fauxclicky.h" -#include <stdbool.h> -#include "musical_notes.h" - -bool fauxclicky_enabled = true; -uint16_t note_start = 0; -bool note_playing = false; -uint16_t note_period = 0; - -void fauxclicky_init() { - // Set port PC6 (OC3A and /OC4A) as output - DDRC |= _BV(PORTC6); - - // TCCR3A / TCCR3B: Timer/Counter #3 Control Registers - TCCR3A = (0 << COM3A1) | (0 << COM3A0) | (1 << WGM31) | (0 << WGM30); - TCCR3B = (1 << WGM33) | (1 << WGM32) | (0 << CS32) | (1 << CS31) | (0 << CS30); -} - -void fauxclicky_stop() { - FAUXCLICKY_DISABLE_OUTPUT; - note_playing = false; -} - -void fauxclicky_play(float note[]) { - if (!fauxclicky_enabled) return; - if (note_playing) fauxclicky_stop(); - FAUXCLICKY_TIMER_PERIOD = (uint16_t)(((float)F_CPU) / (note[0] * (float)FAUXCLICKY_CPU_PRESCALER)); - FAUXCLICKY_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (note[0] * (float)FAUXCLICKY_CPU_PRESCALER)) / (float)2); - note_playing = true; - note_period = (note[1] / (float)16) * ((float)60 / (float)FAUXCLICKY_TEMPO) * 1000; - note_start = timer_read(); - FAUXCLICKY_ENABLE_OUTPUT; -} - -void fauxclicky_check() { - if (!note_playing) return; - - if (timer_elapsed(note_start) > note_period) { - fauxclicky_stop(); - } -} diff --git a/quantum/fauxclicky.h b/quantum/fauxclicky.h deleted file mode 100644 index ed54d0edcf..0000000000 --- a/quantum/fauxclicky.h +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright 2017 Priyadi Iman Nurcahyo - -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/>. -*/ - -#ifdef AUDIO_ENABLE -# error "AUDIO_ENABLE and FAUXCLICKY_ENABLE cannot be both enabled" -#endif - -#include "musical_notes.h" -#include <stdbool.h> - -__attribute__((weak)) float fauxclicky_pressed_note[2] = MUSICAL_NOTE(_D4, 0.25); -__attribute__((weak)) float fauxclicky_released_note[2] = MUSICAL_NOTE(_C4, 0.125); -__attribute__((weak)) float fauxclicky_beep_note[2] = MUSICAL_NOTE(_C4, 0.25); - -extern bool fauxclicky_enabled; - -// -// tempo in BPM -// - -#ifndef FAUXCLICKY_TEMPO -# define FAUXCLICKY_TEMPO TEMPO_DEFAULT -#endif - -// beep on press -#define FAUXCLICKY_ACTION_PRESS fauxclicky_play(fauxclicky_pressed_note) - -// beep on release -#define FAUXCLICKY_ACTION_RELEASE fauxclicky_play(fauxclicky_released_note) - -// general purpose beep -#define FAUXCLICKY_BEEP fauxclicky_play(fauxclicky_beep_note) - -// enable -#define FAUXCLICKY_ON fauxclicky_enabled = true - -// disable -#define FAUXCLICKY_OFF \ - do { \ - fauxclicky_enabled = false; \ - fauxclicky_stop(); \ - } while (0) - -// toggle -#define FAUXCLICKY_TOGGLE \ - do { \ - if (fauxclicky_enabled) { \ - FAUXCLICKY_OFF; \ - } else { \ - FAUXCLICKY_ON; \ - } \ - } while (0) - -// -// pin configuration -// - -#ifndef FAUXCLICKY_CPU_PRESCALER -# define FAUXCLICKY_CPU_PRESCALER 8 -#endif - -#ifndef FAUXCLICKY_ENABLE_OUTPUT -# define FAUXCLICKY_ENABLE_OUTPUT TCCR3A |= _BV(COM3A1) -#endif - -#ifndef FAUXCLICKY_DISABLE_OUTPUT -# define FAUXCLICKY_DISABLE_OUTPUT TCCR3A &= ~(_BV(COM3A1) | _BV(COM3A0)) -#endif - -#ifndef FAUXCLICKY_TIMER_PERIOD -# define FAUXCLICKY_TIMER_PERIOD ICR3 -#endif - -#ifndef FAUXCLICKY_DUTY_CYCLE -# define FAUXCLICKY_DUTY_CYCLE OCR3A -#endif - -// -// definitions -// - -void fauxclicky_init(void); -void fauxclicky_stop(void); -void fauxclicky_play(float note[2]); -void fauxclicky_check(void); diff --git a/quantum/keymap_extras/keymap_us_extended.h b/quantum/keymap_extras/keymap_us_extended.h new file mode 100644 index 0000000000..b2b3a734c9 --- /dev/null +++ b/quantum/keymap_extras/keymap_us_extended.h @@ -0,0 +1,227 @@ +/* Copyright 2020 + * + * 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 + +#include "keymap.h" + +// clang-format off + +/* + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ` │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 0 │ - │ = │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ [ │ ] │ \ │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ A │ S │ D │ F │ G │ H │ J │ K │ L │ ; │ ' │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ Z │ X │ C │ V │ B │ N │ M │ , │ . │ / │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ +// Row 1 +#define US_GRV KC_GRV // ` +#define US_1 KC_1 // 1 +#define US_2 KC_2 // 2 +#define US_3 KC_3 // 3 +#define US_4 KC_4 // 4 +#define US_5 KC_5 // 5 +#define US_6 KC_6 // 6 +#define US_7 KC_7 // 7 +#define US_8 KC_8 // 8 +#define US_9 KC_9 // 9 +#define US_0 KC_0 // 0 +#define US_MINS KC_MINS // - +#define US_EQL KC_EQL // = +// Row 2 +#define US_Q KC_Q // Q +#define US_W KC_W // W +#define US_E KC_E // E +#define US_R KC_R // R +#define US_T KC_T // T +#define US_Y KC_Y // Y +#define US_U KC_U // U +#define US_I KC_I // I +#define US_O KC_O // O +#define US_P KC_P // P +#define US_LBRC KC_LBRC // [ +#define US_RBRC KC_RBRC // ] +#define US_BSLS KC_BSLS // (backslash) +// Row 3 +#define US_A KC_A // A +#define US_S KC_S // S +#define US_D KC_D // D +#define US_F KC_F // F +#define US_G KC_G // G +#define US_H KC_H // H +#define US_J KC_J // J +#define US_K KC_K // K +#define US_L KC_L // L +#define US_SCLN KC_SCLN // ; +#define US_QUOT KC_QUOT // ' +// Row 4 +#define US_Z KC_Z // Z +#define US_X KC_X // X +#define US_C KC_C // C +#define US_V KC_V // V +#define US_B KC_B // B +#define US_N KC_N // N +#define US_M KC_M // M +#define US_COMM KC_COMM // , +#define US_DOT KC_DOT // . +#define US_SLSH KC_SLSH // / + +/* Shifted symbols + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ~ │ ! │ @ │ # │ $ │ % │ ^ │ & │ * │ ( │ ) │ _ │ + │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ │ │ │ │ │ │ │ │ │ │ { │ } │ | │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ │ │ │ │ │ │ │ │ │ : │ " │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ │ │ │ │ │ │ │ < │ > │ ? │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ +// Row 1 +#define US_TILD S(US_GRV) // ~ +#define US_EXLM S(US_1) // ! +#define US_AT S(US_2) // @ +#define US_HASH S(US_3) // # +#define US_DLR S(US_4) // $ +#define US_PERC S(US_5) // % +#define US_CIRC S(US_6) // ^ +#define US_AMPR S(US_7) // & +#define US_ASTR S(US_8) // * +#define US_LPRN S(US_9) // ( +#define US_RPRN S(US_0) // ) +#define US_UNDS S(US_MINS) // _ +#define US_PLUS S(US_EQL) // + +// Row 2 +#define US_LCBR S(US_LBRC) // { +#define US_RCBR S(US_RBRC) // } +#define US_PIPE S(US_BSLS) // | +// Row 3 +#define US_COLN S(US_SCLN) // : +#define US_DQUO S(US_QUOT) // " +// Row 4 +#define US_LABK S(US_COMM) // < +#define US_RABK S(US_DOT) // > +#define US_QUES S(US_SLSH) // ? + +/* AltGr symbols + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ` │ ¹ │ ² │ ³ │ ¤ │ € │ ^ │ ̛ │ ¾ │ ‘ │ ’ │ ¥ │ × │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ Ä │ Å │ É │ ® │ Þ │ Ü │ Ú │ Í │ Ó │ Ö │ « │ » │ ¬ │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ Á │ ß │ Ð │ │ │ │ Ï │ Œ │ Ø │ ¶ │ ' │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ Æ │ │ © │ │ │ Ñ │ µ │ Ç │ ˙ │ ¿ │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ +// Row 1 +#define US_DGRV ALGR(US_GRV) // ` (dead) +#define US_SUP1 ALGR(US_1) // ¹ +#define US_SUP2 ALGR(US_2) // ² +#define US_SUP3 ALGR(US_3) // ³ +#define US_CURR ALGR(US_4) // ¤ +#define US_EURO ALGR(US_5) // € +#define US_DCIR ALGR(US_6) // ^ (dead) +#define US_HORN ALGR(US_7) // ̛̛ (dead) +#define US_OGON ALGR(US_8) // ˛ (dead) +#define US_LSQU ALGR(US_9) // ‘ +#define US_RSQU ALGR(US_0) // ’ +#define US_YEN ALGR(US_MINS) // ¥ +#define US_MUL ALGR(US_EQL) // × +// Row 2 +#define US_ADIA ALGR(US_Q) // Ä +#define US_ARNG ALGR(US_W) // Å +#define US_EACU ALGR(US_E) // É +#define US_EDIA ALGR(US_R) // Ë +#define US_THRN ALGR(US_T) // Þ +#define US_UDIA ALGR(US_Y) // Ü +#define US_UACU ALGR(US_U) // Ú +#define US_IACU ALGR(US_I) // Í +#define US_OACU ALGR(US_O) // Ó +#define US_ODIA ALGR(US_P) // Ö +#define US_LDAQ ALGR(US_LBRC) // « +#define US_RDAQ ALGR(US_RBRC) // » +#define US_NOT ALGR(US_BSLS) // ¬ +// Row 3 +#define US_AACU ALGR(US_A) // Á +#define US_SS ALGR(US_S) // ß +#define US_ETH ALGR(US_D) // Ð +#define US_IDIA ALGR(US_J) // Ï +#define US_OE ALGR(US_K) // Œ +#define US_OSTR ALGR(US_L) // Ø +#define US_PILC ALGR(US_SCLN) // ¶ +#define US_ACUT ALGR(US_QUOT) // ´ (dead) +// Row 4 +#define US_AE ALGR(US_Z) // Æ +#define US_OE_2 ALGR(US_X) // Œ +#define US_COPY ALGR(US_C) // © +#define US_REGD ALGR(US_V) // ® +#define US_NTIL ALGR(US_N) // Ñ +#define US_MICR ALGR(US_M) // µ +#define US_CCED ALGR(US_COMM) // Ç +#define US_DOTA ALGR(US_DOT) // ˙ (dead) +#define US_IQUE ALGR(US_SLSH) // ¿ + +/* Shift+AltGr symbols + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ~ │ ¡ │ ˝ │ ¯ │ £ │ ¸ │ ¼ │ ½ │ ¾ │ ˘ │ ° │ ̣ │ ÷ │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ │ │ │ │ │ │ │ │ │ │ “ │ ” │ ¦ │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ │ § │ │ │ │ │ │ │ │ ° │ " │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ │ │ ¢ │ │ │ │ │ │ ˇ │ ̉ │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ +// Row 1 +#define US_DTIL S(ALGR(US_GRV)) // ~ (dead) +#define US_IEXL S(ALGR(US_1)) // ¡ +#define US_DACU S(ALGR(US_2)) // ˝ (dead) +#define US_MACR S(ALGR(US_3)) // ¯ (dead) +#define US_PND S(ALGR(US_4)) // £ +#define US_CEDL S(ALGR(US_5)) // ¸ (dead) +#define US_QRTR S(ALGR(US_6)) // ¼ +#define US_HALF S(ALGR(US_7)) // ½ +#define US_TQTR S(ALGR(US_8)) // ¾ +#define US_BREV S(ALGR(US_9)) // ˘ (dead) +#define US_RNGA S(ALGR(US_0)) // ° (dead) +#define US_DOTB S(ALGR(US_MINS)) // ̣ (dead) +#define US_DIV S(ALGR(US_EQL)) // ÷ +// Row 2 +#define US_LDQU S(ALGR(US_LBRC)) // “ +#define US_RDQU S(ALGR(US_LBRC)) // ” +#define US_BRKP S(ALGR(US_BSLS)) // ¦ +// Row 3 +#define US_SECT S(ALGR(US_S)) // § +#define US_DEG S(ALGR(US_SCLN)) // ° +#define US_DIAE S(ALGR(US_QUOT)) // ¨ (dead) +// Row 4 +#define US_CENT S(ALGR(US_C)) // ¢ +#define US_CARN S(ALGR(US_DOT)) // ˇ (dead) +#define US_HOKA S(ALGR(US_SLSH)) // ̉ (dead) + diff --git a/quantum/keymap_extras/keymap_us_international.h b/quantum/keymap_extras/keymap_us_international.h index a3bc465971..49afcc4fb2 100644 --- a/quantum/keymap_extras/keymap_us_international.h +++ b/quantum/keymap_extras/keymap_us_international.h @@ -26,7 +26,7 @@ * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ * │ │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ [ │ ] │ \ │ * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ - * │ │ A │ S │ D │ F │ G │ H │ J │ K │ L │ ; │ ' │ │ + * │ │ A │ S │ D │ F │ G │ H │ J │ K │ L │ ; │ ´ │ │ * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ * │ │ Z │ X │ C │ V │ B │ N │ M │ , │ . │ / │ │ * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ @@ -34,7 +34,7 @@ * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ */ // Row 1 -#define US_GRV KC_GRV // ` (dead) +#define US_DGRV KC_GRV // ` (dead) #define US_1 KC_1 // 1 #define US_2 KC_2 // 2 #define US_3 KC_3 // 3 @@ -72,7 +72,7 @@ #define US_K KC_K // K #define US_L KC_L // L #define US_SCLN KC_SCLN // ; -#define US_QUOT KC_QUOT // ' (dead) +#define US_ACUT KC_QUOT // ´ (dead) // Row 4 #define US_Z KC_Z // Z #define US_X KC_X // X @@ -91,7 +91,7 @@ * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ * │ │ │ │ │ │ │ │ │ │ │ │ { │ } │ | │ * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ - * │ │ │ │ │ │ │ │ │ │ │ : │ " │ │ + * │ │ │ │ │ │ │ │ │ │ │ : │ ¨ │ │ * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ * │ │ │ │ │ │ │ │ │ < │ > │ ? │ │ * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ @@ -99,13 +99,13 @@ * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ */ // Row 1 -#define US_TILD S(US_GRV) // ~ (dead) +#define US_DTIL S(US_DGRV) // ~ (dead) #define US_EXLM S(US_1) // ! -#define US_AT S(US_2) // " +#define US_AT S(US_2) // @ #define US_HASH S(US_3) // # #define US_DLR S(US_4) // $ #define US_PERC S(US_5) // % -#define US_CIRC S(US_6) // ^ +#define US_DCIR S(US_6) // ^ (dead) #define US_AMPR S(US_7) // & #define US_ASTR S(US_8) // * #define US_LPRN S(US_9) // ( @@ -118,7 +118,7 @@ #define US_PIPE S(US_BSLS) // | // Row 3 #define US_COLN S(US_SCLN) // : -#define US_DQUO S(US_QUOT) // " (dead) +#define US_DIAE S(US_ACUT) // ¨ (dead) // Row 4 #define US_LABK S(US_COMM) // < #define US_RABK S(US_DOT) // > @@ -170,7 +170,7 @@ #define US_ETH ALGR(US_D) // Ð #define US_OSTR ALGR(US_L) // Ø #define US_PILC ALGR(US_SCLN) // ¶ -#define US_ACUT ALGR(US_QUOT) // ´ +#define US_NDAC ALGR(US_ACUT) // ´ // Row 4 #define US_AE ALGR(US_Z) // Æ #define US_COPY ALGR(US_C) // © @@ -201,6 +201,6 @@ // Row 3 #define US_SECT S(ALGR(US_S)) // § #define US_DEG S(ALGR(US_SCLN)) // ° -#define US_DIAE S(ALGR(US_QUOT)) // ¨ +#define US_NDDR S(ALGR(US_ACUT)) // ¨ // Row 4 #define US_CENT S(ALGR(US_C)) // ¢ diff --git a/quantum/keymap_extras/keymap_us_international_linux.h b/quantum/keymap_extras/keymap_us_international_linux.h new file mode 100644 index 0000000000..2c3e230393 --- /dev/null +++ b/quantum/keymap_extras/keymap_us_international_linux.h @@ -0,0 +1,224 @@ +/* Copyright 2020 + * + * 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 + +#include "keymap.h" + +// clang-format off + +/* + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ` │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 0 │ - │ = │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ [ │ ] │ \ │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ A │ S │ D │ F │ G │ H │ J │ K │ L │ ; │ ´ │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ Z │ X │ C │ V │ B │ N │ M │ , │ . │ / │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ +// Row 1 +#define US_DGRV KC_GRV // ` (dead) +#define US_1 KC_1 // 1 +#define US_2 KC_2 // 2 +#define US_3 KC_3 // 3 +#define US_4 KC_4 // 4 +#define US_5 KC_5 // 5 +#define US_6 KC_6 // 6 +#define US_7 KC_7 // 7 +#define US_8 KC_8 // 8 +#define US_9 KC_9 // 9 +#define US_0 KC_0 // 0 +#define US_MINS KC_MINS // - +#define US_EQL KC_EQL // = +// Row 2 +#define US_Q KC_Q // Q +#define US_W KC_W // W +#define US_E KC_E // E +#define US_R KC_R // R +#define US_T KC_T // T +#define US_Y KC_Y // Y +#define US_U KC_U // U +#define US_I KC_I // I +#define US_O KC_O // O +#define US_P KC_P // P +#define US_LBRC KC_LBRC // [ +#define US_RBRC KC_RBRC // ] +#define US_BSLS KC_BSLS // (backslash) +// Row 3 +#define US_A KC_A // A +#define US_S KC_S // S +#define US_D KC_D // D +#define US_F KC_F // F +#define US_G KC_G // G +#define US_H KC_H // H +#define US_J KC_J // J +#define US_K KC_K // K +#define US_L KC_L // L +#define US_SCLN KC_SCLN // ; +#define US_ACUT KC_QUOT // ´ (dead) +// Row 4 +#define US_Z KC_Z // Z +#define US_X KC_X // X +#define US_C KC_C // C +#define US_V KC_V // V +#define US_B KC_B // B +#define US_N KC_N // N +#define US_M KC_M // M +#define US_COMM KC_COMM // , +#define US_DOT KC_DOT // . +#define US_SLSH KC_SLSH // / + +/* Shifted symbols + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ~ │ ! │ @ │ # │ $ │ % │ ^ │ & │ * │ ( │ ) │ _ │ + │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ │ │ │ │ │ │ │ │ │ │ { │ } │ | │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ │ │ │ │ │ │ │ │ │ : │ ¨ │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ │ │ │ │ │ │ │ < │ > │ ? │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ +// Row 1 +#define US_DTIL S(US_DGRV) // ~ (dead) +#define US_EXLM S(US_1) // ! +#define US_AT S(US_2) // @ +#define US_HASH S(US_3) // # +#define US_DLR S(US_4) // $ +#define US_PERC S(US_5) // % +#define US_DCIR S(US_6) // ^ (dead) +#define US_AMPR S(US_7) // & +#define US_ASTR S(US_8) // * +#define US_LPRN S(US_9) // ( +#define US_RPRN S(US_0) // ) +#define US_UNDS S(US_MINS) // _ +#define US_PLUS S(US_EQL) // + +// Row 2 +#define US_LCBR S(US_LBRC) // { +#define US_RCBR S(US_RBRC) // } +#define US_PIPE S(US_BSLS) // | +// Row 3 +#define US_COLN S(US_SCLN) // : +#define US_DIAE S(US_ACUT) // ¨ (dead) +// Row 4 +#define US_LABK S(US_COMM) // < +#define US_RABK S(US_DOT) // > +#define US_QUES S(US_SLSH) // ? + +/* AltGr symbols + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ` │ ¡ │ ² │ ³ │ ¤ │ € │ ¼ │ ½ │ ¾ │ ‘ │ ’ │ ¥ │ × │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ Ä │ Å │ É │ ® │ Þ │ Ü │ Ú │ Í │ Ó │ Ö │ « │ » │ ¬ │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ Á │ ß │ Ð │ │ │ │ │ Œ │ Ø │ ¶ │ ' │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ Æ │ │ © │ │ │ Ñ │ µ │ Ç │ ˙ │ ¿ │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ + +// Row 1 +#define US_GRV ALGR(US_DGRV) // ` +#define US_IEXL ALGR(US_1) // ¡ +#define US_SUP2 ALGR(US_2) // ² +#define US_SUP3 ALGR(US_3) // ³ +#define US_CURR ALGR(US_4) // ¤ +#define US_EURO ALGR(US_5) // € +#define US_QRTR ALGR(US_6) // ¼ +#define US_HALF ALGR(US_7) // ½ +#define US_TQTR ALGR(US_8) // ¾ +#define US_LSQU ALGR(US_9) // ‘ +#define US_RSQU ALGR(US_0) // ’ +#define US_YEN ALGR(US_MINS) // ¥ +#define US_MUL ALGR(US_EQL) // × +// Row 2 +#define US_ADIA ALGR(US_Q) // Ä +#define US_ARNG ALGR(US_W) // Å +#define US_EACU ALGR(US_E) // É +#define US_REGD ALGR(US_R) // ® +#define US_THRN ALGR(US_T) // Þ +#define US_UDIA ALGR(US_Y) // Ü +#define US_UACU ALGR(US_U) // Ú +#define US_IACU ALGR(US_I) // Í +#define US_OACU ALGR(US_O) // Ó +#define US_ODIA ALGR(US_P) // Ö +#define US_LDAQ ALGR(US_LBRC) // « +#define US_RDAQ ALGR(US_RBRC) // » +#define US_NOT ALGR(US_BSLS) // ¬ +// Row 3 +#define US_AACU ALGR(US_A) // Á +#define US_SS ALGR(US_S) // ß +#define US_ETH ALGR(US_D) // Ð +#define US_OE ALGR(US_K) // Œ +#define US_OSTR ALGR(US_L) // Ø +#define US_PILC ALGR(US_SCLN) // ¶ +#define US_QUOT ALGR(US_ACUT) // ' +// Row 4 +#define US_AE ALGR(US_Z) // Æ +#define US_COPY ALGR(US_C) // © +#define US_NTIL ALGR(US_N) // Ñ +#define US_MICR ALGR(US_M) // µ +#define US_CCED ALGR(US_COMM) // Ç +#define US_DOTA ALGR(US_DOT) // ˙ (dead) +#define US_IQUE ALGR(US_SLSH) // ¿ + +/* Shift+AltGr symbols + * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ + * │ ~ │ ¹ │ ˝ │ ¯ │ £ │ ¸ │ ^ │ ̛ │ ˛ │ ˘ │ ° │ ̣ │ ÷ │ │ + * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ + * │ │ │ │ │ │ │ │ │ │ │ │ “ │ ” │ ¦ │ + * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ + * │ │ │ § │ │ │ │ │ │ │ │ ° │ " │ │ + * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ + * │ │ │ │ ¢ │ │ │ │ │ │ ˇ │ ̉ │ │ + * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ + */ +// Row 1 +#define US_TILD S(ALGR(US_DGRV)) // ~ +#define US_SUP1 S(ALGR(US_1)) // ¹ +#define US_DACU S(ALGR(US_2)) // ˝ (dead) +#define US_MACR S(ALGR(US_3)) // ¯ (dead) +#define US_PND S(ALGR(US_4)) // £ +#define US_CEDL S(ALGR(US_5)) // ¸ (dead) +#define US_CIRC S(ALGR(US_6)) // ^ +#define US_HORN S(ALGR(US_7)) // ̛ (dead) +#define US_OGON S(ALGR(US_8)) // ˛ (dead) +#define US_BREV S(ALGR(US_9)) // ˘ (dead) +#define US_RNGA S(ALGR(US_0)) // ° (dead) +#define US_DOTB S(ALGR(US_MINS)) // ̣ (dead) +#define US_DIV S(ALGR(US_EQL)) // ÷ +// Row 2 +#define US_LDQU S(ALGR(US_LBRC)) // “ +#define US_RDQU S(ALGR(US_LBRC)) // ” +#define US_BRKP S(ALGR(US_BSLS)) // ¦ +// Row 3 +#define US_SECT S(ALGR(US_S)) // § +#define US_DEG S(ALGR(US_SCLN)) // ° +#define US_DQUO S(ALGR(US_ACUT)) // " +// Row 4 +#define US_CENT S(ALGR(US_C)) // ¢ +#define US_CARN S(ALGR(US_DOT)) // ˇ (dead) +#define US_HOKA S(ALGR(US_SLSH)) // ̉ (dead) diff --git a/quantum/keymap_extras/sendstring_us_international.h b/quantum/keymap_extras/sendstring_us_international.h new file mode 100644 index 0000000000..53a5891fb1 --- /dev/null +++ b/quantum/keymap_extras/sendstring_us_international.h @@ -0,0 +1,100 @@ +/* Copyright 2019 Rys Sommefeldt + * + * 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/>. + */ + +// Sendstring lookup tables for UK layouts + +#pragma once + +#include "keymap_us_international.h" +#include "quantum.h" + +// clang-format off + +const uint8_t ascii_to_shift_lut[16] PROGMEM = { + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + + KCLUT_ENTRY(0, 1, 1, 1, 1, 1, 1, 0), + KCLUT_ENTRY(1, 1, 1, 1, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 1, 0, 1, 0, 1, 1), + KCLUT_ENTRY(1, 1, 1, 1, 1, 1, 1, 1), + KCLUT_ENTRY(1, 1, 1, 1, 1, 1, 1, 1), + KCLUT_ENTRY(1, 1, 1, 1, 1, 1, 1, 1), + KCLUT_ENTRY(1, 1, 1, 0, 0, 0, 1, 1), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 1, 1, 1, 1, 0), +}; + +__attribute__((weak)) const uint8_t ascii_to_dead_lut[16] PROGMEM = { + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + + KCLUT_ENTRY(0, 0, 1, 0, 0, 0, 0, 1), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 1, 0), + KCLUT_ENTRY(1, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 1, 0), +}; + +const uint8_t ascii_to_keycode_lut[128] PROGMEM = { + // NUL SOH STX ETX EOT ENQ ACK BEL + XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, + // BS TAB LF VT FF CR SO SI + KC_BSPC, KC_TAB, KC_ENT, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, + // DLE DC1 DC2 DC3 DC4 NAK SYN ETB + XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, + // CAN EM SUB ESC FS GS RS US + XXXXXXX, XXXXXXX, XXXXXXX, KC_ESC, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, + + // ! " # $ % & ' + KC_SPC, US_1, US_ACUT, US_3, US_4, US_5, US_7, US_ACUT, + // ( ) * + , - . / + US_9, US_0, US_8, US_EQL, US_COMM, US_MINS, US_DOT, US_SLSH, + // 0 1 2 3 4 5 6 7 + US_0, US_1, US_2, US_3, US_4, US_5, US_6, US_7, + // 8 9 : ; < = > ? + US_8, US_9, US_SCLN, US_SCLN, US_COMM, US_EQL, US_DOT, US_SLSH, + // @ A B C D E F G + US_2, US_A, US_B, US_C, US_D, US_E, US_F, US_G, + // H I J K L M N O + US_H, US_I, US_J, US_K, US_L, US_M, US_N, US_O, + // P Q R S T U V W + US_P, US_Q, US_R, US_S, US_T, US_U, US_V, US_W, + // X Y Z [ \ ] ^ _ + US_X, US_Y, US_Z, US_LBRC, US_BSLS, US_RBRC, US_6, US_MINS, + // ` a b c d e f g + US_DGRV, US_A, US_B, US_C, US_D, US_E, US_F, US_G, + // h i j k l m n o + US_H, US_I, US_J, US_K, US_L, US_M, US_N, US_O, + // p q r s t u v w + US_P, US_Q, US_R, US_S, US_T, US_U, US_V, US_W, + // x y z { | } ~ DEL + US_X, US_Y, US_Z, US_LBRC, US_BSLS, US_RBRC, US_DGRV, KC_DEL +}; diff --git a/quantum/matrix.c b/quantum/matrix.c index 9083ff3861..c027b7bf27 100644 --- a/quantum/matrix.c +++ b/quantum/matrix.c @@ -101,9 +101,9 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) // Start with a clear matrix row matrix_row_t current_row_value = 0; - // Select row and wait for row selecton to stabilize + // Select row select_row(current_row); - matrix_io_delay(); + matrix_output_select_delay(); // For each col... for (uint8_t col_index = 0; col_index < MATRIX_COLS; col_index++) { @@ -116,6 +116,9 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) // Unselect row unselect_row(current_row); + if (current_row + 1 < MATRIX_ROWS) { + matrix_output_unselect_delay(); // wait for row signal to go HIGH + } // If the row has changed, store the row and return the changed flag. if (current_matrix[current_row] != current_row_value) { @@ -147,9 +150,9 @@ static void init_pins(void) { static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { bool matrix_changed = false; - // Select col and wait for col selecton to stabilize + // Select col select_col(current_col); - matrix_io_delay(); + matrix_output_select_delay(); // For each row... for (uint8_t row_index = 0; row_index < MATRIX_ROWS; row_index++) { @@ -175,6 +178,9 @@ static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) // Unselect col unselect_col(current_col); + if (current_col + 1 < MATRIX_COLS) { + matrix_output_unselect_delay(); // wait for col signal to go HIGH + } return matrix_changed; } diff --git a/quantum/matrix.h b/quantum/matrix.h index b570227a31..ce57010a47 100644 --- a/quantum/matrix.h +++ b/quantum/matrix.h @@ -55,6 +55,9 @@ matrix_row_t matrix_get_row(uint8_t row); /* print matrix for debug */ void matrix_print(void); /* delay between changing matrix pin state and reading values */ +void matrix_output_select_delay(void); +void matrix_output_unselect_delay(void); +/* only for backwards compatibility. delay between changing matrix pin state and reading values */ void matrix_io_delay(void); /* power control */ diff --git a/quantum/matrix_common.c b/quantum/matrix_common.c index 15f1e0e82e..efbad6a5fd 100644 --- a/quantum/matrix_common.c +++ b/quantum/matrix_common.c @@ -1,3 +1,4 @@ +#include "quantum.h" #include "matrix.h" #include "debounce.h" #include "wait.h" @@ -68,7 +69,7 @@ void matrix_print(void) { print_matrix_header(); for (uint8_t row = 0; row < MATRIX_ROWS; row++) { - phex(row); + print_hex8(row); print(": "); print_matrix_row(row); print("\n"); @@ -83,8 +84,12 @@ uint8_t matrix_key_count(void) { return count; } +/* `matrix_io_delay ()` exists for backwards compatibility. From now on, use matrix_output_unselect_delay(). */ __attribute__((weak)) void matrix_io_delay(void) { wait_us(MATRIX_IO_DELAY); } +__attribute__((weak)) void matrix_output_select_delay(void) { waitInputPinDelay(); } +__attribute__((weak)) void matrix_output_unselect_delay(void) { matrix_io_delay(); } + // CUSTOM MATRIX 'LITE' __attribute__((weak)) void matrix_init_custom(void) {} diff --git a/quantum/mcu_selection.mk b/quantum/mcu_selection.mk index 65c94b50f6..f7329fc4d9 100644 --- a/quantum/mcu_selection.mk +++ b/quantum/mcu_selection.mk @@ -279,7 +279,73 @@ ifneq ($(findstring STM32F411, $(MCU)),) DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 endif -ifneq (,$(filter $(MCU),atmega16u2 atmega32u2 atmega16u4 atmega32u4 at90usb646 at90usb647 at90usb1286 at90usb1287)) +ifneq ($(findstring STM32G431, $(MCU)),) + # Cortex version + MCU = cortex-m4 + + # ARM version, CORTEX-M0/M1 are 6, CORTEX-M3/M4/M7 are 7 + ARMV = 7 + + ## chip/board settings + # - the next two should match the directories in + # <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) + MCU_FAMILY = STM32 + MCU_SERIES = STM32G4xx + + # Linker script to use + # - it should exist either in <chibios>/os/common/ports/ARMCMx/compilers/GCC/ld/ + # or <keyboard_dir>/ld/ + MCU_LDSCRIPT ?= STM32G431xB + + # Startup code to use + # - it should exist in <chibios>/os/common/startup/ARMCMx/compilers/GCC/mk/ + MCU_STARTUP ?= stm32g4xx + + # Board: it should exist either in <chibios>/os/hal/boards/, + # <keyboard_dir>/boards/, or drivers/boards/ + BOARD ?= GENERIC_STM32_G431XB + + USE_FPU ?= yes + + # Options to pass to dfu-util when flashing + DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave + DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 +endif + +ifneq ($(findstring STM32G474, $(MCU)),) + # Cortex version + MCU = cortex-m4 + + # ARM version, CORTEX-M0/M1 are 6, CORTEX-M3/M4/M7 are 7 + ARMV = 7 + + ## chip/board settings + # - the next two should match the directories in + # <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) + MCU_FAMILY = STM32 + MCU_SERIES = STM32G4xx + + # Linker script to use + # - it should exist either in <chibios>/os/common/ports/ARMCMx/compilers/GCC/ld/ + # or <keyboard_dir>/ld/ + MCU_LDSCRIPT ?= STM32G474xE + + # Startup code to use + # - it should exist in <chibios>/os/common/startup/ARMCMx/compilers/GCC/mk/ + MCU_STARTUP ?= stm32g4xx + + # Board: it should exist either in <chibios>/os/hal/boards/, + # <keyboard_dir>/boards/, or drivers/boards/ + BOARD ?= GENERIC_STM32_G474XE + + USE_FPU ?= yes + + # Options to pass to dfu-util when flashing + DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave + DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 +endif + +ifneq (,$(filter $(MCU),at90usb162 atmega16u2 atmega32u2 atmega16u4 atmega32u4 at90usb646 at90usb647 at90usb1286 at90usb1287)) PROTOCOL = LUFA # Processor frequency. @@ -317,7 +383,7 @@ ifneq (,$(filter $(MCU),atmega16u2 atmega32u2 atmega16u4 atmega32u4 at90usb646 a ifeq (,$(filter $(NO_INTERRUPT_CONTROL_ENDPOINT),yes)) OPT_DEFS += -DINTERRUPT_CONTROL_ENDPOINT endif - ifneq (,$(filter $(MCU),atmega16u2 atmega32u2)) + ifneq (,$(filter $(MCU),at90usb162 atmega16u2 atmega32u2)) NO_I2C = yes endif endif diff --git a/quantum/mousekey.c b/quantum/mousekey.c new file mode 100644 index 0000000000..63e74baa93 --- /dev/null +++ b/quantum/mousekey.c @@ -0,0 +1,488 @@ +/* + * Copyright 2011 Jun Wako <wakojun@gmail.com> + * + * 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 <stdint.h> +#include "keycode.h" +#include "host.h" +#include "timer.h" +#include "print.h" +#include "debug.h" +#include "mousekey.h" + +inline int8_t times_inv_sqrt2(int8_t x) { + // 181/256 is pretty close to 1/sqrt(2) + // 0.70703125 0.707106781 + // 1 too small for x=99 and x=198 + // This ends up being a mult and discard lower 8 bits + return (x * 181) >> 8; +} + +static report_mouse_t mouse_report = {0}; +static void mousekey_debug(void); +static uint8_t mousekey_accel = 0; +static uint8_t mousekey_repeat = 0; +static uint8_t mousekey_wheel_repeat = 0; +#ifdef MK_KINETIC_SPEED +static uint16_t mouse_timer = 0; +#endif + +#ifndef MK_3_SPEED + +static uint16_t last_timer_c = 0; +static uint16_t last_timer_w = 0; + +/* + * Mouse keys acceleration algorithm + * http://en.wikipedia.org/wiki/Mouse_keys + * + * speed = delta * max_speed * (repeat / time_to_max)**((1000+curve)/1000) + */ +/* milliseconds between the initial key press and first repeated motion event (0-2550) */ +uint8_t mk_delay = MOUSEKEY_DELAY / 10; +/* milliseconds between repeated motion events (0-255) */ +uint8_t mk_interval = MOUSEKEY_INTERVAL; +/* steady speed (in action_delta units) applied each event (0-255) */ +uint8_t mk_max_speed = MOUSEKEY_MAX_SPEED; +/* number of events (count) accelerating to steady speed (0-255) */ +uint8_t mk_time_to_max = MOUSEKEY_TIME_TO_MAX; +/* ramp used to reach maximum pointer speed (NOT SUPPORTED) */ +// int8_t mk_curve = 0; +/* wheel params */ +/* milliseconds between the initial key press and first repeated motion event (0-2550) */ +uint8_t mk_wheel_delay = MOUSEKEY_WHEEL_DELAY / 10; +/* milliseconds between repeated motion events (0-255) */ +uint8_t mk_wheel_interval = MOUSEKEY_WHEEL_INTERVAL; +uint8_t mk_wheel_max_speed = MOUSEKEY_WHEEL_MAX_SPEED; +uint8_t mk_wheel_time_to_max = MOUSEKEY_WHEEL_TIME_TO_MAX; + +# ifndef MK_COMBINED + +static uint8_t move_unit(void) { + uint16_t unit; + if (mousekey_accel & (1 << 0)) { + unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed) / 4; + } else if (mousekey_accel & (1 << 1)) { + unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed) / 2; + } else if (mousekey_accel & (1 << 2)) { + unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed); + } else if (mousekey_repeat == 0) { + unit = MOUSEKEY_MOVE_DELTA; + } else if (mousekey_repeat >= mk_time_to_max) { + unit = MOUSEKEY_MOVE_DELTA * mk_max_speed; + } else { + unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed * mousekey_repeat) / mk_time_to_max; + } + return (unit > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : (unit == 0 ? 1 : unit)); +} + +static uint8_t wheel_unit(void) { + uint16_t unit; + if (mousekey_accel & (1 << 0)) { + unit = (MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed) / 4; + } else if (mousekey_accel & (1 << 1)) { + unit = (MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed) / 2; + } else if (mousekey_accel & (1 << 2)) { + unit = (MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed); + } else if (mousekey_wheel_repeat == 0) { + unit = MOUSEKEY_WHEEL_DELTA; + } else if (mousekey_wheel_repeat >= mk_wheel_time_to_max) { + unit = MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed; + } else { + unit = (MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed * mousekey_wheel_repeat) / mk_wheel_time_to_max; + } + return (unit > MOUSEKEY_WHEEL_MAX ? MOUSEKEY_WHEEL_MAX : (unit == 0 ? 1 : unit)); +} + +# else /* #ifndef MK_COMBINED */ +# ifndef MK_KINETIC_SPEED + +/* + * Kinetic movement acceleration algorithm + * + * current speed = I + A * T/50 + A * 0.5 * T^2 | maximum B + * + * T: time since the mouse movement started + * E: mouse events per second (set through MOUSEKEY_INTERVAL, UHK sends 250, the + * pro micro on my Signum 3.0 sends only 125!) + * I: initial speed at time 0 + * A: acceleration + * B: base mouse travel speed + */ +const uint16_t mk_accelerated_speed = MOUSEKEY_ACCELERATED_SPEED; +const uint16_t mk_base_speed = MOUSEKEY_BASE_SPEED; +const uint16_t mk_decelerated_speed = MOUSEKEY_DECELERATED_SPEED; +const uint16_t mk_initial_speed = MOUSEKEY_INITIAL_SPEED; + +static uint8_t move_unit(void) { + float speed = mk_initial_speed; + + if (mousekey_accel & ((1 << 0) | (1 << 2))) { + speed = mousekey_accel & (1 << 2) ? mk_accelerated_speed : mk_decelerated_speed; + } else if (mousekey_repeat && mouse_timer) { + const float time_elapsed = timer_elapsed(mouse_timer) / 50; + speed = mk_initial_speed + MOUSEKEY_MOVE_DELTA * time_elapsed + MOUSEKEY_MOVE_DELTA * 0.5 * time_elapsed * time_elapsed; + + speed = speed > mk_base_speed ? mk_base_speed : speed; + } + + /* convert speed to USB mouse speed 1 to 127 */ + speed = (uint8_t)(speed / (1000.0f / mk_interval)); + speed = speed < 1 ? 1 : speed; + + return speed > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : speed; +} + +float mk_wheel_interval = 1000.0f / MOUSEKEY_WHEEL_INITIAL_MOVEMENTS; + +static uint8_t wheel_unit(void) { + float speed = MOUSEKEY_WHEEL_INITIAL_MOVEMENTS; + + if (mousekey_accel & ((1 << 0) | (1 << 2))) { + speed = mousekey_accel & (1 << 2) ? MOUSEKEY_WHEEL_ACCELERATED_MOVEMENTS : MOUSEKEY_WHEEL_DECELERATED_MOVEMENTS; + } else if (mousekey_repeat && mouse_timer) { + if (mk_wheel_interval != MOUSEKEY_WHEEL_BASE_MOVEMENTS) { + const float time_elapsed = timer_elapsed(mouse_timer) / 50; + speed = MOUSEKEY_WHEEL_INITIAL_MOVEMENTS + 1 * time_elapsed + 1 * 0.5 * time_elapsed * time_elapsed; + } + speed = speed > MOUSEKEY_WHEEL_BASE_MOVEMENTS ? MOUSEKEY_WHEEL_BASE_MOVEMENTS : speed; + } + + mk_wheel_interval = 1000.0f / speed; + + return 1; +} + +# else /* #ifndef MK_KINETIC_SPEED */ + +static uint8_t move_unit(void) { + uint16_t unit; + if (mousekey_accel & (1 << 0)) { + unit = 1; + } else if (mousekey_accel & (1 << 1)) { + unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed) / 2; + } else if (mousekey_accel & (1 << 2)) { + unit = MOUSEKEY_MOVE_MAX; + } else if (mousekey_repeat == 0) { + unit = MOUSEKEY_MOVE_DELTA; + } else if (mousekey_repeat >= mk_time_to_max) { + unit = MOUSEKEY_MOVE_DELTA * mk_max_speed; + } else { + unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed * mousekey_repeat) / mk_time_to_max; + } + return (unit > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : (unit == 0 ? 1 : unit)); +} + +static uint8_t wheel_unit(void) { + uint16_t unit; + if (mousekey_accel & (1 << 0)) { + unit = 1; + } else if (mousekey_accel & (1 << 1)) { + unit = (MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed) / 2; + } else if (mousekey_accel & (1 << 2)) { + unit = MOUSEKEY_WHEEL_MAX; + } else if (mousekey_repeat == 0) { + unit = MOUSEKEY_WHEEL_DELTA; + } else if (mousekey_repeat >= mk_wheel_time_to_max) { + unit = MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed; + } else { + unit = (MOUSEKEY_WHEEL_DELTA * mk_wheel_max_speed * mousekey_repeat) / mk_wheel_time_to_max; + } + return (unit > MOUSEKEY_WHEEL_MAX ? MOUSEKEY_WHEEL_MAX : (unit == 0 ? 1 : unit)); +} + +# endif /* #ifndef MK_KINETIC_SPEED */ +# endif /* #ifndef MK_COMBINED */ + +void mousekey_task(void) { + // report cursor and scroll movement independently + report_mouse_t const tmpmr = mouse_report; + + mouse_report.x = 0; + mouse_report.y = 0; + mouse_report.v = 0; + mouse_report.h = 0; + + if ((tmpmr.x || tmpmr.y) && timer_elapsed(last_timer_c) > (mousekey_repeat ? mk_interval : mk_delay * 10)) { + if (mousekey_repeat != UINT8_MAX) mousekey_repeat++; + if (tmpmr.x != 0) mouse_report.x = move_unit() * ((tmpmr.x > 0) ? 1 : -1); + if (tmpmr.y != 0) mouse_report.y = move_unit() * ((tmpmr.y > 0) ? 1 : -1); + + /* diagonal move [1/sqrt(2)] */ + if (mouse_report.x && mouse_report.y) { + mouse_report.x = times_inv_sqrt2(mouse_report.x); + if (mouse_report.x == 0) { + mouse_report.x = 1; + } + mouse_report.y = times_inv_sqrt2(mouse_report.y); + if (mouse_report.y == 0) { + mouse_report.y = 1; + } + } + } + if ((tmpmr.v || tmpmr.h) && timer_elapsed(last_timer_w) > (mousekey_wheel_repeat ? mk_wheel_interval : mk_wheel_delay * 10)) { + if (mousekey_wheel_repeat != UINT8_MAX) mousekey_wheel_repeat++; + if (tmpmr.v != 0) mouse_report.v = wheel_unit() * ((tmpmr.v > 0) ? 1 : -1); + if (tmpmr.h != 0) mouse_report.h = wheel_unit() * ((tmpmr.h > 0) ? 1 : -1); + + /* diagonal move [1/sqrt(2)] */ + if (mouse_report.v && mouse_report.h) { + mouse_report.v = times_inv_sqrt2(mouse_report.v); + if (mouse_report.v == 0) { + mouse_report.v = 1; + } + mouse_report.h = times_inv_sqrt2(mouse_report.h); + if (mouse_report.h == 0) { + mouse_report.h = 1; + } + } + } + + if (mouse_report.x || mouse_report.y || mouse_report.v || mouse_report.h) mousekey_send(); + mouse_report = tmpmr; +} + +void mousekey_on(uint8_t code) { +# ifdef MK_KINETIC_SPEED + if (mouse_timer == 0) { + mouse_timer = timer_read(); + } +# endif /* #ifdef MK_KINETIC_SPEED */ + + if (code == KC_MS_UP) + mouse_report.y = move_unit() * -1; + else if (code == KC_MS_DOWN) + mouse_report.y = move_unit(); + else if (code == KC_MS_LEFT) + mouse_report.x = move_unit() * -1; + else if (code == KC_MS_RIGHT) + mouse_report.x = move_unit(); + else if (code == KC_MS_WH_UP) + mouse_report.v = wheel_unit(); + else if (code == KC_MS_WH_DOWN) + mouse_report.v = wheel_unit() * -1; + else if (code == KC_MS_WH_LEFT) + mouse_report.h = wheel_unit() * -1; + else if (code == KC_MS_WH_RIGHT) + mouse_report.h = wheel_unit(); + else if (IS_MOUSEKEY_BUTTON(code)) + mouse_report.buttons |= 1 << (code - KC_MS_BTN1); + else if (code == KC_MS_ACCEL0) + mousekey_accel |= (1 << 0); + else if (code == KC_MS_ACCEL1) + mousekey_accel |= (1 << 1); + else if (code == KC_MS_ACCEL2) + mousekey_accel |= (1 << 2); +} + +void mousekey_off(uint8_t code) { + if (code == KC_MS_UP && mouse_report.y < 0) + mouse_report.y = 0; + else if (code == KC_MS_DOWN && mouse_report.y > 0) + mouse_report.y = 0; + else if (code == KC_MS_LEFT && mouse_report.x < 0) + mouse_report.x = 0; + else if (code == KC_MS_RIGHT && mouse_report.x > 0) + mouse_report.x = 0; + else if (code == KC_MS_WH_UP && mouse_report.v > 0) + mouse_report.v = 0; + else if (code == KC_MS_WH_DOWN && mouse_report.v < 0) + mouse_report.v = 0; + else if (code == KC_MS_WH_LEFT && mouse_report.h < 0) + mouse_report.h = 0; + else if (code == KC_MS_WH_RIGHT && mouse_report.h > 0) + mouse_report.h = 0; + else if (IS_MOUSEKEY_BUTTON(code)) + mouse_report.buttons &= ~(1 << (code - KC_MS_BTN1)); + else if (code == KC_MS_ACCEL0) + mousekey_accel &= ~(1 << 0); + else if (code == KC_MS_ACCEL1) + mousekey_accel &= ~(1 << 1); + else if (code == KC_MS_ACCEL2) + mousekey_accel &= ~(1 << 2); + if (mouse_report.x == 0 && mouse_report.y == 0) { + mousekey_repeat = 0; +# ifdef MK_KINETIC_SPEED + mouse_timer = 0; +# endif /* #ifdef MK_KINETIC_SPEED */ + } + if (mouse_report.v == 0 && mouse_report.h == 0) mousekey_wheel_repeat = 0; +} + +#else /* #ifndef MK_3_SPEED */ + +enum { mkspd_unmod, mkspd_0, mkspd_1, mkspd_2, mkspd_COUNT }; +# ifndef MK_MOMENTARY_ACCEL +static uint8_t mk_speed = mkspd_1; +# else +static uint8_t mk_speed = mkspd_unmod; +static uint8_t mkspd_DEFAULT = mkspd_unmod; +# endif +static uint16_t last_timer_c = 0; +static uint16_t last_timer_w = 0; +uint16_t c_offsets[mkspd_COUNT] = {MK_C_OFFSET_UNMOD, MK_C_OFFSET_0, MK_C_OFFSET_1, MK_C_OFFSET_2}; +uint16_t c_intervals[mkspd_COUNT] = {MK_C_INTERVAL_UNMOD, MK_C_INTERVAL_0, MK_C_INTERVAL_1, MK_C_INTERVAL_2}; +uint16_t w_offsets[mkspd_COUNT] = {MK_W_OFFSET_UNMOD, MK_W_OFFSET_0, MK_W_OFFSET_1, MK_W_OFFSET_2}; +uint16_t w_intervals[mkspd_COUNT] = {MK_W_INTERVAL_UNMOD, MK_W_INTERVAL_0, MK_W_INTERVAL_1, MK_W_INTERVAL_2}; + +void mousekey_task(void) { + // report cursor and scroll movement independently + report_mouse_t const tmpmr = mouse_report; + mouse_report.x = 0; + mouse_report.y = 0; + mouse_report.v = 0; + mouse_report.h = 0; + + if ((tmpmr.x || tmpmr.y) && timer_elapsed(last_timer_c) > c_intervals[mk_speed]) { + mouse_report.x = tmpmr.x; + mouse_report.y = tmpmr.y; + } + if ((tmpmr.h || tmpmr.v) && timer_elapsed(last_timer_w) > w_intervals[mk_speed]) { + mouse_report.v = tmpmr.v; + mouse_report.h = tmpmr.h; + } + + if (mouse_report.x || mouse_report.y || mouse_report.v || mouse_report.h) mousekey_send(); + mouse_report = tmpmr; +} + +void adjust_speed(void) { + uint16_t const c_offset = c_offsets[mk_speed]; + uint16_t const w_offset = w_offsets[mk_speed]; + if (mouse_report.x > 0) mouse_report.x = c_offset; + if (mouse_report.x < 0) mouse_report.x = c_offset * -1; + if (mouse_report.y > 0) mouse_report.y = c_offset; + if (mouse_report.y < 0) mouse_report.y = c_offset * -1; + if (mouse_report.h > 0) mouse_report.h = w_offset; + if (mouse_report.h < 0) mouse_report.h = w_offset * -1; + if (mouse_report.v > 0) mouse_report.v = w_offset; + if (mouse_report.v < 0) mouse_report.v = w_offset * -1; + // adjust for diagonals + if (mouse_report.x && mouse_report.y) { + mouse_report.x = times_inv_sqrt2(mouse_report.x); + if (mouse_report.x == 0) { + mouse_report.x = 1; + } + mouse_report.y = times_inv_sqrt2(mouse_report.y); + if (mouse_report.y == 0) { + mouse_report.y = 1; + } + } + if (mouse_report.h && mouse_report.v) { + mouse_report.h = times_inv_sqrt2(mouse_report.h); + mouse_report.v = times_inv_sqrt2(mouse_report.v); + } +} + +void mousekey_on(uint8_t code) { + uint16_t const c_offset = c_offsets[mk_speed]; + uint16_t const w_offset = w_offsets[mk_speed]; + uint8_t const old_speed = mk_speed; + if (code == KC_MS_UP) + mouse_report.y = c_offset * -1; + else if (code == KC_MS_DOWN) + mouse_report.y = c_offset; + else if (code == KC_MS_LEFT) + mouse_report.x = c_offset * -1; + else if (code == KC_MS_RIGHT) + mouse_report.x = c_offset; + else if (code == KC_MS_WH_UP) + mouse_report.v = w_offset; + else if (code == KC_MS_WH_DOWN) + mouse_report.v = w_offset * -1; + else if (code == KC_MS_WH_LEFT) + mouse_report.h = w_offset * -1; + else if (code == KC_MS_WH_RIGHT) + mouse_report.h = w_offset; + else if (IS_MOUSEKEY_BUTTON(code)) + mouse_report.buttons |= 1 << (code - KC_MS_BTN1); + else if (code == KC_MS_ACCEL0) + mk_speed = mkspd_0; + else if (code == KC_MS_ACCEL1) + mk_speed = mkspd_1; + else if (code == KC_MS_ACCEL2) + mk_speed = mkspd_2; + if (mk_speed != old_speed) adjust_speed(); +} + +void mousekey_off(uint8_t code) { +# ifdef MK_MOMENTARY_ACCEL + uint8_t const old_speed = mk_speed; +# endif + if (code == KC_MS_UP && mouse_report.y < 0) + mouse_report.y = 0; + else if (code == KC_MS_DOWN && mouse_report.y > 0) + mouse_report.y = 0; + else if (code == KC_MS_LEFT && mouse_report.x < 0) + mouse_report.x = 0; + else if (code == KC_MS_RIGHT && mouse_report.x > 0) + mouse_report.x = 0; + else if (code == KC_MS_WH_UP && mouse_report.v > 0) + mouse_report.v = 0; + else if (code == KC_MS_WH_DOWN && mouse_report.v < 0) + mouse_report.v = 0; + else if (code == KC_MS_WH_LEFT && mouse_report.h < 0) + mouse_report.h = 0; + else if (code == KC_MS_WH_RIGHT && mouse_report.h > 0) + mouse_report.h = 0; + else if (IS_MOUSEKEY_BUTTON(code)) + mouse_report.buttons &= ~(1 << (code - KC_MS_BTN1)); +# ifdef MK_MOMENTARY_ACCEL + else if (code == KC_MS_ACCEL0) + mk_speed = mkspd_DEFAULT; + else if (code == KC_MS_ACCEL1) + mk_speed = mkspd_DEFAULT; + else if (code == KC_MS_ACCEL2) + mk_speed = mkspd_DEFAULT; + if (mk_speed != old_speed) adjust_speed(); +# endif +} + +#endif /* #ifndef MK_3_SPEED */ + +void mousekey_send(void) { + mousekey_debug(); + uint16_t time = timer_read(); + if (mouse_report.x || mouse_report.y) last_timer_c = time; + if (mouse_report.v || mouse_report.h) last_timer_w = time; + host_mouse_send(&mouse_report); +} + +void mousekey_clear(void) { + mouse_report = (report_mouse_t){}; + mousekey_repeat = 0; + mousekey_wheel_repeat = 0; + mousekey_accel = 0; +} + +static void mousekey_debug(void) { + if (!debug_mouse) return; + print("mousekey [btn|x y v h](rep/acl): ["); + print_hex8(mouse_report.buttons); + print("|"); + print_decs(mouse_report.x); + print(" "); + print_decs(mouse_report.y); + print(" "); + print_decs(mouse_report.v); + print(" "); + print_decs(mouse_report.h); + print("]("); + print_dec(mousekey_repeat); + print("/"); + print_dec(mousekey_accel); + print(")\n"); +} diff --git a/quantum/mousekey.h b/quantum/mousekey.h new file mode 100644 index 0000000000..70dc4bb5c5 --- /dev/null +++ b/quantum/mousekey.h @@ -0,0 +1,179 @@ +/* +Copyright 2011 Jun Wako <wakojun@gmail.com> + +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 + +#include <stdint.h> +#include "host.h" + +#ifndef MK_3_SPEED + +/* max value on report descriptor */ +# ifndef MOUSEKEY_MOVE_MAX +# define MOUSEKEY_MOVE_MAX 127 +# elif MOUSEKEY_MOVE_MAX > 127 +# error MOUSEKEY_MOVE_MAX needs to be smaller than 127 +# endif + +# ifndef MOUSEKEY_WHEEL_MAX +# define MOUSEKEY_WHEEL_MAX 127 +# elif MOUSEKEY_WHEEL_MAX > 127 +# error MOUSEKEY_WHEEL_MAX needs to be smaller than 127 +# endif + +# ifndef MOUSEKEY_MOVE_DELTA +# ifndef MK_KINETIC_SPEED +# define MOUSEKEY_MOVE_DELTA 5 +# else +# define MOUSEKEY_MOVE_DELTA 25 +# endif +# endif +# ifndef MOUSEKEY_WHEEL_DELTA +# define MOUSEKEY_WHEEL_DELTA 1 +# endif +# ifndef MOUSEKEY_DELAY +# ifndef MK_KINETIC_SPEED +# define MOUSEKEY_DELAY 300 +# else +# define MOUSEKEY_DELAY 8 +# endif +# endif +# ifndef MOUSEKEY_INTERVAL +# ifndef MK_KINETIC_SPEED +# define MOUSEKEY_INTERVAL 50 +# else +# define MOUSEKEY_INTERVAL 8 +# endif +# endif +# ifndef MOUSEKEY_MAX_SPEED +# define MOUSEKEY_MAX_SPEED 10 +# endif +# ifndef MOUSEKEY_TIME_TO_MAX +# define MOUSEKEY_TIME_TO_MAX 20 +# endif +# ifndef MOUSEKEY_WHEEL_DELAY +# define MOUSEKEY_WHEEL_DELAY 300 +# endif +# ifndef MOUSEKEY_WHEEL_INTERVAL +# define MOUSEKEY_WHEEL_INTERVAL 100 +# endif +# ifndef MOUSEKEY_WHEEL_MAX_SPEED +# define MOUSEKEY_WHEEL_MAX_SPEED 8 +# endif +# ifndef MOUSEKEY_WHEEL_TIME_TO_MAX +# define MOUSEKEY_WHEEL_TIME_TO_MAX 40 +# endif + +# ifndef MOUSEKEY_INITIAL_SPEED +# define MOUSEKEY_INITIAL_SPEED 100 +# endif +# ifndef MOUSEKEY_BASE_SPEED +# define MOUSEKEY_BASE_SPEED 1000 +# endif +# ifndef MOUSEKEY_DECELERATED_SPEED +# define MOUSEKEY_DECELERATED_SPEED 400 +# endif +# ifndef MOUSEKEY_ACCELERATED_SPEED +# define MOUSEKEY_ACCELERATED_SPEED 3000 +# endif +# ifndef MOUSEKEY_WHEEL_INITIAL_MOVEMENTS +# define MOUSEKEY_WHEEL_INITIAL_MOVEMENTS 16 +# endif +# ifndef MOUSEKEY_WHEEL_BASE_MOVEMENTS +# define MOUSEKEY_WHEEL_BASE_MOVEMENTS 32 +# endif +# ifndef MOUSEKEY_WHEEL_ACCELERATED_MOVEMENTS +# define MOUSEKEY_WHEEL_ACCELERATED_MOVEMENTS 48 +# endif +# ifndef MOUSEKEY_WHEEL_DECELERATED_MOVEMENTS +# define MOUSEKEY_WHEEL_DECELERATED_MOVEMENTS 8 +# endif + +#else /* #ifndef MK_3_SPEED */ + +# ifndef MK_C_OFFSET_UNMOD +# define MK_C_OFFSET_UNMOD 16 +# endif +# ifndef MK_C_INTERVAL_UNMOD +# define MK_C_INTERVAL_UNMOD 16 +# endif +# ifndef MK_C_OFFSET_0 +# define MK_C_OFFSET_0 1 +# endif +# ifndef MK_C_INTERVAL_0 +# define MK_C_INTERVAL_0 32 +# endif +# ifndef MK_C_OFFSET_1 +# define MK_C_OFFSET_1 4 +# endif +# ifndef MK_C_INTERVAL_1 +# define MK_C_INTERVAL_1 16 +# endif +# ifndef MK_C_OFFSET_2 +# define MK_C_OFFSET_2 32 +# endif +# ifndef MK_C_INTERVAL_2 +# define MK_C_INTERVAL_2 16 +# endif + +# ifndef MK_W_OFFSET_UNMOD +# define MK_W_OFFSET_UNMOD 1 +# endif +# ifndef MK_W_INTERVAL_UNMOD +# define MK_W_INTERVAL_UNMOD 40 +# endif +# ifndef MK_W_OFFSET_0 +# define MK_W_OFFSET_0 1 +# endif +# ifndef MK_W_INTERVAL_0 +# define MK_W_INTERVAL_0 360 +# endif +# ifndef MK_W_OFFSET_1 +# define MK_W_OFFSET_1 1 +# endif +# ifndef MK_W_INTERVAL_1 +# define MK_W_INTERVAL_1 120 +# endif +# ifndef MK_W_OFFSET_2 +# define MK_W_OFFSET_2 1 +# endif +# ifndef MK_W_INTERVAL_2 +# define MK_W_INTERVAL_2 20 +# endif + +#endif /* #ifndef MK_3_SPEED */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint8_t mk_delay; +extern uint8_t mk_interval; +extern uint8_t mk_max_speed; +extern uint8_t mk_time_to_max; +extern uint8_t mk_wheel_max_speed; +extern uint8_t mk_wheel_time_to_max; + +void mousekey_task(void); +void mousekey_on(uint8_t code); +void mousekey_off(uint8_t code); +void mousekey_clear(void); +void mousekey_send(void); + +#ifdef __cplusplus +} +#endif diff --git a/quantum/quantum.c b/quantum/quantum.c index cf16e953a2..db99e80fa0 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -25,10 +25,6 @@ # include "backlight.h" #endif -#ifdef FAUXCLICKY_ENABLE -# include "fauxclicky.h" -#endif - #ifdef API_ENABLE # include "api.h" #endif @@ -225,9 +221,6 @@ bool process_record_quantum(keyrecord_t *record) { #ifdef HAPTIC_ENABLE process_haptic(keycode, record) && #endif // HAPTIC_ENABLE -#if defined(RGB_MATRIX_ENABLE) - process_rgb_matrix(keycode, record) && -#endif #if defined(VIA_ENABLE) process_record_via(keycode, record) && #endif @@ -310,17 +303,6 @@ bool process_record_quantum(keyrecord_t *record) { case EEPROM_RESET: eeconfig_init(); return false; -#ifdef FAUXCLICKY_ENABLE - case FC_TOG: - FAUXCLICKY_TOGGLE; - return false; - case FC_ON: - FAUXCLICKY_ON; - return false; - case FC_OFF: - FAUXCLICKY_OFF; - return false; -#endif #ifdef VELOCIKEY_ENABLE case VLK_TOG: velocikey_toggle(); @@ -391,6 +373,29 @@ __attribute__((weak)) const uint8_t ascii_to_altgr_lut[16] PROGMEM = { KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), }; +/* Bit-Packed look-up table to convert an ASCII character to whether + * [Space] needs to be sent after the keycode + */ +__attribute__((weak)) const uint8_t ascii_to_dead_lut[16] PROGMEM = { + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), + KCLUT_ENTRY(0, 0, 0, 0, 0, 0, 0, 0), +}; + /* Look-up table to convert an ASCII character to a keycode. */ __attribute__((weak)) const uint8_t ascii_to_keycode_lut[128] PROGMEM = { @@ -531,6 +536,7 @@ void send_char(char ascii_code) { uint8_t keycode = pgm_read_byte(&ascii_to_keycode_lut[(uint8_t)ascii_code]); bool is_shifted = PGM_LOADBIT(ascii_to_shift_lut, (uint8_t)ascii_code); bool is_altgred = PGM_LOADBIT(ascii_to_altgr_lut, (uint8_t)ascii_code); + bool is_dead = PGM_LOADBIT(ascii_to_dead_lut, (uint8_t)ascii_code); if (is_shifted) { register_code(KC_LSFT); @@ -545,6 +551,9 @@ void send_char(char ascii_code) { if (is_shifted) { unregister_code(KC_LSFT); } + if (is_dead) { + tap_code(KC_SPACE); + } } void set_single_persistent_default_layer(uint8_t default_layer) { @@ -629,6 +638,26 @@ void matrix_init_quantum() { } void matrix_scan_quantum() { +#if defined(AUDIO_ENABLE) + // There are some tasks that need to be run a little bit + // after keyboard startup, or else they will not work correctly + // because of interaction with the USB device state, which + // may still be in flux... + // + // At the moment the only feature that needs this is the + // startup song. + static bool delayed_tasks_run = false; + static uint16_t delayed_task_timer = 0; + if (!delayed_tasks_run) { + if (!delayed_task_timer) { + delayed_task_timer = timer_read(); + } else if (timer_elapsed(delayed_task_timer) > 300) { + audio_startup(); + delayed_tasks_run = true; + } + } +#endif + #if defined(AUDIO_ENABLE) && !defined(NO_MUSIC_MODE) matrix_scan_music(); #endif @@ -649,10 +678,6 @@ void matrix_scan_quantum() { led_matrix_task(); #endif -#ifdef RGB_MATRIX_ENABLE - rgb_matrix_task(); -#endif - #ifdef WPM_ENABLE decay_wpm(); #endif diff --git a/quantum/quantum.h b/quantum/quantum.h index dd2a6dd53a..36a983d575 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -53,6 +53,7 @@ #include "eeconfig.h" #include "bootloader.h" #include "timer.h" +#include "sync_timer.h" #include "config_common.h" #include "gpio.h" #include "atomic_util.h" @@ -194,6 +195,42 @@ extern layer_state_t layer_state; # include "wpm.h" #endif +#ifdef USBPD_ENABLE +# include "usbpd.h" +#endif + +// Function substitutions to ease GPIO manipulation +#if defined(__AVR__) + +/* The AVR series GPIOs have a one clock read delay for changes in the digital input signal. + * But here's more margin to make it two clocks. */ +# if !defined(GPIO_INPUT_PIN_DELAY) +# define GPIO_INPUT_PIN_DELAY 2 +# endif +# define waitInputPinDelay() wait_cpuclock(GPIO_INPUT_PIN_DELAY) + +#elif defined(__ARMEL__) || defined(__ARMEB__) + +/* For GPIOs on ARM-based MCUs, the input pins are sampled by the clock of the bus + * to which the GPIO is connected. + * The connected buses differ depending on the various series of MCUs. + * And since the instruction execution clock of the CPU and the bus clock of GPIO are different, + * there is a delay of several clocks to read the change of the input signal. + * + * Define this delay with the GPIO_INPUT_PIN_DELAY macro. + * If the GPIO_INPUT_PIN_DELAY macro is not defined, the following default values will be used. + * (A fairly large value of 0.25 microseconds is set.) + */ +# if !defined(GPIO_INPUT_PIN_DELAY) +# if defined(STM32_SYSCLK) +# define GPIO_INPUT_PIN_DELAY (STM32_SYSCLK / 1000000L / 4) +# elif defined(KINETIS_SYSCLK_FREQUENCY) +# define GPIO_INPUT_PIN_DELAY (KINETIS_SYSCLK_FREQUENCY / 1000000L / 4) +# endif +# endif +# define waitInputPinDelay() wait_cpuclock(GPIO_INPUT_PIN_DELAY) + +#endif #define SEND_STRING(string) send_string_P(PSTR(string)) #define SEND_STRING_DELAY(string, interval) send_string_with_delay_P(PSTR(string), interval) @@ -201,6 +238,7 @@ extern layer_state_t layer_state; extern const uint8_t ascii_to_keycode_lut[128]; extern const uint8_t ascii_to_shift_lut[16]; extern const uint8_t ascii_to_altgr_lut[16]; +extern const uint8_t ascii_to_dead_lut[16]; // clang-format off #define KCLUT_ENTRY(a, b, c, d, e, f, g, h) \ ( ((a) ? 1 : 0) << 0 \ diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index 0160c5586d..e0f5dbc61e 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -150,13 +150,6 @@ enum quantum_keycodes { CLICKY_DOWN, CLICKY_RESET, -#ifdef FAUXCLICKY_ENABLE - // Faux clicky - FC_ON, - FC_OFF, - FC_TOG, -#endif - // Music mode on/off/toggle MU_ON, MU_OFF, @@ -717,6 +710,9 @@ enum quantum_keycodes { #define CK_DOWN CLICKY_DOWN #define CK_ON CLICKY_ENABLE #define CK_OFF CLICKY_DISABLE +#define FC_ON CLICKY_ENABLE +#define FC_OFF CLICKY_DISABLE +#define FC_TOGG CLICKY_TOGGLE #define RGB_MOD RGB_MODE_FORWARD #define RGB_RMOD RGB_MODE_REVERSE diff --git a/quantum/rgb_matrix.c b/quantum/rgb_matrix.c index 04af3ae9ec..ec17b4d72c 100644 --- a/quantum/rgb_matrix.c +++ b/quantum/rgb_matrix.c @@ -184,11 +184,12 @@ void rgb_matrix_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) { void rgb_matrix_set_color_all(uint8_t red, uint8_t green, uint8_t blue) { rgb_matrix_driver.set_color_all(red, green, blue); } -bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) { +void process_rgb_matrix(uint8_t row, uint8_t col, bool pressed) { +#ifndef RGB_MATRIX_SPLIT + if (!is_keyboard_master()) return; +#endif #if RGB_DISABLE_TIMEOUT > 0 - if (record->event.pressed) { - rgb_anykey_timer = 0; - } + rgb_anykey_timer = 0; #endif // RGB_DISABLE_TIMEOUT > 0 #ifdef RGB_MATRIX_KEYREACTIVE_ENABLED @@ -196,12 +197,12 @@ bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) { uint8_t led_count = 0; # if defined(RGB_MATRIX_KEYRELEASES) - if (!record->event.pressed) + if (!pressed) # elif defined(RGB_MATRIX_KEYPRESSES) - if (record->event.pressed) + if (pressed) # endif // defined(RGB_MATRIX_KEYRELEASES) { - led_count = rgb_matrix_map_row_column_to_led(record->event.key.row, record->event.key.col, led); + led_count = rgb_matrix_map_row_column_to_led(row, col, led); } if (last_hit_buffer.count + led_count > LED_HITS_TO_REMEMBER) { @@ -224,11 +225,9 @@ bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) { #if defined(RGB_MATRIX_FRAMEBUFFER_EFFECTS) && !defined(DISABLE_RGB_MATRIX_TYPING_HEATMAP) if (rgb_matrix_config.mode == RGB_MATRIX_TYPING_HEATMAP) { - process_rgb_matrix_typing_heatmap(record); + process_rgb_matrix_typing_heatmap(row, col); } #endif // defined(RGB_MATRIX_FRAMEBUFFER_EFFECTS) && !defined(DISABLE_RGB_MATRIX_TYPING_HEATMAP) - - return true; } void rgb_matrix_test(void) { @@ -266,9 +265,9 @@ static bool rgb_matrix_none(effect_params_t *params) { static void rgb_task_timers(void) { #if defined(RGB_MATRIX_KEYREACTIVE_ENABLED) || RGB_DISABLE_TIMEOUT > 0 - uint32_t deltaTime = timer_elapsed32(rgb_timer_buffer); + uint32_t deltaTime = sync_timer_elapsed32(rgb_timer_buffer); #endif // defined(RGB_MATRIX_KEYREACTIVE_ENABLED) || RGB_DISABLE_TIMEOUT > 0 - rgb_timer_buffer = timer_read32(); + rgb_timer_buffer = sync_timer_read32(); // Update double buffer timers #if RGB_DISABLE_TIMEOUT > 0 @@ -296,7 +295,7 @@ static void rgb_task_timers(void) { static void rgb_task_sync(void) { // next task - if (timer_elapsed32(g_rgb_timer) >= RGB_MATRIX_LED_FLUSH_LIMIT) rgb_task_state = STARTING; + if (sync_timer_elapsed32(g_rgb_timer) >= RGB_MATRIX_LED_FLUSH_LIMIT) rgb_task_state = STARTING; } static void rgb_task_start(void) { diff --git a/quantum/rgb_matrix.h b/quantum/rgb_matrix.h index 8c80c1bff0..bb8bcfab68 100644 --- a/quantum/rgb_matrix.h +++ b/quantum/rgb_matrix.h @@ -98,7 +98,7 @@ uint8_t rgb_matrix_map_row_column_to_led(uint8_t row, uint8_t column, uint8_t *l void rgb_matrix_set_color(int index, uint8_t red, uint8_t green, uint8_t blue); void rgb_matrix_set_color_all(uint8_t red, uint8_t green, uint8_t blue); -bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record); +void process_rgb_matrix(uint8_t row, uint8_t col, bool pressed); void rgb_matrix_task(void); diff --git a/quantum/rgb_matrix_animations/typing_heatmap_anim.h b/quantum/rgb_matrix_animations/typing_heatmap_anim.h index e06437bf76..e7dda11a2f 100644 --- a/quantum/rgb_matrix_animations/typing_heatmap_anim.h +++ b/quantum/rgb_matrix_animations/typing_heatmap_anim.h @@ -6,9 +6,7 @@ RGB_MATRIX_EFFECT(TYPING_HEATMAP) # define RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 25 # endif -void process_rgb_matrix_typing_heatmap(keyrecord_t* record) { - uint8_t row = record->event.key.row; - uint8_t col = record->event.key.col; +void process_rgb_matrix_typing_heatmap(uint8_t row, uint8_t col) { uint8_t m_row = row - 1; uint8_t p_row = row + 1; uint8_t m_col = col - 1; diff --git a/quantum/rgblight.c b/quantum/rgblight.c index ac4ff9bfda..904c02d6cd 100644 --- a/quantum/rgblight.c +++ b/quantum/rgblight.c @@ -29,7 +29,7 @@ #endif #include "wait.h" #include "progmem.h" -#include "timer.h" +#include "sync_timer.h" #include "rgblight.h" #include "color.h" #include "debug.h" @@ -42,6 +42,9 @@ #ifndef MIN # define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif #ifdef RGBLIGHT_SPLIT /* for split keyboard */ @@ -81,6 +84,26 @@ static uint8_t mode_base_table[] = { #include "rgblight_modes.h" }; +#if !defined(RGBLIGHT_DEFAULT_MODE) +# define RGBLIGHT_DEFAULT_MODE RGBLIGHT_MODE_STATIC_LIGHT +#endif + +#if !defined(RGBLIGHT_DEFAULT_HUE) +# define RGBLIGHT_DEFAULT_HUE 0 +#endif + +#if !defined(RGBLIGHT_DEFAULT_SAT) +# define RGBLIGHT_DEFAULT_SAT UINT8_MAX +#endif + +#if !defined(RGBLIGHT_DEFAULT_VAL) +# define RGBLIGHT_DEFAULT_VAL RGBLIGHT_LIMIT_VAL +#endif + +#if !defined(RGBLIGHT_DEFAULT_SPD) +# define RGBLIGHT_DEFAULT_SPD 0 +#endif + static inline int is_static_effect(uint8_t mode) { return memchr(static_effect_table, mode, sizeof(static_effect_table)) != NULL; } #ifdef RGBLIGHT_LED_MAP @@ -180,11 +203,11 @@ void eeconfig_update_rgblight_current(void) { eeconfig_update_rgblight(rgblight_ void eeconfig_update_rgblight_default(void) { rgblight_config.enable = 1; - rgblight_config.mode = RGBLIGHT_MODE_STATIC_LIGHT; - rgblight_config.hue = 0; - rgblight_config.sat = UINT8_MAX; - rgblight_config.val = RGBLIGHT_LIMIT_VAL; - rgblight_config.speed = 0; + rgblight_config.mode = RGBLIGHT_DEFAULT_MODE; + rgblight_config.hue = RGBLIGHT_DEFAULT_HUE; + rgblight_config.sat = RGBLIGHT_DEFAULT_SAT; + rgblight_config.val = RGBLIGHT_DEFAULT_VAL; + rgblight_config.speed = RGBLIGHT_DEFAULT_SPD; RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS; eeconfig_update_rgblight(rgblight_config.raw); } @@ -700,18 +723,16 @@ static void rgblight_layers_write(void) { # ifdef RGBLIGHT_LAYER_BLINK rgblight_layer_mask_t _blinked_layer_mask = 0; -uint16_t _blink_duration = 0; static uint16_t _blink_timer; void rgblight_blink_layer(uint8_t layer, uint16_t duration_ms) { rgblight_set_layer_state(layer, true); _blinked_layer_mask |= (rgblight_layer_mask_t)1 << layer; - _blink_timer = timer_read(); - _blink_duration = duration_ms; + _blink_timer = sync_timer_read() + duration_ms; } void rgblight_unblink_layers(void) { - if (_blinked_layer_mask != 0 && timer_elapsed(_blink_timer) > _blink_duration) { + if (_blinked_layer_mask != 0 && timer_expired(sync_timer_read(), _blink_timer)) { for (uint8_t layer = 0; layer < RGBLIGHT_MAX_LAYERS; layer++) { if ((_blinked_layer_mask & (rgblight_layer_mask_t)1 << layer) != 0) { rgblight_set_layer_state(layer, false); @@ -886,7 +907,7 @@ void rgblight_timer_enable(void) { if (!is_static_effect(rgblight_config.mode)) { rgblight_status.timer_enabled = true; } - animation_status.last_timer = timer_read(); + animation_status.last_timer = sync_timer_read(); RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE; dprintf("rgblight timer enabled.\n"); } @@ -989,24 +1010,25 @@ void rgblight_task(void) { # endif # ifdef RGBLIGHT_EFFECT_TWINKLE else if (rgblight_status.base_mode == RGBLIGHT_MODE_TWINKLE) { - interval_time = get_interval_time(&RGBLED_TWINKLE_INTERVALS[delta % 3], 5, 50); + interval_time = get_interval_time(&RGBLED_TWINKLE_INTERVALS[delta % 3], 5, 30); effect_func = (effect_func_t)rgblight_effect_twinkle; } # endif if (animation_status.restart) { animation_status.restart = false; - animation_status.last_timer = timer_read() - interval_time - 1; + animation_status.last_timer = sync_timer_read(); animation_status.pos16 = 0; // restart signal to local each effect } - if (timer_elapsed(animation_status.last_timer) >= interval_time) { + uint16_t now = sync_timer_read(); + if (timer_expired(now, animation_status.last_timer)) { # if defined(RGBLIGHT_SPLIT) && !defined(RGBLIGHT_SPLIT_NO_ANIMATION_SYNC) static uint16_t report_last_timer = 0; static bool tick_flag = false; uint16_t oldpos16; if (tick_flag) { tick_flag = false; - if (timer_elapsed(report_last_timer) >= 30000) { - report_last_timer = timer_read(); + if (timer_expired(now, report_last_timer)) { + report_last_timer += 30000; dprintf("rgblight animation tick report to slave\n"); RGBLIGHT_SPLIT_ANIMATION_TICK; } @@ -1030,8 +1052,7 @@ void rgblight_task(void) { #endif /* RGBLIGHT_USE_TIMER */ -// Effects -#ifdef RGBLIGHT_EFFECT_BREATHING +#if defined(RGBLIGHT_EFFECT_BREATHING) || defined(RGBLIGHT_EFFECT_TWINKLE) # ifndef RGBLIGHT_EFFECT_BREATHE_CENTER # ifndef RGBLIGHT_BREATHE_TABLE_SIZE @@ -1040,17 +1061,24 @@ void rgblight_task(void) { # include <rgblight_breathe_table.h> # endif -__attribute__((weak)) const uint8_t RGBLED_BREATHING_INTERVALS[] PROGMEM = {30, 20, 10, 5}; - -void rgblight_effect_breathing(animation_status_t *anim) { - float val; - +static uint8_t breathe_calc(uint8_t pos) { // http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/ # ifdef RGBLIGHT_EFFECT_BREATHE_TABLE - val = pgm_read_byte(&rgblight_effect_breathe_table[anim->pos / table_scale]); + return pgm_read_byte(&rgblight_effect_breathe_table[pos / table_scale]); # else - val = (exp(sin((anim->pos / 255.0) * M_PI)) - RGBLIGHT_EFFECT_BREATHE_CENTER / M_E) * (RGBLIGHT_EFFECT_BREATHE_MAX / (M_E - 1 / M_E)); + return (exp(sin((pos / 255.0) * M_PI)) - RGBLIGHT_EFFECT_BREATHE_CENTER / M_E) * (RGBLIGHT_EFFECT_BREATHE_MAX / (M_E - 1 / M_E)); # endif +} + +#endif + +// Effects +#ifdef RGBLIGHT_EFFECT_BREATHING + +__attribute__((weak)) const uint8_t RGBLED_BREATHING_INTERVALS[] PROGMEM = {30, 20, 10, 5}; + +void rgblight_effect_breathing(animation_status_t *anim) { + uint8_t val = breathe_calc(anim->pos); rgblight_sethsv_noeeprom_old(rgblight_config.hue, rgblight_config.sat, val); anim->pos = (anim->pos + 1); } @@ -1302,48 +1330,54 @@ void rgblight_effect_alternating(animation_status_t *anim) { #endif #ifdef RGBLIGHT_EFFECT_TWINKLE -__attribute__((weak)) const uint8_t RGBLED_TWINKLE_INTERVALS[] PROGMEM = {50, 25, 10}; +__attribute__((weak)) const uint8_t RGBLED_TWINKLE_INTERVALS[] PROGMEM = {30, 15, 5}; typedef struct PACKED { HSV hsv; uint8_t life; - bool up; + uint8_t max_life; } TwinkleState; static TwinkleState led_twinkle_state[RGBLED_NUM]; void rgblight_effect_twinkle(animation_status_t *anim) { - bool random_color = anim->delta / 3; - bool restart = anim->pos == 0; - anim->pos = 1; + const bool random_color = anim->delta / 3; + const bool restart = anim->pos == 0; + anim->pos = 1; + + const uint8_t bottom = breathe_calc(0); + const uint8_t top = breathe_calc(127); + + uint8_t frac(uint8_t n, uint8_t d) { return (uint16_t)255 * n / d; } + uint8_t scale(uint16_t v, uint8_t scale) { return (v * scale) >> 8; } for (uint8_t i = 0; i < rgblight_ranges.effect_num_leds; i++) { TwinkleState *t = &(led_twinkle_state[i]); HSV * c = &(t->hsv); + + if (!random_color) { + c->h = rgblight_config.hue; + c->s = rgblight_config.sat; + } + if (restart) { // Restart - t->life = 0; - t->hsv.v = 0; + t->life = 0; + c->v = 0; } else if (t->life) { // This LED is already on, either brightening or dimming t->life--; - uint8_t on = t->up ? RGBLIGHT_EFFECT_TWINKLE_LIFE - t->life : t->life; - c->v = (uint16_t)rgblight_config.val * on / RGBLIGHT_EFFECT_TWINKLE_LIFE; - if (t->life == 0 && t->up) { - t->up = false; - t->life = RGBLIGHT_EFFECT_TWINKLE_LIFE; - } - if (!random_color) { - c->h = rgblight_config.hue; - c->s = rgblight_config.sat; - } - } else if (rand() < RAND_MAX * RGBLIGHT_EFFECT_TWINKLE_PROBABILITY) { + uint8_t unscaled = frac(breathe_calc(frac(t->life, t->max_life)) - bottom, top - bottom); + c->v = scale(rgblight_config.val, unscaled); + } else if (rand() < scale((uint16_t)RAND_MAX * RGBLIGHT_EFFECT_TWINKLE_PROBABILITY, 127 + rgblight_config.val / 2)) { // This LED is off, but was randomly selected to start brightening - c->h = random_color ? rand() % 0xFF : rgblight_config.hue; - c->s = random_color ? (rand() % (rgblight_config.sat / 2)) + (rgblight_config.sat / 2) : rgblight_config.sat; - c->v = 0; - t->life = RGBLIGHT_EFFECT_TWINKLE_LIFE; - t->up = true; + if (random_color) { + c->h = rand() % 0xFF; + c->s = (rand() % (rgblight_config.sat / 2)) + (rgblight_config.sat / 2); + } + c->v = 0; + t->max_life = MAX(20, MIN(RGBLIGHT_EFFECT_TWINKLE_LIFE, rgblight_config.val)); + t->life = t->max_life; } else { // This LED is off, and was NOT selected to start brightening } diff --git a/quantum/rgblight.h b/quantum/rgblight.h index 1854fee999..028b3ea416 100644 --- a/quantum/rgblight.h +++ b/quantum/rgblight.h @@ -150,7 +150,7 @@ enum RGBLIGHT_EFFECT_MODE { # endif # ifndef RGBLIGHT_EFFECT_TWINKLE_LIFE -# define RGBLIGHT_EFFECT_TWINKLE_LIFE 75 +# define RGBLIGHT_EFFECT_TWINKLE_LIFE 200 # endif # ifndef RGBLIGHT_EFFECT_TWINKLE_PROBABILITY diff --git a/quantum/split_common/matrix.c b/quantum/split_common/matrix.c index 51bf8b1095..d6636b886a 100644 --- a/quantum/split_common/matrix.c +++ b/quantum/split_common/matrix.c @@ -114,9 +114,9 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) // Start with a clear matrix row matrix_row_t current_row_value = 0; - // Select row and wait for row selecton to stabilize + // Select row select_row(current_row); - matrix_io_delay(); + matrix_output_select_delay(); // For each col... for (uint8_t col_index = 0; col_index < MATRIX_COLS; col_index++) { @@ -129,6 +129,9 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) // Unselect row unselect_row(current_row); + if (current_row + 1 < MATRIX_ROWS) { + matrix_output_unselect_delay(); // wait for row signal to go HIGH + } // If the row has changed, store the row and return the changed flag. if (current_matrix[current_row] != current_row_value) { @@ -160,9 +163,9 @@ static void init_pins(void) { static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { bool matrix_changed = false; - // Select col and wait for col selecton to stabilize + // Select col select_col(current_col); - matrix_io_delay(); + matrix_output_select_delay(); // For each row... for (uint8_t row_index = 0; row_index < ROWS_PER_HAND; row_index++) { @@ -188,6 +191,9 @@ static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) // Unselect col unselect_col(current_col); + if (current_col + 1 < MATRIX_COLS) { + matrix_output_unselect_delay(); // wait for col signal to go HIGH + } return matrix_changed; } @@ -245,48 +251,62 @@ void matrix_init(void) { split_post_init(); } -void matrix_post_scan(void) { +bool matrix_post_scan(void) { + bool changed = false; if (is_keyboard_master()) { static uint8_t error_count; - if (!transport_master(matrix + thatHand)) { + matrix_row_t slave_matrix[ROWS_PER_HAND] = {0}; + if (!transport_master(matrix + thisHand, slave_matrix)) { error_count++; if (error_count > ERROR_DISCONNECT_COUNT) { // reset other half if disconnected for (int i = 0; i < ROWS_PER_HAND; ++i) { matrix[thatHand + i] = 0; + slave_matrix[i] = 0; } + + changed = true; } } else { error_count = 0; + + for (int i = 0; i < ROWS_PER_HAND; ++i) { + if (matrix[thatHand + i] != slave_matrix[i]) { + matrix[thatHand + i] = slave_matrix[i]; + changed = true; + } + } } matrix_scan_quantum(); } else { - transport_slave(matrix + thisHand); + transport_slave(matrix + thatHand, matrix + thisHand); matrix_slave_scan_user(); } + + return changed; } uint8_t matrix_scan(void) { - bool changed = false; + bool local_changed = false; #if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW) // Set row, read cols for (uint8_t current_row = 0; current_row < ROWS_PER_HAND; current_row++) { - changed |= read_cols_on_row(raw_matrix, current_row); + local_changed |= read_cols_on_row(raw_matrix, current_row); } #elif (DIODE_DIRECTION == ROW2COL) // Set col, read rows for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) { - changed |= read_rows_on_col(raw_matrix, current_col); + local_changed |= read_rows_on_col(raw_matrix, current_col); } #endif - debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, changed); + debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, local_changed); - matrix_post_scan(); - return (uint8_t)changed; + bool remote_changed = matrix_post_scan(); + return (uint8_t)(local_changed || remote_changed); } diff --git a/quantum/split_common/transport.c b/quantum/split_common/transport.c index 467ff81a97..61b61ea08c 100644 --- a/quantum/split_common/transport.c +++ b/quantum/split_common/transport.c @@ -6,6 +6,7 @@ #include "quantum.h" #define ROWS_PER_HAND (MATRIX_ROWS / 2) +#define SYNC_TIMER_OFFSET 2 #ifdef RGBLIGHT_ENABLE # include "rgblight.h" @@ -27,8 +28,23 @@ static pin_t encoders_pad[] = ENCODERS_PAD_A; # include "i2c_slave.h" typedef struct _I2C_slave_buffer_t { +# ifndef DISABLE_SYNC_TIMER + uint32_t sync_timer; +# endif +# ifdef SPLIT_TRANSPORT_MIRROR + matrix_row_t mmatrix[ROWS_PER_HAND]; +# endif matrix_row_t smatrix[ROWS_PER_HAND]; - uint8_t backlight_level; +# ifdef SPLIT_MODS_ENABLE + uint8_t real_mods; + uint8_t weak_mods; +# ifndef NO_ACTION_ONESHOT + uint8_t oneshot_mods; +# endif +# endif +# ifdef BACKLIGHT_ENABLE + uint8_t backlight_level; +# endif # if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) rgblight_syncinfo_t rgblight_sync; # endif @@ -42,9 +58,14 @@ typedef struct _I2C_slave_buffer_t { static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg; +# define I2C_SYNC_TIME_START offsetof(I2C_slave_buffer_t, sync_timer) +# define I2C_KEYMAP_MASTER_START offsetof(I2C_slave_buffer_t, mmatrix) +# define I2C_KEYMAP_SLAVE_START offsetof(I2C_slave_buffer_t, smatrix) +# define I2C_REAL_MODS_START offsetof(I2C_slave_buffer_t, real_mods) +# define I2C_WEAK_MODS_START offsetof(I2C_slave_buffer_t, weak_mods) +# define I2C_ONESHOT_MODS_START offsetof(I2C_slave_buffer_t, oneshot_mods) # define I2C_BACKLIGHT_START offsetof(I2C_slave_buffer_t, backlight_level) # define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync) -# define I2C_KEYMAP_START offsetof(I2C_slave_buffer_t, smatrix) # define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state) # define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm) @@ -55,8 +76,11 @@ static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_re # endif // Get rows from other half over i2c -bool transport_master(matrix_row_t matrix[]) { - i2c_readReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_START, (void *)matrix, sizeof(i2c_buffer->smatrix), TIMEOUT); +bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + i2c_readReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_SLAVE_START, (void *)slave_matrix, sizeof(i2c_buffer->smatrix), TIMEOUT); +# ifdef SPLIT_TRANSPORT_MIRROR + i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_MASTER_START, (void *)master_matrix, sizeof(i2c_buffer->mmatrix), TIMEOUT); +# endif // write backlight info # ifdef BACKLIGHT_ENABLE @@ -91,12 +115,48 @@ bool transport_master(matrix_row_t matrix[]) { } } # endif + +# ifdef SPLIT_MODS_ENABLE + uint8_t real_mods = get_mods(); + if (real_mods != i2c_buffer->real_mods) { + if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_REAL_MODS_START, (void *)&real_mods, sizeof(real_mods), TIMEOUT) >= 0) { + i2c_buffer->real_mods = real_mods; + } + } + + uint8_t weak_mods = get_weak_mods(); + if (weak_mods != i2c_buffer->weak_mods) { + if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WEAK_MODS_START, (void *)&weak_mods, sizeof(weak_mods), TIMEOUT) >= 0) { + i2c_buffer->weak_mods = weak_mods; + } + } + +# ifndef NO_ACTION_ONESHOT + uint8_t oneshot_mods = get_oneshot_mods(); + if (oneshot_mods != i2c_buffer->oneshot_mods) { + if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_ONESHOT_MODS_START, (void *)&oneshot_mods, sizeof(oneshot_mods), TIMEOUT) >= 0) { + i2c_buffer->oneshot_mods = oneshot_mods; + } + } +# endif +# endif + +# ifndef DISABLE_SYNC_TIMER + i2c_buffer->sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; + i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_SYNC_TIME_START, (void *)&i2c_buffer->sync_timer, sizeof(i2c_buffer->sync_timer), TIMEOUT); +# endif return true; } -void transport_slave(matrix_row_t matrix[]) { +void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { +# ifndef DISABLE_SYNC_TIMER + sync_timer_update(i2c_buffer->sync_timer); +# endif // Copy matrix to I2C buffer - memcpy((void *)i2c_buffer->smatrix, (void *)matrix, sizeof(i2c_buffer->smatrix)); + memcpy((void *)i2c_buffer->smatrix, (void *)slave_matrix, sizeof(i2c_buffer->smatrix)); +# ifdef SPLIT_TRANSPORT_MIRROR + memcpy((void *)master_matrix, (void *)i2c_buffer->mmatrix, sizeof(i2c_buffer->mmatrix)); +# endif // Read Backlight Info # ifdef BACKLIGHT_ENABLE @@ -118,6 +178,14 @@ void transport_slave(matrix_row_t matrix[]) { # ifdef WPM_ENABLE set_current_wpm(i2c_buffer->current_wpm); # endif + +# ifdef SPLIT_MODS_ENABLE + set_mods(i2c_buffer->real_mods); + set_weak_mods(i2c_buffer->weak_mods); +# ifndef NO_ACTION_ONESHOT + set_oneshot_mods(i2c_buffer->oneshot_mods); +# endif +# endif } void transport_master_init(void) { i2c_init(); } @@ -139,11 +207,24 @@ typedef struct _Serial_s2m_buffer_t { } Serial_s2m_buffer_t; typedef struct _Serial_m2s_buffer_t { +# ifdef SPLIT_MODS_ENABLE + uint8_t real_mods; + uint8_t weak_mods; +# ifndef NO_ACTION_ONESHOT + uint8_t oneshot_mods; +# endif +# endif +# ifndef DISABLE_SYNC_TIMER + uint32_t sync_timer; +# endif +# ifdef SPLIT_TRANSPORT_MIRROR + matrix_row_t mmatrix[ROWS_PER_HAND]; +# endif # ifdef BACKLIGHT_ENABLE - uint8_t backlight_level; + uint8_t backlight_level; # endif # ifdef WPM_ENABLE - uint8_t current_wpm; + uint8_t current_wpm; # endif } Serial_m2s_buffer_t; @@ -221,7 +302,7 @@ void transport_rgblight_slave(void) { # define transport_rgblight_slave() # endif -bool transport_master(matrix_row_t matrix[]) { +bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { # ifndef SERIAL_USE_MULTI_TRANSACTION if (soft_serial_transaction() != TRANSACTION_END) { return false; @@ -235,7 +316,10 @@ bool transport_master(matrix_row_t matrix[]) { // TODO: if MATRIX_COLS > 8 change to unpack() for (int i = 0; i < ROWS_PER_HAND; ++i) { - matrix[i] = serial_s2m_buffer.smatrix[i]; + slave_matrix[i] = serial_s2m_buffer.smatrix[i]; +# ifdef SPLIT_TRANSPORT_MIRROR + serial_m2s_buffer.mmatrix[i] = master_matrix[i]; +# endif } # ifdef BACKLIGHT_ENABLE @@ -249,16 +333,34 @@ bool transport_master(matrix_row_t matrix[]) { # ifdef WPM_ENABLE // Write wpm to slave - serial_m2s_buffer.current_wpm = get_current_wpm(); + serial_m2s_buffer.current_wpm = get_current_wpm(); +# endif + +# ifdef SPLIT_MODS_ENABLE + serial_m2s_buffer.real_mods = get_mods(); + serial_m2s_buffer.weak_mods = get_weak_mods(); +# ifndef NO_ACTION_ONESHOT + serial_m2s_buffer.oneshot_mods = get_oneshot_mods(); +# endif +# endif +# ifndef DISABLE_SYNC_TIMER + serial_m2s_buffer.sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; # endif return true; } -void transport_slave(matrix_row_t matrix[]) { +void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { transport_rgblight_slave(); +# ifndef DISABLE_SYNC_TIMER + sync_timer_update(serial_m2s_buffer.sync_timer); +# endif + // TODO: if MATRIX_COLS > 8 change to pack() for (int i = 0; i < ROWS_PER_HAND; ++i) { - serial_s2m_buffer.smatrix[i] = matrix[i]; + serial_s2m_buffer.smatrix[i] = slave_matrix[i]; +# ifdef SPLIT_TRANSPORT_MIRROR + master_matrix[i] = serial_m2s_buffer.mmatrix[i]; +# endif } # ifdef BACKLIGHT_ENABLE backlight_set(serial_m2s_buffer.backlight_level); @@ -271,6 +373,14 @@ void transport_slave(matrix_row_t matrix[]) { # ifdef WPM_ENABLE set_current_wpm(serial_m2s_buffer.current_wpm); # endif + +# ifdef SPLIT_MODS_ENABLE + set_mods(serial_m2s_buffer.real_mods); + set_weak_mods(serial_m2s_buffer.weak_mods); +# ifndef NO_ACTION_ONESHOT + set_oneshot_mods(serial_m2s_buffer.oneshot_mods); +# endif +# endif } #endif diff --git a/quantum/split_common/transport.h b/quantum/split_common/transport.h index c667bfab85..a9f66301bf 100644 --- a/quantum/split_common/transport.h +++ b/quantum/split_common/transport.h @@ -6,5 +6,5 @@ void transport_master_init(void); void transport_slave_init(void); // returns false if valid data not received from slave -bool transport_master(matrix_row_t matrix[]); -void transport_slave(matrix_row_t matrix[]); +bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); +void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); |