Files
spice2x.github.io/src/spice2x/acio/bi2a/bi2a.cpp
T
bicarus 7b903d35e2 iidx, sdvx: SOCD cleaner for knobs and turntables (#497)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Apply SOCD cleaner algorithm to deal with the case where both
counter-clockwise and clockwise buttons are pressed at the same time.

In SDVX, default algorithm is to prefer the most recently pressed
direction. This is better for dealing with slams in rapid succession,
for example, as you don't have to completely let go of one direction to
hit the other now.

In IIDX, default algorithm is neutral (same behavior as before). Using
last or first algorithm results in double scratches which means you'll
likely end up with an excessive poor after hitting a scratch, so neutral
is actually beneficial. Besides, if you are serious about playing on the
keyboard, you should be using `TT +/-` and `TT +/- Alternate`.

## Testing
Tested SDVX 3, EG (old cab), EG valk cab modes, IIDX 24, 27, 33
(tdj/ldj)
2026-01-04 19:50:11 -08:00

822 lines
30 KiB
C++

#include "bi2a.h"
#include "avs/game.h"
#include "games/ddr/io.h"
#include "games/ddr/ddr.h"
#include "games/sdvx/sdvx.h"
#include "games/sdvx/io.h"
#include "games/drs/io.h"
#include "games/drs/drs.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "util/tapeled.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// state
static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false;
static unsigned int BI2A_VOLL = 0;
static unsigned int BI2A_VOLR = 0;
static bool __cdecl ac_io_bi2a_init_is_finished() {
return true;
}
static bool __cdecl ac_io_bi2a_get_control_status_buffer(void *buffer) {
// copy buffer
memcpy(buffer, STATUS_BUFFER, std::size(STATUS_BUFFER));
return true;
}
static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Sound Voltex
if (avs::game::is_model("KFC")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
/*
* Unmapped Buttons
*
* Control Bit
* EX BUTTON 1 93
* EX BUTTON 2 92
* EX ANALOG 1 170-183
* EX ANALOG 2 186-199
*/
// get buttons
auto &buttons = games::sdvx::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Test))) {
ARRAY_SETB(STATUS_BUFFER, 19);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Service))) {
ARRAY_SETB(STATUS_BUFFER, 18);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::CoinMech))) {
ARRAY_SETB(STATUS_BUFFER, 17);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Start))) {
ARRAY_SETB(STATUS_BUFFER, 85);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_A))) {
ARRAY_SETB(STATUS_BUFFER, 84);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_B))) {
ARRAY_SETB(STATUS_BUFFER, 83);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_C))) {
ARRAY_SETB(STATUS_BUFFER, 82);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_D))) {
ARRAY_SETB(STATUS_BUFFER, 81);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_L))) {
ARRAY_SETB(STATUS_BUFFER, 80);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_R))) {
ARRAY_SETB(STATUS_BUFFER, 95);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Headphone))) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
// volume left
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_l_state == socd::SocdCW) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_r_state == socd::SocdCW) {
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// update volumes
auto &analogs = games::sdvx::get_analogs();
auto vol_left = BI2A_VOLL;
auto vol_right = BI2A_VOLR;
if (analogs.at(0).isSet() || analogs.at(1).isSet()) {
vol_left += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_L)) * 1023.99f);
vol_right += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_R)) * 1023.99f);
}
// proper loops
vol_left %= 1024;
vol_right %= 1024;
// save volumes in buffer
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
log_debug(
"bi2a",
"knobs = {} {}",
*((uint16_t*) &STATUS_BUFFER[17]),
*((uint16_t*) &STATUS_BUFFER[19]));
}
// DanceDanceRevolution
if (avs::game::is_model("MDX")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
// get buttons
auto &buttons = games::ddr::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::COIN_MECH)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::SERVICE)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::TEST)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_START)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 7;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_UP)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_START)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_UP)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 1;
}
}
// DANCERUSH
if (avs::game::is_model("REC")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
// get buttons
auto &buttons = games::drs::get_buttons();
// test
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 19);
}
// service
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 18);
}
// coin
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::CoinMech)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 17);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Start)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Up)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 86);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Down)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 85);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Left)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 84);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Right)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 83);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Start)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 93);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Up)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 92);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Down)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 91);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Left)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 90);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Right)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 89);
}
}
return true;
}
static bool __cdecl ac_io_bi2a_current_coinstock(size_t index, DWORD *coins) {
// check index
if (index > 1)
return false;
// get coins and return success
*coins = (DWORD) eamuse_coin_get_stock();
return true;
}
static bool __cdecl ac_io_bi2a_consume_coinstock(size_t index, int amount) {
// check index
if (index > 1)
return false;
// calculate new stock
auto stock = eamuse_coin_get_stock();
auto stock_new = stock - amount;
// check new stock
if (stock_new < 0)
return false;
// apply new stock
eamuse_coin_set_stock(stock_new);
return true;
}
static bool __cdecl ac_io_bi2a_lock_coincounter(size_t index) {
// check index
if (index > 1)
return false;
// enable coin blocker
eamuse_coin_set_block(true);
return true;
}
static bool __cdecl ac_io_bi2a_unlock_coincounter(size_t index) {
// check index
if (index > 1)
return false;
// disable coin blocker
eamuse_coin_set_block(false);
return true;
}
static void __cdecl ac_io_bi2a_control_coin_blocker_close(size_t index) {
// check index
if (index > 1)
return;
// enable coin blocker
eamuse_coin_set_block(true);
}
static void __cdecl ac_io_bi2a_control_coin_blocker_open(size_t index) {
// check index
if (index > 1)
return;
// disable coin blocker
eamuse_coin_set_block(false);
}
static long __cdecl ac_io_bi2a_control_led_bright(size_t index, uint8_t brightness) {
// Sound Voltex
if (avs::game::is_model("KFC")) {
/*
* Control R G B
* =======================
* WING UP 28 29 30
* WING LOW 31 32 33
* WOOFER 0 1 3
* CONTROLLER 4 5 6
*
* Values go up to 255.
*
*
* Control Index
* ==================
* START BUTTON 8
* A BUTTON 9
* B BUTTON 10
* C BUTTON 11
* D BUTTON 12
* FX L BUTTON 13
* FX R BUTTON 14
* POP 24
* TITLE LEFT 25
* TITLE RIGHT 26
*
* Values go up to 127.
*/
static const struct {
int light1, light2;
float max;
} mapping[] = {
{ games::sdvx::Lights::WOOFER_R, -1, 255 },
{ games::sdvx::Lights::WOOFER_G, -1, 255 },
{ -1, -1, 0 },
{ games::sdvx::Lights::WOOFER_B, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_R, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_G, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_B, -1, 255 },
{ -1, -1, 0 },
{ games::sdvx::Lights::START, -1, 127 },
{ games::sdvx::Lights::BT_A, -1, 127 },
{ games::sdvx::Lights::BT_B, -1, 127 },
{ games::sdvx::Lights::BT_C, -1, 127 },
{ games::sdvx::Lights::BT_D, -1, 127 },
{ games::sdvx::Lights::FX_L, -1, 127 },
{ games::sdvx::Lights::FX_R, -1, 127 },
{ -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
{ games::sdvx::Lights::GENERATOR_R, -1, 255 },
{ games::sdvx::Lights::GENERATOR_G, -1, 255 },
{ games::sdvx::Lights::GENERATOR_B, -1, 255 },
{ -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
{ games::sdvx::Lights::POP, -1, 127 },
{ games::sdvx::Lights::TITLE_LEFT, -1, 127 },
{ games::sdvx::Lights::TITLE_RIGHT, -1, 127 },
{ -1, -1, 0 },
{ games::sdvx::Lights::WING_RIGHT_UP_R, games::sdvx::Lights::WING_LEFT_UP_R, 255 },
{ games::sdvx::Lights::WING_RIGHT_UP_G, games::sdvx::Lights::WING_LEFT_UP_G, 255 },
{ games::sdvx::Lights::WING_RIGHT_UP_B, games::sdvx::Lights::WING_LEFT_UP_B, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_R, games::sdvx::Lights::WING_LEFT_LOW_R, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_G, games::sdvx::Lights::WING_LEFT_LOW_G, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_B, games::sdvx::Lights::WING_LEFT_LOW_B, 255 },
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::sdvx::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light1 >= 0) {
Lights::writeLight(RI_MGR, lights[light.light1], brightness / light.max);
} else {
log_warning("sdvx", "light unset {} {}", index, (int) brightness);
}
if (light.light2 >= 0) {
Lights::writeLight(RI_MGR, lights[light.light2], brightness / light.max);
}
// DANCERUSH
} else if (avs::game::is_model("REC")) {
/*
* Control R G B
* ==============================
* CARD UNIT 13 14 15
* TITLE PANEL 28 29 30
* MONITOR SIDE LEFT (tape LED - see ac_io_bi2a_control_tapeled_bright)
* MONITOR SIDE RIGHT (tape LED - see ac_io_bi2a_control_tapeled_bright)
*
* Values go up to 127.
*
* Control Index
* ==================
* 1P LEFT 11
* 1P RIGHT 12
* 1P UP 9
* 1P DOWN 10
* 1P START 8
* 2P LEFT 19
* 2P RIGHT 20
* 2P UP 17
* 2P DOWN 18
* 2P START 16
*
* Values go up to 127.
*/
static const struct {
int light;
float max;
} mapping[] = {
{ -1, 0 }, // 0
{ -1, 0 }, // 1
{ -1, 0 }, // 2
{ -1, 0 }, // 3
{ -1, 0 }, // 4
{ -1, 0 }, // 5
{ -1, 0 }, // 6
{ -1, 0 }, // 7
{ games::drs::Lights::P1_START, 127 }, // 8
{ games::drs::Lights::P1_MENU_UP, 127 }, // 9
{ games::drs::Lights::P1_MENU_DOWN, 127 }, // 10
{ games::drs::Lights::P1_MENU_LEFT, 127 }, // 11
{ games::drs::Lights::P1_MENU_RIGHT, 127 }, // 12
{ games::drs::Lights::CARD_READER_R, 127 }, // 13
{ games::drs::Lights::CARD_READER_G, 127 }, // 14
{ games::drs::Lights::CARD_READER_B, 127 }, // 15
{ games::drs::Lights::P2_START, 127 }, // 16
{ games::drs::Lights::P2_MENU_UP, 127 }, // 17
{ games::drs::Lights::P2_MENU_DOWN, 127 }, // 18
{ games::drs::Lights::P2_MENU_LEFT, 127 }, // 19
{ games::drs::Lights::P2_MENU_RIGHT, 127 }, // 20
{ -1, 0 }, // 21
{ -1, 0 }, // 22
{ -1, 0 }, // 23
{ -1, 0 }, // 24
{ -1, 0 }, // 25
{ -1, 0 }, // 26
{ -1, 0 }, // 27
{ games::drs::Lights::TITLE_PANEL_R, 127 }, // 28
{ games::drs::Lights::TITLE_PANEL_G, 127 }, // 29
{ games::drs::Lights::TITLE_PANEL_B, 127 }, // 30
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::drs::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light >= 0) {
Lights::writeLight(RI_MGR, lights[light.light], brightness / light.max);
} else {
log_warning("drs", "light unset {} {}", index, (int) brightness);
}
// DanceDanceRevolution
} else if (avs::game::is_model("MDX")) {
static const struct {
int light;
float max;
} mapping[] = {
{ -1, 0 }, // 0
{ -1, 0 }, // 1
{ -1, 0 }, // 2
{ -1, 0 }, // 3
{ -1, 0 }, // 4
{ -1, 0 }, // 5
{ -1, 0 }, // 6
{ -1, 0 }, // 7
{ games::ddr::Lights::GOLD_P1_MENU_START, 127 }, // 8
{ games::ddr::Lights::GOLD_P1_MENU_UP, 127 }, // 9
{ games::ddr::Lights::GOLD_P1_MENU_DOWN, 127 }, // 10
{ games::ddr::Lights::GOLD_P1_MENU_LEFT, 127 }, // 11
{ games::ddr::Lights::GOLD_P1_MENU_RIGHT, 127 }, // 12
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_R, 127 }, // 13
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_G, 127 }, // 14
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_B, 127 }, // 15
{ games::ddr::Lights::GOLD_P2_MENU_START, 127 }, // 16
{ games::ddr::Lights::GOLD_P2_MENU_UP, 127 }, // 17
{ games::ddr::Lights::GOLD_P2_MENU_DOWN, 127 }, // 18
{ games::ddr::Lights::GOLD_P2_MENU_LEFT, 127 }, // 19
{ games::ddr::Lights::GOLD_P2_MENU_RIGHT, 127 }, // 20
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_R, 0 }, // 21
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_G, 0 }, // 22
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_B, 0 }, // 23
{ -1, 0 }, // 24
{ -1, 0 }, // 25
{ -1, 0 }, // 26
{ -1, 0 }, // 27
{ games::ddr::Lights::GOLD_TITLE_PANEL_LEFT, 0 }, // 28
{ games::ddr::Lights::GOLD_TITLE_PANEL_CENTER, 0 }, // 29
{ games::ddr::Lights::GOLD_TITLE_PANEL_RIGHT, 0 }, // 30
{ games::ddr::Lights::GOLD_P1_WOOFER_CORNER, 0 }, // 31
{ games::ddr::Lights::GOLD_P2_WOOFER_CORNER, 0 } // 32
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::ddr::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light >= 0) {
Lights::writeLight(RI_MGR, lights[light.light], brightness / light.max);
}
}
// return success
return true;
}
static long __cdecl ac_io_bi2a_get_watchdog_time_min() {
return -1;
}
static long __cdecl ac_io_bi2a_get_watchdog_time_now() {
return -1;
}
static void __cdecl ac_io_bi2a_watchdog_off() {
}
static bool __cdecl ac_io_bi2a_init(uint8_t param) {
return true;
}
static bool __cdecl ac_io_bi2a_set_watchdog_time(uint16_t time) {
return true;
}
static bool __cdecl ac_io_bi2a_get_watchdog_status() {
return true;
}
static bool __cdecl ac_io_bi2a_set_amp_volume(uint8_t a1, uint8_t a2) {
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_init(uint8_t a1, uint8_t a2) {
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_init_is_finished() {
return true;
}
static bool __cdecl ac_io_bi2a_control_tapeled_rec_set(uint8_t* data, size_t x_sz, size_t y_sz) {
// check dimensions
if (x_sz != DRS_TAPELED_COLS || y_sz != DRS_TAPELED_ROWS) {
log_fatal("drs", "DRS tapeled wrong dimensions");
}
// copy data into our buffer - 4 bytes per pixel BGR
for (size_t i = 0; i < x_sz * y_sz; i++) {
games::drs::DRS_TAPELED[i][0] = data[i*4+2];
games::drs::DRS_TAPELED[i][1] = data[i*4+1];
games::drs::DRS_TAPELED[i][2] = data[i*4];
}
// success
return true;
}
// TODO: DRS tape lights
static bool __cdecl ac_io_bi2a_control_tapeled_bright(size_t off1, size_t off2,
uint8_t r, uint8_t g, uint8_t b, uint8_t bank) {
if (!tapeledutils::is_enabled()) {
return true;
}
if (avs::game::is_model("MDX")) {
/*
* r, g, b values range from [0-255]
* bank always seems to be [0]
*
* [off1.off2] [LEDs] [tape name]
* 0.0 25 P1 Foot Up (0.0 to 0.24, inclusive)
* 0.25 25 P1 Foot Right
* 1.0 25 P1 Foot Left
* 1.25 25 P1 Foot Down
*
* 2.0 25 P2 Foot Up
* 2.25 25 P2 Foot Right
* 3.0 25 P2 Foot Left
* 3.25 25 P2 Foot Down
*
* 5.0 50 Top Panel
* 6.0 50 Monitor side left
* 7.0 50 Monitor side right
*/
// In order to set the data that can be output via Spice API, we first
// need to figure out which device in our buffers this data belongs to
int device = -1;
if (off1 == 0) {
if (off2 < 25) device = 0; // P1 Foot Up
else device = 1; // P1 Foot Right
} else if (off1 == 1) {
if (off2 < 25) device = 2; // P1 Foot Left
else device = 3; // P1 Foot Down
} else if (off1 == 2) {
if (off2 < 25) device = 4; // P2 Foot Up
else device = 5; // P2 Foot Right
} else if (off1 == 3) {
if (off2 < 25) device = 6; // P2 Foot Left
else device = 7; // P2 Foot Down
} else if (off1 >= 5 && off1 <= 7) {
device = off1 + 3; // Top Panel / Monitor Side Left / Monitor Side Right
}
if (device != -1) {
// We subtract 25 from off2 to get the device's LED index, if it's for one of the
// arrow panels that is on the latter half of the logical strip
size_t subtractor = 0;
if (off1 <= 3 && off2 >= 25) {
subtractor = 25;
}
size_t led_index = off2 - subtractor;
games::ddr::DDR_TAPELEDS[device][led_index][0] = r;
games::ddr::DDR_TAPELEDS[device][led_index][1] = g;
games::ddr::DDR_TAPELEDS[device][led_index][2] = b;
}
static struct TapeLedMapping {
bool split; // true == 50 LEDs for one light, false == 25 for two lights
uint8_t index_r0, index_g0, index_b0;
uint8_t index_r1, index_g1, index_b1;
size_t index_for_avg0 = UINT8_MAX;
size_t index_for_avg1 = UINT8_MAX;
TapeLedMapping(
uint8_t index_r0, uint8_t index_g0, uint8_t index_b0,
uint8_t index_r1, uint8_t index_g1, uint8_t index_b1)
: index_r0(index_r0), index_g0(index_g0), index_b0(index_b0),
index_r1(index_r1), index_g1(index_g1), index_b1(index_b1) {
split = (index_r1 != UINT8_MAX);
if (split) {
index_for_avg0 = tapeledutils::get_led_index_using_avg_algo(25);
index_for_avg1 = index_for_avg0 + 25;
} else {
index_for_avg0 = tapeledutils::get_led_index_using_avg_algo(50);
index_for_avg1 = -1;
}
}
} mapping[] = {
{
games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_B,
games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_B
},
{
games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_B,
games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_B
},
{
games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_B,
games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_B
},
{
games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_B,
games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_B
},
{
games::ddr::Lights::GOLD_TOP_PANEL_AVG_R, games::ddr::Lights::GOLD_TOP_PANEL_AVG_G, games::ddr::Lights::GOLD_TOP_PANEL_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
UINT8_MAX, UINT8_MAX, UINT8_MAX,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_R, games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_G, games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_R, games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_G, games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
};
if (off1 < std::size(mapping)) {
auto &map = mapping[off1];
size_t off2_match = -1;
if (!map.split || off2 < 25) {
off2_match = map.index_for_avg0;
} else {
off2_match = map.index_for_avg1;
}
if (off2_match == off2 && map.index_r0 != UINT8_MAX) {
auto &lights = games::ddr::get_lights();
if (!map.split || off2 < 25) {
Lights::writeLight(RI_MGR, lights[map.index_r0], r / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_g0], g / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_b0], b / 255.f);
} else {
Lights::writeLight(RI_MGR, lights[map.index_r1], r / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_g1], g / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_b1], b / 255.f);
}
}
}
}
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_send() {
return true;
}
static int __cdecl ac_io_bi2a_get_exbio2_status(uint8_t *info) {
// surely this meme never gets old
info[5] = 5;
info[6] = 7;
info[7] = 3;
return 0;
}
acio::BI2AModule::BI2AModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("BI2A", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::BI2AModule::attach() {
ACIOModule::attach();
ACIO_MODULE_HOOK(ac_io_bi2a_init_is_finished);
ACIO_MODULE_HOOK(ac_io_bi2a_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bi2a_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bi2a_current_coinstock);
ACIO_MODULE_HOOK(ac_io_bi2a_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_bi2a_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_bi2a_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_bi2a_control_led_bright);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_bi2a_watchdog_off);
ACIO_MODULE_HOOK(ac_io_bi2a_init);
ACIO_MODULE_HOOK(ac_io_bi2a_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_bi2a_set_amp_volume);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init_is_finished);
ACIO_MODULE_HOOK(ac_io_bi2a_get_exbio2_status);
ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_rec_set);
ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_bright);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_send);
}