2020 November 28 Breaking Changes Update (#11053)

* Branch point for 2020 November 28 Breaking Change                                                

* Remove matrix_col_t to allow MATRIX_ROWS > 32 (#10183)                                           

* Add support for soft serial to ATmega32U2 (#10204)                                               

* Change MIDI velocity implementation to allow direct control of velocity value (#9940)            

* Add ability to build a subset of all keyboards based on platform.                                

* Actually use eeprom_driver_init().                                                               

* Make bootloader_jump weak for ChibiOS. (#10417)                                                  

* Joystick 16-bit support (#10439)                                                                 

* Per-encoder resolutions (#10259)                                                                 

* Share button state from mousekey to pointing_device (#10179)                                     

* Add hotfix for chibios keyboards not wake (#10088)                                               

* Add advanced/efficient RGB Matrix Indicators (#8564)                                             

* Naming change.                                                                                   

* Support for STM32 GPIOF,G,H,I,J,K (#10206)                                                       

* Add milc as a dependency and remove the installed milc (#10563)                                  

* ChibiOS upgrade: early init conversions (#10214)                                                 

* ChibiOS upgrade: configuration file migrator (#9952)                                             

* Haptic and solenoid cleanup (#9700)                                                              

* XD75 cleanup (#10524)                                                                            

* OLED display update interval support (#10388)                                                    

* Add definition based on currently-selected serial driver. (#10716)                               

* New feature: Retro Tapping per key (#10622)                                                      

* Allow for modification of output RGB values when using rgblight/rgb_matrix. (#10638)             

* Add housekeeping task callbacks so that keyboards/keymaps are capable of executing code for each main loop iteration. (#10530)

* Rescale both ChibiOS and AVR backlighting.                                                       

* Reduce Helix keyboard build variation (#8669)                                                    

* Minor change to behavior allowing display updates to continue between task ticks (#10750)        

* Some GPIO manipulations in matrix.c change to atomic. (#10491)                                   

* qmk cformat (#10767)                                                                             

* [Keyboard] Update the Speedo firmware for v3.0 (#10657)                                          

* Maartenwut/Maarten namechange to evyd13/Evy (#10274)                                             

* [quantum] combine repeated lines of code (#10837)                                                

* Add step sequencer feature (#9703)                                                               

* aeboards/ext65 refactor (#10820)                                                                 

* Refactor xelus/dawn60 for Rev2 later (#10584)                                                    

* add DEBUG_MATRIX_SCAN_RATE_ENABLE to common_features.mk (#10824)                                 

* [Core] Added `add_oneshot_mods` & `del_oneshot_mods` (#10549)                                    

* update chibios os usb for the otg driver (#8893)                                                 

* Remove HD44780 References, Part 4 (#10735)                                                       

* [Keyboard] Add Valor FRL TKL (+refactor) (#10512)                                                

* Fix cursor position bug in oled_write_raw functions (#10800)                                     

* Fixup version.h writing when using SKIP_VERSION=yes (#10972)                                     

* Allow for certain code in the codebase assuming length of string. (#10974)                       

* Add AT90USB support for serial.c (#10706)                                                        

* Auto shift: support repeats and early registration (#9826)                                       

* Rename ledmatrix.h to match .c file (#7949)                                                      

* Split RGB_MATRIX_ENABLE into _ENABLE and _DRIVER (#10231)                                        

* Split LED_MATRIX_ENABLE into _ENABLE and _DRIVER (#10840)                                        

* Merge point for 2020 Nov 28 Breaking Change
This commit is contained in:
James Young 2020-11-28 12:02:18 -08:00 committed by GitHub
parent 15385d4113
commit c66df16644
Failed to generate hash of commit
884 changed files with 8121 additions and 11685 deletions

View file

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

View file

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

View file

@ -39,7 +39,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
@ -58,11 +58,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
@ -284,6 +279,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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,48 +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_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};
void autoshift_flush(void) {
if (autoshift_lastkey != KC_NO) {
uint16_t elapsed = timer_elapsed(autoshift_time);
if (elapsed > autoshift_timeout) {
tap_code16(LSFT(autoshift_lastkey));
} else {
tap_code(autoshift_lastkey);
}
autoshift_time = 0;
autoshift_lastkey = KC_NO;
/** \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;
}
# 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 !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING)
clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED);
# endif
return false;
}
void autoshift_on(uint16_t keycode) {
autoshift_time = timer_read();
autoshift_lastkey = keycode;
/** \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 {
// 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
}
# 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;
}
/** \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
@ -70,19 +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; }
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;
@ -102,41 +214,28 @@ bool process_auto_shift(uint16_t keycode, keyrecord_t *record) {
autoshift_timer_report();
return true;
# endif
# ifndef NO_AUTO_SHIFT_ALPHA
case KC_A ... KC_Z:
# endif
# ifndef NO_AUTO_SHIFT_NUMERIC
case KC_1 ... KC_0:
# endif
# ifndef NO_AUTO_SHIFT_SPECIAL
case KC_TAB:
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);
# endif
return false;
default:
autoshift_flush();
return true;
}
} else {
autoshift_flush();
}
switch (keycode) {
# ifndef NO_AUTO_SHIFT_ALPHA
case KC_A ... KC_Z:
# endif
# ifndef NO_AUTO_SHIFT_NUMERIC
case KC_1 ... KC_0:
# endif
# ifndef NO_AUTO_SHIFT_SPECIAL
case KC_TAB:
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;
}
}
return true;
}

View file

@ -30,3 +30,4 @@ void autoshift_toggle(void);
bool get_autoshift_state(void);
uint16_t get_autoshift_timeout(void);
void set_autoshift_timeout(uint16_t timeout);
void autoshift_matrix_scan(void);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -58,6 +58,10 @@ float bell_song[][2] = SONG(TERMINAL_SOUND);
# 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
@ -636,6 +643,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
@ -664,6 +675,10 @@ void matrix_scan_quantum() {
dip_switch_read(false);
#endif
#ifdef AUTO_SHIFT_ENABLE
autoshift_matrix_scan();
#endif
matrix_scan_kb();
}

View file

@ -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
@ -220,6 +225,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)

View file

@ -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,
@ -549,6 +554,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
};

View file

@ -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"
@ -401,6 +403,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);
@ -409,10 +415,6 @@ void rgb_matrix_task(void) {
rgb_task_sync();
break;
}
if (!suspend_backlight) {
rgb_matrix_indicators();
}
}
void rgb_matrix_indicators(void) {
@ -424,6 +426,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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
TEST_LIST += sequencer

View file

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