mirror of
https://github.com/simple64/simple64.git
synced 2026-07-11 09:33:59 +02:00
984 lines
37 KiB
C++
984 lines
37 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* simple64-input-qt - main.cpp *
|
|
* Mupen64Plus homepage: https://mupen64plus.org/ *
|
|
* Copyright (C) 2008-2011 Richard Goedeken *
|
|
* Copyright (C) 2008 Tillin9 *
|
|
* Copyright (C) 2002 Blight *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
#define M64P_PLUGIN_PROTOTYPES 1
|
|
#include "m64p_common.h"
|
|
#include "m64p_types.h"
|
|
#include "m64p_config.h"
|
|
#include "m64p_plugin.h"
|
|
#include "main.h"
|
|
#include "configdialog.h"
|
|
#include "vosk/vosk_api.h"
|
|
#include "osal/osal_dynamiclib.h"
|
|
#include "vruwords.h"
|
|
#include "funcs.h"
|
|
|
|
#include <QDir>
|
|
#include <QCoreApplication>
|
|
#include <QLibrary>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QStringDecoder>
|
|
|
|
#define SETTINGS_VER 2
|
|
#define AXIS_PEAK 32768
|
|
#define MAX_AXIS_VALUE 85
|
|
#define DEADZONE_DEFAULT 5.0
|
|
|
|
#define QT_INPUT_PLUGIN_VERSION 0x020500
|
|
#define INPUT_PLUGIN_API_VERSION 0x020101
|
|
static int l_PluginInit = 0;
|
|
static int l_TalkingState = 0;
|
|
static unsigned char myKeyState[SDL_NUM_SCANCODES];
|
|
static QSettings* settings;
|
|
static QSettings* controllerSettings;
|
|
static QSettings* gameSettings;
|
|
static QSettings* gameControllerSettings;
|
|
static SController controller[4]; // 4 controllers
|
|
static m64p_dynlib_handle coreHandle;
|
|
static VoskModel *model;
|
|
static VoskRecognizer *recognizer;
|
|
static QStringList words;
|
|
static QList<int> word_indexes;
|
|
static QJsonObject vruwordsobject;
|
|
static int word_list_length;
|
|
static int word_list_count;
|
|
static QLibrary *voskLib;
|
|
static SDL_AudioDeviceID audio_dev;
|
|
static SDL_AudioSpec *hardware_spec;
|
|
static SDL_TimerID timer_id;
|
|
|
|
typedef VoskModel* (*ptr_vosk_model_new)(const char *model_path);
|
|
typedef void (*ptr_vosk_model_free)(VoskModel *model);
|
|
typedef VoskRecognizer* (*ptr_vosk_recognizer_new_grm)(VoskModel *model, float sample_rate, const char *grammar);
|
|
typedef void (*ptr_vosk_recognizer_free)(VoskRecognizer *recognizer);
|
|
typedef int (*ptr_vosk_recognizer_accept_waveform)(VoskRecognizer *recognizer, const char *data, int length);
|
|
typedef const char *(*ptr_vosk_recognizer_final_result)(VoskRecognizer *recognizer);
|
|
typedef void (*ptr_vosk_set_log_level)(int log_level);
|
|
typedef void (*ptr_vosk_recognizer_set_max_alternatives)(VoskRecognizer *recognizer, int max_alternatives);
|
|
|
|
ptr_vosk_recognizer_accept_waveform VoskAcceptWaveform;
|
|
ptr_vosk_recognizer_final_result VoskFinalResult;
|
|
ptr_vosk_model_new VoskNewModel;
|
|
ptr_vosk_recognizer_new_grm VoskNewRecognizer;
|
|
ptr_vosk_model_free VoskFreeModel;
|
|
ptr_vosk_recognizer_free VoskFreeRecognizer;
|
|
ptr_vosk_set_log_level VoskSetLogLevel;
|
|
ptr_vosk_recognizer_set_max_alternatives VoskSetAlternatives;
|
|
|
|
ptr_ConfigGetUserConfigPath ConfigGetUserConfigPath;
|
|
ptr_ConfigGetUserDataPath ConfigGetUserDataPath;
|
|
|
|
static void (*l_DebugCallback)(void *, int, const char *) = NULL;
|
|
|
|
void DebugMessage(int level, const char *message, ...)
|
|
{
|
|
char msgbuf[1024];
|
|
va_list args;
|
|
|
|
if (l_DebugCallback == NULL)
|
|
return;
|
|
|
|
va_start(args, message);
|
|
vsprintf(msgbuf, message, args);
|
|
|
|
(*l_DebugCallback)((char*)"Input", level, msgbuf);
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreHandle, void * object, void (*DebugCallback)(void *, int, const char *))
|
|
{
|
|
if (l_PluginInit)
|
|
return M64ERR_ALREADY_INIT;
|
|
|
|
coreHandle = CoreHandle;
|
|
l_DebugCallback = DebugCallback;
|
|
|
|
ConfigGetUserConfigPath = (ptr_ConfigGetUserConfigPath) osal_dynlib_getproc(CoreHandle, "ConfigGetUserConfigPath");
|
|
ConfigGetUserDataPath = (ptr_ConfigGetUserDataPath) osal_dynlib_getproc(CoreHandle, "ConfigGetUserDataPath");
|
|
|
|
QDir ini_path(ConfigGetUserConfigPath());
|
|
settings = new QSettings(ini_path.absoluteFilePath("input-profiles.ini"), QSettings::IniFormat, (QObject*)object);
|
|
controllerSettings = new QSettings(ini_path.absoluteFilePath("input-settings.ini"), QSettings::IniFormat, (QObject*)object);
|
|
|
|
if (!settings->contains("version") || settings->value("version").toInt() != SETTINGS_VER)
|
|
{
|
|
settings->clear();
|
|
settings->setValue("version", SETTINGS_VER);
|
|
}
|
|
if (!controllerSettings->contains("version") || controllerSettings->value("version").toInt() != SETTINGS_VER)
|
|
{
|
|
controllerSettings->clear();
|
|
controllerSettings->setValue("version", SETTINGS_VER);
|
|
}
|
|
|
|
QString section;
|
|
for (int i = 1; i < 5; ++i) {
|
|
section = "Controller" + QString::number(i);
|
|
if (!controllerSettings->childGroups().contains(section)) {
|
|
controllerSettings->setValue(section + "/Profile", "Auto");
|
|
controllerSettings->setValue(section + "/Gamepad", "Auto");
|
|
controllerSettings->setValue(section + "/Pak", "Memory");
|
|
}
|
|
}
|
|
|
|
QStringList values;
|
|
section = "Auto-Keyboard";
|
|
values.insert(0, "0"/*blank value*/);
|
|
values.insert(1, "0"/*Keyboard*/);
|
|
if (!settings->childGroups().contains(section)) {
|
|
values.replace(0, QString::number(SDL_SCANCODE_LSHIFT));
|
|
settings->setValue(section + "/A", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_LCTRL));
|
|
settings->setValue(section + "/B", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_Z));
|
|
settings->setValue(section + "/Z", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_X));
|
|
settings->setValue(section + "/L", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_C));
|
|
settings->setValue(section + "/R", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_RETURN));
|
|
settings->setValue(section + "/Start", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_A));
|
|
settings->setValue(section + "/DPadL", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_D));
|
|
settings->setValue(section + "/DPadR", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_W));
|
|
settings->setValue(section + "/DPadU", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_S));
|
|
settings->setValue(section + "/DPadD", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_J));
|
|
settings->setValue(section + "/CLeft", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_L));
|
|
settings->setValue(section + "/CRight", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_I));
|
|
settings->setValue(section + "/CUp", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_K));
|
|
settings->setValue(section + "/CDown", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_LEFT));
|
|
settings->setValue(section + "/AxisLeft", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_RIGHT));
|
|
settings->setValue(section + "/AxisRight", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_UP));
|
|
settings->setValue(section + "/AxisUp", values.join(","));
|
|
values.replace(0, QString::number(SDL_SCANCODE_DOWN));
|
|
settings->setValue(section + "/AxisDown", values.join(","));
|
|
|
|
settings->setValue(section + "/Deadzone", DEADZONE_DEFAULT);
|
|
settings->setValue(section + "/Sensitivity", 100.0);
|
|
}
|
|
|
|
section = "Auto-Gamepad";
|
|
values.replace(1, "1"/*Button*/);
|
|
if (!settings->childGroups().contains(section)) {
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_A));
|
|
settings->setValue(section + "/A", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_X));
|
|
settings->setValue(section + "/B", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_TRIGGERLEFT));
|
|
values.replace(1, "2"/*Axis*/);
|
|
values.insert(2, "1" /* positive axis value*/);
|
|
settings->setValue(section + "/Z", values.join(","));
|
|
values.removeAt(2);
|
|
values.replace(1, "1"/*Button*/);
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_LEFTSHOULDER));
|
|
settings->setValue(section + "/L", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER));
|
|
settings->setValue(section + "/R", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_START));
|
|
settings->setValue(section + "/Start", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_DPAD_LEFT));
|
|
settings->setValue(section + "/DPadL", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_DPAD_RIGHT));
|
|
settings->setValue(section + "/DPadR", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_DPAD_UP));
|
|
settings->setValue(section + "/DPadU", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_BUTTON_DPAD_DOWN));
|
|
settings->setValue(section + "/DPadD", values.join(","));
|
|
values.replace(1, "2"/*Axis*/);
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_RIGHTX));
|
|
values.insert(2, "-1" /* negative axis value*/);
|
|
settings->setValue(section + "/CLeft", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_RIGHTX));
|
|
values.replace(2, "1" /* positive axis value*/);
|
|
settings->setValue(section + "/CRight", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_RIGHTY));
|
|
values.replace(2, "-1" /* negative axis value*/);
|
|
settings->setValue(section + "/CUp", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_RIGHTY));
|
|
values.replace(2, "1" /* positive axis value*/);
|
|
settings->setValue(section + "/CDown", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_LEFTX));
|
|
values.replace(2, "-1" /* negative axis value*/);
|
|
settings->setValue(section + "/AxisLeft", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_LEFTX));
|
|
values.replace(2, "1" /* positive axis value*/);
|
|
settings->setValue(section + "/AxisRight", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_LEFTY));
|
|
values.replace(2, "-1" /* negative axis value*/);
|
|
settings->setValue(section + "/AxisUp", values.join(","));
|
|
values.replace(0, QString::number(SDL_CONTROLLER_AXIS_LEFTY));
|
|
values.replace(2, "1" /* positive axis value*/);
|
|
settings->setValue(section + "/AxisDown", values.join(","));
|
|
|
|
settings->setValue(section + "/Deadzone", DEADZONE_DEFAULT);
|
|
settings->setValue(section + "/Sensitivity", 100.0);
|
|
}
|
|
|
|
if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER))
|
|
SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
|
|
|
|
if (!SDL_WasInit(SDL_INIT_AUDIO))
|
|
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
|
|
|
if (!SDL_WasInit(SDL_INIT_TIMER))
|
|
SDL_InitSubSystem(SDL_INIT_TIMER);
|
|
|
|
l_PluginInit = 1;
|
|
|
|
return M64ERR_SUCCESS;
|
|
}
|
|
|
|
void closeControllers()
|
|
{
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (controller[i].gamepad != NULL)
|
|
SDL_GameControllerClose(controller[i].gamepad);
|
|
else if (controller[i].joystick != NULL)
|
|
SDL_JoystickClose(controller[i].joystick);
|
|
controller[i].gamepad = NULL;
|
|
controller[i].joystick = NULL;
|
|
}
|
|
}
|
|
|
|
EXPORT m64p_error CALL PluginShutdown(void)
|
|
{
|
|
if (!l_PluginInit)
|
|
return M64ERR_NOT_INIT;
|
|
|
|
closeControllers();
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
SDL_QuitSubSystem(SDL_INIT_TIMER);
|
|
|
|
settings->sync();
|
|
controllerSettings->sync();
|
|
|
|
l_PluginInit = 0;
|
|
|
|
return M64ERR_SUCCESS;
|
|
}
|
|
|
|
EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
|
|
{
|
|
/* set version info */
|
|
if (PluginType != NULL)
|
|
*PluginType = M64PLUGIN_INPUT;
|
|
|
|
if (PluginVersion != NULL)
|
|
*PluginVersion = QT_INPUT_PLUGIN_VERSION;
|
|
|
|
if (APIVersion != NULL)
|
|
*APIVersion = INPUT_PLUGIN_API_VERSION;
|
|
|
|
if (PluginNamePtr != NULL)
|
|
*PluginNamePtr = "simple64 Qt Input Plugin";
|
|
|
|
if (Capabilities != NULL)
|
|
{
|
|
*Capabilities = 0;
|
|
}
|
|
|
|
return M64ERR_SUCCESS;
|
|
}
|
|
|
|
static unsigned char DataCRC( unsigned char *Data, int iLenght )
|
|
{
|
|
unsigned char Remainder = Data[0];
|
|
|
|
int iByte = 1;
|
|
unsigned char bBit = 0;
|
|
|
|
while( iByte <= iLenght )
|
|
{
|
|
int HighBit = ((Remainder & 0x80) != 0);
|
|
Remainder = Remainder << 1;
|
|
|
|
Remainder += ( iByte < iLenght && Data[iByte] & (0x80 >> bBit )) ? 1 : 0;
|
|
|
|
Remainder ^= (HighBit) ? 0x85 : 0;
|
|
|
|
bBit++;
|
|
iByte += bBit/8;
|
|
bBit %= 8;
|
|
}
|
|
|
|
return Remainder;
|
|
}
|
|
|
|
EXPORT void CALL SendVRUWord(uint16_t length, uint16_t *word, uint8_t lang)
|
|
{
|
|
QByteArray word_array;
|
|
uint8_t *bytes = (uint8_t*)word;
|
|
for (int i = 0; i < (length*2); i+=2)
|
|
{
|
|
word_array.append(bytes[i+1]);
|
|
word_array.append(bytes[i]);
|
|
}
|
|
|
|
QString hex = word_array.toHex();
|
|
QJsonValue value = vruwordsobject.value(hex.toUpper());
|
|
if (value == QJsonValue::Undefined)
|
|
{
|
|
auto toUtf8 = QStringDecoder(QStringDecoder::Utf8);
|
|
QString encoded_string = toUtf8(word_array);
|
|
if (toUtf8.hasError())
|
|
{
|
|
if (lang == 0 /* English */)
|
|
DebugMessage(M64MSG_ERROR, "Unknown word: %s", hex.toUpper().toUtf8().constData());
|
|
else /* Japanese or demo */
|
|
DebugMessage(M64MSG_ERROR, "Unknown Japanese word: %s", hex.toUpper().toUtf8().constData());
|
|
}
|
|
else
|
|
{
|
|
words.append(encoded_string.toLower());
|
|
word_indexes.append(word_list_count);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QJsonArray value_list = value.toArray();
|
|
for (int i = 0; i < value_list.size(); ++i)
|
|
{
|
|
words.append(value_list.at(i).toString().toLower());
|
|
word_indexes.append(word_list_count);
|
|
DebugMessage(M64MSG_VERBOSE, "word loaded: %s, index: %d", words.last().toUtf8().constData(), word_indexes.last());
|
|
}
|
|
}
|
|
|
|
++word_list_count;
|
|
if (word_list_count == word_list_length)
|
|
{
|
|
words.append("[unk]");
|
|
word_indexes.append(word_list_count);
|
|
QJsonArray array = QJsonArray::fromStringList(words);
|
|
QJsonDocument doc;
|
|
doc.setArray(array);
|
|
if (recognizer)
|
|
VoskFreeRecognizer(recognizer);
|
|
recognizer = VoskNewRecognizer(model, (float)hardware_spec->freq, doc.toJson().constData());
|
|
VoskSetAlternatives(recognizer, 3);
|
|
}
|
|
}
|
|
|
|
Uint32 StopTalking(Uint32, void *)
|
|
{
|
|
l_TalkingState = 0;
|
|
timer_id = 0;
|
|
return(0);
|
|
}
|
|
|
|
EXPORT void CALL SetMicState(int state)
|
|
{
|
|
if (timer_id)
|
|
{
|
|
SDL_RemoveTimer(timer_id);
|
|
timer_id = 0;
|
|
}
|
|
l_TalkingState = state;
|
|
|
|
if (state)
|
|
{
|
|
timer_id = SDL_AddTimer(2000, StopTalking, NULL);
|
|
SDL_ClearQueuedAudio(audio_dev);
|
|
SDL_PauseAudioDevice(audio_dev, 0);
|
|
}
|
|
else
|
|
SDL_PauseAudioDevice(audio_dev, 1);
|
|
}
|
|
|
|
EXPORT void CALL ClearVRUWords(uint8_t length)
|
|
{
|
|
word_list_count = 0;
|
|
word_list_length = length;
|
|
words.clear();
|
|
word_indexes.clear();
|
|
if (recognizer)
|
|
VoskFreeRecognizer(recognizer);
|
|
DebugMessage(M64MSG_VERBOSE, "word list cleared");
|
|
recognizer = nullptr;
|
|
}
|
|
|
|
EXPORT void CALL SetVRUWordMask(uint8_t, uint8_t *)
|
|
{
|
|
}
|
|
|
|
static bool compare(QString &s1, QString &s2)
|
|
{
|
|
return s1.size() > s2.size();
|
|
}
|
|
|
|
EXPORT void CALL ReadVRUResults(uint16_t *error_flags, uint16_t *num_results, uint16_t *mic_level, uint16_t *voice_level, uint16_t *voice_length, uint16_t *matches)
|
|
{
|
|
SDL_PauseAudioDevice(audio_dev, 1);
|
|
uint16_t match[5] = {0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF};
|
|
*error_flags = 0;
|
|
QByteArray data(SDL_GetQueuedAudioSize(audio_dev), 0);
|
|
uint32_t audio_length = SDL_DequeueAudio(audio_dev, data.data(), SDL_GetQueuedAudioSize(audio_dev));
|
|
VoskAcceptWaveform(recognizer, data.constData(), audio_length);
|
|
QJsonDocument doc = QJsonDocument::fromJson(VoskFinalResult(recognizer));
|
|
QJsonObject object = doc.object();
|
|
QStringList found;
|
|
QStringList alternatives;
|
|
QString textResult;
|
|
QJsonArray alternatives_array = object.value("alternatives").toArray();
|
|
for (int i = 0; i < alternatives_array.size(); ++i)
|
|
{
|
|
textResult = alternatives_array.at(i).toObject().value("text").toString();
|
|
if (!textResult.isEmpty())
|
|
alternatives.append(textResult);
|
|
}
|
|
for (int i = 0; i < words.size() - 1 /* -1 to skip the [unk] entry */; ++i)
|
|
{
|
|
if (!found.contains(words.at(i)) && alternatives.filter(words.at(i)).size() > 0)
|
|
{
|
|
/* found a match */
|
|
found.append(words.at(i));
|
|
if (found.size() == 5)
|
|
break;
|
|
}
|
|
}
|
|
/* Use the longest match */
|
|
std::sort(found.begin(), found.end(), compare);
|
|
for (int i = 0; i < found.size(); ++i)
|
|
{
|
|
match[i] = word_indexes.at(words.indexOf(found.at(i)));
|
|
DebugMessage(M64MSG_INFO, "match %d: %s (%u)", i, found.at(i).toUtf8().constData(), match[i]);
|
|
}
|
|
if (found.size() == 0 && alternatives.size() > 0)
|
|
{
|
|
/* heard something but it didn't match anything */
|
|
*error_flags = 0x4000;
|
|
match[0] = 0; // we match index 0, but mark the error flag saying we are really not sure
|
|
found.append("0");
|
|
DebugMessage(M64MSG_INFO, "heard a noise, but no word match");
|
|
}
|
|
|
|
*num_results = found.size();
|
|
/* The N64 programming manual states what range these values should be within */
|
|
*mic_level = 0xBB8;
|
|
*voice_level = 0xBB8;
|
|
*voice_length = 0x8004;
|
|
matches[0] = match[0];
|
|
matches[1] = *error_flags ? 0x700 : 0;
|
|
matches[2] = match[1];
|
|
matches[3] = (match[1] == 0x7FFF) ? 0 : 0x100;
|
|
matches[4] = match[2];
|
|
matches[5] = (match[2] == 0x7FFF) ? 0 : 0x200;
|
|
matches[6] = match[3];
|
|
matches[7] = (match[3] == 0x7FFF) ? 0 : 0x300;
|
|
matches[8] = match[4];
|
|
matches[9] = (match[4] == 0x7FFF) ? 0 : 0x400;
|
|
}
|
|
|
|
EXPORT void CALL ControllerCommand(int Control, unsigned char *Command)
|
|
{
|
|
unsigned char *Data = &Command[5];
|
|
|
|
if (Control == -1)
|
|
return;
|
|
|
|
switch (Command[2])
|
|
{
|
|
case RD_GETSTATUS:
|
|
case RD_READKEYS:
|
|
break;
|
|
case RD_READPAK:
|
|
if (controller[Control].control->Plugin == PLUGIN_RAW)
|
|
{
|
|
unsigned int dwAddress = (Command[3] << 8) + (Command[4] & 0xE0);
|
|
|
|
if(( dwAddress >= 0x8000 ) && ( dwAddress < 0x9000 ) )
|
|
memset( Data, 0x80, 32 );
|
|
else
|
|
memset( Data, 0x00, 32 );
|
|
|
|
Data[32] = DataCRC( Data, 32 );
|
|
}
|
|
break;
|
|
case RD_WRITEPAK:
|
|
if (controller[Control].control->Plugin == PLUGIN_RAW)
|
|
{
|
|
unsigned int dwAddress = (Command[3] << 8) + (Command[4] & 0xE0);
|
|
if(dwAddress == PAK_IO_RUMBLE) {
|
|
if (*Data) {
|
|
if (controller[Control].gamepad)
|
|
SDL_GameControllerRumble(controller[Control].gamepad, 0xFFFF, 0xFFFF, SDL_HAPTIC_INFINITY);
|
|
else
|
|
SDL_JoystickRumble(controller[Control].joystick, 0xFFFF, 0xFFFF, SDL_HAPTIC_INFINITY);
|
|
} else {
|
|
if (controller[Control].gamepad)
|
|
SDL_GameControllerRumble(controller[Control].gamepad, 0, 0, 0);
|
|
else
|
|
SDL_JoystickRumble(controller[Control].joystick, 0, 0, 0);
|
|
}
|
|
}
|
|
Data[32] = DataCRC( Data, 32 );
|
|
}
|
|
break;
|
|
case RD_RESETCONTROLLER:
|
|
case RD_READEEPROM:
|
|
case RD_WRITEEPROM:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void boundAxis(double* x, double* y) {
|
|
double radius = 95.0; // this is roughly the maxium diagonal distance of the controller
|
|
|
|
// Calculate the distance from the origin (0, 0)
|
|
double distance = sqrt((*x) * (*x) + (*y) * (*y));
|
|
|
|
// If the distance is greater than the radius, scale the coordinates
|
|
if (distance > radius) {
|
|
double scaleFactor = radius / distance;
|
|
*x *= scaleFactor;
|
|
*y *= scaleFactor;
|
|
}
|
|
}
|
|
|
|
int modifyAxisValue(int axis_value, int Control, int direction)
|
|
{
|
|
axis_value = ((abs(axis_value) - controller[Control].deadzone) * MAX_AXIS_VALUE) / controller[Control].range;
|
|
axis_value *= direction;
|
|
axis_value = (float)axis_value * controller[Control].sensitivity;
|
|
axis_value = std::max(-MAX_AXIS_VALUE, std::min(axis_value, MAX_AXIS_VALUE));
|
|
|
|
return axis_value;
|
|
}
|
|
|
|
void setAxis(int Control, int axis, BUTTONS *Keys, QString axis_dir, int direction)
|
|
{
|
|
int axis_value;
|
|
QStringList value = gameSettings->value(controller[Control].profile + "/" + axis_dir).toString().split(",");
|
|
|
|
switch (value.at(1).toInt()) {
|
|
case 0 /*Keyboard*/:
|
|
if (myKeyState[value.at(0).toInt()]) {
|
|
if (axis == 0)
|
|
Keys->X_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
else
|
|
Keys->Y_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
}
|
|
break;
|
|
case 1 /*Button*/:
|
|
if (SDL_GameControllerGetButton(controller[Control].gamepad, (SDL_GameControllerButton)value.at(0).toInt())) {
|
|
if (axis == 0)
|
|
Keys->X_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
else
|
|
Keys->Y_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
}
|
|
break;
|
|
case 2 /*Axis*/:
|
|
axis_value = SDL_GameControllerGetAxis(controller[Control].gamepad, (SDL_GameControllerAxis)value.at(0).toInt());
|
|
if (abs(axis_value) > controller[Control].deadzone && axis_value * value.at(2).toInt() > 0) {
|
|
axis_value = modifyAxisValue(axis_value, Control, direction);
|
|
if (axis == 0)
|
|
Keys->X_AXIS = (int8_t)axis_value;
|
|
else
|
|
Keys->Y_AXIS = (int8_t)axis_value;
|
|
}
|
|
break;
|
|
case 3 /*Joystick Hat*/:
|
|
if (SDL_JoystickGetHat(controller[Control].joystick, value.at(0).toInt()) & value.at(2).toInt()) {
|
|
if (axis == 0)
|
|
Keys->X_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
else
|
|
Keys->Y_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
}
|
|
break;
|
|
case 4 /*Joystick Button*/:
|
|
if (SDL_JoystickGetButton(controller[Control].joystick, value.at(0).toInt())) {
|
|
if (axis == 0)
|
|
Keys->X_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
else
|
|
Keys->Y_AXIS = (int8_t)(MAX_AXIS_VALUE * direction);
|
|
}
|
|
break;
|
|
case 5 /*Joystick Axis*/:
|
|
axis_value = SDL_JoystickGetAxis(controller[Control].joystick, value.at(0).toInt());
|
|
if (abs(axis_value) > controller[Control].deadzone && axis_value * value.at(2).toInt() > 0) {
|
|
axis_value = modifyAxisValue(axis_value, Control, direction);
|
|
if (axis == 0)
|
|
Keys->X_AXIS = (int8_t)axis_value;
|
|
else
|
|
Keys->Y_AXIS = (int8_t)axis_value;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void setKey(int Control, uint32_t key, BUTTONS *Keys, QString button)
|
|
{
|
|
int axis_value;
|
|
QStringList value = gameSettings->value(controller[Control].profile + "/" + button).toString().split(",");
|
|
switch (value.at(1).toInt()) {
|
|
case 0 /*Keyboard*/:
|
|
if (myKeyState[value.at(0).toInt()])
|
|
Keys->Value |= key;
|
|
break;
|
|
case 1 /*Button*/:
|
|
if (SDL_GameControllerGetButton(controller[Control].gamepad, (SDL_GameControllerButton)value.at(0).toInt()))
|
|
Keys->Value |= key;
|
|
break;
|
|
case 2 /*Axis*/:
|
|
axis_value = SDL_GameControllerGetAxis(controller[Control].gamepad, (SDL_GameControllerAxis)value.at(0).toInt());
|
|
if (abs(axis_value) >= (AXIS_PEAK / 2) && axis_value * value.at(2).toInt() > 0)
|
|
Keys->Value |= key;
|
|
break;
|
|
case 3 /*Joystick Hat*/:
|
|
if (SDL_JoystickGetHat(controller[Control].joystick, value.at(0).toInt()) & value.at(2).toInt())
|
|
Keys->Value |= key;
|
|
break;
|
|
case 4 /*Joystick Button*/:
|
|
if (SDL_JoystickGetButton(controller[Control].joystick, value.at(0).toInt()))
|
|
Keys->Value |= key;
|
|
break;
|
|
case 5 /*Joystick Axis*/:
|
|
axis_value = SDL_JoystickGetAxis(controller[Control].joystick, value.at(0).toInt());
|
|
if (key == 0x0020/*Z*/ || key == 0x1000/*R_TRIG*/ || key == 0x2000/*L_TRIG*/)
|
|
{
|
|
if (axis_value > 0)
|
|
Keys->Value |= key;
|
|
break;
|
|
}
|
|
|
|
if (abs(axis_value) >= (AXIS_PEAK / 2) && axis_value * value.at(2).toInt() > 0)
|
|
Keys->Value |= key;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void setPak(int Control)
|
|
{
|
|
QString pak = gameControllerSettings->value("Controller" + QString::number(Control + 1) + "/Pak").toString();
|
|
if (controller[Control].control->Type == CONT_TYPE_VRU)
|
|
controller[Control].control->Plugin = PLUGIN_NONE;
|
|
else if (pak == "Transfer")
|
|
controller[Control].control->Plugin = PLUGIN_TRANSFER_PAK;
|
|
else if (pak == "Rumble")
|
|
controller[Control].control->Plugin = PLUGIN_RAW;
|
|
else if (pak == "None")
|
|
controller[Control].control->Plugin = PLUGIN_NONE;
|
|
else
|
|
controller[Control].control->Plugin = PLUGIN_MEMPAK;
|
|
}
|
|
|
|
EXPORT void CALL GetKeys( int Control, BUTTONS *Keys )
|
|
{
|
|
if (controller[Control].control->Present == 0)
|
|
return;
|
|
|
|
if (controller[Control].joystick && SDL_JoystickGetAttached(controller[Control].joystick) == SDL_FALSE)
|
|
{
|
|
for (int j = 0; j < SDL_NumJoysticks(); ++j)
|
|
{
|
|
if (controller[Control].device_path == SDL_JoystickPathForIndex(j))
|
|
{
|
|
if (SDL_IsGameController(j))
|
|
{
|
|
SDL_GameControllerClose(controller[Control].gamepad);
|
|
controller[Control].gamepad = SDL_GameControllerOpen(j);
|
|
controller[Control].joystick = SDL_GameControllerGetJoystick(controller[Control].gamepad);
|
|
}
|
|
else
|
|
{
|
|
SDL_JoystickClose(controller[Control].joystick);
|
|
controller[Control].joystick = SDL_JoystickOpen(j);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
setPak(Control);
|
|
if (controller[Control].control->Type == CONT_TYPE_VRU)
|
|
{
|
|
if (l_TalkingState == 0)
|
|
{
|
|
Keys->Value = 0;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// In the future it would be better to only set this when the player is talking
|
|
Keys->Value = 0x0020;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Keys->Value = 0;
|
|
setKey(Control, 0x0001/*R_DPAD*/, Keys, "DPadR");
|
|
setKey(Control, 0x0002/*L_DPAD*/, Keys, "DPadL");
|
|
setKey(Control, 0x0004/*D_DPAD*/, Keys, "DPadD");
|
|
setKey(Control, 0x0008/*U_DPAD*/, Keys, "DPadU");
|
|
setKey(Control, 0x0010/*START_BUTTON*/, Keys, "Start");
|
|
setKey(Control, 0x0020/*Z_TRIG*/, Keys, "Z");
|
|
setKey(Control, 0x0040/*B_BUTTON*/, Keys, "B");
|
|
setKey(Control, 0x0080/*A_BUTTON*/, Keys, "A");
|
|
setKey(Control, 0x0100/*R_CBUTTON*/, Keys, "CRight");
|
|
setKey(Control, 0x0200/*L_CBUTTON*/, Keys, "CLeft");
|
|
setKey(Control, 0x0400/*D_CBUTTON*/, Keys, "CDown");
|
|
setKey(Control, 0x0800/*U_CBUTTON*/, Keys, "CUp");
|
|
setKey(Control, 0x1000/*R_TRIG*/, Keys, "R");
|
|
setKey(Control, 0x2000/*L_TRIG*/, Keys, "L");
|
|
|
|
setAxis(Control, 0/*X_AXIS*/, Keys, "AxisLeft", -1);
|
|
setAxis(Control, 0/*X_AXIS*/, Keys, "AxisRight", 1);
|
|
setAxis(Control, 1/*Y_AXIS*/, Keys, "AxisUp", 1);
|
|
setAxis(Control, 1/*Y_AXIS*/, Keys, "AxisDown", -1);
|
|
|
|
double boundX = Keys->X_AXIS;
|
|
double boundY = Keys->Y_AXIS;
|
|
boundAxis(&boundX, &boundY);
|
|
Keys->X_AXIS = round(boundX);
|
|
Keys->Y_AXIS = round(boundY);
|
|
}
|
|
|
|
static int setupVosk()
|
|
{
|
|
if (voskLib)
|
|
return 0;
|
|
voskLib = new QLibrary((QDir(QCoreApplication::applicationDirPath()).filePath("vosk")));
|
|
VoskAcceptWaveform = (ptr_vosk_recognizer_accept_waveform) voskLib->resolve("vosk_recognizer_accept_waveform");
|
|
VoskFinalResult = (ptr_vosk_recognizer_final_result) voskLib->resolve("vosk_recognizer_final_result");
|
|
VoskNewModel = (ptr_vosk_model_new) voskLib->resolve("vosk_model_new");
|
|
VoskNewRecognizer = (ptr_vosk_recognizer_new_grm) voskLib->resolve("vosk_recognizer_new_grm");
|
|
VoskFreeModel = (ptr_vosk_model_free) voskLib->resolve("vosk_model_free");
|
|
VoskFreeRecognizer = (ptr_vosk_recognizer_free) voskLib->resolve("vosk_recognizer_free");
|
|
VoskSetLogLevel = (ptr_vosk_set_log_level) voskLib->resolve("vosk_set_log_level");
|
|
VoskSetAlternatives = (ptr_vosk_recognizer_set_max_alternatives) voskLib->resolve("vosk_recognizer_set_max_alternatives");
|
|
|
|
VoskSetLogLevel(-1);
|
|
|
|
QJsonDocument vruwordjson = QJsonDocument::fromJson(vruwords.toUtf8());
|
|
vruwordsobject = vruwordjson.object();
|
|
|
|
l_TalkingState = 0;
|
|
SDL_AudioSpec *desired, *obtained;
|
|
if(hardware_spec != NULL) free(hardware_spec);
|
|
desired = (SDL_AudioSpec*)malloc(sizeof(SDL_AudioSpec));
|
|
obtained = (SDL_AudioSpec*)malloc(sizeof(SDL_AudioSpec));
|
|
desired->freq = 44100;
|
|
desired->format = AUDIO_S16SYS;
|
|
desired->channels = 1;
|
|
desired->samples = 1024;
|
|
desired->callback = NULL;
|
|
desired->userdata = NULL;
|
|
audio_dev = SDL_OpenAudioDevice(NULL, 1, desired, obtained, SDL_AUDIO_ALLOW_SAMPLES_CHANGE | SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
|
|
free(desired);
|
|
hardware_spec = obtained;
|
|
recognizer = nullptr;
|
|
model = nullptr;
|
|
timer_id = 0;
|
|
if (QFile::exists(QDir(ConfigGetUserDataPath()).filePath("vosk-model-small-en-us-0.15/conf/mfcc.conf")))
|
|
{
|
|
model = VoskNewModel(QDir(ConfigGetUserDataPath()).filePath("vosk-model-small-en-us-0.15").toUtf8().constData());
|
|
return 1;
|
|
}
|
|
delete voskLib;
|
|
voskLib = NULL;
|
|
return 0;
|
|
}
|
|
|
|
EXPORT void CALL InitiateControllers(CONTROL_INFO ControlInfo)
|
|
{
|
|
model = nullptr;
|
|
recognizer = nullptr;
|
|
gameSettings = new QSettings(settings->fileName(), QSettings::IniFormat);
|
|
gameControllerSettings = new QSettings(controllerSettings->fileName(), QSettings::IniFormat);
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < SDL_NUM_SCANCODES; i++)
|
|
{
|
|
myKeyState[i] = 0;
|
|
}
|
|
// set our CONTROL struct pointers to the array that was passed in to this function from the core
|
|
// this small struct tells the core whether each controller is plugged in, and what type of pak is connected
|
|
QString gamepad;
|
|
int controller_index;
|
|
QString gamepad_name;
|
|
QList<int> used_controllers;
|
|
for (i = 0; i < 4; i++) {
|
|
controller[i].control = ControlInfo.Controls + i;
|
|
controller[i].control->RawData = 0;
|
|
controller[i].control->Present = 0;
|
|
controller[i].control->Type = CONT_TYPE_STANDARD;
|
|
controller[i].gamepad = NULL;
|
|
controller[i].joystick = NULL;
|
|
gamepad = gameControllerSettings->value("Controller" + QString::number(i + 1) + "/Gamepad").toString();
|
|
if (gamepad == "Keyboard")
|
|
controller[i].control->Present = 1;
|
|
else if (gamepad == "None")
|
|
controller[i].control->Present = 0;
|
|
else if (gamepad == "Auto") {
|
|
for (j = 0; j < SDL_NumJoysticks(); ++j)
|
|
{
|
|
if (used_controllers.contains(j))
|
|
continue;
|
|
|
|
if (SDL_IsGameController(j)) {
|
|
controller[i].gamepad = SDL_GameControllerOpen(j);
|
|
if (controller[i].gamepad)
|
|
{
|
|
controller[i].control->Present = 1;
|
|
used_controllers.append(j);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
controller[i].joystick = SDL_JoystickOpen(j);
|
|
if (controller[i].joystick)
|
|
{
|
|
controller[i].control->Present = 1;
|
|
used_controllers.append(j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (i == 0) controller[i].control->Present = 1; //Player 1
|
|
}
|
|
else if (gamepad == "Emulate VRU") {
|
|
controller[i].control->Type = CONT_TYPE_VRU;
|
|
controller[i].control->Plugin = PLUGIN_NONE;
|
|
if (!setupVosk())
|
|
{
|
|
controller[i].control->Present = 0;
|
|
gameControllerSettings->setValue("Controller" + QString::number(i + 1) + "/Pak", "None");
|
|
DebugMessage(M64MSG_ERROR, "Could not set up Vosk library, disabling VRU");
|
|
}
|
|
else
|
|
controller[i].control->Present = 1;
|
|
}
|
|
else /*specific gamepad selected*/ {
|
|
controller_index = gamepad.split(":")[0].toInt();
|
|
gamepad_name = gamepad.split(":")[1];
|
|
if (SDL_IsGameController(controller_index) && gamepad_name == QString(SDL_JoystickNameForIndex(controller_index))) {
|
|
controller[i].gamepad = SDL_GameControllerOpen(controller_index);
|
|
if (controller[i].gamepad)
|
|
{
|
|
controller[i].control->Present = 1;
|
|
used_controllers.append(controller_index);
|
|
}
|
|
}
|
|
else if (gamepad_name == QString(SDL_JoystickNameForIndex(controller_index))) {
|
|
controller[i].joystick = SDL_JoystickOpen(controller_index);
|
|
if (controller[i].joystick)
|
|
{
|
|
controller[i].control->Present = 1;
|
|
used_controllers.append(controller_index);
|
|
}
|
|
}
|
|
if (controller[i].control->Present == 0) {
|
|
gameControllerSettings->setValue("Controller" + QString::number(i + 1) + "/Gamepad", "Auto");
|
|
--i; //Try again using Auto
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (controller[i].gamepad)
|
|
controller[i].joystick = SDL_GameControllerGetJoystick(controller[i].gamepad);
|
|
if (controller[i].joystick)
|
|
controller[i].device_path = SDL_JoystickPath(controller[i].joystick);
|
|
|
|
controller[i].profile = gameControllerSettings->value("Controller" + QString::number(i + 1) + "/Profile").toString();
|
|
if (!gameSettings->childGroups().contains(controller[i].profile))
|
|
controller[i].profile = "Auto";
|
|
|
|
if (controller[i].profile == "Auto") {
|
|
if (controller[i].gamepad)
|
|
controller[i].profile = "Auto-Gamepad";
|
|
else
|
|
controller[i].profile = "Auto-Keyboard";
|
|
}
|
|
|
|
if (!gameSettings->contains(controller[i].profile + "/Deadzone"))
|
|
gameSettings->setValue(controller[i].profile + "/Deadzone", DEADZONE_DEFAULT);
|
|
if (!gameSettings->contains(controller[i].profile + "/Sensitivity"))
|
|
gameSettings->setValue(controller[i].profile + "/Sensitivity", 100.0);
|
|
|
|
controller[i].deadzone = AXIS_PEAK * (gameSettings->value(controller[i].profile + "/Deadzone").toFloat() / 100.0);
|
|
controller[i].range = AXIS_PEAK - controller[i].deadzone;
|
|
controller[i].sensitivity = gameSettings->value(controller[i].profile + "/Sensitivity").toFloat() / 100.0;
|
|
|
|
setPak(i);
|
|
}
|
|
}
|
|
|
|
EXPORT void CALL ReadController(int, unsigned char *)
|
|
{
|
|
}
|
|
|
|
EXPORT int CALL RomOpen(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
EXPORT void CALL RomClosed(void)
|
|
{
|
|
closeControllers();
|
|
|
|
gameSettings->sync();
|
|
gameControllerSettings->sync();
|
|
if (model)
|
|
VoskFreeModel(model);
|
|
if (recognizer)
|
|
VoskFreeRecognizer(recognizer);
|
|
if (voskLib)
|
|
delete voskLib;
|
|
voskLib = NULL;
|
|
SDL_CloseAudioDevice(audio_dev);
|
|
if(hardware_spec != NULL) free(hardware_spec);
|
|
hardware_spec = NULL;
|
|
delete gameSettings;
|
|
delete gameControllerSettings;
|
|
}
|
|
|
|
EXPORT void CALL SDL_KeyDown(int, int keysym)
|
|
{
|
|
myKeyState[keysym] = 1;
|
|
}
|
|
|
|
EXPORT void CALL SDL_KeyUp(int, int keysym)
|
|
{
|
|
myKeyState[keysym] = 0;
|
|
}
|
|
|
|
EXPORT void CALL PluginConfig()
|
|
{
|
|
ConfigDialog config(coreHandle, settings, controllerSettings);
|
|
config.exec();
|
|
}
|