diff options
Diffstat (limited to 'quantum')
45 files changed, 1677 insertions, 137 deletions
diff --git a/quantum/backlight/backlight_avr.c b/quantum/backlight/backlight_avr.c index b3e882ffe1..4d66da80ba 100644 --- a/quantum/backlight/backlight_avr.c +++ b/quantum/backlight/backlight_avr.c @@ -3,6 +3,11 @@ #include "backlight_driver_common.h" #include "debug.h" +// Maximum duty cycle limit +#ifndef BACKLIGHT_LIMIT_VAL +# define BACKLIGHT_LIMIT_VAL 255 +#endif + // This logic is a bit complex, we support 3 setups: // // 1. Hardware PWM when backlight is wired to a PWM pin. @@ -240,6 +245,9 @@ static uint16_t cie_lightness(uint16_t v) { } } +// rescale the supplied backlight value to be in terms of the value limit +static uint32_t rescale_limit_val(uint32_t val) { return (val * (BACKLIGHT_LIMIT_VAL + 1)) / 256; } + // range for val is [0..TIMER_TOP]. PWM pin is high while the timer count is below val. static inline void set_pwm(uint16_t val) { OCRxx = val; } @@ -269,7 +277,7 @@ void backlight_set(uint8_t level) { #endif } // Set the brightness - set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS)); + set_pwm(cie_lightness(rescale_limit_val(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS))); } void backlight_task(void) {} @@ -375,7 +383,7 @@ ISR(TIMERx_OVF_vect) breathing_interrupt_disable(); } - set_pwm(cie_lightness(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U))); + set_pwm(cie_lightness(rescale_limit_val(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U)))); } #endif // BACKLIGHT_BREATHING diff --git a/quantum/backlight/backlight_chibios.c b/quantum/backlight/backlight_chibios.c index 0fe812bf27..4d5a69e14e 100644 --- a/quantum/backlight/backlight_chibios.c +++ b/quantum/backlight/backlight_chibios.c @@ -3,6 +3,11 @@ #include <hal.h> #include "debug.h" +// Maximum duty cycle limit +#ifndef BACKLIGHT_LIMIT_VAL +# define BACKLIGHT_LIMIT_VAL 255 +#endif + // GPIOV2 && GPIOV3 #ifndef BACKLIGHT_PAL_MODE # define BACKLIGHT_PAL_MODE 2 @@ -58,6 +63,11 @@ static uint16_t cie_lightness(uint16_t v) { } } +static uint32_t rescale_limit_val(uint32_t val) { + // rescale the supplied backlight value to be in terms of the value limit + return (val * (BACKLIGHT_LIMIT_VAL + 1)) / 256; +} + void backlight_init_ports(void) { #ifdef USE_GPIOV1 palSetPadMode(PAL_PORT(BACKLIGHT_PIN), PAL_PAD(BACKLIGHT_PIN), PAL_MODE_STM32_ALTERNATE_PUSHPULL); @@ -85,7 +95,7 @@ void backlight_set(uint8_t level) { pwmDisableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1); } else { // Turn backlight on - uint32_t duty = (uint32_t)(cie_lightness(0xFFFF * (uint32_t)level / BACKLIGHT_LEVELS)); + uint32_t duty = (uint32_t)(cie_lightness(rescale_limit_val(0xFFFF * (uint32_t)level / BACKLIGHT_LEVELS))); pwmEnableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty)); } } @@ -129,7 +139,7 @@ void breathing_callback(PWMDriver *pwmp) { static uint16_t breathing_counter = 0; breathing_counter = (breathing_counter + 1) % (breathing_period * 256); uint8_t index = breathing_counter / interval % BREATHING_STEPS; - uint32_t duty = cie_lightness(scale_backlight(breathing_table[index] * 256)); + uint32_t duty = cie_lightness(rescale_limit_val(scale_backlight(breathing_table[index] * 256))); chSysLockFromISR(); pwmEnableChannelI(pwmp, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty)); diff --git a/quantum/config_common.h b/quantum/config_common.h index c6a96e673c..53377f2b61 100644 --- a/quantum/config_common.h +++ b/quantum/config_common.h @@ -40,7 +40,7 @@ # define PIND_ADDRESS 0x9 # define PINE_ADDRESS 0xC # define PINF_ADDRESS 0xF -# elif defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) +# elif defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) # define ADDRESS_BASE 0x00 # define PINB_ADDRESS 0x3 # define PINC_ADDRESS 0x6 @@ -59,11 +59,6 @@ # define PINC_ADDRESS 0x3 # define PINB_ADDRESS 0x6 # define PINA_ADDRESS 0x9 -# elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) -# define ADDRESS_BASE 0x00 -# define PINB_ADDRESS 0x3 -# define PINC_ADDRESS 0x6 -# define PIND_ADDRESS 0x9 # elif defined(__AVR_ATtiny85__) # define ADDRESS_BASE 0x10 # define PINB_ADDRESS 0x6 @@ -285,6 +280,91 @@ # define F13 PAL_LINE(GPIOF, 13) # define F14 PAL_LINE(GPIOF, 14) # define F15 PAL_LINE(GPIOF, 15) +# define G0 PAL_LINE(GPIOG, 0) +# define G1 PAL_LINE(GPIOG, 1) +# define G2 PAL_LINE(GPIOG, 2) +# define G3 PAL_LINE(GPIOG, 3) +# define G4 PAL_LINE(GPIOG, 4) +# define G5 PAL_LINE(GPIOG, 5) +# define G6 PAL_LINE(GPIOG, 6) +# define G7 PAL_LINE(GPIOG, 7) +# define G8 PAL_LINE(GPIOG, 8) +# define G9 PAL_LINE(GPIOG, 9) +# define G10 PAL_LINE(GPIOG, 10) +# define G11 PAL_LINE(GPIOG, 11) +# define G12 PAL_LINE(GPIOG, 12) +# define G13 PAL_LINE(GPIOG, 13) +# define G14 PAL_LINE(GPIOG, 14) +# define G15 PAL_LINE(GPIOG, 15) +# define H0 PAL_LINE(GPIOH, 0) +# define H1 PAL_LINE(GPIOH, 1) +# define H2 PAL_LINE(GPIOH, 2) +# define H3 PAL_LINE(GPIOH, 3) +# define H4 PAL_LINE(GPIOH, 4) +# define H5 PAL_LINE(GPIOH, 5) +# define H6 PAL_LINE(GPIOH, 6) +# define H7 PAL_LINE(GPIOH, 7) +# define H8 PAL_LINE(GPIOH, 8) +# define H9 PAL_LINE(GPIOH, 9) +# define H10 PAL_LINE(GPIOH, 10) +# define H11 PAL_LINE(GPIOH, 11) +# define H12 PAL_LINE(GPIOH, 12) +# define H13 PAL_LINE(GPIOH, 13) +# define H14 PAL_LINE(GPIOH, 14) +# define H15 PAL_LINE(GPIOH, 15) +# define I0 PAL_LINE(GPIOI, 0) +# define I1 PAL_LINE(GPIOI, 1) +# define I2 PAL_LINE(GPIOI, 2) +# define I3 PAL_LINE(GPIOI, 3) +# define I4 PAL_LINE(GPIOI, 4) +# define I5 PAL_LINE(GPIOI, 5) +# define I6 PAL_LINE(GPIOI, 6) +# define I7 PAL_LINE(GPIOI, 7) +# define I8 PAL_LINE(GPIOI, 8) +# define I9 PAL_LINE(GPIOI, 9) +# define I10 PAL_LINE(GPIOI, 10) +# define I11 PAL_LINE(GPIOI, 11) +# define I12 PAL_LINE(GPIOI, 12) +# define I13 PAL_LINE(GPIOI, 13) +# define I14 PAL_LINE(GPIOI, 14) +# define I15 PAL_LINE(GPIOI, 15) +# define J0 PAL_LINE(GPIOJ, 0) +# define J1 PAL_LINE(GPIOJ, 1) +# define J2 PAL_LINE(GPIOJ, 2) +# define J3 PAL_LINE(GPIOJ, 3) +# define J4 PAL_LINE(GPIOJ, 4) +# define J5 PAL_LINE(GPIOJ, 5) +# define J6 PAL_LINE(GPIOJ, 6) +# define J7 PAL_LINE(GPIOJ, 7) +# define J8 PAL_LINE(GPIOJ, 8) +# define J9 PAL_LINE(GPIOJ, 9) +# define J10 PAL_LINE(GPIOJ, 10) +# define J11 PAL_LINE(GPIOJ, 11) +# define J12 PAL_LINE(GPIOJ, 12) +# define J13 PAL_LINE(GPIOJ, 13) +# define J14 PAL_LINE(GPIOJ, 14) +# define J15 PAL_LINE(GPIOJ, 15) +// Keyboards can `#define KEYBOARD_REQUIRES_GPIOK` if they need to access GPIO-K pins. These conflict with a whole +// bunch of layout definitions, so it's intentionally left out unless absolutely required -- in that case, the +// keyboard designer should use a different symbol when defining their layout macros. +# ifdef KEYBOARD_REQUIRES_GPIOK +# define K0 PAL_LINE(GPIOK, 0) +# define K1 PAL_LINE(GPIOK, 1) +# define K2 PAL_LINE(GPIOK, 2) +# define K3 PAL_LINE(GPIOK, 3) +# define K4 PAL_LINE(GPIOK, 4) +# define K5 PAL_LINE(GPIOK, 5) +# define K6 PAL_LINE(GPIOK, 6) +# define K7 PAL_LINE(GPIOK, 7) +# define K8 PAL_LINE(GPIOK, 8) +# define K9 PAL_LINE(GPIOK, 9) +# define K10 PAL_LINE(GPIOK, 10) +# define K11 PAL_LINE(GPIOK, 11) +# define K12 PAL_LINE(GPIOK, 12) +# define K13 PAL_LINE(GPIOK, 13) +# define K14 PAL_LINE(GPIOK, 14) +# define K15 PAL_LINE(GPIOK, 15) +# endif # endif #endif diff --git a/quantum/encoder.c b/quantum/encoder.c index 81ec1bb376..7ca31afedc 100644 --- a/quantum/encoder.c +++ b/quantum/encoder.c @@ -23,7 +23,7 @@ // for memcpy #include <string.h> -#ifndef ENCODER_RESOLUTION +#if !defined(ENCODER_RESOLUTIONS) && !defined(ENCODER_RESOLUTION) # define ENCODER_RESOLUTION 4 #endif @@ -34,6 +34,9 @@ #define NUMBER_OF_ENCODERS (sizeof(encoders_pad_a) / sizeof(pin_t)) static pin_t encoders_pad_a[] = ENCODERS_PAD_A; static pin_t encoders_pad_b[] = ENCODERS_PAD_B; +#ifdef ENCODER_RESOLUTIONS +static uint8_t encoder_resolutions[] = ENCODER_RESOLUTIONS; +#endif #ifndef ENCODER_DIRECTION_FLIP # define ENCODER_CLOCKWISE true @@ -65,9 +68,15 @@ void encoder_init(void) { if (!isLeftHand) { const pin_t encoders_pad_a_right[] = ENCODERS_PAD_A_RIGHT; const pin_t encoders_pad_b_right[] = ENCODERS_PAD_B_RIGHT; +# if defined(ENCODER_RESOLUTIONS_RIGHT) + const uint8_t encoder_resolutions_right[] = ENCODER_RESOLUTIONS_RIGHT; +# endif for (uint8_t i = 0; i < NUMBER_OF_ENCODERS; i++) { encoders_pad_a[i] = encoders_pad_a_right[i]; encoders_pad_b[i] = encoders_pad_b_right[i]; +# if defined(ENCODER_RESOLUTIONS_RIGHT) + encoder_resolutions[i] = encoder_resolutions_right[i]; +# endif } } #endif @@ -87,19 +96,26 @@ void encoder_init(void) { static void encoder_update(int8_t index, uint8_t state) { uint8_t i = index; + +#ifdef ENCODER_RESOLUTIONS + int8_t resolution = encoder_resolutions[i]; +#else + int8_t resolution = ENCODER_RESOLUTION; +#endif + #ifdef SPLIT_KEYBOARD index += thisHand; #endif encoder_pulses[i] += encoder_LUT[state & 0xF]; - if (encoder_pulses[i] >= ENCODER_RESOLUTION) { + if (encoder_pulses[i] >= resolution) { encoder_value[index]++; encoder_update_kb(index, ENCODER_COUNTER_CLOCKWISE); } - if (encoder_pulses[i] <= -ENCODER_RESOLUTION) { // direction is arbitrary here, but this clockwise + if (encoder_pulses[i] <= -resolution) { // direction is arbitrary here, but this clockwise encoder_value[index]--; encoder_update_kb(index, ENCODER_CLOCKWISE); } - encoder_pulses[i] %= ENCODER_RESOLUTION; + encoder_pulses[i] %= resolution; } void encoder_read(void) { diff --git a/quantum/joystick.h b/quantum/joystick.h index a95472b9fd..87dbc24aff 100644 --- a/quantum/joystick.h +++ b/quantum/joystick.h @@ -1,5 +1,9 @@ #pragma once +#include "quantum.h" + +#include <stdint.h> + #ifndef JOYSTICK_BUTTON_COUNT # define JOYSTICK_BUTTON_COUNT 8 #endif @@ -8,9 +12,13 @@ # define JOYSTICK_AXES_COUNT 4 #endif -#include "quantum.h" +#ifndef JOYSTICK_AXES_RESOLUTION +# define JOYSTICK_AXES_RESOLUTION 8 +#elif JOYSTICK_AXES_RESOLUTION < 8 || JOYSTICK_AXES_RESOLUTION > 16 +# error JOYSTICK_AXES_RESOLUTION must be between 8 and 16 +#endif -#include <stdint.h> +#define JOYSTICK_RESOLUTION ((1L << (JOYSTICK_AXES_RESOLUTION - 1)) - 1) // configure on input_pin of the joystick_axes array entry to JS_VIRTUAL_AXIS // to prevent it from being read from the ADC. This allows outputing forged axis value. diff --git a/quantum/led_matrix.c b/quantum/led_matrix.c index 5c24c797a9..eb523990a6 100644 --- a/quantum/led_matrix.c +++ b/quantum/led_matrix.c @@ -20,7 +20,7 @@ #include <stdint.h> #include <stdbool.h> #include "quantum.h" -#include "ledmatrix.h" +#include "led_matrix.h" #include "progmem.h" #include "config.h" #include "eeprom.h" diff --git a/quantum/ledmatrix.h b/quantum/led_matrix.h index 5867ba9876..5867ba9876 100644 --- a/quantum/ledmatrix.h +++ b/quantum/led_matrix.h diff --git a/quantum/led_matrix_drivers.c b/quantum/led_matrix_drivers.c index 6877bf4c6b..9decaa33c2 100644 --- a/quantum/led_matrix_drivers.c +++ b/quantum/led_matrix_drivers.c @@ -18,7 +18,7 @@ #include <stdint.h> #include <stdbool.h> #include "quantum.h" -#include "ledmatrix.h" +#include "led_matrix.h" /* Each driver needs to define a struct: * diff --git a/quantum/matrix.c b/quantum/matrix.c index c68c56cac2..cab0d2ddca 100644 --- a/quantum/matrix.c +++ b/quantum/matrix.c @@ -32,6 +32,19 @@ static const pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; extern matrix_row_t raw_matrix[MATRIX_ROWS]; // raw values extern matrix_row_t matrix[MATRIX_ROWS]; // debounced values +static inline void setPinOutput_writeLow(pin_t pin) { + ATOMIC_BLOCK_FORCEON { + setPinOutput(pin); + writePinLow(pin); + } +} + +static inline void setPinInputHigh_atomic(pin_t pin) { + ATOMIC_BLOCK_FORCEON { + setPinInputHigh(pin); + } +} + // matrix code #ifdef DIRECT_PINS @@ -70,22 +83,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) # if (DIODE_DIRECTION == COL2ROW) static void select_row(uint8_t row) { - setPinOutput(row_pins[row]); - writePinLow(row_pins[row]); + setPinOutput_writeLow(row_pins[row]); } -static void unselect_row(uint8_t row) { setPinInputHigh(row_pins[row]); } +static void unselect_row(uint8_t row) { + setPinInputHigh_atomic(row_pins[row]); +} static void unselect_rows(void) { for (uint8_t x = 0; x < MATRIX_ROWS; x++) { - setPinInputHigh(row_pins[x]); + setPinInputHigh_atomic(row_pins[x]); } } static void init_pins(void) { unselect_rows(); for (uint8_t x = 0; x < MATRIX_COLS; x++) { - setPinInputHigh(col_pins[x]); + setPinInputHigh_atomic(col_pins[x]); } } @@ -120,22 +134,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) # elif (DIODE_DIRECTION == ROW2COL) static void select_col(uint8_t col) { - setPinOutput(col_pins[col]); - writePinLow(col_pins[col]); + setPinOutput_writeLow(col_pins[col]); } -static void unselect_col(uint8_t col) { setPinInputHigh(col_pins[col]); } +static void unselect_col(uint8_t col) { + setPinInputHigh_atomic(col_pins[col]); +} static void unselect_cols(void) { for (uint8_t x = 0; x < MATRIX_COLS; x++) { - setPinInputHigh(col_pins[x]); + setPinInputHigh_atomic(col_pins[x]); } } static void init_pins(void) { unselect_cols(); for (uint8_t x = 0; x < MATRIX_ROWS; x++) { - setPinInputHigh(row_pins[x]); + setPinInputHigh_atomic(row_pins[x]); } } diff --git a/quantum/mcu_selection.mk b/quantum/mcu_selection.mk index 6fb5f88b05..ea88d9ab9e 100644 --- a/quantum/mcu_selection.mk +++ b/quantum/mcu_selection.mk @@ -318,6 +318,9 @@ 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)) + NO_I2C = yes + endif endif ifneq (,$(filter $(MCU),atmega32a)) diff --git a/quantum/process_keycode/process_auto_shift.c b/quantum/process_keycode/process_auto_shift.c index 76530959c9..41e60496b9 100644 --- a/quantum/process_keycode/process_auto_shift.c +++ b/quantum/process_keycode/process_auto_shift.c @@ -16,49 +16,149 @@ #ifdef AUTO_SHIFT_ENABLE +# include <stdbool.h> # include <stdio.h> # include "process_auto_shift.h" -static bool autoshift_enabled = true; -static uint16_t autoshift_time = 0; +static uint16_t autoshift_time = 0; static uint16_t autoshift_timeout = AUTO_SHIFT_TIMEOUT; static uint16_t autoshift_lastkey = KC_NO; +static struct { + // Whether autoshift is enabled. + bool enabled : 1; + // Whether the last auto-shifted key was released after the timeout. This + // is used to replicate the last key for a tap-then-hold. + bool lastshifted : 1; + // Whether an auto-shiftable key has been pressed but not processed. + bool in_progress : 1; + // Whether the auto-shifted keypress has been registered. + bool holding_shift : 1; +} autoshift_flags = {true, false, false, false}; + +/** \brief Record the press of an autoshiftable key + * + * \return Whether the record should be further processed. + */ +static bool autoshift_press(uint16_t keycode, uint16_t now, keyrecord_t *record) { + if (!autoshift_flags.enabled) { + return true; + } -void autoshift_flush(void) { - if (autoshift_lastkey != KC_NO) { - uint16_t elapsed = timer_elapsed(autoshift_time); +# ifndef AUTO_SHIFT_MODIFIERS + if (get_mods() & (~MOD_BIT(KC_LSFT))) { + return true; + } +# endif +# ifdef AUTO_SHIFT_REPEAT + const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time); +# ifndef AUTO_SHIFT_NO_AUTO_REPEAT + if (!autoshift_flags.lastshifted) { +# endif + if (elapsed < TAPPING_TERM && keycode == autoshift_lastkey) { + // Allow a tap-then-hold for keyrepeat. + if (!autoshift_flags.lastshifted) { + register_code(autoshift_lastkey); + } else { + // Simulate pressing the shift key. + add_weak_mods(MOD_BIT(KC_LSFT)); + register_code(autoshift_lastkey); + } + return false; + } +# ifndef AUTO_SHIFT_NO_AUTO_REPEAT + } +# endif +# endif + + // Record the keycode so we can simulate it later. + autoshift_lastkey = keycode; + autoshift_time = now; + autoshift_flags.in_progress = true; - if (elapsed > autoshift_timeout) { - tap_code16(LSFT(autoshift_lastkey)); +# if !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING) + clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED); +# endif + return false; +} + +/** \brief Registers an autoshiftable key under the right conditions + * + * If the autoshift delay has elapsed, register a shift and the key. + * + * If the autoshift key is released before the delay has elapsed, register the + * key without a shift. + */ +static void autoshift_end(uint16_t keycode, uint16_t now, bool matrix_trigger) { + // Called on key down with KC_NO, auto-shifted key up, and timeout. + if (autoshift_flags.in_progress) { + // Process the auto-shiftable key. + autoshift_flags.in_progress = false; + + // Time since the initial press was recorded. + const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time); + if (elapsed < autoshift_timeout) { + register_code(autoshift_lastkey); + autoshift_flags.lastshifted = false; } else { - tap_code(autoshift_lastkey); + // Simulate pressing the shift key. + add_weak_mods(MOD_BIT(KC_LSFT)); + register_code(autoshift_lastkey); + autoshift_flags.lastshifted = true; +# if defined(AUTO_SHIFT_REPEAT) && !defined(AUTO_SHIFT_NO_AUTO_REPEAT) + if (matrix_trigger) { + // Prevents release. + return; + } +# endif } - autoshift_time = 0; - autoshift_lastkey = KC_NO; +# if TAP_CODE_DELAY > 0 + wait_ms(TAP_CODE_DELAY); +# endif + unregister_code(autoshift_lastkey); + del_weak_mods(MOD_BIT(KC_LSFT)); + } else { + // Release after keyrepeat. + unregister_code(keycode); + if (keycode == autoshift_lastkey) { + // This will only fire when the key was the last auto-shiftable + // pressed. That prevents aaaaBBBB then releasing a from unshifting + // later Bs (if B wasn't auto-shiftable). + del_weak_mods(MOD_BIT(KC_LSFT)); + } } + send_keyboard_report(); // del_weak_mods doesn't send one. + // Roll the autoshift_time forward for detecting tap-and-hold. + autoshift_time = now; } - -void autoshift_on(uint16_t keycode) { - autoshift_time = timer_read(); - autoshift_lastkey = keycode; +/** \brief Simulates auto-shifted key releases when timeout is hit + * + * Can be called from \c matrix_scan_user so that auto-shifted keys are sent + * immediately after the timeout has expired, rather than waiting for the key + * to be released. + */ +void autoshift_matrix_scan(void) { + if (autoshift_flags.in_progress) { + const uint16_t now = timer_read(); + const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time); + if (elapsed >= autoshift_timeout) { + autoshift_end(autoshift_lastkey, now, true); + } + } } void autoshift_toggle(void) { - if (autoshift_enabled) { - autoshift_enabled = false; - autoshift_flush(); - } else { - autoshift_enabled = true; - } + autoshift_flags.enabled = !autoshift_flags.enabled; + del_weak_mods(MOD_BIT(KC_LSFT)); } -void autoshift_enable(void) { autoshift_enabled = true; } +void autoshift_enable(void) { autoshift_flags.enabled = true; } + void autoshift_disable(void) { - autoshift_enabled = false; - autoshift_flush(); + autoshift_flags.enabled = false; + del_weak_mods(MOD_BIT(KC_LSFT)); } # ifndef AUTO_SHIFT_NO_SETUP @@ -71,24 +171,30 @@ void autoshift_timer_report(void) { } # endif -bool get_autoshift_state(void) { return autoshift_enabled; } +bool get_autoshift_state(void) { return autoshift_flags.enabled; } -uint16_t get_autoshift_timeout(void) { - return autoshift_timeout; -} +uint16_t get_autoshift_timeout(void) { return autoshift_timeout; } -void set_autoshift_timeout(uint16_t timeout) { - autoshift_timeout = timeout; -} +void set_autoshift_timeout(uint16_t timeout) { autoshift_timeout = timeout; } bool process_auto_shift(uint16_t keycode, keyrecord_t *record) { + // Note that record->event.time isn't reliable, see: + // https://github.com/qmk/qmk_firmware/pull/9826#issuecomment-733559550 + const uint16_t now = timer_read(); if (record->event.pressed) { + if (autoshift_flags.in_progress) { + // Evaluate previous key if there is one. Doing this elsewhere is + // more complicated and easier to break. + autoshift_end(KC_NO, now, false); + } + // For pressing another key while keyrepeating shifted autoshift. + del_weak_mods(MOD_BIT(KC_LSFT)); + switch (keycode) { case KC_ASTG: autoshift_toggle(); return true; - case KC_ASON: autoshift_enable(); return true; @@ -108,43 +214,30 @@ bool process_auto_shift(uint16_t keycode, keyrecord_t *record) { autoshift_timer_report(); return true; # endif + } + } + + switch (keycode) { # ifndef NO_AUTO_SHIFT_ALPHA - case KC_A ... KC_Z: + case KC_A ... KC_Z: # endif # ifndef NO_AUTO_SHIFT_NUMERIC - case KC_1 ... KC_0: + case KC_1 ... KC_0: # endif # ifndef NO_AUTO_SHIFT_SPECIAL -# ifndef NO_AUTO_SHIFT_TAB - case KC_TAB: -# endif - case KC_MINUS ... KC_SLASH: - case KC_NONUS_BSLASH: -# endif - autoshift_flush(); - if (!autoshift_enabled) return true; - -# ifndef AUTO_SHIFT_MODIFIERS - if (get_mods()) { - return true; - } -# endif - autoshift_on(keycode); - - // We need some extra handling here for OSL edge cases -# if !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING) - clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED); +# ifndef NO_AUTO_SHIFT_TAB + case KC_TAB: +# endif + case KC_MINUS ... KC_SLASH: + case KC_NONUS_BSLASH: # endif + if (record->event.pressed) { + return autoshift_press(keycode, now, record); + } else { + autoshift_end(keycode, now, false); return false; - - default: - autoshift_flush(); - return true; - } - } else { - autoshift_flush(); + } } - return true; } diff --git a/quantum/process_keycode/process_auto_shift.h b/quantum/process_keycode/process_auto_shift.h index b909d28eef..5b2718f11c 100644 --- a/quantum/process_keycode/process_auto_shift.h +++ b/quantum/process_keycode/process_auto_shift.h @@ -24,9 +24,10 @@ bool process_auto_shift(uint16_t keycode, keyrecord_t *record); -void autoshift_enable(void); -void autoshift_disable(void); -void autoshift_toggle(void); -bool get_autoshift_state(void); +void autoshift_enable(void); +void autoshift_disable(void); +void autoshift_toggle(void); +bool get_autoshift_state(void); uint16_t get_autoshift_timeout(void); -void set_autoshift_timeout(uint16_t timeout); +void set_autoshift_timeout(uint16_t timeout); +void autoshift_matrix_scan(void); diff --git a/quantum/process_keycode/process_joystick.c b/quantum/process_keycode/process_joystick.c index 5778a7434c..3ffaf42bf8 100644 --- a/quantum/process_keycode/process_joystick.c +++ b/quantum/process_keycode/process_joystick.c @@ -129,17 +129,17 @@ bool process_joystick_analogread_quantum() { // test the converted value against the lower range int32_t ref = joystick_axes[axis_index].mid_digit; int32_t range = joystick_axes[axis_index].min_digit; - int32_t ranged_val = ((axis_val - ref) * -127) / (range - ref); + int32_t ranged_val = ((axis_val - ref) * -JOYSTICK_RESOLUTION) / (range - ref); if (ranged_val > 0) { // the value is in the higher range range = joystick_axes[axis_index].max_digit; - ranged_val = ((axis_val - ref) * 127) / (range - ref); + ranged_val = ((axis_val - ref) * JOYSTICK_RESOLUTION) / (range - ref); } // clamp the result in the valid range - ranged_val = ranged_val < -127 ? -127 : ranged_val; - ranged_val = ranged_val > 127 ? 127 : ranged_val; + ranged_val = ranged_val < -JOYSTICK_RESOLUTION ? -JOYSTICK_RESOLUTION : ranged_val; + ranged_val = ranged_val > JOYSTICK_RESOLUTION ? JOYSTICK_RESOLUTION : ranged_val; if (ranged_val != joystick_status.axes[axis_index]) { joystick_status.axes[axis_index] = ranged_val; diff --git a/quantum/process_keycode/process_midi.c b/quantum/process_keycode/process_midi.c index e525770144..8e2fb955e7 100644 --- a/quantum/process_keycode/process_midi.c +++ b/quantum/process_keycode/process_midi.c @@ -41,12 +41,12 @@ static int8_t midi_modulation_step; static uint16_t midi_modulation_timer; midi_config_t midi_config; -inline uint8_t compute_velocity(uint8_t setting) { return (setting + 1) * (128 / (MIDI_VELOCITY_MAX - MIDI_VELOCITY_MIN + 1)); } +inline uint8_t compute_velocity(uint8_t setting) { return setting * (128 / (MIDI_VELOCITY_MAX - MIDI_VELOCITY_MIN)); } void midi_init(void) { midi_config.octave = MI_OCT_2 - MIDI_OCTAVE_MIN; midi_config.transpose = 0; - midi_config.velocity = (MIDI_VELOCITY_MAX - MIDI_VELOCITY_MIN); + midi_config.velocity = 127; midi_config.channel = 0; midi_config.modulation_interval = 8; @@ -66,7 +66,7 @@ bool process_midi(uint16_t keycode, keyrecord_t *record) { case MIDI_TONE_MIN ... MIDI_TONE_MAX: { uint8_t channel = midi_config.channel; uint8_t tone = keycode - MIDI_TONE_MIN; - uint8_t velocity = compute_velocity(midi_config.velocity); + uint8_t velocity = midi_config.velocity; if (record->event.pressed) { if (tone_status[tone] == MIDI_INVALID_NOTE) { uint8_t note = midi_compute_note(keycode); @@ -124,19 +124,30 @@ bool process_midi(uint16_t keycode, keyrecord_t *record) { return false; case MIDI_VELOCITY_MIN ... MIDI_VELOCITY_MAX: if (record->event.pressed) { - midi_config.velocity = keycode - MIDI_VELOCITY_MIN; + midi_config.velocity = compute_velocity(keycode - MIDI_VELOCITY_MIN); dprintf("midi velocity %d\n", midi_config.velocity); } return false; case MI_VELD: if (record->event.pressed && midi_config.velocity > 0) { - midi_config.velocity--; + if (midi_config.velocity == 127) { + midi_config.velocity -= 10; + } else if (midi_config.velocity > 12) { + midi_config.velocity -= 13; + } else { + midi_config.velocity = 0; + } + dprintf("midi velocity %d\n", midi_config.velocity); } return false; case MI_VELU: - if (record->event.pressed) { - midi_config.velocity++; + if (record->event.pressed && midi_config.velocity < 127) { + if (midi_config.velocity < 115) { + midi_config.velocity += 13; + } else { + midi_config.velocity = 127; + } dprintf("midi velocity %d\n", midi_config.velocity); } return false; diff --git a/quantum/process_keycode/process_midi.h b/quantum/process_keycode/process_midi.h index 0007b3ed25..ef5661dd4d 100644 --- a/quantum/process_keycode/process_midi.h +++ b/quantum/process_keycode/process_midi.h @@ -35,7 +35,7 @@ typedef union { struct { uint8_t octave : 4; int8_t transpose : 4; - uint8_t velocity : 4; + uint8_t velocity : 7; uint8_t channel : 4; uint8_t modulation_interval : 4; }; diff --git a/quantum/process_keycode/process_sequencer.c b/quantum/process_keycode/process_sequencer.c new file mode 100644 index 0000000000..334b4c0092 --- /dev/null +++ b/quantum/process_keycode/process_sequencer.c @@ -0,0 +1,62 @@ +/* Copyright 2020 Rodolphe Belouin + * + * 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 "process_sequencer.h" + +bool process_sequencer(uint16_t keycode, keyrecord_t *record) { + if (record->event.pressed) { + switch (keycode) { + case SQ_ON: + sequencer_on(); + return false; + case SQ_OFF: + sequencer_off(); + return false; + case SQ_TOG: + sequencer_toggle(); + return false; + case SQ_TMPD: + sequencer_decrease_tempo(); + return false; + case SQ_TMPU: + sequencer_increase_tempo(); + return false; + case SEQUENCER_RESOLUTION_MIN ... SEQUENCER_RESOLUTION_MAX: + sequencer_set_resolution(keycode - SEQUENCER_RESOLUTION_MIN); + return false; + case SQ_RESD: + sequencer_decrease_resolution(); + return false; + case SQ_RESU: + sequencer_increase_resolution(); + return false; + case SQ_SALL: + sequencer_set_all_steps_on(); + return false; + case SQ_SCLR: + sequencer_set_all_steps_off(); + return false; + case SEQUENCER_STEP_MIN ... SEQUENCER_STEP_MAX: + sequencer_toggle_step(keycode - SEQUENCER_STEP_MIN); + return false; + case SEQUENCER_TRACK_MIN ... SEQUENCER_TRACK_MAX: + sequencer_toggle_single_active_track(keycode - SEQUENCER_TRACK_MIN); + return false; + } + } + + return true; +} diff --git a/quantum/process_keycode/process_sequencer.h b/quantum/process_keycode/process_sequencer.h new file mode 100644 index 0000000000..2b85f24299 --- /dev/null +++ b/quantum/process_keycode/process_sequencer.h @@ -0,0 +1,21 @@ +/* Copyright 2020 Rodolphe Belouin + * + * 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 "quantum.h" + +bool process_sequencer(uint16_t keycode, keyrecord_t *record); diff --git a/quantum/quantum.c b/quantum/quantum.c index 4f21e6eea9..0af3f62c4d 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -55,6 +55,10 @@ float default_layer_songs[][16][2] = DEFAULT_LAYER_SONGS; # endif #endif +#ifdef AUTO_SHIFT_ENABLE +# include "process_auto_shift.h" +#endif + static void do_code16(uint16_t code, void (*f)(uint8_t)) { switch (code) { case QK_MODS ... QK_MODS_MAX: @@ -228,6 +232,9 @@ bool process_record_quantum(keyrecord_t *record) { process_record_via(keycode, record) && #endif process_record_kb(keycode, record) && +#if defined(SEQUENCER_ENABLE) + process_sequencer(keycode, record) && +#endif #if defined(MIDI_ENABLE) && defined(MIDI_ADVANCED) process_midi(keycode, record) && #endif @@ -649,6 +656,10 @@ void matrix_scan_quantum() { matrix_scan_music(); #endif +#ifdef SEQUENCER_ENABLE + matrix_scan_sequencer(); +#endif + #ifdef TAP_DANCE_ENABLE matrix_scan_tap_dance(); #endif @@ -677,6 +688,10 @@ void matrix_scan_quantum() { dip_switch_read(false); #endif +#ifdef AUTO_SHIFT_ENABLE + autoshift_matrix_scan(); +#endif + matrix_scan_kb(); } diff --git a/quantum/quantum.h b/quantum/quantum.h index 2c06578652..94188a8543 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -31,7 +31,7 @@ #ifdef BACKLIGHT_ENABLE # ifdef LED_MATRIX_ENABLE -# include "ledmatrix.h" +# include "led_matrix.h" # else # include "backlight.h" # endif @@ -68,6 +68,11 @@ extern layer_state_t default_layer_state; extern layer_state_t layer_state; #endif +#if defined(SEQUENCER_ENABLE) +# include "sequencer.h" +# include "process_sequencer.h" +#endif + #if defined(MIDI_ENABLE) && defined(MIDI_ADVANCED) # include "process_midi.h" #endif @@ -229,6 +234,61 @@ typedef ioline_t pin_t; # define togglePin(pin) palToggleLine(pin) #endif +// Atomic macro to help make GPIO and other controls atomic. +#ifdef IGNORE_ATOMIC_BLOCK +/* do nothing atomic macro */ +# define ATOMIC_BLOCK for (uint8_t __ToDo = 1; __ToDo; __ToDo = 0) +# define ATOMIC_BLOCK_RESTORESTATE ATOMIC_BLOCK +# define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK + +#elif defined(__AVR__) +/* atomic macro for AVR */ +# include <util/atomic.h> + +# define ATOMIC_BLOCK_RESTORESTATE ATOMIC_BLOCK(ATOMIC_RESTORESTATE) +# define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON) + +#elif defined(PROTOCOL_CHIBIOS) || defined(PROTOCOL_ARM_ATSAM) +/* atomic macro for ChibiOS / ARM ATSAM */ +# if defined(PROTOCOL_ARM_ATSAM) +# include "arm_atsam_protocol.h" +# endif + +static __inline__ uint8_t __interrupt_disable__(void) { +# if defined(PROTOCOL_CHIBIOS) + chSysLock(); +# endif +# if defined(PROTOCOL_ARM_ATSAM) + __disable_irq(); +# endif + return 1; +} + +static __inline__ void __interrupt_enable__(const uint8_t *__s) { +# if defined(PROTOCOL_CHIBIOS) + chSysUnlock(); +# endif +# if defined(PROTOCOL_ARM_ATSAM) + __enable_irq(); +# endif + __asm__ volatile("" ::: "memory"); + (void)__s; +} + +# define ATOMIC_BLOCK(type) for (type, __ToDo = __interrupt_disable__(); __ToDo; __ToDo = 0) +# define ATOMIC_FORCEON uint8_t sreg_save __attribute__((__cleanup__(__interrupt_enable__))) = 0 + +# define ATOMIC_BLOCK_RESTORESTATE _Static_assert(0, "ATOMIC_BLOCK_RESTORESTATE dose not implement") +# define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON) + +/* Other platform */ +#else + +# define ATOMIC_BLOCK_RESTORESTATE _Static_assert(0, "ATOMIC_BLOCK_RESTORESTATE dose not implement") +# define ATOMIC_BLOCK_FORCEON _Static_assert(0, "ATOMIC_BLOCK_FORCEON dose not implement") + +#endif + #define SEND_STRING(string) send_string_P(PSTR(string)) #define SEND_STRING_DELAY(string, interval) send_string_with_delay_P(PSTR(string), interval) diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index 38618964e3..34102aab2d 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -16,6 +16,10 @@ #ifndef QUANTUM_KEYCODES_H #define QUANTUM_KEYCODES_H +#if defined(SEQUENCER_ENABLE) +# include "sequencer.h" +#endif + #ifndef MIDI_ENABLE_STRICT # define MIDI_ENABLE_STRICT 0 #endif @@ -343,7 +347,8 @@ enum quantum_keycodes { MI_TRNSU, // transpose up MIDI_VELOCITY_MIN, - MI_VEL_1 = MIDI_VELOCITY_MIN, + MI_VEL_0 = MIDI_VELOCITY_MIN, + MI_VEL_1, MI_VEL_2, MI_VEL_3, MI_VEL_4, @@ -551,6 +556,37 @@ enum quantum_keycodes { JS_BUTTON31, JS_BUTTON_MAX = JS_BUTTON31, +#if defined(SEQUENCER_ENABLE) + SQ_ON, + SQ_OFF, + SQ_TOG, + + SQ_TMPD, // Decrease tempo + SQ_TMPU, // Increase tempo + + SEQUENCER_RESOLUTION_MIN, + SEQUENCER_RESOLUTION_MAX = SEQUENCER_RESOLUTION_MIN + SEQUENCER_RESOLUTIONS, + SQ_RESD, // Decrease resolution + SQ_RESU, // Increase resolution + + SQ_SALL, // All steps on + SQ_SCLR, // All steps off + SEQUENCER_STEP_MIN, + SEQUENCER_STEP_MAX = SEQUENCER_STEP_MIN + SEQUENCER_STEPS, + + SEQUENCER_TRACK_MIN, + SEQUENCER_TRACK_MAX = SEQUENCER_TRACK_MIN + SEQUENCER_TRACKS, + +/** + * Helpers to assign a keycode to a step, a resolution, or a track. + * Falls back to NOOP if n is out of range. + */ +# define SQ_S(n) (n < SEQUENCER_STEPS ? SEQUENCER_STEP_MIN + n : XXXXXXX) +# define SQ_R(n) (n < SEQUENCER_RESOLUTIONS ? SEQUENCER_RESOLUTION_MIN + n : XXXXXXX) +# define SQ_T(n) (n < SEQUENCER_TRACKS ? SEQUENCER_TRACK_MIN + n : XXXXXXX) + +#endif + // always leave at the end SAFE_RANGE }; diff --git a/quantum/rgb_matrix.c b/quantum/rgb_matrix.c index 2a55a8926f..404e5bc1f9 100644 --- a/quantum/rgb_matrix.c +++ b/quantum/rgb_matrix.c @@ -31,6 +31,8 @@ const point_t k_rgb_matrix_center = {112, 32}; const point_t k_rgb_matrix_center = RGB_MATRIX_CENTER; #endif +__attribute__((weak)) RGB rgb_matrix_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv); } + // Generic effect runners #include "rgb_matrix_runners/effect_runner_dx_dy_dist.h" #include "rgb_matrix_runners/effect_runner_dx_dy.h" @@ -402,6 +404,10 @@ void rgb_matrix_task(void) { break; case RENDERING: rgb_task_render(effect); + if (!suspend_backlight) { + rgb_matrix_indicators(); + rgb_matrix_indicators_advanced(&rgb_effect_params); + } break; case FLUSHING: rgb_task_flush(effect); @@ -410,10 +416,6 @@ void rgb_matrix_task(void) { rgb_task_sync(); break; } - - if (!suspend_backlight) { - rgb_matrix_indicators(); - } } void rgb_matrix_indicators(void) { @@ -425,6 +427,28 @@ __attribute__((weak)) void rgb_matrix_indicators_kb(void) {} __attribute__((weak)) void rgb_matrix_indicators_user(void) {} +void rgb_matrix_indicators_advanced(effect_params_t *params) { + /* special handling is needed for "params->iter", since it's already been incremented. + * Could move the invocations to rgb_task_render, but then it's missing a few checks + * and not sure which would be better. Otherwise, this should be called from + * rgb_task_render, right before the iter++ line. + */ +#if defined(RGB_MATRIX_LED_PROCESS_LIMIT) && RGB_MATRIX_LED_PROCESS_LIMIT > 0 && RGB_MATRIX_LED_PROCESS_LIMIT < DRIVER_LED_TOTAL + uint8_t min = RGB_MATRIX_LED_PROCESS_LIMIT * (params->iter - 1); + uint8_t max = min + RGB_MATRIX_LED_PROCESS_LIMIT; + if (max > DRIVER_LED_TOTAL) max = DRIVER_LED_TOTAL; +#else + uint8_t min = 0; + uint8_t max = DRIVER_LED_TOTAL; +#endif + rgb_matrix_indicators_advanced_kb(min, max); + rgb_matrix_indicators_advanced_user(min, max); +} + +__attribute__((weak)) void rgb_matrix_indicators_advanced_kb(uint8_t led_min, uint8_t led_max) {} + +__attribute__((weak)) void rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) {} + void rgb_matrix_init(void) { rgb_matrix_driver.init(); diff --git a/quantum/rgb_matrix.h b/quantum/rgb_matrix.h index 733333349f..771a1fcd35 100644 --- a/quantum/rgb_matrix.h +++ b/quantum/rgb_matrix.h @@ -57,6 +57,11 @@ uint8_t max = DRIVER_LED_TOTAL; #endif +#define RGB_MATRIX_INDICATOR_SET_COLOR(i, r, g, b) \ + if (i >= led_min && i <= led_max) { \ + rgb_matrix_set_color(i, r, g, b); \ + } + #define RGB_MATRIX_TEST_LED_FLAGS() \ if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) continue @@ -103,6 +108,10 @@ void rgb_matrix_indicators(void); void rgb_matrix_indicators_kb(void); void rgb_matrix_indicators_user(void); +void rgb_matrix_indicators_advanced(effect_params_t *params); +void rgb_matrix_indicators_advanced_kb(uint8_t led_min, uint8_t led_max); +void rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max); + void rgb_matrix_init(void); void rgb_matrix_set_suspend_state(bool state); diff --git a/quantum/rgb_matrix_animations/alpha_mods_anim.h b/quantum/rgb_matrix_animations/alpha_mods_anim.h index 0778ab2098..426d88ef35 100644 --- a/quantum/rgb_matrix_animations/alpha_mods_anim.h +++ b/quantum/rgb_matrix_animations/alpha_mods_anim.h @@ -7,9 +7,9 @@ bool ALPHAS_MODS(effect_params_t* params) { RGB_MATRIX_USE_LIMITS(led_min, led_max); HSV hsv = rgb_matrix_config.hsv; - RGB rgb1 = hsv_to_rgb(hsv); + RGB rgb1 = rgb_matrix_hsv_to_rgb(hsv); hsv.h += rgb_matrix_config.speed; - RGB rgb2 = hsv_to_rgb(hsv); + RGB rgb2 = rgb_matrix_hsv_to_rgb(hsv); for (uint8_t i = led_min; i < led_max; i++) { RGB_MATRIX_TEST_LED_FLAGS(); diff --git a/quantum/rgb_matrix_animations/breathing_anim.h b/quantum/rgb_matrix_animations/breathing_anim.h index 887425f9da..340bd93e5d 100644 --- a/quantum/rgb_matrix_animations/breathing_anim.h +++ b/quantum/rgb_matrix_animations/breathing_anim.h @@ -8,7 +8,7 @@ bool BREATHING(effect_params_t* params) { HSV hsv = rgb_matrix_config.hsv; uint16_t time = scale16by8(g_rgb_timer, rgb_matrix_config.speed / 8); hsv.v = scale8(abs8(sin8(time) - 128) * 2, hsv.v); - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(hsv); for (uint8_t i = led_min; i < led_max; i++) { RGB_MATRIX_TEST_LED_FLAGS(); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); diff --git a/quantum/rgb_matrix_animations/gradient_left_right_anim.h b/quantum/rgb_matrix_animations/gradient_left_right_anim.h index 2eab2eb759..53dfd04e2c 100644 --- a/quantum/rgb_matrix_animations/gradient_left_right_anim.h +++ b/quantum/rgb_matrix_animations/gradient_left_right_anim.h @@ -12,7 +12,7 @@ bool GRADIENT_LEFT_RIGHT(effect_params_t* params) { // The x range will be 0..224, map this to 0..7 // Relies on hue being 8-bit and wrapping hsv.h = rgb_matrix_config.hsv.h + (scale * g_led_config.point[i].x >> 5); - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(hsv); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgb_matrix_animations/gradient_up_down_anim.h b/quantum/rgb_matrix_animations/gradient_up_down_anim.h index 0f1f8e23cf..7e0d2898cf 100644 --- a/quantum/rgb_matrix_animations/gradient_up_down_anim.h +++ b/quantum/rgb_matrix_animations/gradient_up_down_anim.h @@ -12,7 +12,7 @@ bool GRADIENT_UP_DOWN(effect_params_t* params) { // The y range will be 0..64, map this to 0..4 // Relies on hue being 8-bit and wrapping hsv.h = rgb_matrix_config.hsv.h + scale * (g_led_config.point[i].y >> 4); - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(hsv); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h b/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h index ef2d1500b0..9493b38508 100644 --- a/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h +++ b/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h @@ -5,7 +5,7 @@ RGB_MATRIX_EFFECT(JELLYBEAN_RAINDROPS) static void jellybean_raindrops_set_color(int i, effect_params_t* params) { if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) return; HSV hsv = {rand() & 0xFF, rand() & 0xFF, rgb_matrix_config.hsv.v}; - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(hsv); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } diff --git a/quantum/rgb_matrix_animations/raindrops_anim.h b/quantum/rgb_matrix_animations/raindrops_anim.h index 6e1b5acb0d..38359cdca7 100644 --- a/quantum/rgb_matrix_animations/raindrops_anim.h +++ b/quantum/rgb_matrix_animations/raindrops_anim.h @@ -15,7 +15,7 @@ static void raindrops_set_color(int i, effect_params_t* params) { } hsv.h = rgb_matrix_config.hsv.h + (deltaH * (rand() & 0x03)); - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(hsv); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } diff --git a/quantum/rgb_matrix_animations/solid_color_anim.h b/quantum/rgb_matrix_animations/solid_color_anim.h index c8f5e70e7a..79d63cf133 100644 --- a/quantum/rgb_matrix_animations/solid_color_anim.h +++ b/quantum/rgb_matrix_animations/solid_color_anim.h @@ -4,7 +4,7 @@ RGB_MATRIX_EFFECT(SOLID_COLOR) bool SOLID_COLOR(effect_params_t* params) { RGB_MATRIX_USE_LIMITS(led_min, led_max); - RGB rgb = hsv_to_rgb(rgb_matrix_config.hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(rgb_matrix_config.hsv); for (uint8_t i = led_min; i < led_max; i++) { RGB_MATRIX_TEST_LED_FLAGS(); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); diff --git a/quantum/rgb_matrix_animations/typing_heatmap_anim.h b/quantum/rgb_matrix_animations/typing_heatmap_anim.h index e82c1b49ee..b855fdc190 100644 --- a/quantum/rgb_matrix_animations/typing_heatmap_anim.h +++ b/quantum/rgb_matrix_animations/typing_heatmap_anim.h @@ -51,7 +51,7 @@ bool TYPING_HEATMAP(effect_params_t* params) { if (!HAS_ANY_FLAGS(g_led_config.flags[led[j]], params->flags)) continue; HSV hsv = {170 - qsub8(val, 85), rgb_matrix_config.hsv.s, scale8((qadd8(170, val) - 170) * 3, rgb_matrix_config.hsv.v)}; - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(hsv); rgb_matrix_set_color(led[j], rgb.r, rgb.g, rgb.b); } diff --git a/quantum/rgb_matrix_runners/effect_runner_dx_dy.h b/quantum/rgb_matrix_runners/effect_runner_dx_dy.h index 9d0c9fab19..4867609c81 100644 --- a/quantum/rgb_matrix_runners/effect_runner_dx_dy.h +++ b/quantum/rgb_matrix_runners/effect_runner_dx_dy.h @@ -10,7 +10,7 @@ bool effect_runner_dx_dy(effect_params_t* params, dx_dy_f effect_func) { RGB_MATRIX_TEST_LED_FLAGS(); int16_t dx = g_led_config.point[i].x - k_rgb_matrix_center.x; int16_t dy = g_led_config.point[i].y - k_rgb_matrix_center.y; - RGB rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, time)); + RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, time)); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h b/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h index 2824c82527..9545b418d9 100644 --- a/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h +++ b/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h @@ -11,7 +11,7 @@ bool effect_runner_dx_dy_dist(effect_params_t* params, dx_dy_dist_f effect_func) int16_t dx = g_led_config.point[i].x - k_rgb_matrix_center.x; int16_t dy = g_led_config.point[i].y - k_rgb_matrix_center.y; uint8_t dist = sqrt16(dx * dx + dy * dy); - RGB rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, dist, time)); + RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, dist, time)); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgb_matrix_runners/effect_runner_i.h b/quantum/rgb_matrix_runners/effect_runner_i.h index 5e6bf5daaf..95bfe8b390 100644 --- a/quantum/rgb_matrix_runners/effect_runner_i.h +++ b/quantum/rgb_matrix_runners/effect_runner_i.h @@ -8,7 +8,7 @@ bool effect_runner_i(effect_params_t* params, i_f effect_func) { uint8_t time = scale16by8(g_rgb_timer, rgb_matrix_config.speed / 4); for (uint8_t i = led_min; i < led_max; i++) { RGB_MATRIX_TEST_LED_FLAGS(); - RGB rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, i, time)); + RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, i, time)); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgb_matrix_runners/effect_runner_reactive.h b/quantum/rgb_matrix_runners/effect_runner_reactive.h index 53e77e3fb2..8485b61f3d 100644 --- a/quantum/rgb_matrix_runners/effect_runner_reactive.h +++ b/quantum/rgb_matrix_runners/effect_runner_reactive.h @@ -20,7 +20,7 @@ bool effect_runner_reactive(effect_params_t* params, reactive_f effect_func) { } uint16_t offset = scale16by8(tick, rgb_matrix_config.speed); - RGB rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, offset)); + RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, offset)); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h b/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h index b5d284a40f..5c69d0fbb9 100644 --- a/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h +++ b/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h @@ -20,7 +20,7 @@ bool effect_runner_reactive_splash(uint8_t start, effect_params_t* params, react hsv = effect_func(hsv, dx, dy, dist, tick); } hsv.v = scale8(hsv.v, rgb_matrix_config.hsv.v); - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgb_matrix_hsv_to_rgb(hsv); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h b/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h index 3fb7d48051..02351de51e 100644 --- a/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h +++ b/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h @@ -10,7 +10,7 @@ bool effect_runner_sin_cos_i(effect_params_t* params, sin_cos_i_f effect_func) { int8_t sin_value = sin8(time) - 128; for (uint8_t i = led_min; i < led_max; i++) { RGB_MATRIX_TEST_LED_FLAGS(); - RGB rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, cos_value, sin_value, i, time)); + RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, cos_value, sin_value, i, time)); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } return led_max < DRIVER_LED_TOTAL; diff --git a/quantum/rgblight.c b/quantum/rgblight.c index 5ed3e8ac5f..4aa5cb4ac9 100644 --- a/quantum/rgblight.c +++ b/quantum/rgblight.c @@ -123,9 +123,11 @@ void rgblight_set_effect_range(uint8_t start_pos, uint8_t num_leds) { rgblight_ranges.effect_num_leds = num_leds; } +__attribute__((weak)) RGB rgblight_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv); } + void sethsv_raw(uint8_t hue, uint8_t sat, uint8_t val, LED_TYPE *led1) { HSV hsv = {hue, sat, val}; - RGB rgb = hsv_to_rgb(hsv); + RGB rgb = rgblight_hsv_to_rgb(hsv); setrgb(rgb.r, rgb.g, rgb.b, led1); } diff --git a/quantum/sequencer/sequencer.c b/quantum/sequencer/sequencer.c new file mode 100644 index 0000000000..0eaf3a17aa --- /dev/null +++ b/quantum/sequencer/sequencer.c @@ -0,0 +1,275 @@ +/* Copyright 2020 Rodolphe Belouin + * + * 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 "sequencer.h" + +#ifdef MIDI_ENABLE +# include "process_midi.h" +#endif + +#ifdef MIDI_MOCKED +# include "tests/midi_mock.h" +#endif + +sequencer_config_t sequencer_config = { + false, // enabled + {false}, // steps + {0}, // track notes + 60, // tempo + SQ_RES_4, // resolution +}; + +sequencer_state_t sequencer_internal_state = {0, 0, 0, 0, SEQUENCER_PHASE_ATTACK}; + +bool is_sequencer_on(void) { return sequencer_config.enabled; } + +void sequencer_on(void) { + dprintln("sequencer on"); + sequencer_config.enabled = true; + sequencer_internal_state.current_track = 0; + sequencer_internal_state.current_step = 0; + sequencer_internal_state.timer = timer_read(); + sequencer_internal_state.phase = SEQUENCER_PHASE_ATTACK; +} + +void sequencer_off(void) { + dprintln("sequencer off"); + sequencer_config.enabled = false; + sequencer_internal_state.current_step = 0; +} + +void sequencer_toggle(void) { + if (is_sequencer_on()) { + sequencer_off(); + } else { + sequencer_on(); + } +} + +void sequencer_set_track_notes(const uint16_t track_notes[SEQUENCER_TRACKS]) { + for (uint8_t i = 0; i < SEQUENCER_TRACKS; i++) { + sequencer_config.track_notes[i] = track_notes[i]; + } +} + +bool is_sequencer_track_active(uint8_t track) { return (sequencer_internal_state.active_tracks >> track) & true; } + +void sequencer_set_track_activation(uint8_t track, bool value) { + if (value) { + sequencer_internal_state.active_tracks |= (1 << track); + } else { + sequencer_internal_state.active_tracks &= ~(1 << track); + } + dprintf("sequencer: track %d is %s\n", track, value ? "active" : "inactive"); +} + +void sequencer_toggle_track_activation(uint8_t track) { sequencer_set_track_activation(track, !is_sequencer_track_active(track)); } + +void sequencer_toggle_single_active_track(uint8_t track) { + if (is_sequencer_track_active(track)) { + sequencer_internal_state.active_tracks = 0; + } else { + sequencer_internal_state.active_tracks = 1 << track; + } +} + +bool is_sequencer_step_on(uint8_t step) { return step < SEQUENCER_STEPS && (sequencer_config.steps[step] & sequencer_internal_state.active_tracks) > 0; } + +bool is_sequencer_step_on_for_track(uint8_t step, uint8_t track) { return step < SEQUENCER_STEPS && (sequencer_config.steps[step] >> track) & true; } + +void sequencer_set_step(uint8_t step, bool value) { + if (step < SEQUENCER_STEPS) { + if (value) { + sequencer_config.steps[step] |= sequencer_internal_state.active_tracks; + } else { + sequencer_config.steps[step] &= ~sequencer_internal_state.active_tracks; + } + dprintf("sequencer: step %d is %s\n", step, value ? "on" : "off"); + } else { + dprintf("sequencer: step %d is out of range\n", step); + } +} + +void sequencer_toggle_step(uint8_t step) { + if (is_sequencer_step_on(step)) { + sequencer_set_step_off(step); + } else { + sequencer_set_step_on(step); + } +} + +void sequencer_set_all_steps(bool value) { + for (uint8_t step = 0; step < SEQUENCER_STEPS; step++) { + if (value) { + sequencer_config.steps[step] |= sequencer_internal_state.active_tracks; + } else { + sequencer_config.steps[step] &= ~sequencer_internal_state.active_tracks; + } + } + dprintf("sequencer: all steps are %s\n", value ? "on" : "off"); +} + +uint8_t sequencer_get_tempo(void) { return sequencer_config.tempo; } + +void sequencer_set_tempo(uint8_t tempo) { + if (tempo > 0) { + sequencer_config.tempo = tempo; + dprintf("sequencer: tempo set to %d bpm\n", tempo); + } else { + dprintln("sequencer: cannot set tempo to 0"); + } +} + +void sequencer_increase_tempo(void) { + // Handling potential uint8_t overflow + if (sequencer_config.tempo < UINT8_MAX) { + sequencer_set_tempo(sequencer_config.tempo + 1); + } else { + dprintf("sequencer: cannot set tempo above %d\n", UINT8_MAX); + } +} + +void sequencer_decrease_tempo(void) { sequencer_set_tempo(sequencer_config.tempo - 1); } + +sequencer_resolution_t sequencer_get_resolution(void) { return sequencer_config.resolution; } + +void sequencer_set_resolution(sequencer_resolution_t resolution) { + if (resolution >= 0 && resolution < SEQUENCER_RESOLUTIONS) { + sequencer_config.resolution = resolution; + dprintf("sequencer: resolution set to %d\n", resolution); + } else { + dprintf("sequencer: resolution %d is out of range\n", resolution); + } +} + +void sequencer_increase_resolution(void) { sequencer_set_resolution(sequencer_config.resolution + 1); } + +void sequencer_decrease_resolution(void) { sequencer_set_resolution(sequencer_config.resolution - 1); } + +uint8_t sequencer_get_current_step(void) { return sequencer_internal_state.current_step; } + +void sequencer_phase_attack(void) { + dprintf("sequencer: step %d\n", sequencer_internal_state.current_step); + dprintf("sequencer: time %d\n", timer_read()); + + if (sequencer_internal_state.current_track == 0) { + sequencer_internal_state.timer = timer_read(); + } + + if (timer_elapsed(sequencer_internal_state.timer) < sequencer_internal_state.current_track * SEQUENCER_TRACK_THROTTLE) { + return; + } + +#if defined(MIDI_ENABLE) || defined(MIDI_MOCKED) + if (is_sequencer_step_on_for_track(sequencer_internal_state.current_step, sequencer_internal_state.current_track)) { + process_midi_basic_noteon(midi_compute_note(sequencer_config.track_notes[sequencer_internal_state.current_track])); + } +#endif + + if (sequencer_internal_state.current_track < SEQUENCER_TRACKS - 1) { + sequencer_internal_state.current_track++; + } else { + sequencer_internal_state.phase = SEQUENCER_PHASE_RELEASE; + } +} + +void sequencer_phase_release(void) { + if (timer_elapsed(sequencer_internal_state.timer) < SEQUENCER_PHASE_RELEASE_TIMEOUT + sequencer_internal_state.current_track * SEQUENCER_TRACK_THROTTLE) { + return; + } +#if defined(MIDI_ENABLE) || defined(MIDI_MOCKED) + if (is_sequencer_step_on_for_track(sequencer_internal_state.current_step, sequencer_internal_state.current_track)) { + process_midi_basic_noteoff(midi_compute_note(sequencer_config.track_notes[sequencer_internal_state.current_track])); + } +#endif + if (sequencer_internal_state.current_track > 0) { + sequencer_internal_state.current_track--; + } else { + sequencer_internal_state.phase = SEQUENCER_PHASE_PAUSE; + } +} + +void sequencer_phase_pause(void) { + if (timer_elapsed(sequencer_internal_state.timer) < sequencer_get_step_duration()) { + return; + } + + sequencer_internal_state.current_step = (sequencer_internal_state.current_step + 1) % SEQUENCER_STEPS; + sequencer_internal_state.phase = SEQUENCER_PHASE_ATTACK; +} + +void matrix_scan_sequencer(void) { + if (!sequencer_config.enabled) { + return; + } + + if (sequencer_internal_state.phase == SEQUENCER_PHASE_PAUSE) { + sequencer_phase_pause(); + } + + if (sequencer_internal_state.phase == SEQUENCER_PHASE_RELEASE) { + sequencer_phase_release(); + } + + if (sequencer_internal_state.phase == SEQUENCER_PHASE_ATTACK) { + sequencer_phase_attack(); + } +} + +uint16_t sequencer_get_beat_duration(void) { return get_beat_duration(sequencer_config.tempo); } + +uint16_t sequencer_get_step_duration(void) { return get_step_duration(sequencer_config.tempo, sequencer_config.resolution); } + +uint16_t get_beat_duration(uint8_t tempo) { + // Don’t crash in the unlikely case where the given tempo is 0 + if (tempo == 0) { + return get_beat_duration(60); + } + + /** + * Given + * t = tempo and d = duration, both strictly greater than 0 + * When + * t beats / minute = 1 beat / d ms + * Then + * t beats / 60000ms = 1 beat / d ms + * d ms = 60000ms / t + */ + return 60000 / tempo; +} + +uint16_t get_step_duration(uint8_t tempo, sequencer_resolution_t resolution) { + /** + * Resolution cheatsheet: + * 1/2 => 2 steps per 4 beats + * 1/2T => 3 steps per 4 beats + * 1/4 => 4 steps per 4 beats + * 1/4T => 6 steps per 4 beats + * 1/8 => 8 steps per 4 beats + * 1/8T => 12 steps per 4 beats + * 1/16 => 16 steps per 4 beats + * 1/16T => 24 steps per 4 beats + * 1/32 => 32 steps per 4 beats + * + * The number of steps for binary resolutions follows the powers of 2. + * The ternary variants are simply 1.5x faster. + */ + bool is_binary = resolution % 2 == 0; + uint8_t binary_steps = 2 << (resolution / 2); + uint16_t binary_step_duration = get_beat_duration(tempo) * 4 / binary_steps; + + return is_binary ? binary_step_duration : 2 * binary_step_duration / 3; +} diff --git a/quantum/sequencer/sequencer.h b/quantum/sequencer/sequencer.h new file mode 100644 index 0000000000..aeca7a1e9b --- /dev/null +++ b/quantum/sequencer/sequencer.h @@ -0,0 +1,122 @@ +/* Copyright 2020 Rodolphe Belouin + * + * 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 <stdbool.h> +#include "debug.h" +#include "timer.h" + +// Maximum number of steps: 256 +#ifndef SEQUENCER_STEPS +# define SEQUENCER_STEPS 16 +#endif + +// Maximum number of tracks: 8 +#ifndef SEQUENCER_TRACKS +# define SEQUENCER_TRACKS 8 +#endif + +#ifndef SEQUENCER_TRACK_THROTTLE +# define SEQUENCER_TRACK_THROTTLE 3 +#endif + +#ifndef SEQUENCER_PHASE_RELEASE_TIMEOUT +# define SEQUENCER_PHASE_RELEASE_TIMEOUT 30 +#endif + +/** + * Make sure that the items of this enumeration follow the powers of 2, separated by a ternary variant. + * Check the implementation of `get_step_duration` for further explanation. + */ +typedef enum { SQ_RES_2, SQ_RES_2T, SQ_RES_4, SQ_RES_4T, SQ_RES_8, SQ_RES_8T, SQ_RES_16, SQ_RES_16T, SQ_RES_32, SEQUENCER_RESOLUTIONS } sequencer_resolution_t; + +typedef struct { + bool enabled; + uint8_t steps[SEQUENCER_STEPS]; + uint16_t track_notes[SEQUENCER_TRACKS]; + uint8_t tempo; // Is a maximum tempo of 255 reasonable? + sequencer_resolution_t resolution; +} sequencer_config_t; + +/** + * Because Digital Audio Workstations get overwhelmed when too many MIDI signals are sent concurrently, + * We use a "phase" state machine to delay some of the events. + */ +typedef enum sequencer_phase_t { + SEQUENCER_PHASE_ATTACK, // t=0ms, send the MIDI note on signal + SEQUENCER_PHASE_RELEASE, // t=SEQUENCER_PHASE_RELEASE_TIMEOUT ms, send the MIDI note off signal + SEQUENCER_PHASE_PAUSE // t=step duration ms, loop +} sequencer_phase_t; + +typedef struct { + uint8_t active_tracks; + uint8_t current_track; + uint8_t current_step; + uint16_t timer; + sequencer_phase_t phase; +} sequencer_state_t; + +extern sequencer_config_t sequencer_config; + +// We expose the internal state to make the feature more "unit-testable" +extern sequencer_state_t sequencer_internal_state; + +bool is_sequencer_on(void); +void sequencer_toggle(void); +void sequencer_on(void); +void sequencer_off(void); + +void sequencer_set_track_notes(const uint16_t track_notes[SEQUENCER_TRACKS]); + +bool is_sequencer_track_active(uint8_t track); +void sequencer_set_track_activation(uint8_t track, bool value); +void sequencer_toggle_track_activation(uint8_t track); +void sequencer_toggle_single_active_track(uint8_t track); + +#define sequencer_activate_track(track) sequencer_set_track_activation(track, true) +#define sequencer_deactivate_track(track) sequencer_set_track_activation(track, false) + +bool is_sequencer_step_on(uint8_t step); +bool is_sequencer_step_on_for_track(uint8_t step, uint8_t track); +void sequencer_set_step(uint8_t step, bool value); +void sequencer_toggle_step(uint8_t step); +void sequencer_set_all_steps(bool value); + +#define sequencer_set_step_on(step) sequencer_set_step(step, true) +#define sequencer_set_step_off(step) sequencer_set_step(step, false) +#define sequencer_set_all_steps_on() sequencer_set_all_steps(true) +#define sequencer_set_all_steps_off() sequencer_set_all_steps(false) + +uint8_t sequencer_get_tempo(void); +void sequencer_set_tempo(uint8_t tempo); +void sequencer_increase_tempo(void); +void sequencer_decrease_tempo(void); + +sequencer_resolution_t sequencer_get_resolution(void); +void sequencer_set_resolution(sequencer_resolution_t resolution); +void sequencer_increase_resolution(void); +void sequencer_decrease_resolution(void); + +uint8_t sequencer_get_current_step(void); + +uint16_t sequencer_get_beat_duration(void); +uint16_t sequencer_get_step_duration(void); + +uint16_t get_beat_duration(uint8_t tempo); +uint16_t get_step_duration(uint8_t tempo, sequencer_resolution_t resolution); + +void matrix_scan_sequencer(void); diff --git a/quantum/sequencer/tests/midi_mock.c b/quantum/sequencer/tests/midi_mock.c new file mode 100644 index 0000000000..236e16f9d7 --- /dev/null +++ b/quantum/sequencer/tests/midi_mock.c @@ -0,0 +1,26 @@ +/* Copyright 2020 Rodolphe Belouin + * + * 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 "midi_mock.h" + +uint16_t last_noteon = 0; +uint16_t last_noteoff = 0; + +uint16_t midi_compute_note(uint16_t keycode) { return keycode; } + +void process_midi_basic_noteon(uint16_t note) { last_noteon = note; } + +void process_midi_basic_noteoff(uint16_t note) { last_noteoff = note; } diff --git a/quantum/sequencer/tests/midi_mock.h b/quantum/sequencer/tests/midi_mock.h new file mode 100644 index 0000000000..4d8c2eb307 --- /dev/null +++ b/quantum/sequencer/tests/midi_mock.h @@ -0,0 +1,26 @@ +/* Copyright 2020 Rodolphe Belouin + * + * 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> + +extern uint16_t last_noteon; +extern uint16_t last_noteoff; + +uint16_t midi_compute_note(uint16_t keycode); +void process_midi_basic_noteon(uint16_t note); +void process_midi_basic_noteoff(uint16_t note); diff --git a/quantum/sequencer/tests/rules.mk b/quantum/sequencer/tests/rules.mk new file mode 100644 index 0000000000..76c221cf92 --- /dev/null +++ b/quantum/sequencer/tests/rules.mk @@ -0,0 +1,11 @@ +# The letter case of these variables might seem odd. However: +# - it is consistent with the serial_link example that is used as a reference in the Unit Testing article (https://docs.qmk.fm/#/unit_testing?id=adding-tests-for-new-or-existing-features) +# - Neither `make test:sequencer` or `make test:SEQUENCER` work when using SCREAMING_SNAKE_CASE + +sequencer_DEFS := -DNO_DEBUG -DMIDI_MOCKED + +sequencer_SRC := \ + $(QUANTUM_PATH)/sequencer/tests/midi_mock.c \ + $(QUANTUM_PATH)/sequencer/tests/sequencer_tests.cpp \ + $(QUANTUM_PATH)/sequencer/sequencer.c \ + $(TMK_PATH)/common/test/timer.c diff --git a/quantum/sequencer/tests/sequencer_tests.cpp b/quantum/sequencer/tests/sequencer_tests.cpp new file mode 100644 index 0000000000..e81984e5b5 --- /dev/null +++ b/quantum/sequencer/tests/sequencer_tests.cpp @@ -0,0 +1,590 @@ +/* Copyright 2020 Rodolphe Belouin + * + * 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 "gtest/gtest.h" + +extern "C" { +#include "sequencer.h" +#include "midi_mock.h" +#include "quantum/quantum_keycodes.h" +} + +extern "C" { +void set_time(uint32_t t); +void advance_time(uint32_t ms); +} + +class SequencerTest : public ::testing::Test { + protected: + void SetUp() override { + config_copy.enabled = sequencer_config.enabled; + + for (int i = 0; i < SEQUENCER_STEPS; i++) { + config_copy.steps[i] = sequencer_config.steps[i]; + } + + for (int i = 0; i < SEQUENCER_TRACKS; i++) { + config_copy.track_notes[i] = sequencer_config.track_notes[i]; + } + + config_copy.tempo = sequencer_config.tempo; + config_copy.resolution = sequencer_config.resolution; + + state_copy.active_tracks = sequencer_internal_state.active_tracks; + state_copy.current_track = sequencer_internal_state.current_track; + state_copy.current_step = sequencer_internal_state.current_step; + state_copy.timer = sequencer_internal_state.timer; + + last_noteon = 0; + last_noteoff = 0; + + set_time(0); + } + + void TearDown() override { + sequencer_config.enabled = config_copy.enabled; + + for (int i = 0; i < SEQUENCER_STEPS; i++) { + sequencer_config.steps[i] = config_copy.steps[i]; + } + + for (int i = 0; i < SEQUENCER_TRACKS; i++) { + sequencer_config.track_notes[i] = config_copy.track_notes[i]; + } + + sequencer_config.tempo = config_copy.tempo; + sequencer_config.resolution = config_copy.resolution; + + sequencer_internal_state.active_tracks = state_copy.active_tracks; + sequencer_internal_state.current_track = state_copy.current_track; + sequencer_internal_state.current_step = state_copy.current_step; + sequencer_internal_state.timer = state_copy.timer; + } + + sequencer_config_t config_copy; + sequencer_state_t state_copy; +}; + +TEST_F(SequencerTest, TestOffByDefault) { EXPECT_EQ(is_sequencer_on(), false); } + +TEST_F(SequencerTest, TestOn) { + sequencer_config.enabled = false; + + sequencer_on(); + EXPECT_EQ(is_sequencer_on(), true); + + // sequencer_on is idempotent + sequencer_on(); + EXPECT_EQ(is_sequencer_on(), true); +} + +TEST_F(SequencerTest, TestOff) { + sequencer_config.enabled = true; + + sequencer_off(); + EXPECT_EQ(is_sequencer_on(), false); + + // sequencer_off is idempotent + sequencer_off(); + EXPECT_EQ(is_sequencer_on(), false); +} + +TEST_F(SequencerTest, TestToggle) { + sequencer_config.enabled = false; + + sequencer_toggle(); + EXPECT_EQ(is_sequencer_on(), true); + + sequencer_toggle(); + EXPECT_EQ(is_sequencer_on(), false); +} + +TEST_F(SequencerTest, TestNoActiveTrackByDefault) { + for (int i = 0; i < SEQUENCER_TRACKS; i++) { + EXPECT_EQ(is_sequencer_track_active(i), false); + } +} + +TEST_F(SequencerTest, TestGetActiveTracks) { + sequencer_internal_state.active_tracks = (1 << 7) + (1 << 6) + (1 << 3) + (1 << 1) + (1 << 0); + + EXPECT_EQ(is_sequencer_track_active(0), true); + EXPECT_EQ(is_sequencer_track_active(1), true); + EXPECT_EQ(is_sequencer_track_active(2), false); + EXPECT_EQ(is_sequencer_track_active(3), true); + EXPECT_EQ(is_sequencer_track_active(4), false); + EXPECT_EQ(is_sequencer_track_active(5), false); + EXPECT_EQ(is_sequencer_track_active(6), true); + EXPECT_EQ(is_sequencer_track_active(7), true); +} + +TEST_F(SequencerTest, TestGetActiveTracksOutOfBound) { + sequencer_set_track_activation(-1, true); + sequencer_set_track_activation(8, true); + + EXPECT_EQ(is_sequencer_track_active(-1), false); + EXPECT_EQ(is_sequencer_track_active(8), false); +} + +TEST_F(SequencerTest, TestToggleTrackActivation) { + sequencer_internal_state.active_tracks = (1 << 7) + (1 << 6) + (1 << 3) + (1 << 1) + (1 << 0); + + sequencer_toggle_track_activation(6); + + EXPECT_EQ(is_sequencer_track_active(0), true); + EXPECT_EQ(is_sequencer_track_active(1), true); + EXPECT_EQ(is_sequencer_track_active(2), false); + EXPECT_EQ(is_sequencer_track_active(3), true); + EXPECT_EQ(is_sequencer_track_active(4), false); + EXPECT_EQ(is_sequencer_track_active(5), false); + EXPECT_EQ(is_sequencer_track_active(6), false); + EXPECT_EQ(is_sequencer_track_active(7), true); +} + +TEST_F(SequencerTest, TestToggleSingleTrackActivation) { + sequencer_internal_state.active_tracks = (1 << 7) + (1 << 6) + (1 << 3) + (1 << 1) + (1 << 0); + + sequencer_toggle_single_active_track(2); + + EXPECT_EQ(is_sequencer_track_active(0), false); + EXPECT_EQ(is_sequencer_track_active(1), false); + EXPECT_EQ(is_sequencer_track_active(2), true); + EXPECT_EQ(is_sequencer_track_active(3), false); + EXPECT_EQ(is_sequencer_track_active(4), false); + EXPECT_EQ(is_sequencer_track_active(5), false); + EXPECT_EQ(is_sequencer_track_active(6), false); + EXPECT_EQ(is_sequencer_track_active(7), false); +} + +TEST_F(SequencerTest, TestStepOffByDefault) { + for (int i = 0; i < SEQUENCER_STEPS; i++) { + EXPECT_EQ(is_sequencer_step_on(i), false); + } +} + +TEST_F(SequencerTest, TestIsStepOffWithNoActiveTracks) { + sequencer_config.steps[3] = 0xFF; + EXPECT_EQ(is_sequencer_step_on(3), false); +} + +TEST_F(SequencerTest, TestIsStepOffWithGivenActiveTracks) { + sequencer_set_track_activation(2, true); + sequencer_set_track_activation(3, true); + + sequencer_config.steps[3] = (1 << 0) + (1 << 1); + + // No active tracks have the step enabled, so it is off + EXPECT_EQ(is_sequencer_step_on(3), false); +} + +TEST_F(SequencerTest, TestIsStepOnWithGivenActiveTracks) { + sequencer_set_track_activation(2, true); + sequencer_set_track_activation(3, true); + + sequencer_config.steps[3] = (1 << 2); + + // Track 2 has the step enabled, so it is on + EXPECT_EQ(is_sequencer_step_on(3), true); +} + +TEST_F(SequencerTest, TestIsStepOffForGivenTrack) { + sequencer_config.steps[3] = 0x00; + EXPECT_EQ(is_sequencer_step_on_for_track(3, 5), false); +} + +TEST_F(SequencerTest, TestIsStepOnForGivenTrack) { + sequencer_config.steps[3] = (1 << 5); + EXPECT_EQ(is_sequencer_step_on_for_track(3, 5), true); +} + +TEST_F(SequencerTest, TestSetStepOn) { + sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2); + sequencer_config.steps[2] = (1 << 5) + (1 << 2); + + sequencer_set_step(2, true); + + EXPECT_EQ(sequencer_config.steps[2], (1 << 6) + (1 << 5) + (1 << 3) + (1 << 2)); +} + +TEST_F(SequencerTest, TestSetStepOff) { + sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2); + sequencer_config.steps[2] = (1 << 5) + (1 << 2); + + sequencer_set_step(2, false); + + EXPECT_EQ(sequencer_config.steps[2], (1 << 5)); +} + +TEST_F(SequencerTest, TestToggleStepOff) { + sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2); + sequencer_config.steps[2] = (1 << 5) + (1 << 2); + + sequencer_toggle_step(2); + + EXPECT_EQ(sequencer_config.steps[2], (1 << 5)); +} + +TEST_F(SequencerTest, TestToggleStepOn) { + sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2); + sequencer_config.steps[2] = 0; + + sequencer_toggle_step(2); + + EXPECT_EQ(sequencer_config.steps[2], (1 << 6) + (1 << 3) + (1 << 2)); +} + +TEST_F(SequencerTest, TestSetAllStepsOn) { + sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2); + sequencer_config.steps[2] = (1 << 7) + (1 << 6); + sequencer_config.steps[4] = (1 << 3) + (1 << 1); + + sequencer_set_all_steps(true); + + EXPECT_EQ(sequencer_config.steps[2], (1 << 7) + (1 << 6) + (1 << 3) + (1 << 2)); + EXPECT_EQ(sequencer_config.steps[4], (1 << 6) + (1 << 3) + (1 << 2) + (1 << 1)); +} + +TEST_F(SequencerTest, TestSetAllStepsOff) { + sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2); + sequencer_config.steps[2] = (1 << 7) + (1 << 6); + sequencer_config.steps[4] = (1 << 3) + (1 << 1); + + sequencer_set_all_steps(false); + + EXPECT_EQ(sequencer_config.steps[2], (1 << 7)); + EXPECT_EQ(sequencer_config.steps[4], (1 << 1)); +} + +TEST_F(SequencerTest, TestSetTempoZero) { + sequencer_config.tempo = 123; + + sequencer_set_tempo(0); + + EXPECT_EQ(sequencer_config.tempo, 123); +} + +TEST_F(SequencerTest, TestIncreaseTempoMax) { + sequencer_config.tempo = UINT8_MAX; + + sequencer_increase_tempo(); + + EXPECT_EQ(sequencer_config.tempo, UINT8_MAX); +} + +TEST_F(SequencerTest, TestSetResolutionLowerBound) { + sequencer_config.resolution = SQ_RES_4; + + sequencer_set_resolution((sequencer_resolution_t)-1); + + EXPECT_EQ(sequencer_config.resolution, SQ_RES_4); +} + +TEST_F(SequencerTest, TestSetResolutionUpperBound) { + sequencer_config.resolution = SQ_RES_4; + + sequencer_set_resolution(SEQUENCER_RESOLUTIONS); + + EXPECT_EQ(sequencer_config.resolution, SQ_RES_4); +} + +TEST_F(SequencerTest, TestGetBeatDuration) { + EXPECT_EQ(get_beat_duration(60), 1000); + EXPECT_EQ(get_beat_duration(120), 500); + EXPECT_EQ(get_beat_duration(240), 250); + EXPECT_EQ(get_beat_duration(0), 1000); +} + +TEST_F(SequencerTest, TestGetStepDuration60) { + /** + * Resolution cheatsheet: + * 1/2 => 2 steps per 4 beats + * 1/2T => 3 steps per 4 beats + * 1/4 => 4 steps per 4 beats + * 1/4T => 6 steps per 4 beats + * 1/8 => 8 steps per 4 beats + * 1/8T => 12 steps per 4 beats + * 1/16 => 16 steps per 4 beats + * 1/16T => 24 steps per 4 beats + * 1/32 => 32 steps per 4 beats + * + * The number of steps for binary resolutions follows the powers of 2. + * The ternary variants are simply 1.5x faster. + */ + EXPECT_EQ(get_step_duration(60, SQ_RES_2), 2000); + EXPECT_EQ(get_step_duration(60, SQ_RES_4), 1000); + EXPECT_EQ(get_step_duration(60, SQ_RES_8), 500); + EXPECT_EQ(get_step_duration(60, SQ_RES_16), 250); + EXPECT_EQ(get_step_duration(60, SQ_RES_32), 125); + + EXPECT_EQ(get_step_duration(60, SQ_RES_2T), 1333); + EXPECT_EQ(get_step_duration(60, SQ_RES_4T), 666); + EXPECT_EQ(get_step_duration(60, SQ_RES_8T), 333); + EXPECT_EQ(get_step_duration(60, SQ_RES_16T), 166); +} + +TEST_F(SequencerTest, TestGetStepDuration120) { + /** + * Resolution cheatsheet: + * 1/2 => 2 steps per 4 beats + * 1/2T => 3 steps per 4 beats + * 1/4 => 4 steps per 4 beats + * 1/4T => 6 steps per 4 beats + * 1/8 => 8 steps per 4 beats + * 1/8T => 12 steps per 4 beats + * 1/16 => 16 steps per 4 beats + * 1/16T => 24 steps per 4 beats + * 1/32 => 32 steps per 4 beats + * + * The number of steps for binary resolutions follows the powers of 2. + * The ternary variants are simply 1.5x faster. + */ + EXPECT_EQ(get_step_duration(30, SQ_RES_2), 4000); + EXPECT_EQ(get_step_duration(30, SQ_RES_4), 2000); + EXPECT_EQ(get_step_duration(30, SQ_RES_8), 1000); + EXPECT_EQ(get_step_duration(30, SQ_RES_16), 500); + EXPECT_EQ(get_step_duration(30, SQ_RES_32), 250); + + EXPECT_EQ(get_step_duration(30, SQ_RES_2T), 2666); + EXPECT_EQ(get_step_duration(30, SQ_RES_4T), 1333); + EXPECT_EQ(get_step_duration(30, SQ_RES_8T), 666); + EXPECT_EQ(get_step_duration(30, SQ_RES_16T), 333); +} + +void setUpMatrixScanSequencerTest(void) { + sequencer_config.enabled = true; + sequencer_config.tempo = 120; + sequencer_config.resolution = SQ_RES_16; + + // Configure the notes for each track + sequencer_config.track_notes[0] = MI_C; + sequencer_config.track_notes[1] = MI_D; + sequencer_config.track_notes[2] = MI_E; + sequencer_config.track_notes[3] = MI_F; + sequencer_config.track_notes[4] = MI_G; + sequencer_config.track_notes[5] = MI_A; + sequencer_config.track_notes[6] = MI_B; + sequencer_config.track_notes[7] = MI_C; + + // Turn on some steps + sequencer_config.steps[0] = (1 << 0); + sequencer_config.steps[2] = (1 << 1) + (1 << 0); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldAttackFirstTrackOfFirstStep) { + setUpMatrixScanSequencerTest(); + + matrix_scan_sequencer(); + EXPECT_EQ(last_noteon, MI_C); + EXPECT_EQ(last_noteoff, 0); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldAttackSecondTrackAfterFirstTrackOfFirstStep) { + setUpMatrixScanSequencerTest(); + + matrix_scan_sequencer(); + EXPECT_EQ(sequencer_internal_state.current_step, 0); + EXPECT_EQ(sequencer_internal_state.current_track, 1); + EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldNotAttackInactiveTrackFirstStep) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = 1; + + // Wait some time after the first track has been attacked + advance_time(SEQUENCER_TRACK_THROTTLE); + + matrix_scan_sequencer(); + EXPECT_EQ(last_noteon, 0); + EXPECT_EQ(last_noteoff, 0); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldAttackThirdTrackAfterSecondTrackOfFirstStep) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = 1; + + // Wait some time after the second track has been attacked + advance_time(2 * SEQUENCER_TRACK_THROTTLE); + + matrix_scan_sequencer(); + EXPECT_EQ(sequencer_internal_state.current_step, 0); + EXPECT_EQ(sequencer_internal_state.current_track, 2); + EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldEnterReleasePhaseAfterLastTrackHasBeenProcessedFirstStep) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = SEQUENCER_TRACKS - 1; + + // Wait until all notes have been attacked + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + + matrix_scan_sequencer(); + EXPECT_EQ(last_noteon, 0); + EXPECT_EQ(last_noteoff, 0); + EXPECT_EQ(sequencer_internal_state.current_step, 0); + EXPECT_EQ(sequencer_internal_state.current_track, SEQUENCER_TRACKS - 1); + EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_RELEASE); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldReleaseBackwards) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = SEQUENCER_TRACKS - 1; + sequencer_internal_state.phase = SEQUENCER_PHASE_RELEASE; + + // Wait until all notes have been attacked + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the release timeout + advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT); + + matrix_scan_sequencer(); + EXPECT_EQ(sequencer_internal_state.current_step, 0); + EXPECT_EQ(sequencer_internal_state.current_track, SEQUENCER_TRACKS - 2); + EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_RELEASE); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldNotReleaseInactiveTrackFirstStep) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = SEQUENCER_TRACKS - 1; + sequencer_internal_state.phase = SEQUENCER_PHASE_RELEASE; + + // Wait until all notes have been attacked + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the release timeout + advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT); + + matrix_scan_sequencer(); + EXPECT_EQ(last_noteon, 0); + EXPECT_EQ(last_noteoff, 0); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldReleaseFirstTrackFirstStep) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = 0; + sequencer_internal_state.phase = SEQUENCER_PHASE_RELEASE; + + // Wait until all notes have been attacked + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the release timeout + advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT); + // + all the other notes have been released + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + + matrix_scan_sequencer(); + EXPECT_EQ(last_noteon, 0); + EXPECT_EQ(last_noteoff, MI_C); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldEnterPausePhaseAfterRelease) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = 0; + sequencer_internal_state.phase = SEQUENCER_PHASE_RELEASE; + + // Wait until all notes have been attacked + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the release timeout + advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT); + // + all the other notes have been released + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + + matrix_scan_sequencer(); + EXPECT_EQ(sequencer_internal_state.current_step, 0); + EXPECT_EQ(sequencer_internal_state.current_track, 0); + EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_PAUSE); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldProcessFirstTrackOfSecondStepAfterPause) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 0; + sequencer_internal_state.current_track = 0; + sequencer_internal_state.phase = SEQUENCER_PHASE_PAUSE; + + // Wait until all notes have been attacked + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the release timeout + advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT); + // + all the other notes have been released + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the step duration (one 16th at tempo=120 lasts 125ms) + advance_time(125); + + matrix_scan_sequencer(); + EXPECT_EQ(sequencer_internal_state.current_step, 1); + EXPECT_EQ(sequencer_internal_state.current_track, 1); + EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldProcessSecondTrackTooEarly) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 2; + sequencer_internal_state.current_track = 1; + + matrix_scan_sequencer(); + EXPECT_EQ(last_noteon, 0); + EXPECT_EQ(last_noteoff, 0); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldProcessSecondTrackOnTime) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = 2; + sequencer_internal_state.current_track = 1; + + // Wait until first track has been attacked + advance_time(SEQUENCER_TRACK_THROTTLE); + + matrix_scan_sequencer(); + EXPECT_EQ(last_noteon, MI_D); + EXPECT_EQ(last_noteoff, 0); +} + +TEST_F(SequencerTest, TestMatrixScanSequencerShouldLoopOnceSequenceIsOver) { + setUpMatrixScanSequencerTest(); + + sequencer_internal_state.current_step = SEQUENCER_STEPS - 1; + sequencer_internal_state.current_track = 0; + sequencer_internal_state.phase = SEQUENCER_PHASE_PAUSE; + + // Wait until all notes have been attacked + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the release timeout + advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT); + // + all the other notes have been released + advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE); + // + the step duration (one 16th at tempo=120 lasts 125ms) + advance_time(125); + + matrix_scan_sequencer(); + EXPECT_EQ(sequencer_internal_state.current_step, 0); + EXPECT_EQ(sequencer_internal_state.current_track, 1); + EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK); +} diff --git a/quantum/sequencer/tests/testlist.mk b/quantum/sequencer/tests/testlist.mk new file mode 100644 index 0000000000..bb38991109 --- /dev/null +++ b/quantum/sequencer/tests/testlist.mk @@ -0,0 +1 @@ +TEST_LIST += sequencer diff --git a/quantum/split_common/matrix.c b/quantum/split_common/matrix.c index 5bad9db08f..cd5a024c3d 100644 --- a/quantum/split_common/matrix.c +++ b/quantum/split_common/matrix.c @@ -45,6 +45,19 @@ uint8_t thisHand, thatHand; // user-defined overridable functions __attribute__((weak)) void matrix_slave_scan_user(void) {} +static inline void setPinOutput_writeLow(pin_t pin) { + ATOMIC_BLOCK_FORCEON { + setPinOutput(pin); + writePinLow(pin); + } +} + +static inline void setPinInputHigh_atomic(pin_t pin) { + ATOMIC_BLOCK_FORCEON { + setPinInputHigh(pin); + } +} + // matrix code #ifdef DIRECT_PINS @@ -83,22 +96,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) # if (DIODE_DIRECTION == COL2ROW) static void select_row(uint8_t row) { - setPinOutput(row_pins[row]); - writePinLow(row_pins[row]); + setPinOutput_writeLow(row_pins[row]); } -static void unselect_row(uint8_t row) { setPinInputHigh(row_pins[row]); } +static void unselect_row(uint8_t row) { + setPinInputHigh_atomic(row_pins[row]); +} static void unselect_rows(void) { for (uint8_t x = 0; x < ROWS_PER_HAND; x++) { - setPinInputHigh(row_pins[x]); + setPinInputHigh_atomic(row_pins[x]); } } static void init_pins(void) { unselect_rows(); for (uint8_t x = 0; x < MATRIX_COLS; x++) { - setPinInputHigh(col_pins[x]); + setPinInputHigh_atomic(col_pins[x]); } } @@ -133,22 +147,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) # elif (DIODE_DIRECTION == ROW2COL) static void select_col(uint8_t col) { - setPinOutput(col_pins[col]); - writePinLow(col_pins[col]); + setPinOutput_writeLow(col_pins[col]); } -static void unselect_col(uint8_t col) { setPinInputHigh(col_pins[col]); } +static void unselect_col(uint8_t col) { + setPinInputHigh_atomic(col_pins[col]); +} static void unselect_cols(void) { for (uint8_t x = 0; x < MATRIX_COLS; x++) { - setPinInputHigh(col_pins[x]); + setPinInputHigh_atomic(col_pins[x]); } } static void init_pins(void) { unselect_cols(); for (uint8_t x = 0; x < ROWS_PER_HAND; x++) { - setPinInputHigh(row_pins[x]); + setPinInputHigh_atomic(row_pins[x]); } } |