diff --git a/Directory.Packages.props b/Directory.Packages.props index 5dac804..857504e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,5 +20,8 @@ + + + diff --git a/OpenShock.VoiceRecognizer.Common/Enums/BrowserProxyType.cs b/OpenShock.VoiceRecognizer.Common/Enums/BrowserProxyType.cs new file mode 100644 index 0000000..68b4bef --- /dev/null +++ b/OpenShock.VoiceRecognizer.Common/Enums/BrowserProxyType.cs @@ -0,0 +1,13 @@ +using OpenShock.VoiceRecognizer.Common.Utility; +using System.Text.Json.Serialization; + +namespace OpenShock.VoiceRecognizer.Common.Enums; + +[JsonConverter(typeof(TypedStringEnumConverter))] +public enum BrowserProxyType +{ + Chrome, + Edge, + Opera, + Firefox, +}; diff --git a/OpenShock.VoiceRecognizer.Configuration/AudioConfigurationState.cs b/OpenShock.VoiceRecognizer.Configuration/AudioConfigurationState.cs index ef32f6b..17b0eec 100644 --- a/OpenShock.VoiceRecognizer.Configuration/AudioConfigurationState.cs +++ b/OpenShock.VoiceRecognizer.Configuration/AudioConfigurationState.cs @@ -16,5 +16,5 @@ public void LoadFileConfiguration(ConfigurationFileFormat configurationFileForma InputDeviceID.Value = configurationFileFormat.InputDeviceID; public void LoadDefaultConfiguration() => - InputDeviceID = new(AudioDevices.GetInputAudioDevices().First().ID); + InputDeviceID.Value = AudioDevices.GetInputAudioDevices().First().ID; } diff --git a/OpenShock.VoiceRecognizer.Configuration/BrowserProxyConfigurationState.cs b/OpenShock.VoiceRecognizer.Configuration/BrowserProxyConfigurationState.cs new file mode 100644 index 0000000..0cfb11a --- /dev/null +++ b/OpenShock.VoiceRecognizer.Configuration/BrowserProxyConfigurationState.cs @@ -0,0 +1,29 @@ +using OpenShock.VoiceRecognizer.Common; +using OpenShock.VoiceRecognizer.Common.Audio; +using OpenShock.VoiceRecognizer.Common.Enums; + +namespace OpenShock.VoiceRecognizer.Configuration; + +public class BrowserProxyConfigurationState +{ + public ReactiveObject Proxy { get; set; } + public ReactiveObject ProxyPort { get; set; } + + public BrowserProxyConfigurationState() + { + Proxy = new(BrowserProxyType.Chrome); + ProxyPort = new(0); + } + + public void LoadFileConfiguration(ConfigurationFileFormat configurationFileFormat) + { + Proxy.Value = configurationFileFormat.BrowserProxyType; + ProxyPort.Value = configurationFileFormat.BrowserProxyPort; + } + + public void LoadDefaultConfiguration() + { + Proxy.Value = BrowserProxyType.Chrome; + ProxyPort.Value = 0; + } +} diff --git a/OpenShock.VoiceRecognizer.Configuration/ConfigurationFileFormat.cs b/OpenShock.VoiceRecognizer.Configuration/ConfigurationFileFormat.cs index 75c60ed..af570ae 100644 --- a/OpenShock.VoiceRecognizer.Configuration/ConfigurationFileFormat.cs +++ b/OpenShock.VoiceRecognizer.Configuration/ConfigurationFileFormat.cs @@ -9,8 +9,17 @@ public class ConfigurationFileFormat public string InputDeviceID { get; set; } = string.Empty; public string VoskModelDirectory { get; set; } = string.Empty; public int OscListenPort { get; set; } = 0; - public ShockCollarType CollarType { get; } + public ShockCollarType CollarType { get; set; } = ShockCollarType.OpenShock; public ObservableCollection Words { get; set; } = []; + /*public string OpenShockAPIKey { get; set; } = string.Empty; + public string OpenShockGroupName { get; set; } = string.Empty;*/ + public string OpenShockSendHost { get; set; } = string.Empty; + public int OpenShockSendPort { get; set; } = 0; + public string OpenShockOscListenIntensityEndpoint { get; set; } = string.Empty; + public string OpenShockOscSendShockEndpoint { get; set; } = string.Empty; + public string OpenShockOscSendVibrateEndpoint { get; set; } = string.Empty; + public BrowserProxyType BrowserProxyType { get; set; } = BrowserProxyType.Chrome; + public int BrowserProxyPort { get; set; } = 0; public ConfigurationFileFormat() { } @@ -21,6 +30,15 @@ public ConfigurationFileFormat(ConfigurationState state) OscListenPort = state.OSC.ListenPort.Value; CollarType = state.Shock.CollarType.Value; Words = state.Shock.Words.Value; + /*OpenShockAPIKey = state.OpenShock.APIKey.Value; + OpenShockGroupName = state.OpenShock.GroupName.Value;*/ + OpenShockSendHost = state.OpenShock.SendHost.Value; + OpenShockSendPort = state.OpenShock.SendPort.Value; + OpenShockOscListenIntensityEndpoint = state.OpenShock.ExternalListenSetIntensityEndpoint; + OpenShockOscSendShockEndpoint = state.OpenShock.ExternalSendStartShockEndpoint; + OpenShockOscSendVibrateEndpoint = state.OpenShock.ExternalSendStartShockEndpoint; + BrowserProxyType = state.BrowserProxy.Proxy.Value; + BrowserProxyPort = state.BrowserProxy.ProxyPort.Value; } public static bool TryLoad(string filepath, out ConfigurationFileFormat? configurationFileFormat) diff --git a/OpenShock.VoiceRecognizer.Configuration/ConfigurationState.cs b/OpenShock.VoiceRecognizer.Configuration/ConfigurationState.cs index 74ff1ef..1ba43ce 100644 --- a/OpenShock.VoiceRecognizer.Configuration/ConfigurationState.cs +++ b/OpenShock.VoiceRecognizer.Configuration/ConfigurationState.cs @@ -1,6 +1,4 @@ -using OpenShock.VoiceRecognizer.Common.Enums; - -namespace OpenShock.VoiceRecognizer.Configuration; +namespace OpenShock.VoiceRecognizer.Configuration; public class ConfigurationState { @@ -10,6 +8,8 @@ public class ConfigurationState public VoskConfigurationState Vosk { get; } public OSCConfigurationState OSC { get; } public ShockConfigurationState Shock { get; } + public OpenShockConfigurationState OpenShock { get; } + public BrowserProxyConfigurationState BrowserProxy { get; } public static ConfigurationState? Instance { get; private set; } = null; @@ -19,6 +19,8 @@ private ConfigurationState() Vosk = new(); OSC = new(); Shock = new(); + OpenShock = new(); + BrowserProxy = new(); } public void LoadConfiguration() @@ -41,6 +43,8 @@ private void LoadConfigurationStateFromFile() Vosk.LoadFileConfiguration(configurationFileFormat!); OSC.LoadFileConfiguration(configurationFileFormat!); Shock.LoadFileConfiguration(configurationFileFormat!); + OpenShock.LoadFileConfiguration(configurationFileFormat!); + BrowserProxy.LoadFileConfiguration(configurationFileFormat!); } } @@ -50,6 +54,8 @@ private void LoadDefaultConfigurationState() Vosk.LoadDefaultConfiguration(); OSC.LoadDefaultConfiguration(); Shock.LoadDefaultConfiguration(); + OpenShock.LoadDefaultConfiguration(); + BrowserProxy.LoadDefaultConfiguration(); } public void SaveConfigurationStateToFile() => diff --git a/OpenShock.VoiceRecognizer.Configuration/OSCConfigurationState.cs b/OpenShock.VoiceRecognizer.Configuration/OSCConfigurationState.cs index c7e2f5c..5fb0ba3 100644 --- a/OpenShock.VoiceRecognizer.Configuration/OSCConfigurationState.cs +++ b/OpenShock.VoiceRecognizer.Configuration/OSCConfigurationState.cs @@ -15,5 +15,5 @@ public void LoadFileConfiguration(ConfigurationFileFormat configurationFileForma ListenPort.Value = configurationFileFormat.OscListenPort; public void LoadDefaultConfiguration() => - ListenPort = new(9005); + ListenPort.Value = 9005; } diff --git a/OpenShock.VoiceRecognizer.Configuration/OpenShockConfigurationState.cs b/OpenShock.VoiceRecognizer.Configuration/OpenShockConfigurationState.cs new file mode 100644 index 0000000..18b1c53 --- /dev/null +++ b/OpenShock.VoiceRecognizer.Configuration/OpenShockConfigurationState.cs @@ -0,0 +1,48 @@ +using OpenShock.VoiceRecognizer.Common; + +namespace OpenShock.VoiceRecognizer.Configuration; + +public class OpenShockConfigurationState +{ + /*public ReactiveObject APIKey { get; private set; } + public ReactiveObject GroupName { get; private set; }*/ + public ReactiveObject SendHost { get; private set; } + public ReactiveObject SendPort { get; private set; } + + public ReactiveObject ExternalListenSetIntensityEndpoint { get; set; } + public ReactiveObject ExternalSendStartShockEndpoint { get; set; } + public ReactiveObject ExternalSendStartVibrationEndpoint { get; set; } + + public OpenShockConfigurationState() + { + /*APIKey = new(string.Empty); + GroupName = new(string.Empty);*/ + SendHost = new(string.Empty); + SendPort = new(9006); + ExternalListenSetIntensityEndpoint = new(string.Empty); + ExternalSendStartShockEndpoint = new(string.Empty); + ExternalSendStartVibrationEndpoint = new(string.Empty); + } + + public void LoadFileConfiguration(ConfigurationFileFormat configurationFileFormat) + { + /*APIKey.Value = configurationFileFormat.OpenShockAPIKey; + GroupName.Value = configurationFileFormat.OpenShockGroupName;*/ + SendHost.Value = configurationFileFormat.OpenShockSendHost; + SendPort.Value = configurationFileFormat.OpenShockSendPort; + ExternalListenSetIntensityEndpoint.Value = configurationFileFormat.OpenShockOscListenIntensityEndpoint; + ExternalSendStartShockEndpoint.Value = configurationFileFormat.OpenShockOscSendShockEndpoint; + ExternalSendStartVibrationEndpoint.Value = configurationFileFormat.OpenShockOscSendVibrateEndpoint; + } + + public void LoadDefaultConfiguration() + { + /*APIKey.Value = string.Empty; + GroupName.Value = string.Empty;*/ + SendHost.Value = "127.0.0.1"; + SendPort.Value = 0; + ExternalListenSetIntensityEndpoint.Value = string.Empty; + ExternalSendStartShockEndpoint.Value = string.Empty; + ExternalSendStartVibrationEndpoint.Value = string.Empty; + } +} diff --git a/OpenShock.VoiceRecognizer.Configuration/ShockConfigurationState.cs b/OpenShock.VoiceRecognizer.Configuration/ShockConfigurationState.cs index b8e65d1..3eaf314 100644 --- a/OpenShock.VoiceRecognizer.Configuration/ShockConfigurationState.cs +++ b/OpenShock.VoiceRecognizer.Configuration/ShockConfigurationState.cs @@ -24,8 +24,8 @@ public void LoadFileConfiguration(ConfigurationFileFormat configurationFileForma public void LoadDefaultConfiguration() { - CollarType = new(ShockCollarType.OpenShock); - Words = new([]); + CollarType.Value = ShockCollarType.OpenShock; + Words.Value = []; } } @@ -33,5 +33,6 @@ public class WordRecognition { public string Word { get; set; } = string.Empty; public ShockType Type { get; set; } - public float Delay { get; set; } + public float MinDelay { get; set; } + public float MaxDelay { get; set; } } diff --git a/OpenShock.VoiceRecognizer.Configuration/VoskConfigurationState.cs b/OpenShock.VoiceRecognizer.Configuration/VoskConfigurationState.cs index 0e8488b..61ec930 100644 --- a/OpenShock.VoiceRecognizer.Configuration/VoskConfigurationState.cs +++ b/OpenShock.VoiceRecognizer.Configuration/VoskConfigurationState.cs @@ -15,5 +15,5 @@ public void LoadFileConfiguration(ConfigurationFileFormat configurationFileForma ModelDirectory.Value = configurationFileFormat.VoskModelDirectory; public void LoadDefaultConfiguration() => - ModelDirectory = new(AppDomain.CurrentDomain.BaseDirectory); + ModelDirectory.Value = AppDomain.CurrentDomain.BaseDirectory; } diff --git a/OpenShock.VoiceRecognizer.Integrations/BrowserProxy/BaseBrowserProxy.cs b/OpenShock.VoiceRecognizer.Integrations/BrowserProxy/BaseBrowserProxy.cs new file mode 100644 index 0000000..5542de9 --- /dev/null +++ b/OpenShock.VoiceRecognizer.Integrations/BrowserProxy/BaseBrowserProxy.cs @@ -0,0 +1,28 @@ +using System.Net; + +namespace OpenShock.VoiceRecognizer.Integrations.BrowserProxy; + +public abstract class BaseBrowserProxy +{ + protected HttpListener? _listener = null; + protected Thread? _thread = null; + protected bool _shouldExit = false; + protected int _port = 0; + protected List _speechDetectionResults = []; + protected List _speechDetectionOnEnd = []; + protected List _pendingJavascript = []; + + public BaseBrowserProxy() { } + + public abstract void StartProxy(int port); + + public abstract void StopProxy(); + + public abstract void DetectBrowser(); + + public abstract void CloseBrowser(); + + public void AddJavascriptCommand(string command) => _pendingJavascript.Add(command); + + protected abstract void WorkerThread(); +} diff --git a/OpenShock.VoiceRecognizer.Integrations/BrowserProxy/ChromeBrowserProxy.cs b/OpenShock.VoiceRecognizer.Integrations/BrowserProxy/ChromeBrowserProxy.cs new file mode 100644 index 0000000..a0c7296 --- /dev/null +++ b/OpenShock.VoiceRecognizer.Integrations/BrowserProxy/ChromeBrowserProxy.cs @@ -0,0 +1,161 @@ +using System.Net; +using System.Text; +using System.Web; +using NAudio.CoreAudioApi; + +namespace OpenShock.VoiceRecognizer.Integrations.BrowserProxy; + +public class ChromeBrowserProxy : BaseBrowserProxy +{ + public override void StartProxy(int port) + { + _port = port; + _listener = new(); + _listener.Prefixes.Add($"http://*:{port}/"); + _listener.Start(); + _thread = new(new ThreadStart(WorkerThread)); + _thread.Start(); + } + + public override void StopProxy() + { + try + { + _listener!.Abort(); + } + catch (Exception) { } + + try + { + _listener!.Stop(); + } + catch(Exception) { } + } + + public override void CloseBrowser() + { + + } + + public override void DetectBrowser() + { + + } + + protected override void WorkerThread() + { + while (!_shouldExit) + { + if (_listener is null) + { + Thread.Sleep(1000); + continue; + } + + HttpListenerContext? context = null; + try + { + context = _listener!.GetContext(); + string text = string.Empty; + string? contextUrlLocalPath = context!.Request?.Url?.LocalPath; + if (!string.IsNullOrEmpty(contextUrlLocalPath)) + { + if (contextUrlLocalPath.EndsWith('/')) + { + _speechDetectionResults.Clear(); + _speechDetectionOnEnd.Clear(); + DetectBrowser(); + byte[] bytes = []; + try + { + using FileStream stream = File.OpenRead("proxy.html"); + using StreamReader reader = new(stream, Encoding.UTF8); + text = reader.ReadToEnd(); + bytes = Encoding.UTF8.GetBytes(text); + text = Encoding.UTF8.GetString(bytes).Replace("__PROXY_PORT__", _port.ToString()); + } + catch (Exception) { } + } + else if (contextUrlLocalPath.EndsWith("/crossdomain.xml")) + { + text = $"{Environment.NewLine}"; + text += $"{Environment.NewLine}"; + text += $"{Environment.NewLine}"; + } + else if (contextUrlLocalPath.EndsWith("/ProxyData")) + { + DetectBrowser(); + var message = HttpUtility.ParseQueryString(context!.Request?.Url?.Query ?? string.Empty)["message"]; + if (!string.IsNullOrEmpty(message)) + { + byte[] messageBytes = Convert.FromBase64String(message); + string @string = Encoding.UTF8.GetString(messageBytes); + //HandleProxyData(@string); + } + } + else if (contextUrlLocalPath.EndsWith("/CloseBrowserTab")) + { + CloseBrowser(); + } + else if (contextUrlLocalPath.EndsWith("/CloseProxy")) + { + _shouldExit = true; + } + else if (contextUrlLocalPath.EndsWith("/OpenBrowserTab")) + { + // pending javascript + CloseBrowser(); + } + else if (contextUrlLocalPath.EndsWith("/SetProxyPort")) + { + // do stuff + } + else if (contextUrlLocalPath.EndsWith("/SpeechDetectionGetResult")) + { + if (_speechDetectionResults.Count > 0) + { + text = _speechDetectionResults.First(); + _speechDetectionResults.RemoveAt(0); + } + } + else if (contextUrlLocalPath.EndsWith("/SpeechDetectionInit")) + { + _speechDetectionResults.Clear(); + } + else if (contextUrlLocalPath.EndsWith("/SpeechDetectionStartRecognition")) + { + AddJavascriptCommand("WebGLSpeechDetectionPlugin.Start()"); + } + else if (contextUrlLocalPath.EndsWith("/SpeechDetectionStopRecognition")) + { + AddJavascriptCommand("WebGLSpeechDetectionPlugin.Stop()"); + } + else if (contextUrlLocalPath.EndsWith("/SpeechDetectionAbort")) + { + AddJavascriptCommand("WebGLSpeechDetectionPlugin.Abort()"); + } + else if (contextUrlLocalPath.EndsWith("/SpeechDetectionGetLanguages")) + { + AddJavascriptCommand("WebGLSpeechDetectionPlugin.GetLanguages()"); + } + else if (contextUrlLocalPath.EndsWith("/SpeechDetectionSetLanguage")) + { + _speechDetectionResults.Clear(); + string languageCode = HttpUtility.ParseQueryString(context.Request!.Url!.Query)["lang"]!; + if (languageCode is not null) + { + AddJavascriptCommand($"WebGLSpeechDetectionPlugin.SetLanguage(\"{languageCode}\")"); + } + } + } + + byte[] textBytes = Encoding.UTF8.GetBytes(text); + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.AddHeader("ContentType", "utf8"); + context.Response.OutputStream.Write(textBytes, 0, textBytes.Length); + context.Response.OutputStream.Flush(); + } + catch (Exception) { } + } + } +} diff --git a/OpenShock.VoiceRecognizer.Integrations/OSC/OSCClient.cs b/OpenShock.VoiceRecognizer.Integrations/OSC/OSCClient.cs new file mode 100644 index 0000000..75a548d --- /dev/null +++ b/OpenShock.VoiceRecognizer.Integrations/OSC/OSCClient.cs @@ -0,0 +1,34 @@ +using BuildSoft.OscCore; +using OpenShock.VoiceRecognizer.Common; +using OpenShock.VoiceRecognizer.Configuration; + +namespace OpenShock.VoiceRecognizer.Integrations.OSC; + +public class OSCClient +{ + public OscClient? Client = null; + + public OSCClient() + { + GenerateNewClient(); + AttachHandlers(); + } + + private void AttachHandlers() + { + ConfigurationState.Instance!.OpenShock.SendHost.ValueChanged += OnSendHostChange; + ConfigurationState.Instance!.OpenShock.SendPort.ValueChanged += OnSendPortChanged; + } + + private void OnSendHostChange(object? sender, ValueChangedEventArgs e) => + GenerateNewClient(); + + private void OnSendPortChanged(object? _, ValueChangedEventArgs e) => + GenerateNewClient(); + + private void GenerateNewClient() => + Client = new( + ConfigurationState.Instance!.OpenShock.SendHost.Value, + ConfigurationState.Instance!.OpenShock.SendPort.Value + ); +} diff --git a/OpenShock.VoiceRecognizer.Integrations/OSC/OSCServer.cs b/OpenShock.VoiceRecognizer.Integrations/OSC/OSCServer.cs index c257664..b17e5bb 100644 --- a/OpenShock.VoiceRecognizer.Integrations/OSC/OSCServer.cs +++ b/OpenShock.VoiceRecognizer.Integrations/OSC/OSCServer.cs @@ -10,6 +10,7 @@ public class OSCServer private OscServer? _server = null; public event EventHandler? ToggleRecognizer; public event EventHandler? SetRecognizer; + public event EventHandler? OscMessage; public OSCServer() { @@ -29,6 +30,9 @@ private void AddServerMethods() { _server!.TryAddMethod("/recognizer/toggle", OnChangeRecognizerState); _server!.TryAddMethod("/recognizer/set", OnSetRecognizerState); + _server!.AddMonitorCallback((BlobString endpoint, OscMessageValues values) => + OscMessage?.Invoke(this, new OscMessageEventArgs(endpoint.ToString(), values)) + ); } private void OnListenPortChanged(object? sender, ValueChangedEventArgs e) => @@ -53,3 +57,9 @@ public class RecognizerSetEventArgs(bool state) : EventArgs { public bool State { get; private set; } = state; } + +public class OscMessageEventArgs(string endpoint, OscMessageValues values) : EventArgs +{ + public string Endpoint { get; private set; } = endpoint; + public OscMessageValues Values { get; private set; } = values; +} diff --git a/OpenShock.VoiceRecognizer.Integrations/OpenShock.VoiceRecognizer.Integrations.csproj b/OpenShock.VoiceRecognizer.Integrations/OpenShock.VoiceRecognizer.Integrations.csproj index 502f80f..5e50ba1 100644 --- a/OpenShock.VoiceRecognizer.Integrations/OpenShock.VoiceRecognizer.Integrations.csproj +++ b/OpenShock.VoiceRecognizer.Integrations/OpenShock.VoiceRecognizer.Integrations.csproj @@ -8,6 +8,8 @@ + + diff --git a/OpenShock.VoiceRecognizer.Integrations/OpenShock/OpenShockAPI.cs b/OpenShock.VoiceRecognizer.Integrations/OpenShock/OpenShockAPI.cs new file mode 100644 index 0000000..8135432 --- /dev/null +++ b/OpenShock.VoiceRecognizer.Integrations/OpenShock/OpenShockAPI.cs @@ -0,0 +1,52 @@ +using OpenShock.SDK.CSharp; +using OpenShock.SDK.CSharp.Models; +using OpenShock.VoiceRecognizer.Common; +using OpenShock.VoiceRecognizer.Configuration; + +namespace OpenShock.VoiceRecognizer.Integrations.OpenShock; + +public class OpenShockAPI +{ + public OpenShockApiClient? Client { get; private set; } = null; + + public OpenShockAPI() + { + GenerateNewClient(); + AttachHandlers(); + } + + public async Task> GetShockers() + { + var shockerList = new List(); + var shockers = await Client.GetOwnShockers(); + if (shockers.IsT0) + { + foreach (var shockerCollection in shockers.AsT0.Value) + { + shockerList.AddRange(shockerCollection.Shockers); + } + } + + return shockerList; + } + + public async Task GetDeviceGateway() + { + + } + + private void AttachHandlers() { } + //ConfigurationState.Instance!.OpenShock.APIKey.ValueChanged += OnAPIKeyChanged; + + //private void OnAPIKeyChanged(object? sender, ValueChangedEventArgs e) => + // GenerateNewClient(); + + private void GenerateNewClient() + { + /*var apiKey = ConfigurationState.Instance!.OpenShock.APIKey.Value; + if (!string.IsNullOrEmpty(apiKey)) + { + Client = new(new ApiClientOptions { Token = apiKey }); + }*/ + } +} diff --git a/OpenShock.VoiceRecognizer.NGramRecognizer/NGramRecognizer.cs b/OpenShock.VoiceRecognizer.NGramRecognizer/NGramRecognizer.cs index 07255f1..0610f07 100644 --- a/OpenShock.VoiceRecognizer.NGramRecognizer/NGramRecognizer.cs +++ b/OpenShock.VoiceRecognizer.NGramRecognizer/NGramRecognizer.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using OpenShock.VoiceRecognizer.Common.Enums; using OpenShock.VoiceRecognizer.Configuration; namespace OpenShock.VoiceRecognizer.NGramRecognizer; @@ -12,7 +11,7 @@ public static class NGramRecognizer foreach (var word in words) { - if (recognizedSpeech.Contains(word.Word) && (int?)result?.Type < (int)word.Type) + if (recognizedSpeech.Contains(word.Word) && (result?.Type is null || (int?)result?.Type < (int)word.Type)) { result = word; } diff --git a/OpenShock.VoiceRecognizer.STT/BaseRecognizer.cs b/OpenShock.VoiceRecognizer.STT/BaseRecognizer.cs index 81fb239..53291f4 100644 --- a/OpenShock.VoiceRecognizer.STT/BaseRecognizer.cs +++ b/OpenShock.VoiceRecognizer.STT/BaseRecognizer.cs @@ -1,6 +1,7 @@ using OpenShock.VoiceRecognizer.Configuration; using OpenShock.VoiceRecognizer.Common; -using OpenShock.VoiceRecognizer.NGramRecognizer; +using OpenShock.VoiceRecognizer.Integrations.OSC; +using OpenShock.VoiceRecognizer.Shockers; namespace OpenShock.VoiceRecognizer.STT; @@ -10,6 +11,9 @@ public abstract class BaseRecognizer : IDisposable public event EventHandler? StateChanged; public event EventHandler? RecognizedSpeech; + protected OSCServer _server; + protected OpenShockShocker _shocker; + /// /// Specifies if the Recognizer is currently listening /// to the Input device. @@ -22,8 +26,10 @@ public abstract class BaseRecognizer : IDisposable /// public bool Stopped { get; protected set; } - public BaseRecognizer() + public BaseRecognizer(OSCServer server) { + _shocker = new OpenShockShocker(server); + _server = server; Paused = false; Stopped = true; ConfigurationState.Instance!.Audio.InputDeviceID.ValueChanged += DeviceChanged; @@ -64,7 +70,8 @@ protected void OnRecognizedSpeech(string text) if (recognized is not null) { - switch (recognized.Type) + _shocker.HandleRecognizedWord(recognized); + /*switch (recognized.Type) { case Utility.Common.ShockType.Vibrate: break; @@ -72,8 +79,7 @@ protected void OnRecognizedSpeech(string text) break; case Utility.Common.ShockType.VibrateThenShock: break; - - } + }*/ } } @@ -98,12 +104,7 @@ protected void DeviceChanged(object? _, ValueChangedEventArgs e) => public abstract void Dispose(); } -public class RecognizedSpeechEventArgs +public class RecognizedSpeechEventArgs(string text) { - public string Text { get; } - - public RecognizedSpeechEventArgs(string text) - { - Text = text; - } + public string Text { get; } = text; } diff --git a/OpenShock.VoiceRecognizer.STT/OpenShock.VoiceRecognizer.STT.csproj b/OpenShock.VoiceRecognizer.STT/OpenShock.VoiceRecognizer.STT.csproj index 2d1dbb8..67dd340 100644 --- a/OpenShock.VoiceRecognizer.STT/OpenShock.VoiceRecognizer.STT.csproj +++ b/OpenShock.VoiceRecognizer.STT/OpenShock.VoiceRecognizer.STT.csproj @@ -9,7 +9,9 @@ + + diff --git a/OpenShock.VoiceRecognizer.STT/VoskRecognizer.cs b/OpenShock.VoiceRecognizer.STT/VoskRecognizer.cs index 0385e3e..adb1137 100644 --- a/OpenShock.VoiceRecognizer.STT/VoskRecognizer.cs +++ b/OpenShock.VoiceRecognizer.STT/VoskRecognizer.cs @@ -5,6 +5,7 @@ using OpenShock.VoiceRecognizer.Configuration; using OpenShock.VoiceRecognizer.Common.Audio; using Vosk; +using OpenShock.VoiceRecognizer.Integrations.OSC; namespace OpenShock.VoiceRecognizer.STT; @@ -14,7 +15,7 @@ public class VoskSpeechRecognizer : BaseRecognizer private VoskRecognizer? _recognizer; private WasapiCapture? _capture; - public VoskSpeechRecognizer() : base() + public VoskSpeechRecognizer(OSCServer server) : base(server) { AttachEventHandlers(); } diff --git a/OpenShock.VoiceRecognizer.Shockers/BaseShocker.cs b/OpenShock.VoiceRecognizer.Shockers/BaseShocker.cs new file mode 100644 index 0000000..98bc2db --- /dev/null +++ b/OpenShock.VoiceRecognizer.Shockers/BaseShocker.cs @@ -0,0 +1,24 @@ +using OpenShock.VoiceRecognizer.Configuration; +using OpenShock.VoiceRecognizer.Integrations.OSC; + +namespace OpenShock.VoiceRecognizer.Shockers; + +public abstract class BaseShocker : IDisposable +{ + protected readonly OSCServer _server; + + public BaseShocker(OSCServer server) + { + _server = server; + } + + protected abstract void OnOscMessageReceived(object? sender, OscMessageEventArgs e); + + public abstract void HandleRecognizedWord(WordRecognition recognized); + + protected abstract void HandleVibrate(); + + protected abstract void HandleShock(); + + public abstract void Dispose(); +} diff --git a/OpenShock.VoiceRecognizer.Shockers/OpenShock.VoiceRecognizer.Shockers.csproj b/OpenShock.VoiceRecognizer.Shockers/OpenShock.VoiceRecognizer.Shockers.csproj new file mode 100644 index 0000000..e05ba41 --- /dev/null +++ b/OpenShock.VoiceRecognizer.Shockers/OpenShock.VoiceRecognizer.Shockers.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/OpenShock.VoiceRecognizer.Shockers/OpenShockShocker.cs b/OpenShock.VoiceRecognizer.Shockers/OpenShockShocker.cs new file mode 100644 index 0000000..6a2b71c --- /dev/null +++ b/OpenShock.VoiceRecognizer.Shockers/OpenShockShocker.cs @@ -0,0 +1,79 @@ +using OpenShock.VoiceRecognizer.Configuration; +using OpenShock.VoiceRecognizer.Integrations.OSC; +using OpenShock.VoiceRecognizer.Utility.Common; + +namespace OpenShock.VoiceRecognizer.Shockers; + +public class OpenShockShocker : BaseShocker +{ + // goes 1 to 10 - anna + private float _externalSetIntensity = 0.0f; + private readonly OSCClient _client; + + public OpenShockShocker(OSCServer server) : base(server) + { + _client = new(); + AttachHandlers(); + } + + private void AttachHandlers() => + _server.OscMessage += OnOscMessageReceived; + + protected override void OnOscMessageReceived(object? sender, OscMessageEventArgs e) + { + if (e.Endpoint.Equals(ConfigurationState.Instance!.OpenShock.ExternalListenSetIntensityEndpoint.Value)) + { + try + { + _externalSetIntensity = e.Values.ReadFloatElement(0); + } + catch (Exception) { } + } + } + + public override async void HandleRecognizedWord(WordRecognition recognized) + { + switch (recognized.Type) + { + case ShockType.Vibrate: + HandleVibrate(); + break; + case ShockType.Shock: + HandleShock(); + break; + case ShockType.VibrateThenShock: + HandleVibrate(); + var delay = (new Random().NextSingle() * (recognized.MaxDelay - recognized.MinDelay)) + recognized.MinDelay; + await TaskDelay(delay).ConfigureAwait(false); + HandleShock(); + break; + } + } + + private static async Task TaskDelay(float seconds) + { + var ms = Convert.ToInt32(Math.Truncate(seconds * 1000)); + await Task.Delay(ms).ConfigureAwait(false); + } + + protected override void HandleShock() + { + _client.Client?.Send( + $"{ConfigurationState.Instance!.OpenShock.ExternalSendStartShockEndpoint.Value}", + true + ); + } + + protected override void HandleVibrate() + { + _client.Client?.Send( + $"{ConfigurationState.Instance!.OpenShock.ExternalSendStartVibrationEndpoint.Value}", + true + ); + } + + public override void Dispose() + { + + } +} diff --git a/OpenShock.VoiceRecognizer.sln b/OpenShock.VoiceRecognizer.sln index a6238ef..8e2c95c 100644 --- a/OpenShock.VoiceRecognizer.sln +++ b/OpenShock.VoiceRecognizer.sln @@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenShock.VoiceRecognizer.S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenShock.VoiceRecognizer.NGramRecognizer", "OpenShock.VoiceRecognizer.NGramRecognizer\OpenShock.VoiceRecognizer.NGramRecognizer.csproj", "{4DC9F43A-2783-4829-AC1F-27904F60E6A5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenShock.VoiceRecognizer.Shockers", "OpenShock.VoiceRecognizer.Shockers\OpenShock.VoiceRecognizer.Shockers.csproj", "{A1EF76BD-C3CF-4883-B158-296B02AAF8D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +62,10 @@ Global {4DC9F43A-2783-4829-AC1F-27904F60E6A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DC9F43A-2783-4829-AC1F-27904F60E6A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DC9F43A-2783-4829-AC1F-27904F60E6A5}.Release|Any CPU.Build.0 = Release|Any CPU + {A1EF76BD-C3CF-4883-B158-296B02AAF8D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1EF76BD-C3CF-4883-B158-296B02AAF8D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1EF76BD-C3CF-4883-B158-296B02AAF8D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1EF76BD-C3CF-4883-B158-296B02AAF8D5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OpenShock.VoiceRecognizer/Assets/Proxies/browserproxy.html b/OpenShock.VoiceRecognizer/Assets/Proxies/browserproxy.html new file mode 100644 index 0000000..2418e82 --- /dev/null +++ b/OpenShock.VoiceRecognizer/Assets/Proxies/browserproxy.html @@ -0,0 +1,877 @@ + + + Speech Proxy + + + Please do not close this tab whilst OVR Toolkit is open! This is used for speech recognition.
+
+ +
+ + + diff --git a/OpenShock.VoiceRecognizer/UI/ViewModels/MainModelViewModel.cs b/OpenShock.VoiceRecognizer/UI/ViewModels/MainModelViewModel.cs index e78ba75..5493cc3 100644 --- a/OpenShock.VoiceRecognizer/UI/ViewModels/MainModelViewModel.cs +++ b/OpenShock.VoiceRecognizer/UI/ViewModels/MainModelViewModel.cs @@ -32,7 +32,7 @@ public MainModelViewModel() private void SelectRecognizer() { - BaseSpeechRecognizer = new VoskSpeechRecognizer(); + BaseSpeechRecognizer = new VoskSpeechRecognizer(_oscServer); BaseSpeechRecognizer.RecognizedSpeech += OnRecognizedSpeech; BaseSpeechRecognizer.StateChanged += OnRecognizerStateChanged; } diff --git a/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsGeneralViewModel.cs b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsGeneralViewModel.cs index 57fb069..9dcf95e 100644 --- a/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsGeneralViewModel.cs +++ b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsGeneralViewModel.cs @@ -1,6 +1,7 @@ using OpenShock.VoiceRecognizer.Common.Audio; using OpenShock.VoiceRecognizer.Common.Enums; using OpenShock.VoiceRecognizer.Configuration; +using OpenShock.VoiceRecognizer.UI.ViewModels.Enums; namespace OpenShock.VoiceRecognizer.UI.ViewModels.Settings; @@ -8,6 +9,7 @@ public class SettingsGeneralViewModel : BaseViewModel { public AudioDeviceSelectorViewModel InputDeviceSelectorVM { get; } public NumberInputViewModel ListenPortSelectorVM { get; } + public ShockCollarTypeSelectorViewModel ShockCollarTypeSelectorVM { get; } public SettingsGeneralViewModel() { @@ -22,6 +24,11 @@ public SettingsGeneralViewModel() ConfigurationState.Instance!.OSC.ListenPort.Value ); + ShockCollarTypeSelectorVM = new( + "Shock Collar Type", + ConfigurationState.Instance!.Shock.CollarType.Value + ); + AttachEventHandlers(); } @@ -29,6 +36,7 @@ private void AttachEventHandlers() { InputDeviceSelectorVM.DeviceChanged += AudioDeviceChanged; ListenPortSelectorVM.NumberValueChanged += ListenPortChanged; + ShockCollarTypeSelectorVM.EnumChanged += ShockCollarTypeChanged; } private void AudioDeviceChanged(object? sender, AudioDeviceChangedEventArgs e) @@ -43,4 +51,7 @@ private void AudioDeviceChanged(object? sender, AudioDeviceChangedEventArgs e) private void ListenPortChanged(object? sender, NumberValueChangedEventArgs e) => ConfigurationState.Instance!.OSC.ListenPort.Value = e.Value; + + private void ShockCollarTypeChanged(object? sender, EnumChangedEventArgs e) => + ConfigurationState.Instance!.Shock.CollarType.Value = e.Value; } diff --git a/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsOpenShockViewModel.cs b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsOpenShockViewModel.cs new file mode 100644 index 0000000..39c6d37 --- /dev/null +++ b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsOpenShockViewModel.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenShock.VoiceRecognizer.Configuration; + +namespace OpenShock.VoiceRecognizer.UI.ViewModels.Settings; + +public class SettingsOpenShockViewModel : BaseViewModel +{ + public StringInputViewModel SendHostSelectorVM { get; set; } + public NumberInputViewModel SendPortSelectorVM { get; set; } + public StringInputViewModel ListenIntensityEndpointSelectorVM { get; set; } + public StringInputViewModel SendShockEndpointSelectorVM { get; set; } + public StringInputViewModel SendVibrateEndpointSelectorVM { get; set; } + + public SettingsOpenShockViewModel() + { + SendHostSelectorVM = new( + "Host", + ConfigurationState.Instance!.OpenShock.SendHost.Value + ); + SendPortSelectorVM = new( + "Port", + ConfigurationState.Instance!.OpenShock.SendPort.Value + ); + ListenIntensityEndpointSelectorVM = new( + "Intensity Listen Endpoint", + ConfigurationState.Instance!.OpenShock.ExternalListenSetIntensityEndpoint.Value + ); + SendShockEndpointSelectorVM = new( + "Send Shock Endpoint", + ConfigurationState.Instance!.OpenShock.ExternalSendStartShockEndpoint.Value + ); + SendVibrateEndpointSelectorVM = new( + "Send Vibrate Endpoint", + ConfigurationState.Instance!.OpenShock.ExternalSendStartVibrationEndpoint.Value + ); + + AttachEventHandlers(); + } + + private void AttachEventHandlers() + { + SendHostSelectorVM.PropertyChanged += OnSendHostChanged; + SendPortSelectorVM.PropertyChanged += OnSendPortChanged; + ListenIntensityEndpointSelectorVM.PropertyChanged += OnListenIntensityEndpointChanged; + SendShockEndpointSelectorVM.PropertyChanged += OnSendShockEndpointChanged; + SendVibrateEndpointSelectorVM.PropertyChanged += OnSendVibrateEndpointChanged; + } + + private void OnSendHostChanged(object? sender, PropertyChangedEventArgs e) + => ConfigurationState.Instance!.OpenShock.SendHost.Value = SendHostSelectorVM.Text!; + + private void OnSendPortChanged(object? sender, PropertyChangedEventArgs e) + => ConfigurationState.Instance!.OpenShock.SendPort.Value = SendPortSelectorVM.Value!; + + private void OnListenIntensityEndpointChanged(object? sender, PropertyChangedEventArgs e) + => ConfigurationState.Instance!.OpenShock.ExternalListenSetIntensityEndpoint.Value = ListenIntensityEndpointSelectorVM.Text!; + + private void OnSendShockEndpointChanged(object? sender, PropertyChangedEventArgs e) + => ConfigurationState.Instance!.OpenShock.ExternalSendStartShockEndpoint.Value = SendShockEndpointSelectorVM.Text!; + + private void OnSendVibrateEndpointChanged(object? sender, PropertyChangedEventArgs e) + => ConfigurationState.Instance!.OpenShock.ExternalSendStartVibrationEndpoint.Value = SendVibrateEndpointSelectorVM.Text!; +} diff --git a/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsWindowViewModel.cs b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsWindowViewModel.cs index 66ee10b..f8f8bb5 100644 --- a/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsWindowViewModel.cs +++ b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsWindowViewModel.cs @@ -10,12 +10,14 @@ public class SettingsWindowViewModel public SettingsGeneralViewModel GeneralVM { get; } public SettingsVoskModelViewModel VoskVM { get; } public SettingsZapViewModel ZapVM { get; } + public SettingsOpenShockViewModel OpenShockVM { get; } public SettingsWindowViewModel() { GeneralVM = new(); VoskVM = new(); ZapVM = new(); + OpenShockVM = new(); } public void SaveSettings() diff --git a/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsZapViewModel.cs b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsZapViewModel.cs index 815bd98..70fcd29 100644 --- a/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsZapViewModel.cs +++ b/OpenShock.VoiceRecognizer/UI/ViewModels/Settings/SettingsZapViewModel.cs @@ -13,7 +13,8 @@ public class SettingsZapViewModel : BaseViewModel public StringInputViewModel TextInputVM { get; set; } public ShockTypeSelectorViewModel ShockTypeSelectorVM { get; set; } - public NumberInputViewModel DelayInputVM { get; set; } + public NumberInputViewModel MinDelayInputVM { get; set; } + public NumberInputViewModel MaxDelayInputVM { get; set; } public SettingsZapViewModel() { @@ -21,11 +22,13 @@ public SettingsZapViewModel() Words = ConfigurationState.Instance!.Shock.Words.Value; InputText = string.Empty; ShockType = ShockType.VibrateThenShock; - Delay = 0.0f; + MinDelay = 0.0f; + MaxDelay = 0.0f; TextInputVM = new("Text", string.Empty); ShockTypeSelectorVM = new("Shock Type", ShockType); - DelayInputVM = new("Delay", 0); + MinDelayInputVM = new("Minimum Delay (seconds)", 0); + MaxDelayInputVM = new("Maximum Delay (seconds)", 0); AttachHandlers(); } @@ -34,15 +37,18 @@ private void AttachHandlers() { TextInputVM.PropertyChanged += OnTextInputChanged; ShockTypeSelectorVM.EnumChanged += OnShockTypeSelected; - DelayInputVM.PropertyChanged += OnDelayInputChanged; + MinDelayInputVM.PropertyChanged += OnMinDelayInputChanged; + MaxDelayInputVM.PropertyChanged += OnMaxDelayInputChanged; } private void OnTextInputChanged(object? sender, EventArgs e) => InputText = TextInputVM.Text ?? string.Empty; private void OnShockTypeSelected(object? sender, EnumChangedEventArgs e) => ShockType = e.Value; - private void OnDelayInputChanged(object? sender, EventArgs e) => - Delay = DelayInputVM.Value; + private void OnMinDelayInputChanged(object? sender, EventArgs e) => + MinDelay = MinDelayInputVM.Value; + private void OnMaxDelayInputChanged(object? sender, EventArgs e) => + MaxDelay = MaxDelayInputVM.Value; public int SelectedWordIndex { @@ -57,7 +63,8 @@ public int SelectedWordIndex public string InputText { get; set; } public ShockType ShockType { get; set; } - public float Delay { get; set; } + public float MinDelay { get; set; } + public float MaxDelay { get; set; } public bool HasSelectedWord => SelectedWordIndex <= Words.Count && SelectedWordIndex > -1; } diff --git a/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsGeneralView.axaml b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsGeneralView.axaml index 46bf24b..4c631b0 100644 --- a/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsGeneralView.axaml +++ b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsGeneralView.axaml @@ -6,6 +6,7 @@ mc:Ignorable="d" xmlns:vm="using:OpenShock.VoiceRecognizer.UI.ViewModels.Settings" xmlns:views="using:OpenShock.VoiceRecognizer.UI.Views" + xmlns:enumViews="using:OpenShock.VoiceRecognizer.UI.Views.Enums" x:DataType="vm:SettingsGeneralViewModel"> + diff --git a/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsOpenShockView.axaml b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsOpenShockView.axaml new file mode 100644 index 0000000..b56cc63 --- /dev/null +++ b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsOpenShockView.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsOpenShockView.axaml.cs b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsOpenShockView.axaml.cs new file mode 100644 index 0000000..0324397 --- /dev/null +++ b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsOpenShockView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace OpenShock.VoiceRecognizer.UI.Views.Settings; + +public partial class SettingsOpenShockView : UserControl +{ + public SettingsOpenShockView() + { + InitializeComponent(); + } +} diff --git a/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsZapView.axaml b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsZapView.axaml index ef83c70..7b5da7c 100644 --- a/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsZapView.axaml +++ b/OpenShock.VoiceRecognizer/UI/Views/Settings/SettingsZapView.axaml @@ -54,7 +54,10 @@ - + + + + w.Word.Contains(word))) { @@ -27,7 +28,8 @@ private void WordAddClicked(object? sender, RoutedEventArgs e) { Word = word, Type = shockType, - Delay = delay, + MinDelay = minDelay, + MaxDelay = maxDelay, }); ConfigurationState.Instance!.Shock.Words.Value = words; } diff --git a/OpenShock.VoiceRecognizer/UI/Windows/SettingsWindow.axaml b/OpenShock.VoiceRecognizer/UI/Windows/SettingsWindow.axaml index e19484a..04a3982 100644 --- a/OpenShock.VoiceRecognizer/UI/Windows/SettingsWindow.axaml +++ b/OpenShock.VoiceRecognizer/UI/Windows/SettingsWindow.axaml @@ -33,6 +33,7 @@ + + GeneralPage, "VoskPage" => VoskPage, "ZapPage" => ZapPage, + "OpenShockPage" => OpenShockPage, _ => throw new NotImplementedException(), }; }