forked from mirrors/qmk_userspace
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:
parent
15385d4113
commit
c66df16644
884 changed files with 8121 additions and 11685 deletions
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
*
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
62
quantum/process_keycode/process_sequencer.c
Normal file
62
quantum/process_keycode/process_sequencer.c
Normal 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;
|
||||
}
|
21
quantum/process_keycode/process_sequencer.h
Normal file
21
quantum/process_keycode/process_sequencer.h
Normal 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);
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
275
quantum/sequencer/sequencer.c
Normal file
275
quantum/sequencer/sequencer.c
Normal 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) {
|
||||
// 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;
|
||||
}
|
122
quantum/sequencer/sequencer.h
Normal file
122
quantum/sequencer/sequencer.h
Normal 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);
|
26
quantum/sequencer/tests/midi_mock.c
Normal file
26
quantum/sequencer/tests/midi_mock.c
Normal 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; }
|
26
quantum/sequencer/tests/midi_mock.h
Normal file
26
quantum/sequencer/tests/midi_mock.h
Normal 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);
|
11
quantum/sequencer/tests/rules.mk
Normal file
11
quantum/sequencer/tests/rules.mk
Normal 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
|
590
quantum/sequencer/tests/sequencer_tests.cpp
Normal file
590
quantum/sequencer/tests/sequencer_tests.cpp
Normal 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);
|
||||
}
|
1
quantum/sequencer/tests/testlist.mk
Normal file
1
quantum/sequencer/tests/testlist.mk
Normal file
|
@ -0,0 +1 @@
|
|||
TEST_LIST += sequencer
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue