Improve Accuracy of Vibration + Unify Translation + Add Comments

This commit is contained in:
◱ PixelyIon 2020-09-06 20:21:17 +05:30 committed by ◱ PixelyIon
parent 1a58a2e967
commit 21e2c826a1
4 changed files with 86 additions and 72 deletions

View File

@ -11,7 +11,7 @@ namespace skyline::input {
*/
struct GuestController {
NpadControllerType type{};
i8 partnerIndex{-1}; //!< The index of a Joy-Con partner, if this has one
i8 partnerIndex{constant::NullIndex}; //!< The index of a Joy-Con partner, if this has one
NpadDevice *device{nullptr}; //!< A pointer to the NpadDevice that all events from this are redirected to
};

View File

@ -352,33 +352,75 @@ namespace skyline::input {
globalTimestamp++;
}
void NpadDevice::VibrateDevice(i8 vibrateIndex, const NpadVibrationValue &value) {
std::array<jlong, 3> timings;
std::array<jint, 3> amplitudes;
struct VibrationInfo {
jlong period;
jint amplitude;
jlong start;
jlong end;
jlong periodLow = 1000 / value.frequencyLow;
jlong periodHigh = 1000 / value.frequencyHigh;
VibrationInfo(float frequency, float amplitude) : period(constant::MsInSecond / frequency), amplitude(amplitude), start(0), end(period) {}
};
jint amplitudeLow = value.amplitudeLow * 127;
jint amplitudeHigh = value.amplitudeHigh * 127;
template<size_t Size>
void VibrateDevice(const std::shared_ptr<JvmManager> &jvm, i8 index, std::array<VibrationInfo, Size> vibrations) {
jlong totalTime{};
std::sort(vibrations.begin(), vibrations.end(), [](const VibrationInfo &a, const VibrationInfo &b) {
return a.period < b.period;
});
if (amplitudeLow + amplitudeHigh == 0 || periodLow + periodHigh == 0) {
manager.state.jvm->ClearVibrationDevice(vibrateIndex);
jint totalAmplitude{};
for (const auto &vibration : vibrations)
totalAmplitude += vibration.amplitude;
// If this vibration is essentially null then we don't play rather clear any running vibrations
if (totalAmplitude == 0 || vibrations[3].period == 0) {
jvm->ClearVibrationDevice(index);
return;
}
if (periodLow == periodHigh) {
timings = {periodLow, periodHigh, 0};
amplitudes = {std::min(amplitudeLow + amplitudeHigh, 255), 0, 0};
} else if (periodLow < periodHigh) {
timings = {periodLow, periodHigh - periodLow, periodHigh};
amplitudes = {std::min(amplitudeLow + amplitudeHigh, 255), amplitudeHigh, 0};
} else if (periodHigh < periodLow) {
timings = {periodHigh, periodLow - periodHigh, periodLow};
amplitudes = {std::min(amplitudeHigh + amplitudeLow, 255), amplitudeLow, 0};
// We output an approximation of the combined + linearized vibration data into these arrays, larger arrays would allow for more accurate reproduction of data
std::array<jlong, 50> timings;
std::array<jint, 50> amplitudes;
// We are essentially unrolling the bands into a linear sequence, due to the data not being always linearizable there will be inaccuracies at the ends unless there's a pattern that's repeatable which will happen when all band's frequencies are factors of each other
u8 i{};
for (; i < timings.size(); i++) {
jlong time{};
u8 startCycleCount{};
for (u8 n{}; n < vibrations.size(); n++) {
auto &vibration = vibrations[n];
if (totalTime <= vibration.start) {
vibration.start = vibration.end + vibration.period;
totalAmplitude += vibration.amplitude;
time = std::max(vibration.period, time);
startCycleCount++;
} else if (totalTime <= vibration.start) {
vibration.end = vibration.start + vibration.period;
totalAmplitude -= vibration.amplitude;
time = std::max(vibration.period, time);
}
}
manager.state.jvm->VibrateDevice(vibrateIndex, timings, amplitudes);
// If all bands start again at this point then we can end the pattern here as a loop to the front will be flawless
if (i && startCycleCount == vibrations.size())
break;
timings[i] = time;
totalTime += time;
amplitudes[i] = std::min(totalAmplitude, constant::AmplitudeMax);
}
jvm->VibrateDevice(index, std::span(timings.begin(), timings.begin() + i), std::span(amplitudes.begin(), amplitudes.begin() + i));
}
void VibrateDevice(const std::shared_ptr<JvmManager> &jvm, i8 index, const NpadVibrationValue &value) {
std::array<VibrationInfo, 2> vibrations{
VibrationInfo{value.frequencyLow, value.amplitudeLow * (constant::AmplitudeMax / 2)},
{value.frequencyHigh, value.amplitudeHigh * (constant::AmplitudeMax / 2)},
};
VibrateDevice(jvm, index, vibrations);
}
void NpadDevice::Vibrate(bool isRight, const NpadVibrationValue &value) {
@ -390,52 +432,21 @@ namespace skyline::input {
if (vibrationRight)
Vibrate(vibrationLeft, *vibrationRight);
else
VibrateDevice(index, value);
VibrateDevice(manager.state.jvm, index, value);
}
void NpadDevice::Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right) {
if (partnerIndex == -1) {
std::array<jlong, 5> timings;
std::array<jint, 5> amplitudes;
std::array<std::pair<jlong, jint>, 4> vibrations{std::pair<jlong, jint>{1000 / left.frequencyLow, left.amplitudeLow * 64},
{1000 / left.frequencyHigh, left.amplitudeHigh * 64},
{1000 / right.frequencyLow, right.amplitudeLow * 64},
{1000 / right.frequencyHigh, right.amplitudeHigh * 64},
if (partnerIndex == constant::NullIndex) {
std::array<VibrationInfo, 4> vibrations{
VibrationInfo{left.frequencyLow, left.amplitudeLow * (constant::AmplitudeMax / 4)},
{left.frequencyHigh, left.amplitudeHigh * (constant::AmplitudeMax / 4)},
{right.frequencyLow, right.amplitudeLow * (constant::AmplitudeMax / 4)},
{right.frequencyHigh, right.amplitudeHigh * (constant::AmplitudeMax / 4)},
};
jlong totalTime{};
std::sort(vibrations.begin(), vibrations.end(), [](const std::pair<jlong, jint> &a, const std::pair<jlong, jint> &b) {
return a.first < b.first;
});
jint totalAmplitude{};
for (const auto &vibration : vibrations)
totalAmplitude += vibration.second;
if (totalAmplitude == 0 || vibrations[3].first == 0) {
manager.state.jvm->ClearVibrationDevice(index);
return;
}
for (u8 i{0}; i < vibrations.size(); i++) {
const auto &vibration = vibrations[i];
auto time = vibration.first - totalTime;
timings[i] = time;
totalTime += time;
amplitudes[i] = std::min(totalAmplitude, 255);
totalAmplitude -= vibration.second;
}
timings[4] = totalTime;
amplitudes[4] = 0;
manager.state.jvm->VibrateDevice(index, timings, amplitudes);
VibrateDevice<4>(manager.state.jvm, index, vibrations);
} else {
VibrateDevice(index, left);
VibrateDevice(partnerIndex, right);
VibrateDevice(manager.state.jvm, index, left);
VibrateDevice(manager.state.jvm, partnerIndex, right);
}
}
}

View File

@ -6,6 +6,12 @@
#include <kernel/types/KEvent.h>
#include "shared_mem.h"
namespace skyline::constant {
constexpr jlong MsInSecond = 1000; //!< The amount of milliseconds in a single second of time
constexpr jint AmplitudeMax = std::numeric_limits<u8>::max(); //!< The maximum amplitude for Android Vibration APIs
constexpr i8 NullIndex = -1; //!< The placeholder index value when there is no device present
}
namespace skyline::input {
/**
* @brief This enumerates the orientations the Joy-Con(s) can be held in
@ -74,7 +80,7 @@ namespace skyline::input {
bool isSixAxisSingle : 1; //!< If the Six-Axis device is a single unit, either Handheld or Pro-Controller
};
constexpr NpadControllerType GetType() {
constexpr NpadControllerType GetType() const {
switch (type) {
case 3:
return NpadControllerType::ProController;
@ -129,12 +135,10 @@ namespace skyline::input {
*/
NpadControllerInfo &GetControllerInfo();
void VibrateDevice(i8 index, const NpadVibrationValue &value);
public:
NpadId id;
i8 index{-1}; //!< The index of the device assigned to this player
i8 partnerIndex{-1}; //!< The index of a partner device, if present
i8 index{constant::NullIndex}; //!< The index of the device assigned to this player
i8 partnerIndex{constant::NullIndex}; //!< The index of a partner device, if present
NpadVibrationValue vibrationLeft; //!< Vibration for the left Joy-Con (Handheld/Pair), left LRA in a Pro-Controller or individual Joy-Cons
std::optional<NpadVibrationValue> vibrationRight; //!< Vibration for the right Joy-Con (Handheld/Pair) or right LRA in a Pro-Controller
NpadControllerType type{};

View File

@ -121,18 +121,17 @@ namespace skyline::service::hid {
auto &valueBuf = request.inputBuf.at(1);
std::span values(reinterpret_cast<NpadVibrationValue *>(valueBuf.address), valueBuf.size / sizeof(NpadVibrationValue));
for (int i = 0; i < handles.size(); ++i) {
auto &handle = handles[i];
for (size_t i{}; i < handles.size(); ++i) {
const auto &handle = handles[i];
auto &device = state.input->npad.at(handle.id);
if (device.type == handle.GetType()) {
if (i + 1 != handles.size() && handles[i + 1].id == handle.id && handles[i + 1].isRight && !handle.isRight) {
state.logger->Info("Vibration #{}&{} - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz - {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", i, i + 1, u8(handle.id), u8(handle.type), values[i].amplitudeLow, values[i].frequencyLow, values[i].amplitudeHigh, values[i].frequencyHigh, values[i + 1].amplitudeLow, values[i + 1].frequencyLow, values[i + 1].amplitudeHigh, values[i + 1].frequencyHigh);
state.logger->Debug("Vibration #{}&{} - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz - {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", i, i + 1, u8(handle.id), u8(handle.type), values[i].amplitudeLow, values[i].frequencyLow, values[i].amplitudeHigh, values[i].frequencyHigh, values[i + 1].amplitudeLow, values[i + 1].frequencyLow, values[i + 1].amplitudeHigh, values[i + 1].frequencyHigh);
device.Vibrate(values[i], values[i + 1]);
i++;
} else {
auto &value = values[i];
state.logger->Info("Vibration #{} - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", i, u8(handle.id), u8(handle.type), value.amplitudeLow, value.frequencyLow, value.amplitudeHigh, value.frequencyHigh);
const auto &value = values[i];
state.logger->Debug("Vibration #{} - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", i, u8(handle.id), u8(handle.type), value.amplitudeLow, value.frequencyLow, value.amplitudeHigh, value.frequencyHigh);
device.Vibrate(handle.isRight, value);
}
}