Update to spice2x-25-04-25 (pre-apply)
> broken commit
This commit is contained in:
+121
-7
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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
@@ -5,8 +5,7 @@
|
||||
namespace cfg {
|
||||
|
||||
enum class ConfigType {
|
||||
Config,
|
||||
KFControl,
|
||||
Config
|
||||
};
|
||||
|
||||
// globals
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 ¶m =
|
||||
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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user