Update to spice2x-25-04-25 (pre-apply)

> broken commit
This commit is contained in:
[ ]
2025-05-07 00:25:31 +09:00
parent 04dee88276
commit c94de456b5
543 changed files with 78491 additions and 91698 deletions
+121 -7
View File
@@ -55,20 +55,39 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
}
case rawinput::MIDI: {
auto midi = device->midiInfo;
// update strings in button.cpp as well
if (index < midi->controls_precision.size()) {
return "MIDI PREC " + indexString + " (" + device->desc + ")";
const int channel = (index / 32) + 1;
const int cc_index = (index % 32);
return fmt::format("MIDI Prec Ctrl Ch.{} CC#{} ({})", channel, cc_index, device->desc);
} else if (index < midi->controls_precision.size() + midi->controls_single.size()) {
return "MIDI CTRL " + indexString + " (" + device->desc + ")";
const int index_rel = index - midi->controls_precision.size();
const int channel = (index_rel / 44) + 1;
int cc_index = (index_rel % 44);
if (cc_index < 26) {
cc_index += 0x46; // single byte range
} else {
cc_index = cc_index - 26 + 0x66; // undefined single byte range
}
return fmt::format("MIDI Ctrl Ch.{} CC#{} ({})", channel, cc_index, device->desc);
} else if (index < midi->controls_precision.size() + midi->controls_single.size()
+ midi->controls_onoff.size())
{
return "MIDI ONOFF " + indexString + " (" + device->desc + ")";
} else if (index == midi->controls_precision.size() + midi->controls_single.size()
+ midi->controls_onoff.size())
const int index_rel = index - midi->controls_precision.size() - midi->controls_single.size();
const int channel = (index_rel / 6) + 1;
const int cc_index = (index_rel % 6) + 0x40;
return fmt::format("MIDI OnOff Ch.{} CC#{} ({})", channel, cc_index, device->desc);
} else if (index <
midi->controls_precision.size() + midi->controls_single.size() + midi->controls_onoff.size() + midi->pitch_bend.size())
{
return "MIDI Pitch Bend (" + device->desc + ")";
const int index_rel =
index -
midi->controls_precision.size() -
midi->controls_single.size() -
midi->controls_onoff.size();
return fmt::format("MIDI Pitch Ch.{} ({})", index_rel + 1, device->desc);
} else {
return "MIDI Unknown " + indexString + " (" + device->desc + ")";
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
}
}
case rawinput::DESTROYED:
@@ -158,3 +177,98 @@ float Analog::normalizeAngle(float rads) {
}
return angle;
}
float Analog::applyMultiplier(float value) {
if (1 < this->multiplier) {
// multiplier - just multiply the value and take the decimal part
return normalizeAnalogValue(value * this->multiplier);
} else if (this->multiplier < -1) {
const unsigned short number_of_divisions = -this->multiplier;
// divisor - need to take care of over/underflow
if (0.75f < this->divisor_previous_value && value < 0.25f) {
this->divisor_region = (this->divisor_region + 1) % number_of_divisions;
} else if (this->divisor_previous_value < 0.25f && 0.75f < value) {
if (1 <= this->divisor_region) {
this->divisor_region -= 1;
} else {
this->divisor_region = number_of_divisions - 1;
}
}
this->divisor_previous_value = value;
return ((float)this->divisor_region + value) / (float)number_of_divisions;
} else {
// multiplier in [-1, 1] range is just treated as 1
return value;
}
}
float Analog::normalizeAnalogValue(float value) {
// effectively the same as fmodf(value, 1.f)
// for small values, this is MUCH faster than fmodf.
float new_value = value;
while (new_value > 1.f) {
new_value -= 1.f;
}
while (new_value < 0.f) {
new_value += 1.f;
}
return new_value;
}
float Analog::applyDeadzone(float raw_value) {
float value = raw_value;
const auto deadzone = this->getDeadzone();
if (deadzone > 0) {
// calculate values
const auto delta = value - 0.5f;
const auto dtlen = 1.f - deadzone;
// check mirror
if (this->getDeadzoneMirror()) {
// deadzone on the edges
if (dtlen != 0.f) {
value = std::max(0.f, std::min(1.f, 0.5f + (delta / dtlen)));
} else {
value = 0.5f;
}
} else {
// deadzone around the middle
const auto limit = deadzone * 0.5f;
if (dtlen != 0.f) {
if (delta > limit) {
value = std::min(1.f, 0.5f + std::max(0.f, (delta - limit) / dtlen));
} else if (delta < -limit) {
value = std::max(0.f, 0.5f + std::min(0.f, (delta + limit) / dtlen));
} else {
value = 0.5f;
}
} else {
value = 0.5f;
}
}
} else if (deadzone < 0) {
// invert for mirror
if (this->getDeadzoneMirror()) {
value = 1.f - value;
}
// deadzone from minimum value
if (deadzone > -1 && value > -deadzone) {
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
} else {
value = 0.f;
}
// revert value for mirror
if (this->getDeadzoneMirror()) {
value = 1.f - value;
}
}
return value;
}
+59 -2
View File
@@ -3,6 +3,7 @@
#include <array>
#include <string>
#include <cmath>
#include <queue>
#define ANALOG_HISTORY_CNT 10
#define M_TAU (2 * M_PI)
@@ -22,7 +23,7 @@ class Analog {
private:
std::string name;
std::string device_identifier = "";
unsigned short index = 0xFF;
unsigned short index = USHRT_MAX;
float sensitivity = 1.f;
float deadzone = 0.f;
bool deadzone_mirror = false;
@@ -30,7 +31,7 @@ private:
float last_state = 0.5f;
bool sensitivity_set = false;
bool deadzone_set = false;
// smoothing function
bool smoothing = false;
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
@@ -41,8 +42,22 @@ private:
float previous_raw_rads = 0.f;
float adjusted_rads = 0.f;
// multiplier/divisor
int multiplier = 1;
float divisor_previous_value = 0.5f;
unsigned short divisor_region = 0;
// relative input mode
float absolute_value_for_rel_mode = 0.5f;
bool relative_mode = false;
// circular buffer (delayed input)
int delay_buffer_depth = 0;
std::queue<float> delay_buffer;
float calculateAngularDifference(float old_rads, float new_rads);
float normalizeAngle(float rads);
float normalizeAnalogValue(float value);
public:
@@ -57,6 +72,8 @@ public:
std::string getDisplayString(rawinput::RawInputManager* manager);
float getSmoothedValue(float raw_rads);
float applyAngularSensitivity(float raw_rads);
float applyMultiplier(float raw_value);
float applyDeadzone(float raw_value);
inline bool isSet() {
if (this->override_enabled) {
@@ -72,6 +89,9 @@ public:
setDeadzone(0.f);
invert = false;
smoothing = false;
setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
}
inline const std::string &getName() const {
@@ -144,6 +164,16 @@ public:
this->smoothing = smoothing;
}
inline int getMultiplier() const {
return this->multiplier;
}
inline void setMultiplier(int multiplier) {
this->multiplier = multiplier;
this->divisor_region = 0;
this->divisor_previous_value = 0.5f;
}
inline float getLastState() const {
return this->last_state;
}
@@ -151,4 +181,31 @@ public:
inline void setLastState(float last_state) {
this->last_state = last_state;
}
inline bool isRelativeMode() const {
return this->relative_mode;
}
inline void setRelativeMode(bool relative_mode) {
this->relative_mode = relative_mode;
this->absolute_value_for_rel_mode = 0.5f;
}
inline float getAbsoluteValue(float relative_delta) {
this->absolute_value_for_rel_mode =
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
return this->absolute_value_for_rel_mode;
}
inline int getDelayBufferDepth() const {
return this->delay_buffer_depth;
}
inline void setDelayBufferDepth(int depth) {
this->delay_buffer_depth = depth;
}
inline std::queue<float> &getDelayBuffer() {
return this->delay_buffer;
}
};
+287 -121
View File
@@ -17,6 +17,8 @@ std::vector<Button> GameAPI::Buttons::getButtons(Game *game) {
return Config::getInstance().getButtons(game);
}
static Buttons::State getMidiV2ButtonState(float last_on_time, float last_off_time);
std::vector<Button> GameAPI::Buttons::sortButtons(
const std::vector<Button> &buttons,
const std::vector<std::string> &button_names,
@@ -161,14 +163,17 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
break;
}
case BAT_NEGATIVE:
case BAT_POSITIVE: {
case BAT_POSITIVE:
case BAT_ANY: {
auto value_states = &hid->value_states;
if (vKey < value_states->size()) {
auto value = value_states->at(vKey);
if (current_button->getAnalogType() == BAT_POSITIVE) {
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
} else if (current_button->getAnalogType() == BAT_NEGATIVE) {
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
} else {
state = BUTTON_NOT_PRESSED;
@@ -216,50 +221,135 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
auto midi = device->midiInfo;
switch (bat) {
case BAT_NONE: {
if (vKey < 16 * 128) {
if (rawinput::get_midi_algorithm() == rawinput::MidiNoteAlgorithm::LEGACY) {
// spicetools legacy midi logic: use event log
//
// drums send NOTE_ON and NOTE_OFF in rapid succession, before game engine has a chance
// to poll for it - to address this, keep a counter (states_events array) and the last
// state (states array), incrementing the states_events on rising edges (NOTE_ON)
// and popping events off the queue every time it's checked.
//
// if the same drum pad is mapped to multiple buttons, multiple issues arise:
// 1. we run through this logic for each button, which consumes an event every time;
// therefore, the first button may see the ON event, but subsequent mappings may
// completely miss it as it already has been drained
// 2. it is impossible to implement velocity threshold with this logic since the
// velocity is a per-note value that goes away as soon as NOTE_OFF is detected
if (vKey < midi->states_events.size()) {
// check for event
auto midi_event = midi->states_events[vKey];
if (midi_event) {
// check for event
auto midi_event = midi->states_events[vKey];
if (midi_event) {
// choose state based on event
state = (midi_event % 2) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
// choose state based on event
state = (midi_event % 2) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
// update event
if (!midi->states[vKey] || midi_event > 1) {
midi->states_events[vKey]--;
}
} else {
state = BUTTON_NOT_PRESSED;
}
}
} else {
// spice2x midi logic (new!)
//
// for every MIDI NOTE ON message, latch the "on" for a certain time, even if NOTE
// OFF message is seen immediately afterwards.
//
// each ON event is held long enough for the game's input poll to see it (e.g., gitadora
// polls every 16ms or so, rawinput holds it for 20ms by default)
//
// this is much simpler and does not have the issues mentioned above for the legacy
// logic, however the downside is that there is a risk of coalescing rapid inputs into
// one.
//
// that being said:
// * default value of 20ms should be reasonable; humans can't realistically hit the
// same note faster than this; in fact it's likely to be a misfire
// * we can tweak it per-game if needed to suit the game's polling period (in the
// future)
// * as a last resort the user can always override it via the option (MidiNoteSustain)
if (vKey < midi->v2_last_on_time.size()) {
// update event
if (!midi->states[vKey] || midi_event > 1)
midi->states_events[vKey]--;
// take the velocity threshold from first button binding we encounter here
// this hardware key may be mapped to multiple bindings, but the UI should keep them
// the same value, as only one threshold value can be set per MIDI key
// (otherwise it makes the sustain logic too complicated)
const auto sw_threshold = current_button->getVelocityThreshold();
if (0 < sw_threshold && !midi->v2_velocity_threshold_set_on_device[vKey]) {
midi->v2_velocity_threshold_set_on_device[vKey] = true;
midi->v2_velocity_threshold[vKey] = sw_threshold;
}
} else
state = getMidiV2ButtonState(
midi->v2_last_on_time[vKey],
midi->v2_last_off_time[vKey]);
} else {
state = BUTTON_NOT_PRESSED;
}
}
break;
}
case BAT_MIDI_CTRL_PRECISION: {
if (vKey < 16 * 32)
state = midi->controls_precision[vKey] > 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
else
if (vKey < midi->controls_precision.size()) {
if (rawinput::get_midi_algorithm() == rawinput::MidiNoteAlgorithm::LEGACY) {
state = midi->controls_precision[vKey] > 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
// not using getVelocityHelper here to avoid locking and other checks
const auto v = device->midiInfo->controls_precision[vKey];
// velocity threshold ranges from [0, 127], so do some math for double precision
const auto threshold = (current_button->getVelocityThreshold() << 7u) | 0x7f;
state = (threshold < v) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
} else {
state = BUTTON_NOT_PRESSED;
}
break;
}
case BAT_MIDI_CTRL_SINGLE: {
if (vKey < 16 * 44)
state = midi->controls_single[vKey] > 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
else
if (vKey < midi->controls_single.size()) {
if (rawinput::get_midi_algorithm() == rawinput::MidiNoteAlgorithm::LEGACY) {
state = midi->controls_single[vKey] > 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
// not using getVelocityHelper here to avoid locking and other checks
const auto v = device->midiInfo->controls_single[vKey];
state = (current_button->getVelocityThreshold() < v) ?
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
} else {
state = BUTTON_NOT_PRESSED;
}
break;
}
case BAT_MIDI_CTRL_ONOFF: {
if (vKey < 16 * 6)
state = midi->controls_onoff[vKey] ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
else
if (vKey < midi->controls_onoff.size()) {
if (rawinput::get_midi_algorithm() == rawinput::MidiNoteAlgorithm::LEGACY) {
state = midi->controls_onoff[vKey] ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
state = getMidiV2ButtonState(
midi->v2_controls_onoff_last_on_time[vKey],
midi->v2_controls_onoff_last_off_time[vKey]);
}
} else {
state = BUTTON_NOT_PRESSED;
}
break;
}
case BAT_MIDI_PITCH_DOWN:
state = midi->pitch_bend < 0x2000 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
if (vKey < midi->pitch_bend.size()) {
state = midi->pitch_bend[vKey] < 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
state = BUTTON_NOT_PRESSED;
}
break;
case BAT_MIDI_PITCH_UP:
state = midi->pitch_bend > 0x2000 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
if (vKey < midi->pitch_bend.size()) {
state = midi->pitch_bend[vKey] > 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
state = BUTTON_NOT_PRESSED;
}
break;
default: {
state = BUTTON_NOT_PRESSED;
@@ -372,24 +462,64 @@ static float getVelocityHelper(rawinput::RawInputManager *manager, Button &butto
device->mutex->lock();
// determine velocity based on device type
switch (device->type) {
case rawinput::MIDI: {
if (device->type == rawinput::MIDI) {
switch (button.getAnalogType()) {
case ButtonAnalogType::BAT_MIDI_CTRL_PRECISION:
if (vKey < device->midiInfo->controls_precision.size()) {
velocity = device->midiInfo->controls_precision[vKey] / 16383.f;
} else {
velocity = 0.f;
}
break;
// read control
if (vKey < 16 * 128) {
velocity = (float) device->midiInfo->velocity[vKey] / 127.f;
} else {
velocity = 0.f;
}
case ButtonAnalogType::BAT_MIDI_CTRL_SINGLE:
if (vKey < device->midiInfo->controls_single.size()) {
velocity = device->midiInfo->controls_single[vKey] / 127.f;
} else {
velocity = 0.f;
}
break;
// invert
if (button.getInvert()) {
velocity = 1.f - velocity;
}
break;
case ButtonAnalogType::BAT_MIDI_CTRL_ONOFF:
if (vKey < device->midiInfo->controls_onoff.size()) {
velocity = device->midiInfo->controls_onoff[vKey] ? 1.f : 0.f;
} else {
velocity = 0.f;
}
break;
case ButtonAnalogType::BAT_MIDI_PITCH_DOWN:
if (vKey < device->midiInfo->pitch_bend.size()) {
velocity = device->midiInfo->pitch_bend[vKey] < 0 ? 1.f : 0.f;
} else {
velocity = 0.f;
}
break;
case ButtonAnalogType::BAT_MIDI_PITCH_UP:
if (vKey < device->midiInfo->pitch_bend.size()) {
// pitch range is [-8192, 8191]
velocity = (device->midiInfo->pitch_bend[vKey]) > 0 ? 1.f : 0.f;
} else {
velocity = 0.f;
}
break;
case ButtonAnalogType::BAT_NONE:
default:
// velocity sensitive
if (vKey < device->midiInfo->velocity.size()) {
velocity = (float) device->midiInfo->velocity[vKey] / 127.f;
} else {
velocity = 0.f;
}
break;
}
// invert
if (button.getInvert()) {
velocity = 1.f - velocity;
}
default:
break;
}
// unlock device
@@ -490,29 +620,82 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
value = device->hidInfo->value_states[index];
}
// smoothing/sensitivity
if (analog.getSmoothing() || analog.isSensitivitySet()) {
float rads = value * (float) M_TAU;
// deadzone
if (analog.isDeadzoneSet()) {
value = analog.applyDeadzone(value);
}
// smoothing
if (analog.getSmoothing()) {
if (analog.isRelativeMode()) {
float relative_delta = value - 0.5f;
// built-in scaling to make values reasonable
relative_delta /= 80.f;
// preserve direction
if (rads >= M_TAU) {
rads -= 0.0001f;
// integer multiplier/divisor
const auto mult = analog.getMultiplier();
if (mult < -1) {
relative_delta /= -mult;
} else if (1 < mult) {
relative_delta *= mult;
}
// sensitivity (ranges from 0.0 to 4.0)
if (analog.isSensitivitySet()) {
relative_delta *= analog.getSensitivity();
}
// translate relative movement to absolute value
value = analog.getAbsoluteValue(relative_delta);
} else {
// integer multiplier
value = analog.applyMultiplier(value);
// smoothing/sensitivity
if (analog.getSmoothing() || analog.isSensitivitySet()) {
float rads = value * (float) M_TAU;
// smoothing
if (analog.getSmoothing()) {
// preserve direction
if (rads >= M_TAU) {
rads -= 0.0001f;
}
// calculate angle
rads = analog.getSmoothedValue(rads);
}
// calculate angle
rads = analog.getSmoothedValue(rads);
// sensitivity
if (analog.isSensitivitySet()) {
rads = analog.applyAngularSensitivity(rads);
}
// apply to value
value = rads * (float) M_1_TAU;
}
}
// delay
if (0 < analog.getDelayBufferDepth()) {
auto& queue = analog.getDelayBuffer();
// ensure the queue isn't too long; drop old values
while (analog.getDelayBufferDepth() <= (int)queue.size()) {
queue.pop();
}
// sensitivity
if (analog.isSensitivitySet()) {
rads = analog.applyAngularSensitivity(rads);
}
// always push new value
queue.push(value);
// apply to value
value = rads * (float) M_1_TAU;
// get a new value to return
if ((int)queue.size() < analog.getDelayBufferDepth()) {
// not enough in the queue, stall for now, shouldn't happen often
value = analog.getLastState();
} else {
value = queue.front();
queue.pop();
}
}
break;
@@ -524,6 +707,7 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
auto prec_count = (int) midi->controls_precision.size();
auto single_count = (int) midi->controls_single.size();
auto onoff_count = (int) midi->controls_onoff.size();
auto pitch_count = (int) midi->pitch_bend.size();
// decide on value
if (index < prec_count)
@@ -532,81 +716,19 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
value = midi->controls_single[index - prec_count] / 127.f;
else if (index < prec_count + single_count + onoff_count)
value = midi->controls_onoff[index - prec_count - single_count] ? 1.f : 0.f;
else if (index == prec_count + single_count + onoff_count)
value = midi->pitch_bend / 16383.f;
else if (index < prec_count + single_count + onoff_count + pitch_count)
value = (midi->pitch_bend[index - prec_count - single_count - onoff_count] + 0x2000) / 16383.f;
// invert value
if (inverted) {
value = 1.f - value;
}
}
default:
break;
}
// deadzone logic
switch (device->type) {
case rawinput::HID:
case rawinput::MIDI: {
// check if set
// deadzone
if (analog.isDeadzoneSet()) {
// check sign
auto deadzone = analog.getDeadzone();
if (deadzone > 0) {
// calculate values
auto delta = value - 0.5f;
auto dtlen = 1.f - deadzone;
// check mirror
if (analog.getDeadzoneMirror()) {
// deadzone on the edges
if (dtlen != 0.f) {
value = std::max(0.f, std::min(1.f, 0.5f + (delta / dtlen)));
} else {
value = 0.5f;
}
} else {
// deadzone around the middle
auto limit = deadzone * 0.5f;
if (dtlen != 0.f) {
if (delta > limit) {
value = std::min(1.f, 0.5f + std::max(0.f, (delta - limit) / dtlen));
} else if (delta < -limit) {
value = std::max(0.f, 0.5f + std::min(0.f, (delta + limit) / dtlen));
} else {
value = 0.5f;
}
} else {
value = 0.5f;
}
}
} else if (deadzone < 0) {
// invert for mirror
if (analog.getDeadzoneMirror()) {
value = 1.f - value;
}
// deadzone from minimum value
if (deadzone > -1 && value > -deadzone) {
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
} else {
value = 0.f;
}
// revert value for mirror
if (analog.getDeadzoneMirror()) {
value = 1.f - value;
}
}
value = analog.applyDeadzone(value);
}
break;
}
default:
break;
@@ -761,6 +883,7 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
if (index < rawinput::SextetDevice::LIGHT_COUNT) {
device->sextetInfo->light_state[index] = value > 0;
device->sextetInfo->push_light_state();
device->output_pending = true;
} else {
log_warning("api", "invalid sextet light index: {}", index);
}
@@ -769,11 +892,30 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
case rawinput::PIUIO_DEVICE: {
if (index < rawinput::PIUIO::PIUIO_MAX_NUM_OF_LIGHTS) {
device->piuioDev->SetLight(index, value > 0);
device->output_pending = true;
} else {
log_warning("api", "invalid piuio light index: {}", index);
}
break;
}
case rawinput::SMX_STAGE: {
if (index < rawinput::SmxStageDevice::TOTAL_LIGHT_COUNT) {
device->smxstageInfo->SetLightByIndex(index, static_cast<uint8_t>(value*255.f));
device->output_pending = true;
} else {
log_warning("api", "invalid smx stage light index: {}", index);
}
break;
}
case rawinput::SMX_DEDICAB: {
if (index < rawinput::SmxDedicabDevice::LIGHTS_COUNT) {
device->smxdedicabInfo->SetLightByIndex(index, static_cast<uint8_t>(value * 255.f));
device->output_pending = true;
} else {
log_warning("api", "invalid SMX dedicab light index: {}", index);
}
break;
}
default:
break;
}
@@ -922,3 +1064,27 @@ void GameAPI::Options::sortOptions(std::vector<Option> &options, const std::vect
options = std::move(sorted);
}
static Buttons::State getMidiV2ButtonState(float on, float off) {
if (on == 0.0) {
return Buttons::State::BUTTON_NOT_PRESSED;
} else if (off < on) {
// if OFF was not observed strictly after ON, we can confidently say that the note
// remains ON; in case of a tie (rarely in v2, all the time in v2_drum), prefer to keep note
// off since that's better than a note stuck on
return Buttons::State::BUTTON_PRESSED;
} else {
// otherwise, this is an ON-OFF sequence
// check for time the most recent ON message
//
// if recent, consider the button to be on - even if there were OFF messages following it
// this is needed to detect things like MIDI drums which send a quick ON-OFF sequence
// between the game's polling period
const auto now = get_performance_milliseconds();
if ((now - on) < (double)rawinput::MIDI_NOTE_SUSTAIN) {
return Buttons::State::BUTTON_PRESSED;
} else {
return Buttons::State::BUTTON_NOT_PRESSED;
}
}
}
+151 -13
View File
@@ -22,6 +22,7 @@ const char *ButtonAnalogTypeStr[] = {
"MIDI Control On/Off",
"MIDI Pitch Down",
"MIDI Pitch Up",
"Any Direction",
};
std::string Button::getVKeyString() {
@@ -267,6 +268,15 @@ std::string Button::getVKeyString() {
}
}
std::string Button::getMidiNoteString() {
static const std::string note_names[] = {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"};
int channel;
int index;
this->getMidiVKey(channel, index);
return fmt::format("{}{}", note_names[index % 12], ((index / 12) - 1));
}
std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
// get VKey string
@@ -317,8 +327,16 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
else
return "Invalid button (" + device->desc + ")";
case BAT_NEGATIVE:
case BAT_POSITIVE: {
const char *sign = this->analog_type == BAT_NEGATIVE ? "-" : "+";
case BAT_POSITIVE:
case BAT_ANY: {
const char *sign;
if (this->analog_type == BAT_NEGATIVE) {
sign = "-";
} else if (this->analog_type == BAT_POSITIVE) {
sign = "+";
} else {
sign = "*";
}
if (vKey < hid->value_caps_names.size()) {
return hid->value_caps_names[vKey] + sign + " (" + device->desc + ")";
} else {
@@ -347,23 +365,34 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
return "Unknown analog type (" + device->desc + ")";
}
}
case rawinput::MIDI:
case rawinput::MIDI: {
int channel = 0;
int ctrl = 0;
this->getMidiVKey(channel, ctrl);
switch (this->analog_type) {
case BAT_NONE:
return "MIDI " + vKeyString + " (" + device->desc + ")";
case BAT_MIDI_CTRL_PRECISION:
return "MIDI PREC " + vKeyString + " (" + device->desc + ")";
case BAT_MIDI_CTRL_SINGLE:
return "MIDI CTRL " + vKeyString + " (" + device->desc + ")";
case BAT_MIDI_CTRL_ONOFF:
return "MIDI ONOFF " + vKeyString + " (" + device->desc + ")";
// update strings in analog.cpp as well
case BAT_NONE: {
const auto note = this->getMidiNoteString();
return fmt::format("MIDI Note Ch.{} #{} {} ({})", channel, ctrl, note, device->desc);
}
case BAT_MIDI_CTRL_PRECISION: {
return fmt::format("MIDI Prec Ctrl Ch.{} CC#{} ({})", channel, ctrl, device->desc);
}
case BAT_MIDI_CTRL_SINGLE: {
return fmt::format("MIDI Ctrl Ch.{} CC#{} ({})", channel, ctrl, device->desc);
}
case BAT_MIDI_CTRL_ONOFF: {
return fmt::format("MIDI OnOff Ch.{} CC#{} ({})", channel, ctrl, device->desc);
}
case BAT_MIDI_PITCH_DOWN:
return "MIDI Pitch Down (" + device->desc + ")";
return fmt::format("MIDI Pitch Down Ch.{} ({})", channel, device->desc);
case BAT_MIDI_PITCH_UP:
return "MIDI Pitch Up (" + device->desc + ")";
return fmt::format("MIDI Pitch Up Ch.{} ({})", channel, device->desc);
default:
return "MIDI Unknown " + vKeyString + " (" + device->desc + ")";
}
return "";
}
case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString;
case rawinput::DESTROYED:
@@ -374,6 +403,115 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
}
}
void Button::getMidiVKey(int& channel, int& index) {
switch (this->analog_type) {
// update strings in analog.cpp as well
case BAT_NONE:
channel = (vKey / 0x80) + 1;
index = vKey & 0x7f;
break;
case BAT_MIDI_CTRL_PRECISION:
channel = (vKey / 32) + 1;
index = (vKey % 32);
break;
case BAT_MIDI_CTRL_SINGLE:
channel = (vKey / 44) + 1;
index = (vKey % 44);
if (index <= 25) {
index += 0x46; // single byte range
} else {
index = index - 26 + 0x66; // undefined single byte range
}
break;
case BAT_MIDI_CTRL_ONOFF:
channel = (vKey / 6) + 1;
index = (vKey % 6) + 0x40;
break;
case BAT_MIDI_PITCH_DOWN:
case BAT_MIDI_PITCH_UP:
channel = vKey + 1;
index = 0;
break;
default:
channel = 0;
index = 0;
break;
}
}
void Button::setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index) {
int vKey = 0;
if (is_note) {
vKey = (channel - 1) * 0x80 + index;
this->setVKey(vKey);
this->setAnalogType(BAT_NONE);
// ensure that velocity threshold is read back from what rawinput has for other bindings
if (manager && !this->device_identifier.empty()) {
auto device = manager->devices_get(this->device_identifier);
if (device &&
device->midiInfo &&
(size_t)vKey < device->midiInfo->v2_velocity_threshold.size()) {
this->setVelocityThreshold(device->midiInfo->v2_velocity_threshold[vKey]);
}
}
return;
}
if (channel < 1 || 16 < channel) {
this->setVKey(0);
this->setAnalogType(BAT_NONE);
return;
}
if (index < 0 || 127 < index) {
this->setVKey(0);
this->setAnalogType(BAT_NONE);
return;
}
// continuous controller MSB
if (0x00 <= index && index <= 0x1F) {
vKey = (channel - 1) * 32 + index;
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_PRECISION);
return;
}
// continuous controller LSB
if (0x20 <= index && index <= 0x3F) {
vKey = (channel - 1) * 32 + index - 0x20;
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_PRECISION);
return;
}
// on/off controls
if (0x40 <= index && index <= 0x45) {
vKey = (channel - 1) * 6 + (index - 0x40);
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_ONOFF);
return;
}
// single byte controllers
if (0x46 <= index && index <= 0x5F) {
vKey = (channel - 1) * 44;
vKey += index - 0x46; // single byte range
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_SINGLE);
return;
}
// undefined single byte controllers
if (0x66 <= index && index <= 0x77) {
vKey = (channel - 1) * 44;
vKey += index - 0x66 + (0x5F - 0x46 + 1) ; // undefined single byte range
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_SINGLE);
return;
}
}
#define HAT_SWITCH_INCREMENT (1.f / 7)
void Button::getHatSwitchValues(float analog_state, ButtonAnalogType* buffer) {
+14
View File
@@ -28,6 +28,7 @@ enum ButtonAnalogType {
BAT_MIDI_CTRL_ONOFF = 14,
BAT_MIDI_PITCH_DOWN = 15,
BAT_MIDI_PITCH_UP = 16,
BAT_ANY = 17,
};
extern const char *ButtonAnalogTypeStr[];
@@ -45,8 +46,10 @@ private:
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
float last_velocity = 0.f;
unsigned short velocity_threshold = 0;
std::string getVKeyString();
std::string getMidiNoteString();
public:
@@ -159,6 +162,17 @@ public:
this->last_velocity = last_velocity;
}
inline unsigned short getVelocityThreshold() const {
return this->velocity_threshold;
}
inline void setVelocityThreshold(unsigned short velocity_threshold) {
this->velocity_threshold = velocity_threshold;
}
void getMidiVKey(int& channel, int& index);
void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
/*
* Map hat switch float value from [0-1] to directions.
* Buffer must be sized 3 or bigger.
+106 -13
View File
@@ -19,10 +19,16 @@ Config::Config() {
this->status = false;
if (CONFIG_PATH_OVERRIDE.length() > 0) {
this->configLocation = CONFIG_PATH_OVERRIDE;
log_info("cfg", "using custom config file: {}", this->configLocation.string());
} else {
this->configLocation = std::string(getenv("APPDATA")) + "\\spicetools.xml";
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
// avoids logging the expanded appdata path as it contains user name
log_info("cfg", "using global config file: %appdata%\\spicetools.xml");
}
this->configLocationTemp = this->configLocation;
this->configLocationTemp.replace_extension(L"tmp");
tinyxml2::XMLError configLoadError, *previousConfigLoadError = nullptr;
do {
@@ -42,7 +48,7 @@ Config::Config() {
this->firstFillConfigFile();
break;
case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
log_fatal("cfg", "could not open config file: {}", this->configLocation);
log_fatal("cfg", "could not open config file: {}", this->configLocation.string());
break;
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
this->createConfigFile();
@@ -57,7 +63,7 @@ Config::Config() {
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
case tinyxml2::XMLError::XML_ERROR_PARSING:
log_warning("cfg", "Couldn't read config file: {}", this->configLocation);
log_warning("cfg", "Couldn't read config file: {}", this->configLocation.string());
this->createConfigFile();
this->firstFillConfigFile();
break;
@@ -147,12 +153,14 @@ bool Config::addGame(Game &game) {
auto analogType = (int) BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
int velocity_threshold = 0;
bool invert = false;
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
const char *devid = gameButtonNode->Attribute("devid");
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert);
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
gameButtonsNode->DeleteChild(gameButtonNode);
@@ -163,6 +171,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
gameButtonNode->SetAttribute("debounce_up", debounce_up);
gameButtonNode->SetAttribute("debounce_down", debounce_down);
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
gameButtonNode->SetAttribute("invert", invert);
gameButtonsNode->InsertEndChild(gameButtonNode);
} else {
@@ -170,6 +179,7 @@ bool Config::addGame(Game &game) {
button->setAnalogType((ButtonAnalogType) analogType);
button->setDebounceUp(debounce_up);
button->setDebounceDown(debounce_down);
button->setVelocityThreshold(velocity_threshold);
button->setInvert(invert);
if (devid) {
button->setDeviceIdentifier(devid);
@@ -188,6 +198,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
gameButtonsNode->InsertEndChild(gameButtonNode);
@@ -226,12 +237,18 @@ bool Config::addGame(Game &game) {
bool deadzone_mirror = false;
bool invert = false;
bool smoothing = false;
int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
gameAnalogNode->QueryBoolAttribute("deadzone_mirror", &deadzone_mirror);
gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
const char *devid = gameAnalogNode->Attribute("devid");
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
@@ -245,6 +262,9 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("deadzone_mirror", it.getDeadzoneMirror());
gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogsNode->InsertEndChild(gameAnalogNode);
} else {
it.setIndex(static_cast<unsigned short int>(index));
@@ -254,6 +274,9 @@ bool Config::addGame(Game &game) {
it.setDeadzoneMirror(deadzone_mirror);
it.setInvert(invert);
it.setSmoothing(smoothing);
it.setMultiplier(multiplier);
it.setRelativeMode(relative_mode);
it.setDelayBufferDepth(delay_buffer_depth);
}
} else {
gameAnalogNode = this->configFile.NewElement("analog");
@@ -264,6 +287,9 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("deadzone_mirror", it.getDeadzoneMirror());
gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode);
}
@@ -397,6 +423,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
gameButtonsNode->InsertEndChild(gameButtonNode);
@@ -414,6 +441,9 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("deadzone_mirror", it.getDeadzoneMirror());
gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogsNode->InsertEndChild(gameAnalogNode);
}
@@ -442,7 +472,7 @@ bool Config::addGame(Game &game) {
}
// save config
this->configFile.SaveFile(this->configLocation.c_str(), false);
this->saveConfigFile();
// return success
return true;
@@ -494,6 +524,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", button.getInvert());
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
break;
@@ -509,19 +540,47 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", 0);
gameButtonNode->SetAttribute("debounce_up", 0.0);
gameButtonNode->SetAttribute("debounce_down", 0.0);
gameButtonNode->SetAttribute("velocity_threshold", 0);
gameButtonNode->SetAttribute("invert", false);
gameButtonNode->SetAttribute("devid", "");
gameButtonsNode->InsertEndChild(gameButtonNode);
}
}
// for MIDI notes, need to keep velocity threshold consistent for all bindings
// ;MIDI; is a unique prefix that we use at rawinput layer to identify MIDI devices
const bool fixup_other_buttons =
(button.getAnalogType() == (int)BAT_NONE &&
!button.getDeviceIdentifier().empty() &&
button.getDeviceIdentifier().find(";MIDI;", 0) == 0);
if (fixup_other_buttons) {
gameButtonNode = gameButtonsNode->FirstChildElement("button");
while (gameButtonNode != nullptr) {
const char *devid = gameButtonNode->Attribute("devid");
if (button.getDeviceIdentifier() == devid) {
int other_vKey = 0;
int other_vel = 0;
int other_type = 0;
gameButtonNode->QueryIntAttribute("velocity_threshold", &other_vel);
gameButtonNode->QueryIntAttribute("vkey", &other_vKey);
gameButtonNode->QueryIntAttribute("analogtype", &other_type);
if (other_vKey == button.getVKey() &&
other_type == button.getAnalogType() &&
other_vel != button.getVelocityThreshold()) {
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
}
}
gameButtonNode = gameButtonNode->NextSiblingElement("button");
}
}
// check if button was not found
if (button_count == 0) {
return false;
}
// save config
this->configFile.SaveFile(this->configLocation.c_str(), false);
this->saveConfigFile();
// return success
return true;
@@ -579,6 +638,9 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("deadzone_mirror", analog.getDeadzoneMirror());
gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
} else {
gameAnalogNode = this->configFile.NewElement("analog");
@@ -588,11 +650,14 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("deadzone_mirror", analog.getDeadzoneMirror());
gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode);
}
this->configFile.SaveFile(this->configLocation.c_str(), false);
this->saveConfigFile();
return true;
}
@@ -633,7 +698,7 @@ bool Config::updateBinding(const Game &game, ConfigKeypadBindings &keypads) {
gameKeypadNode->SetAttribute("cardpath1", reinterpret_cast<const char *>(keypads.card_paths[0].u8string().c_str()));
gameKeypadNode->SetAttribute("cardpath2", reinterpret_cast<const char *>(keypads.card_paths[1].u8string().c_str()));
this->configFile.SaveFile(this->configLocation.c_str(), false);
this->saveConfigFile();
return true;
}
@@ -701,7 +766,7 @@ bool Config::updateBinding(const Game &game, const Light &light, int alternative
}
// save config
this->configFile.SaveFile(this->configLocation.c_str(), false);
this->saveConfigFile();
// return success
return true;
@@ -760,7 +825,7 @@ bool Config::updateBinding(const Game &game, const Option &option) {
gameOptionsNode->InsertEndChild(gameOptionNode);
}
this->configFile.SaveFile(this->configLocation.c_str(), false);
this->saveConfigFile();
return true;
}
@@ -806,11 +871,13 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
auto analogType = (int) BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
int velocity_threshold = 0;
bool invert = false;
gameButtonNode->QueryIntAttribute("vkey", &vKey);
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert);
const char *devid = gameButtonNode->Attribute("devid");
@@ -824,6 +891,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
alt.setAnalogType((ButtonAnalogType) analogType);
alt.setDebounceUp(debounce_up);
alt.setDebounceDown(debounce_down);
alt.setVelocityThreshold(velocity_threshold);
alt.setInvert(invert);
if (devid) {
alt.setDeviceIdentifier(std::string(devid));
@@ -835,13 +903,13 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
// if no alternative was found
if (!alternative_found) {
// create button and add to list
auto &button = buttons.emplace_back(buttonNodeName);
button.setVKey((unsigned short) vKey);
button.setAnalogType((ButtonAnalogType) analogType);
button.setDebounceUp(debounce_up);
button.setDebounceDown(debounce_down);
button.setVelocityThreshold(velocity_threshold);
button.setInvert(invert);
if (devid) {
button.setDeviceIdentifier(devid);
@@ -985,12 +1053,18 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
bool deadzone_mirror = false;
bool invert = false;
bool smoothing = false;
int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
gameAnalogNode->QueryBoolAttribute("deadzone_mirror", &deadzone_mirror);
gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
const char *devid = gameAnalogNode->Attribute("devid");
// create analog and add to list
@@ -1001,6 +1075,9 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
analog.setDeadzoneMirror(deadzone_mirror);
analog.setInvert(invert);
analog.setSmoothing(smoothing);
analog.setMultiplier(multiplier);
analog.setRelativeMode(relative_mode);
analog.setDelayBufferDepth(delay_buffer_depth);
if (devid) {
analog.setDeviceIdentifier(devid);
}
@@ -1136,7 +1213,7 @@ std::vector<Option> Config::getOptions(Game *game) {
bool Config::createConfigFile() {
std::ofstream ofsConfig;
ofsConfig.open(this->configLocation);
ofsConfig.open(this->configLocationTemp);
if (!ofsConfig.is_open() || ofsConfig.fail() || ofsConfig.bad()) {
this->status = false;
return false;
@@ -1146,7 +1223,7 @@ bool Config::createConfigFile() {
}
bool Config::firstFillConfigFile() {
this->configFile.LoadFile(this->configLocation.c_str());
this->configFile.LoadFile(this->configLocationTemp.c_str());
this->configFile.Clear();
tinyxml2::XMLNode *declarationNode = this->configFile.NewDeclaration();
@@ -1155,6 +1232,22 @@ bool Config::firstFillConfigFile() {
tinyxml2::XMLNode *rootNode = this->configFile.NewElement("games");
this->configFile.InsertEndChild(rootNode);
this->configFile.SaveFile(this->configLocation.c_str(), false);
this->saveConfigFile();
return true;
}
void Config::saveConfigFile() {
// create a .tmp file and write to it...
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
if (xml_result != tinyxml2::XMLError::XML_SUCCESS) {
log_info("cfg", "failed to write file: {}", this->configLocationTemp.string());
return;
}
// copy the .tmp file to the main file...
if (CopyFileW(this->configLocationTemp.c_str(), this->configLocation.c_str(), false) == 0) {
log_warning("cfg", "CopyFileA failed: 0x{:08x}", GetLastError());
return;
}
// delete the .tmp file (not critical if this fails)
DeleteFileW(this->configLocationTemp.c_str());
}
+3 -1
View File
@@ -54,7 +54,9 @@ private:
tinyxml2::XMLDocument configFile;
bool status;
std::string configLocation;
std::filesystem::path configLocation;
std::filesystem::path configLocationTemp;
bool firstFillConfigFile();
void saveConfigFile();
};
-8
View File
@@ -1,7 +1,6 @@
#include "configurator.h"
#include "overlay/overlay.h"
#include "script/manager.h"
namespace cfg {
@@ -26,14 +25,7 @@ namespace cfg {
overlay::OVERLAY->hotkeys_enable = false;
ImGui::GetIO().MouseDrawCursor = false;
// scripts
script::manager_scan();
script::manager_config();
// run window
this->wnd.run();
// clean up
script::manager_shutdown();
}
}
+1 -2
View File
@@ -5,8 +5,7 @@
namespace cfg {
enum class ConfigType {
Config,
KFControl,
Config
};
// globals
+1 -5
View File
@@ -29,13 +29,9 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
// determine window title
if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
WINDOW_TITLE = "spice2x config - a fork of SpiceTools (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_TITLE = "spice2x config (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_SIZE_X = 800;
WINDOW_SIZE_Y = 600;
} else if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::KFControl) {
WINDOW_TITLE = "KFControl (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_SIZE_X = 400;
WINDOW_SIZE_Y = 316;
}
// open window
+20
View File
@@ -5,6 +5,8 @@
#include "rawinput/piuio.h"
#include "rawinput/rawinput.h"
#include "rawinput/sextet.h"
#include "rawinput/smxdedicab.h"
#include "rawinput/smxstage.h"
#include "util/logging.h"
std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
@@ -65,6 +67,24 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
return "Invalid PIUIO Light (" + index_string + ")";
}
case rawinput::SMX_STAGE: {
// get light name of SMX Stage device
if (index < rawinput::SmxStageDevice::TOTAL_LIGHT_COUNT) {
return rawinput::SmxStageDevice::GetLightNameByIndex(index) + " (" + index_string + ")";
}
return "Invalid SMX Stage Light (" + index_string + ")";
}
case rawinput::SMX_DEDICAB: {
// get light name of SMX Dedicab device
if (index < rawinput::SmxDedicabDevice::LIGHTS_COUNT) {
return rawinput::SmxDedicabDevice::GetLightNameByIndex(index) + " (" + index_string + ")";
}
return "Invalid SMX Dedicab Light (" + index_string + ")";
}
case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")";
default:
+13
View File
@@ -1,6 +1,7 @@
#include "option.h"
#include "util/logging.h"
#include "util/utils.h"
void Option::value_add(std::string new_value) {
@@ -97,3 +98,15 @@ uint64_t Option::value_hex64() const {
}
return affinity;
}
bool Option::search_match(const std::string &query_in_lower_case) {
if (this->search_string.empty()) {
const auto &param =
this->definition.display_name.empty() ?
this->definition.name : this->definition.display_name;
const auto s = this->definition.title + " -" + param;
this->search_string = strtolower(s);
}
return this->search_string.find(query_in_lower_case) != std::string::npos;
}
+13 -1
View File
@@ -3,6 +3,7 @@
#include <string>
#include <utility>
#include <vector>
#include <cstdint>
enum class OptionType {
Bool,
@@ -14,7 +15,14 @@ enum class OptionType {
struct OptionDefinition {
std::string title;
// unique identifier used for flag matching but also stored in config files
// (should not be changed once published for compat)
std::string name;
// what's displayed in the UI/logs as the flag name
std::string display_name = "";
// slash-delimited list of strings that also work as flag
std::string aliases = "";
// what's displayed in the UI/logs as the tooltip
std::string desc;
OptionType type;
bool hidden = false;
@@ -23,11 +31,13 @@ struct OptionDefinition {
std::string category = "Development";
bool sensitive = false;
std::vector<std::pair<std::string, std::string>> elements = {};
bool disabled = false;
};
class Option {
private:
OptionDefinition definition;
std::string search_string;
public:
std::string value;
@@ -35,7 +45,8 @@ public:
bool disabled = false;
explicit Option(OptionDefinition definition, std::string value = "") :
definition(std::move(definition)), value(std::move(value)) {};
definition(std::move(definition)), value(std::move(value)) {
};
inline const OptionDefinition &get_definition() const {
return this->definition;
@@ -57,4 +68,5 @@ public:
std::vector<std::string> values_text() const;
uint32_t value_uint32() const;
uint64_t value_hex64() const;
bool search_match(const std::string &query_in_lower_case);
};
+39 -24
View File
@@ -12,10 +12,21 @@ namespace cfg {
// globals
std::unique_ptr<cfg::ScreenResize> SCREENRESIZE;
std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
ScreenResize::ScreenResize() {
this->config_path = std::string(getenv("APPDATA")) + "\\spicetools_screen_resize.json";
if (fileutils::file_exists(this->config_path)) {
bool file_exists = false;
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
if (fileutils::file_exists(this->config_path)) {
log_info("ScreenResize", "loading config from: {}", this->config_path.string());
file_exists = true;
}
} else {
this->config_path =
fileutils::get_config_file_path("ScreenResize", "spicetools_screen_resize.json", &file_exists);
}
if (file_exists) {
this->config_load();
}
}
@@ -24,10 +35,9 @@ namespace cfg {
}
void ScreenResize::config_load() {
log_info("ScreenResize", "loading config");
std::string config = fileutils::text_read(this->config_path);
if (config.empty()) {
log_info("ScreenResize", "config is empty");
return;
}
@@ -66,14 +76,18 @@ namespace cfg {
eamuse_get_game(),
use_game_setting,
root);
load_int_value(doc, root + "offset_x", this->offset_x);
load_int_value(doc, root + "offset_y", this->offset_y);
load_float_value(doc, root + "scale_x", this->scale_x);
load_float_value(doc, root + "scale_y", this->scale_y);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
load_bool_value(doc, root + "keep_aspect_ratio", this->keep_aspect_ratio);
load_bool_value(doc, root + "centered", this->centered);
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i];
const std::string prefix = fmt::format("scenes/{}/", i);
load_int_value(doc, root + prefix + "offset_x", scene.offset_x);
load_int_value(doc, root + prefix + "offset_y", scene.offset_y);
load_float_value(doc, root + prefix + "scale_x", scene.scale_x);
load_float_value(doc, root + prefix + "scale_y", scene.scale_y);
load_bool_value(doc, root + prefix + "keep_aspect_ratio", scene.keep_aspect_ratio);
}
// windowed settings are always under game settings
root = "/sp2x_games/" + eamuse_get_game() + "/";
@@ -95,7 +109,7 @@ namespace cfg {
bool ScreenResize::load_bool_value(rapidjson::Document& doc, std::string path, bool& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsBool()) {
@@ -109,7 +123,7 @@ namespace cfg {
bool ScreenResize::load_int_value(rapidjson::Document& doc, std::string path, int& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsInt()) {
@@ -123,7 +137,7 @@ namespace cfg {
bool ScreenResize::load_uint32_value(rapidjson::Document& doc, std::string path, uint32_t& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsUint()) {
@@ -137,7 +151,7 @@ namespace cfg {
bool ScreenResize::load_float_value(rapidjson::Document& doc, std::string path, float& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (v->IsInt()) {
@@ -156,8 +170,6 @@ namespace cfg {
}
void ScreenResize::config_save() {
log_info("ScreenResize", "saving config");
rapidjson::Document doc;
std::string config = fileutils::text_read(this->config_path);
if (!config.empty()) {
@@ -179,14 +191,17 @@ namespace cfg {
root);
// full screen image settings
rapidjson::Pointer(root + "offset_x").Set(doc, this->offset_x);
rapidjson::Pointer(root + "offset_y").Set(doc, this->offset_y);
rapidjson::Pointer(root + "scale_x").Set(doc, this->scale_x);
rapidjson::Pointer(root + "scale_y").Set(doc, this->scale_y);
rapidjson::Pointer(root + "enable_screen_resize").Set(doc, this->enable_screen_resize);
rapidjson::Pointer(root + "enable_linear_filter").Set(doc, this->enable_linear_filter);
rapidjson::Pointer(root + "keep_aspect_ratio").Set(doc, this->keep_aspect_ratio);
rapidjson::Pointer(root + "centered").Set(doc, this->centered);
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i];
const std::string prefix = fmt::format("scenes/{}/", i);
rapidjson::Pointer(root + prefix + "offset_x").Set(doc, scene.offset_x);
rapidjson::Pointer(root + prefix + "offset_y").Set(doc, scene.offset_y);
rapidjson::Pointer(root + prefix + "scale_x").Set(doc, scene.scale_x);
rapidjson::Pointer(root + prefix + "scale_y").Set(doc, scene.scale_y);
rapidjson::Pointer(root + prefix + "keep_aspect_ratio").Set(doc, scene.keep_aspect_ratio);
}
// windowed mode settings
rapidjson::Pointer(root + "w_always_on_top").Set(doc, this->window_always_on_top);
@@ -204,10 +219,10 @@ namespace cfg {
doc.Accept(writer);
// save to file
if (fileutils::text_write(this->config_path, buffer.GetString())) {
if (fileutils::write_config_file("ScreenResize", this->config_path, buffer.GetString())) {
// this->config_dirty = false;
} else {
log_warning("ScreenResize", "unable to save config file to {}", this->config_path);
log_warning("ScreenResize", "unable to save config file");
}
}
}
+15 -7
View File
@@ -2,6 +2,8 @@
#include <memory>
#include <string>
#include <optional>
#include <filesystem>
#include "external/rapidjson/document.h"
namespace cfg {
@@ -12,9 +14,19 @@ namespace cfg {
ResizableFrame = 2
};
struct fullscreen_setting {
int offset_x = 0;
int offset_y = 0;
float scale_x = 1.0;
float scale_y = 1.0;
bool keep_aspect_ratio = true;
};
extern std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
class ScreenResize {
private:
std::string config_path;
std::filesystem::path config_path;
// bool config_dirty = false;
bool load_bool_value(rapidjson::Document& doc, std::string path, bool& value);
@@ -27,14 +39,10 @@ namespace cfg {
~ScreenResize();
// full screen (directx) image settings
int offset_x = 0;
int offset_y = 0;
float scale_x = 1.0;
float scale_y = 1.0;
bool enable_screen_resize = false;
int8_t screen_resize_current_scene = 0;
bool enable_linear_filter = true;
bool keep_aspect_ratio = true;
bool centered = true;
fullscreen_setting scene_settings[4];
// windowed mode sizing
// Windows terminology: