diff --git a/ESP32-S3-Analog/adjustable.h b/ESP32-S3-Analog/adjustable.h index bfccc53..69d0682 100644 --- a/ESP32-S3-Analog/adjustable.h +++ b/ESP32-S3-Analog/adjustable.h @@ -1,8 +1,5 @@ #include "params.h" -// Select which player this board is. 1 for P1 and 2 for P2. -#define PLAYER_SELECT 1 - const byte inPins[CHANNELS] = { P1_L_DON_IN, P1_L_KAT_IN, P1_R_DON_IN, P1_R_KAT_IN }; @@ -13,6 +10,16 @@ const byte sensitivityPins[CHANNELS] = { Cache inputWindow[CHANNELS]; unsigned long power[CHANNELS]; +#ifndef RAW_ANALOG_MODE +unsigned long lastPower[PLAYERS][CHANNELS]; +bool triggered[PLAYERS]; +unsigned long triggeredTime[PLAYERS][CHANNELS]; +int outputValue[PLAYERS] = {0, 0}; +uint resetTimer[PLAYERS] = {0, 0}; +short maxIndex[PLAYERS] = {0, 0}; +float maxPower[PLAYERS] = {0, 0}; +#endif + uint axisValues[CHANNELS] = {0, 0, 0, 0}; Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 10, 4, @@ -22,9 +29,17 @@ Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 10, 4, void setup() { for (byte i = 0; i < CHANNELS; i++) { power[i] = 0; +#ifndef RAW_ANALOG_MODE + lastPower[i] = 0; + triggered = false; +#endif pinMode(inPins[i], INPUT); pinMode(sensitivityPins[i], INPUT); } +#ifndef RAW_ANALOG_MODE + maxIndex = -1; + maxPower = 0; +#endif USB.PID(0x4869); USB.VID(0x4869); USB.productName("Taiko Controller"); @@ -42,20 +57,65 @@ void loop() { for (byte i = 0; i < CHANNELS; i++) { inputWindow[i].put(analogRead(inPins[i])); power[i] = power[i] - inputWindow[i].get(1) + inputWindow[i].get(); +#ifndef RAW_ANALOG_MODE + if (lastPower[i] > maxPower && power[i] < lastPower[i]) { + maxPower = lastPower[i]; + maxIndex = i; + } + lastPower[i] = power[i]; +#else float x = analogRead(sensitivityPins[i]) / 2048.0 - 1; float x2 = x * x; float x3 = x2 * x; float x4 = x3 * x; float v = (1.0 + x + 0.5 * x2 + 0.166667 * x3) * power[i]; axisValues[i] = AXIS_RANGE * (v >= MAX_THRES ? 1 : (v / MAX_THRES)); +#endif } +#ifndef RAW_ANALOG_MODE + if (!triggered && maxPower >= HIT_THRES) { + triggered = true; + digitalWrite(outPins[maxIndex], HIGH); + outputValue = (int)(AXIS_RANGE * (maxPower >= MAX_THRES ? 1 : maxPower / MAX_THRES)); + } + + if (triggered && resetTimer >= RESET_TIME) { + triggered = false; + resetTimer = 0; + digitalWrite(outPins[maxIndex], LOW); + maxPower = 0; + maxIndex = -1; + outputValue = 0; + } + + for (byte i = 0; i < CHANNELS; i++) { + if (triggered && i == maxIndex) { + axisValues[i] = outputValue; + } else { + axisValues[i] = 0; + } + } + + if (triggered) { + resetTimer++; + } +#endif #if PLAYER_SELECT == 1 Joystick.setXAxis(axisValues[0] > axisValues[1] ? axisValues[0] : -axisValues[1]); Joystick.setYAxis(axisValues[2] > axisValues[3] ? axisValues[2] : -axisValues[3]); + Joystick.setRxAxis(0); + Joystick.setRyAxis(0); #elif PLAYER_SELECT == 2 + Joystick.setXAxis(0); + Joystick.setYAxis(0); Joystick.setRxAxis(axisValues[0] > axisValues[1] ? axisValues[0] : -axisValues[1]); Joystick.setRyAxis(axisValues[2] > axisValues[3] ? axisValues[2] : -axisValues[3]); +#else + Joystick.setXAxis(0); + Joystick.setYAxis(0); + Joystick.setRxAxis(0); + Joystick.setRyAxis(0); #endif Joystick.sendState(); diff --git a/ESP32-S3-Analog/params.h b/ESP32-S3-Analog/params.h index 2183962..184bd9c 100644 --- a/ESP32-S3-Analog/params.h +++ b/ESP32-S3-Analog/params.h @@ -1,5 +1,34 @@ -// Sample window length. Larger values reduce noise but add more latency. -#define SAMPLE_CACHE_LENGTH 64 +// Enable this mode to pass raw analog data to the game without any post- +// processing. +// The game has a built-in mechanism to calculate which sensor is triggered +// and the force of the hit, so it's recommended to enable this mode. +// This also the provides the most similar experience with the arcade. +// To disable this mode, remove or comment out this line +#define RAW_ANALOG_MODE + +// For MODE_ADJUSTABLE +// Select which player this board is. 1 for P1 and 2 for P2. +#define PLAYER_SELECT 1 + +// For MODE_TWO_PLAYERS +// Sensitivity multipliers for each channel, 1.0 as the baseline. +#define P1_L_DON_SENS 1.0 +#define P1_L_KAT_SENS 1.0 +#define P1_R_DON_SENS 1.0 +#define P1_R_KAT_SENS 1.0 +#define P2_L_DON_SENS 1.0 +#define P2_L_KAT_SENS 1.0 +#define P2_R_DON_SENS 1.0 +#define P2_R_KAT_SENS 1.0 + +/********************************************** + CHANGING THE FOLLOWING PARAMETERS ARE NOT + RECOMMENDED UNLESS YOU KNOW HOW THEY WORK +***********************************************/ + +// Cache length must be the power of 2 (8, 16, 32, etc.). +// See cache.h for the reason. +#define SAMPLE_CACHE_LENGTH 32 // The maximum value of a hit (not the minumum value to trigger a heavy hit) // To configure the light and heavy thresholds, do it in the game settings. @@ -22,12 +51,22 @@ #define P1_R_DON_SENS_IN 17 #define P1_R_KAT_SENS_IN 18 -// Controller axis value range #define AXIS_RANGE 1023 -// Number of input channels for each player +#define PLAYERS 2 #define CHANNELS 4 +// The minimum value to trigger a light hit. +// Disabled if RAW_ANALOG_MODE is on. +#define HIT_THRES 1000 + +// If the reset time is too short, the game may not be able to +// receive the input. From testing I found 40 seems to be the +// minimum value so that the game won't miss any hit. If the game +// occassionally miss the drum input, increase this value. +// Disabled if RAW_ANALOG_MODE is on. +#define RESET_TIME 40 + #include #include "joystick.h" #include "cache.h" \ No newline at end of file diff --git a/ESP32-S3-Analog/two_players.h b/ESP32-S3-Analog/two_players.h index 44f6f43..adf4526 100644 --- a/ESP32-S3-Analog/two_players.h +++ b/ESP32-S3-Analog/two_players.h @@ -1,148 +1,48 @@ -#include "driver/adc.h" -// #include "driver/i2s.h" -#include "freertos/FreeRTOS.h" - #include "params.h" -// Sensitivity multipliers for each channel, 1.0 as the baseline. -#define P1_L_DON_SENS 10.0 -#define P1_L_KAT_SENS 20.0 -#define P1_R_DON_SENS 10.0 -#define P1_R_KAT_SENS 20.0 -#define P2_L_DON_SENS 1.0 -#define P2_L_KAT_SENS 1.0 -#define P2_R_DON_SENS 1.0 -#define P2_R_KAT_SENS 1.0 - -#define PLAYERS 2 -#define BUF_LENGH 1024 -#define CONV_NUM BUF_LENGH * PLAYERS / CHANNELS - -#define TIMES 256 -#define GET_UNIT(x) ((x>>3) & 0x1) - const byte inPins[PLAYERS][CHANNELS] = { P1_L_DON_IN, P1_L_KAT_IN, P1_R_DON_IN, P1_R_KAT_IN, P2_L_DON_IN, P2_L_KAT_IN, P2_R_DON_IN, P2_R_KAT_IN }; const float sensitivities[PLAYERS][CHANNELS] = { - P1_L_DON_SENS, P1_L_KAT_SENS, P1_R_DON_SENS, P1_R_KAT_SENS, - P2_L_DON_SENS, P2_L_KAT_SENS, P2_R_DON_SENS, P2_R_KAT_SENS}; + P1_L_DON_SENS, P1_L_KAT_SENS, P1_R_DON_SENS, P1_R_KAT_SENS, + P2_L_DON_SENS, P2_L_KAT_SENS, P2_R_DON_SENS, P2_R_KAT_SENS +}; Cache inputWindow[PLAYERS][CHANNELS]; unsigned long power[PLAYERS][CHANNELS]; +#ifndef RAW_ANALOG_MODE +unsigned long lastPower[PLAYERS][CHANNELS]; +bool triggered[PLAYERS]; +unsigned long triggeredTime[PLAYERS][CHANNELS]; +int outputValue[PLAYERS] = {0, 0}; +uint resetTimer[PLAYERS] = {0, 0}; +short maxIndex[PLAYERS] = {0, 0}; +float maxPower[PLAYERS] = {0, 0}; +#endif + uint axisValues[PLAYERS][CHANNELS] = {0, 0, 0, 0, 0, 0, 0, 0}; Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 10, 4, - true, true, false, true, true, false, false, false, false, - false, false); - -uint maxVal[PLAYERS] = {0, 0}; -uint maxIndex[PLAYERS] = {-1, -1}; - -adc_channel_t adcChannles[PLAYERS][CHANNELS] = { - ADC_CHANNEL_3, - ADC_CHANNEL_4, - ADC_CHANNEL_5, - ADC_CHANNEL_6, - ADC_CHANNEL_7, - ADC_CHANNEL_0, - ADC_CHANNEL_8, - ADC_CHANNEL_9 -}; - -static void adc_init(adc_channel_t *channel, uint8_t channel_num) -{ - adc_digi_init_config_t adc_dma_config = { - .max_store_buf_size = BUF_LENGH, - .conv_num_each_intr = CONV_NUM, - .adc1_chan_mask = BIT3 | BIT4 | BIT5 | BIT6 | BIT7 | BIT0 | BIT8 | BIT9, - .adc2_chan_mask = 0 - }; - ESP_ERROR_CHECK(adc_digi_initialize(&adc_dma_config)); - - adc_digi_configuration_t dig_cfg = { - .conv_limit_en = 1, - .conv_limit_num = 4, - .sample_freq_hz = 2 * 1000, - .conv_mode = ADC_CONV_SINGLE_UNIT_1, - .format = ADC_DIGI_OUTPUT_FORMAT_TYPE1, - }; - - adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0}; - dig_cfg.pattern_num = channel_num; - for (int i = 0; i < channel_num; i++) { - uint8_t unit = GET_UNIT(channel[i]); - uint8_t ch = channel[i] & 0x7; - adc_pattern[i].atten = ADC_ATTEN_DB_11; - adc_pattern[i].channel = ch; - adc_pattern[i].unit = unit; - adc_pattern[i].bit_width = SOC_ADC_DIGI_MAX_BITWIDTH; - - ESP_LOGI(TAG, "adc_pattern[%d].atten is :%x", i, adc_pattern[i].atten); - ESP_LOGI(TAG, "adc_pattern[%d].channel is :%x", i, adc_pattern[i].channel); - ESP_LOGI(TAG, "adc_pattern[%d].unit is :%x", i, adc_pattern[i].unit); - } - dig_cfg.adc_pattern = adc_pattern; - ESP_ERROR_CHECK(adc_digi_controller_configure(&dig_cfg)); -} - -void setP1Axes(int index, int value) { - switch (index) { - case 0: - Joystick.setXAxis(value); - Joystick.setYAxis(0); - break; - case 1: - Joystick.setXAxis(-value); - Joystick.setYAxis(0); - break; - case 2: - Joystick.setXAxis(0); - Joystick.setYAxis(value); - break; - case 3: - Joystick.setXAxis(0); - Joystick.setYAxis(-value); - break; - default: - Joystick.setXAxis(0); - Joystick.setYAxis(0); - } -} - -void setP2Axes(int index, int value) { - switch (index) { - case 0: - Joystick.setRxAxis(value); - Joystick.setRyAxis(0); - break; - case 1: - Joystick.setRxAxis(-value); - Joystick.setRyAxis(0); - break; - case 2: - Joystick.setRxAxis(0); - Joystick.setRyAxis(value); - break; - case 3: - Joystick.setRxAxis(0); - Joystick.setRyAxis(-value); - break; - default: - Joystick.setXAxis(0); - Joystick.setYAxis(0); - } -} + true, true, false, true, true, false, + false, false, false, false, false); void setup() { for (byte p = 0; p < PLAYERS; p++) { for (byte i = 0; i < CHANNELS; i++) { power[p][i] = 0; +#ifndef RAW_ANALOG_MODE + lastPower[p][i] = 0; + triggered[p] = false; +#endif pinMode(inPins[p][i], INPUT); } +#ifndef RAW_ANALOG_MODE + maxIndex[p] = -1; + maxPower[p] = 0; +#endif } USB.PID(0x4869); USB.VID(0x4869); @@ -154,47 +54,57 @@ void setup() { Joystick.setYAxisRange(-AXIS_RANGE, AXIS_RANGE); Joystick.setRxAxisRange(-AXIS_RANGE, AXIS_RANGE); Joystick.setRyAxisRange(-AXIS_RANGE, AXIS_RANGE); - - } void loop() { + for (byte p = 0; p < PLAYERS; p++) { + for (byte i = 0; i < CHANNELS; i++) { inputWindow[p][i].put(analogRead(inPins[p][i])); - } - } - - for (byte p = 0; p < PLAYERS; p++) { - maxVal[p] = 0; - maxIndex[p] = -1; - - for (byte i = 0; i < CHANNELS; i++) { - power[p][i] = power[p][i] - inputWindow[p][i].get(1) + - inputWindow[p][i].get(); - float v = power[p][i] * sensitivities[p][i]; - axisValues[p][i] = - AXIS_RANGE * (v >= MAX_THRES ? 1 : (v / MAX_THRES)); - if (axisValues[p][i] > maxVal[p]) { - maxVal[p] = axisValues[p][i]; + power[p][i] = power[p][i] - inputWindow[p][i].get(1) + inputWindow[p][i].get(); +#ifndef RAW_ANALOG_MODE + if (lastPower[p][i] > maxPower[p] && power[p][i] < lastPower[p][i]) { + maxPower[p] = lastPower[p][i]; maxIndex[p] = i; } + lastPower[p][i] = power[p][i]; +#else + float v = power[p][i] * sensitivities[p][i]; + axisValues[p][i] = AXIS_RANGE * (v >= MAX_THRES ? 1 : (v / MAX_THRES)); +#endif + } +#ifndef RAW_ANALOG_MODE + if (!triggered[p] && maxPower[p] >= HIT_THRES) { + triggered[p] = true; + outputValue[p] = (int)(AXIS_RANGE * (maxPower[p] >= MAX_THRES ? 1 : maxPower[p] / MAX_THRES)); } - if (maxIndex[p] >= 0) { - if (p == 0) { - setP1Axes(maxIndex[p], maxVal[p]); - } else if (p == 1) { - setP2Axes(maxIndex[p], maxVal[p]); - } - } else { - if (p == 0) { - setP1Axes(-1, 0); - } else if (p == 1) { - setP2Axes(-1, 0); + if (triggered[p] && resetTimer[p] >= RESET_TIME) { + triggered[p] = false; + resetTimer[p] = 0; + maxPower[p] = 0; + maxIndex[p] = -1; + outputValue[p] = 0; + } + + for (byte i = 0; i < CHANNELS; i++) { + if (triggered[p] && i == maxIndex[p]) { + axisValues[p][i] = outputValue[p]; + } else { + axisValues[p][i] = 0; } } + + if (triggered[p]) { + resetTimer[p]++; + } +#endif } + Joystick.setXAxis(axisValues[0][0] > axisValues[0][1] ? axisValues[0][0] : -axisValues[0][1]); + Joystick.setYAxis(axisValues[0][2] > axisValues[0][3] ? axisValues[0][2] : -axisValues[0][3]); + Joystick.setRxAxis(axisValues[1][0] > axisValues[1][1] ? axisValues[1][0] : -axisValues[1][1]); + Joystick.setRyAxis(axisValues[1][2] > axisValues[1][3] ? axisValues[1][2] : -axisValues[1][3]); Joystick.sendState(); } diff --git a/ESP32-S3/ESP32-S3.ino b/ESP32-S3/ESP32-S3.ino index e25c83b..b127cdb 100644 --- a/ESP32-S3/ESP32-S3.ino +++ b/ESP32-S3/ESP32-S3.ino @@ -6,8 +6,8 @@ // The thresholds are also dependent on SAMPLE_CACHE_LENGTH, if you // changed SAMPLE_CACHE_LENGTH, you should also adjust thresholds -#define HIT_THRES 1750 -#define RESET_THRES 200 +#define HIT_THRES 400 +#define RESET_THRES 80 // Sampling period in μs, e.g., 500μs = 0.5ms = 2000Hz #define SAMPLING_PERIOD 500 @@ -36,10 +36,22 @@ #define R_DON_KEY 'j' #define R_KAT_KEY 'k' +// Switch controller output for each channel +// this default config should work for all the 3 game settings +#define L_DON_KEY_NS NSGAMEPAD_DPAD_DOWN +#define L_KAT_KEY_NS NSButton_LeftTrigger +#define R_DON_KEY_NS NSButton_B +#define R_KAT_KEY_NS NSButton_RightTrigger + // Enable debug mode to view analog input values from the Serial // Enabling this also disables the keyboard simulation #define DEBUG 0 +// 0 = keyboard; 1 = switch controller +#define controller_mode 1 + +#include "switch_ESP32.h" +NSGamepad Gamepad; #include "USB.h" #include "USBHIDKeyboard.h" #include "cache.h" @@ -56,6 +68,7 @@ unsigned long triggeredTime[CHANNELS]; const byte inPins[] = {L_DON_IN, L_KAT_IN, R_DON_IN, R_KAT_IN}; const byte outPins[] = {L_DON_LED, L_KAT_LED, R_DON_LED, R_KAT_LED}; const char outKeys[] = {L_DON_KEY, L_KAT_KEY, R_DON_KEY, R_KAT_KEY}; +const uint8_t outKeysNS[] = {L_DON_KEY_NS, L_KAT_KEY_NS, R_DON_KEY_NS, R_KAT_KEY_NS}; float sensitivities[] = {L_DON_SENS, L_KAT_SENS, R_DON_SENS, R_KAT_SENS}; short maxIndex; @@ -75,16 +88,23 @@ void setup() { maxIndex = -1; maxPower = 0; lastTime = micros(); -#if !DEBUG - Keyboard.begin(); - USB.begin(); -#endif + #if !DEBUG + if (controller_mode) { + Gamepad.begin(); + } else { + Keyboard.begin(); + } + USB.begin(); + #endif } void loop() { if (maxIndex != -1 && lastPower[maxIndex] < RESET_THRES) { triggered = false; digitalWrite(outPins[maxIndex], LOW); + if (controller_mode) { + Gamepad.releaseAll(); + } maxIndex = -1; maxPower = 0; } @@ -99,22 +119,32 @@ void loop() { maxIndex = i; } lastPower[i] = power[i]; -#if DEBUG + #if DEBUG Serial.print(power[i]); Serial.print(" "); -#endif + #endif } if (!triggered && maxPower >= HIT_THRES) { triggered = true; digitalWrite(outPins[maxIndex], HIGH); -#if !DEBUG - Keyboard.write(outKeys[maxIndex]); -#endif + #if !DEBUG + if (controller_mode) { + // special case for the DPAD + if (maxIndex == 0) { + Gamepad.dPad(outKeysNS[maxIndex]); + } else { + Gamepad.press(outKeysNS[maxIndex]); + } + Gamepad.loop(); + } else { + Keyboard.write(outKeys[maxIndex]); + } + #endif } -#if DEBUG - Serial.print("\n"); -#endif + #if DEBUG + Serial.print("\n"); + #endif unsigned int frameTime = micros() - lastTime; if (frameTime < SAMPLING_PERIOD) {