From 650fde361779d755824f6eb0c1d61e6b09f3e02f Mon Sep 17 00:00:00 2001 From: glax Date: Tue, 9 Jul 2024 00:50:23 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 5 + .idea/.idea.VRCOSCRouter/.idea/.gitignore | 13 ++ .idea/.idea.VRCOSCRouter/.idea/encodings.xml | 4 + .../.idea.VRCOSCRouter/.idea/indexLayout.xml | 8 + .idea/.idea.VRCOSCRouter/.idea/vcs.xml | 6 + VRCOSCRouter.sln | 16 ++ VRCOSCRouter.sln.DotSettings.user | 3 + VRCOSCRouter/Endpoint.cs | 7 + VRCOSCRouter/OSCProgram.cs | 140 ++++++++++++++++++ VRCOSCRouter/Program.cs | 27 ++++ VRCOSCRouter/ProgramInfo.cs | 8 + VRCOSCRouter/VRCOSCRouter.csproj | 27 ++++ examplePrograms/eyetrackvr.json | 33 +++++ 13 files changed, 297 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.idea.VRCOSCRouter/.idea/.gitignore create mode 100644 .idea/.idea.VRCOSCRouter/.idea/encodings.xml create mode 100644 .idea/.idea.VRCOSCRouter/.idea/indexLayout.xml create mode 100644 .idea/.idea.VRCOSCRouter/.idea/vcs.xml create mode 100644 VRCOSCRouter.sln create mode 100644 VRCOSCRouter.sln.DotSettings.user create mode 100644 VRCOSCRouter/Endpoint.cs create mode 100644 VRCOSCRouter/OSCProgram.cs create mode 100644 VRCOSCRouter/Program.cs create mode 100644 VRCOSCRouter/ProgramInfo.cs create mode 100644 VRCOSCRouter/VRCOSCRouter.csproj create mode 100644 examplePrograms/eyetrackvr.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.idea/.idea.VRCOSCRouter/.idea/.gitignore b/.idea/.idea.VRCOSCRouter/.idea/.gitignore new file mode 100644 index 0000000..622b0c3 --- /dev/null +++ b/.idea/.idea.VRCOSCRouter/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +/.idea.VRCOSCRouter.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.VRCOSCRouter/.idea/encodings.xml b/.idea/.idea.VRCOSCRouter/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.VRCOSCRouter/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.VRCOSCRouter/.idea/indexLayout.xml b/.idea/.idea.VRCOSCRouter/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.VRCOSCRouter/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.VRCOSCRouter/.idea/vcs.xml b/.idea/.idea.VRCOSCRouter/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.VRCOSCRouter/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/VRCOSCRouter.sln b/VRCOSCRouter.sln new file mode 100644 index 0000000..c37b671 --- /dev/null +++ b/VRCOSCRouter.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCOSCRouter", "VRCOSCRouter\VRCOSCRouter.csproj", "{5A903D10-304F-4348-901C-6B96893ADF72}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5A903D10-304F-4348-901C-6B96893ADF72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A903D10-304F-4348-901C-6B96893ADF72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A903D10-304F-4348-901C-6B96893ADF72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A903D10-304F-4348-901C-6B96893ADF72}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/VRCOSCRouter.sln.DotSettings.user b/VRCOSCRouter.sln.DotSettings.user new file mode 100644 index 0000000..5de9c22 --- /dev/null +++ b/VRCOSCRouter.sln.DotSettings.user @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/VRCOSCRouter/Endpoint.cs b/VRCOSCRouter/Endpoint.cs new file mode 100644 index 0000000..a3183dc --- /dev/null +++ b/VRCOSCRouter/Endpoint.cs @@ -0,0 +1,7 @@ +namespace VRCOSCRouter; + +public struct Endpoint(string vrcEndpoint, string programEndpoint, Type type) +{ + public readonly string VrcEndpoint = vrcEndpoint, ProgramEndpoint = programEndpoint; + public readonly Type Type = type; +} \ No newline at end of file diff --git a/VRCOSCRouter/OSCProgram.cs b/VRCOSCRouter/OSCProgram.cs new file mode 100644 index 0000000..ab9b80b --- /dev/null +++ b/VRCOSCRouter/OSCProgram.cs @@ -0,0 +1,140 @@ +using GlaxOSC; +using Microsoft.Extensions.Logging; +using BuildSoft.OscCore; + +namespace VRCOSCRouter; + +public class OscProgram +{ + public string ServiceName; + public readonly int SendPort; + public readonly int ReceivePort; + private readonly OSC _vrcOscClient; + private readonly OscServer _programOscReceiver; + private readonly OscClient _programOscSender; + public bool _keepRunning = true; + private readonly Thread _listenThread; + private const int UpdateInterval = 10; + private readonly ILogger? _logger; + internal readonly List Endpoints; + + public OscProgram(int sendPort, int receivePort, List endpoints, string? serviceName = null, string ip = "127.0.0.1", ILogger? logger = null) + { + this._logger = logger; + this.ServiceName = serviceName ?? $"VRCOSCRouter_{RandomString(4)}"; + + this.Endpoints = endpoints; + + this.SendPort = sendPort; + this.ReceivePort = receivePort; + this._vrcOscClient = new OSC(this.ServiceName, endpoints.ToDictionary(e => e.VrcEndpoint, e => e.Type), ip, sendPort, logger: logger); + + _programOscReceiver = new OscServer(sendPort); + foreach (KeyValuePair endpoint in endpoints.ToDictionary(e => e.ProgramEndpoint, e=> e.Type)) + this._programOscReceiver.TryAddMethod(endpoint.Key, values => HandleProgramOscMessage(endpoint.Key, endpoint.Value, values)); + + _programOscSender = new OscClient("127.0.0.1", this.ReceivePort); + this._vrcOscClient.OnParameterChangeEvent += HandleVrcOscMessage; + + this._listenThread = new(Listen); + this._listenThread.Start(); + + this._logger?.LogInformation($"Started {this.ServiceName}." + + $"\n\tProgramSend: {this.SendPort}" + + $"\n\tProgramReceive: {this.ReceivePort}" + + $"\n\tVRCOSC-Port: {this._vrcOscClient.OSCPort}" + + $"\n\tEndpoints:\n\t\t{string.Join("\n\t\t", this.Endpoints.Select(e => $"({e.Type.Name}) {e.ProgramEndpoint} -> {e.VrcEndpoint}"))}"); + } + + private void HandleVrcOscMessage(string endpoint, object? oldvalue, object? newvalue) + { + if (newvalue is null) + return; + + if (this.Endpoints.All(e => e.VrcEndpoint != endpoint)) + return; + string programEndpoint = this.Endpoints.First(e => e.VrcEndpoint == endpoint).ProgramEndpoint; + + Type type = newvalue.GetType(); + if (type == typeof(int)) + this._programOscSender.Send(endpoint, (int)newvalue); + else if (type == typeof(long)) + this._programOscSender.Send(endpoint, (long)newvalue); + else if (type == typeof(float)) + this._programOscSender.Send(endpoint, (float)newvalue); + else if (type == typeof(double)) + this._programOscSender.Send(endpoint, (double)newvalue); + else if (type == typeof(string)) + this._programOscSender.Send(endpoint, (string)newvalue); + else if (type == typeof(char)) + this._programOscSender.Send(endpoint, (char)newvalue); + else if (type == typeof(bool)) + this._programOscSender.Send(endpoint, (bool)newvalue); + + this._logger?.LogDebug($"[{this.ServiceName}] VRC '{endpoint}' - {newvalue} -> Program {programEndpoint}"); + } + + private void HandleProgramOscMessage(string endpoint, Type type, OscMessageValues values) + { + if (this.Endpoints.All(e => e.ProgramEndpoint != endpoint)) + return; + string vrcEndpoint = this.Endpoints.First(e => e.ProgramEndpoint == endpoint).VrcEndpoint; + + object o = "fail"; + if (type == typeof(int)) + { + this._vrcOscClient.Client.Send(vrcEndpoint, values.ReadIntElement(0)); + o = values.ReadIntElement(0); + } + else if (type == typeof(long)) + { + this._vrcOscClient.Client.Send(vrcEndpoint, values.ReadInt64Element(0)); + o = values.ReadInt64Element(0); + } + else if (type == typeof(float)) + { + this._vrcOscClient.Client.Send(vrcEndpoint, values.ReadFloatElement(0)); + o = values.ReadFloatElement(0); + } + else if (type == typeof(double)) + { + this._vrcOscClient.Client.Send(vrcEndpoint, values.ReadFloat64Element(0)); + o = values.ReadFloat64Element(0); + } + else if (type == typeof(string)) + { + this._vrcOscClient.Client.Send(vrcEndpoint, values.ReadStringElement(0)); + o = values.ReadStringElement(0); + } + else if (type == typeof(char)) + { + this._vrcOscClient.Client.Send(vrcEndpoint, values.ReadAsciiCharElement(0)); + o = values.ReadAsciiCharElement(0); + } + else if (type == typeof(bool)) + { + this._vrcOscClient.Client.Send(vrcEndpoint, values.ReadBooleanElement(0)); + o = values.ReadBooleanElement(0); + } + + this._logger?.LogDebug($"[{this.ServiceName}] Program '{endpoint}' - {o} -> VRC {vrcEndpoint}"); + } + + private void Listen() + { + this._programOscReceiver.Start(); + while (_keepRunning) + { + this._programOscReceiver.Update(); + Thread.Sleep(UpdateInterval); + } + } + + private static string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var random = new Random(); + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } +} \ No newline at end of file diff --git a/VRCOSCRouter/Program.cs b/VRCOSCRouter/Program.cs new file mode 100644 index 0000000..15fa887 --- /dev/null +++ b/VRCOSCRouter/Program.cs @@ -0,0 +1,27 @@ +// See https://aka.ms/new-console-template for more information + +using GlaxLogger; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using VRCOSCRouter; + +Logger logger = new (LogLevel.Debug, consoleOut: Console.Out); +logger.LogInformation("Starting VRCOSCRouter..."); + +string[] files = Directory.GetFiles(Environment.CurrentDirectory, "*.json"); +List programs = new(); +foreach (string filePath in files) +{ + string fileContent = File.ReadAllText(filePath); + if(!fileContent.Contains("Name") + || !fileContent.Contains("Ip") + || !fileContent.Contains("SendPort") + || !fileContent.Contains("ReceivePort") + || !fileContent.Contains("Endpoints")) + continue; + ProgramInfo pi = JsonConvert.DeserializeObject(fileContent); + programs.Add(new OscProgram(pi.SendPort, pi.ReceivePort, pi.Endpoints, pi.Name, pi.Ip, logger)); +} + +while(!Console.KeyAvailable && Console.ReadKey().Key != ConsoleKey.Escape) + Thread.Sleep(10); \ No newline at end of file diff --git a/VRCOSCRouter/ProgramInfo.cs b/VRCOSCRouter/ProgramInfo.cs new file mode 100644 index 0000000..197dcd0 --- /dev/null +++ b/VRCOSCRouter/ProgramInfo.cs @@ -0,0 +1,8 @@ +namespace VRCOSCRouter; + +public struct ProgramInfo(string name, string ip, int sendPort, int receivePort, List endpoints) +{ + public string Name = name, Ip = ip; + public int SendPort = sendPort, ReceivePort = receivePort; + public List Endpoints = endpoints; +} \ No newline at end of file diff --git a/VRCOSCRouter/VRCOSCRouter.csproj b/VRCOSCRouter/VRCOSCRouter.csproj new file mode 100644 index 0000000..75b083c --- /dev/null +++ b/VRCOSCRouter/VRCOSCRouter.csproj @@ -0,0 +1,27 @@ + + + + Exe + net8.0 + enable + enable + + + + + ..\..\GlaxOSC\GlaxOSC\bin\Debug\net7.0\GlaxOSC.dll + + + ..\..\vrc-oscquery-lib\vrc-oscquery-lib\bin\Debug\net6.0\vrc-oscquery-lib.dll + + + + + + + + + + + + diff --git a/examplePrograms/eyetrackvr.json b/examplePrograms/eyetrackvr.json new file mode 100644 index 0000000..b3f14d8 --- /dev/null +++ b/examplePrograms/eyetrackvr.json @@ -0,0 +1,33 @@ +{ + "Name": "EyeTrackVR", + "Ip": "127.0.0.1", + "SendPort": 8000, + "ReceivePort": 8001, + "Endpoints": [ + { + "VrcEndpoint": "/avatar/parameters/FT/v2/LeftEyeX", + "ProgramEndpoint": "/avatar/parameters/LeftEyeX", + "Type": "System.Single" + }, + { + "VrcEndpoint": "/avatar/parameters/FT/v2/RightEyeX", + "ProgramEndpoint": "/avatar/parameters/RightEyeX", + "Type": "System.Single" + }, + { + "VrcEndpoint": "/avatar/parameters/FT/v2/EyesY", + "ProgramEndpoint": "/avatar/parameters/EyesY", + "Type": "System.Single" + }, + { + "VrcEndpoint": "/avatar/parameters/FT/v2/EyeLidLeft", + "ProgramEndpoint": "/avatar/parameters/LeftEyeLidExpandedSqueeze", + "Type": "System.Single" + }, + { + "VrcEndpoint": "/avatar/parameters/FT/v2/EyeLidRight", + "ProgramEndpoint": "/avatar/parameters/RightEyeLidExpandedSqueeze", + "Type": "System.Single" + }, + ] +} \ No newline at end of file