Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
VolcanicArts committed Dec 9, 2024
2 parents 1fe3267 + 5852591 commit 690427d
Show file tree
Hide file tree
Showing 21 changed files with 484 additions and 174 deletions.
31 changes: 28 additions & 3 deletions VRCOSC.App/AppManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Windows.Media;
using org.mariuszgromada.math.mxparser;
using Valve.VR;
using VRCOSC.App.Actions.Files;
using VRCOSC.App.Audio;
using VRCOSC.App.Audio.Whisper;
using VRCOSC.App.ChatBox;
Expand All @@ -29,7 +30,9 @@
using VRCOSC.App.SDK.Parameters;
using VRCOSC.App.SDK.VRChat;
using VRCOSC.App.Settings;
using VRCOSC.App.Startup;
using VRCOSC.App.UI.Themes;
using VRCOSC.App.UI.Windows;
using VRCOSC.App.Updater;
using VRCOSC.App.Utils;
using VRCOSC.App.VRChatAPI;
Expand Down Expand Up @@ -404,28 +407,50 @@ private async Task startAsync()
{
State.Value = AppManagerState.Starting;

StartupManager.GetInstance().OpenFileLocations();
RouterManager.GetInstance().Start();
VRChatOscClient.EnableSend();
ChatBoxManager.GetInstance().Start();
await VRChatClient.Player.RetrieveAll();
await ModuleManager.GetInstance().StartAsync();
VRChatLogReader.Start();

if (ModuleManager.GetInstance().GetRunningModulesOfType<ISpeechHandler>().Any())
{
if (string.IsNullOrWhiteSpace(SettingsManager.GetInstance().GetValue<string>(VRCOSCSetting.SpeechModelPath)))
{
var result = MessageBox.Show("You have enabled modules that require the speech engine.\nWould you like to automatically set it up?", "Set Up Speech Engine?", MessageBoxButton.YesNo);

if (result == MessageBoxResult.Yes)
{
await installSpeechModel();
}
}

SpeechEngine.Initialise();
}

updateTask = new Repeater(update);
updateTask.Start(TimeSpan.FromSeconds(1d / 60d));

VRChatOscClient.OnParameterReceived += onParameterReceived;
VRChatOscClient.EnableReceive();

if (ModuleManager.GetInstance().GetRunningModulesOfType<ISpeechHandler>().Any())
SpeechEngine.Initialise();

State.Value = AppManagerState.Started;

sendMetadataParameters();
sendControlParameters();
}

private Task installSpeechModel()
{
var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin?download=true"), Storage.GetStorageForDirectory("runtime/whisper"), "ggml-tiny.bin");

action.OnComplete += () => { SettingsManager.GetInstance().GetObservable<string>(VRCOSCSetting.SpeechModelPath).Value = Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-tiny.bin"); };

return MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action);
}

private void initialiseOSCClient(IPAddress sendAddress, int sendPort, IPAddress receiveAddress, int receivePort)
{
try
Expand Down
51 changes: 30 additions & 21 deletions VRCOSC.App/Audio/AudioCapture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using NAudio.CoreAudioApi;
using NAudio.Wave;
using VRCOSC.App.Utils;

namespace VRCOSC.App.Audio;

Expand Down Expand Up @@ -52,26 +53,34 @@ public float[] GetBufferedData()
{
lock (lockObject)
{
var targetWaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(16000, 1);
var inputFormat = capture.WaveFormat;

var bufferArray = buffer.ToArray();
var bytesRecorded = bufferArray.Length;

using var memoryStream = new MemoryStream(bufferArray, 0, bytesRecorded);
using var waveStream = new RawSourceWaveStream(memoryStream, inputFormat);
using var resampler = new MediaFoundationResampler(waveStream, targetWaveFormat);
resampler.ResamplerQuality = 60;

var maxBytesNeeded = (int)(buffer.Length * (targetWaveFormat.SampleRate / (float)inputFormat.SampleRate) * (targetWaveFormat.BitsPerSample / (float)inputFormat.BitsPerSample) * (targetWaveFormat.Channels / (float)inputFormat.Channels));
var resampledBuffer = new byte[maxBytesNeeded];
var bytesRead = resampler.Read(resampledBuffer, 0, maxBytesNeeded);

Array.Resize(ref resampledBuffer, bytesRead);

var floatArray = new float[resampledBuffer.Length / sizeof(float)];
Buffer.BlockCopy(resampledBuffer, 0, floatArray, 0, resampledBuffer.Length);
return floatArray;
try
{
var targetFormat = WaveFormat.CreateIeeeFloatWaveFormat(16000, 1);
var inputFormat = capture.WaveFormat;

var bufferArray = buffer.ToArray();
var bytesRecorded = bufferArray.Length;

using var memoryStream = new MemoryStream(bufferArray, 0, bytesRecorded);
using var waveStream = new RawSourceWaveStream(memoryStream, inputFormat);
using var resampler = new MediaFoundationResampler(waveStream, targetFormat);
resampler.ResamplerQuality = 60;

var maxBytesNeeded = (int)(buffer.Length * (targetFormat.SampleRate / (float)inputFormat.SampleRate) * (targetFormat.BitsPerSample / (float)inputFormat.BitsPerSample) * (targetFormat.Channels / (float)inputFormat.Channels));
var resampledBuffer = new byte[maxBytesNeeded];
var bytesRead = resampler.Read(resampledBuffer, 0, maxBytesNeeded);

Array.Resize(ref resampledBuffer, bytesRead);

var floatArray = new float[resampledBuffer.Length / sizeof(float)];
Buffer.BlockCopy(resampledBuffer, 0, floatArray, 0, resampledBuffer.Length);
return floatArray;
}
catch (Exception e)
{
Logger.Error(e, "The selected microphone has provided bad data");
return Array.Empty<float>();
}
}
}

Expand Down Expand Up @@ -103,4 +112,4 @@ public void SaveConvertedToFile(float[] data, string filePath)
using var waveFileWriter = new WaveFileWriter(filePath, waveFormat);
waveFileWriter.Write(byteArray, 0, byteArray.Length);
}
}
}
9 changes: 6 additions & 3 deletions VRCOSC.App/Audio/Whisper/AudioProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public AudioProcessor(MMDevice device)
catch (Exception e)
{
whisper = null;
ExceptionHandler.Handle(e, "Please make sure the model path for Whisper is correct in the speech settings");
ExceptionHandler.Handle(e, "The Whisper model path is empty or incorrect. Please go into the app's speech settings and restore the model by clicking 'Auto Install Model'");
}

try
Expand All @@ -50,6 +50,7 @@ public AudioProcessor(MMDevice device)
}
catch (Exception e)
{
audioCapture = null;
ExceptionHandler.Handle(e);
}
}
Expand All @@ -59,8 +60,10 @@ public void Start()
speechResult = null;
isProcessing = false;

audioCapture?.ClearBuffer();
audioCapture?.StartCapture();
if (whisper is null || audioCapture is null) return;

audioCapture.ClearBuffer();
audioCapture.StartCapture();
}

public void Stop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ public DateTimeClipVariable(ClipVariableReference reference)
[ClipVariableOption("datetime_format", "Date/Time Format", "How should the date/time be formatted?")]
public string DateTimeFormat { get; set; } = "yyyy/MM/dd HH:mm:ss";

[ClipVariableOption("timezone_id", "Time Zone ID", "What timezone should this date/time be converted to?\nNote: Daylight savings is handled automatically")]
public string TimeZoneID { get; set; } = TimeZoneInfo.Local.Id;
[ClipVariableOption("timezone_id", "Time Zone ID", "What timezone should this date/time be converted to?\nLeave empty for your local timezone\nNote: Daylight savings is handled automatically")]
public string TimeZoneID { get; set; } = string.Empty;

public override bool IsDefault() => base.IsDefault() && DateTimeFormat == "yyyy/MM/dd HH:mm:ss" && TimeZoneID == TimeZoneInfo.Local.Id;
public override bool IsDefault() => base.IsDefault() && DateTimeFormat == "yyyy/MM/dd HH:mm:ss" && TimeZoneID == string.Empty;

public override DateTimeClipVariable Clone()
{
Expand All @@ -41,12 +41,21 @@ protected override string Format(object value)

try
{
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneID);
var convertedDateTime = TimeZoneInfo.ConvertTimeFromUtc(dateTimeValue.UtcDateTime, timeZoneInfo);
DateTime convertedDateTime;

if (string.IsNullOrEmpty(TimeZoneID))
{
convertedDateTime = TimeZoneInfo.ConvertTimeFromUtc(dateTimeValue.UtcDateTime, TimeZoneInfo.Local);
}
else
{
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneID);
convertedDateTime = TimeZoneInfo.ConvertTimeFromUtc(dateTimeValue.UtcDateTime, timeZoneInfo);
}

try
{
return convertedDateTime.ToString(DateTimeFormat, CultureInfo.CurrentCulture);
return convertedDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
}
catch (Exception)
{
Expand Down
23 changes: 10 additions & 13 deletions VRCOSC.App/Router/RouterManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class RouterManager

private bool started;

public RouterManager()
private RouterManager()
{
serialisationManager = new SerialisationManager();
serialisationManager.RegisterSerialiser(1, new RouterManagerSerialiser(AppManager.GetInstance().Storage, this));
Expand All @@ -37,21 +37,18 @@ public void Load()
started = false;
Routes.Clear();

Routes.CollectionChanged += (_, e) =>
serialisationManager.Deserialise();

Routes.OnCollectionChanged((newItems, _) =>
{
if (e.NewItems is not null)
foreach (var newInstance in newItems)
{
foreach (RouterInstance routerInstance in e.NewItems)
{
routerInstance.Name.Subscribe(_ => serialisationManager.Serialise());
routerInstance.Endpoint.Subscribe(_ => serialisationManager.Serialise());
}
newInstance.Name.Subscribe(_ => serialisationManager.Serialise());
newInstance.Endpoint.Subscribe(_ => serialisationManager.Serialise());
}
}, true);

serialisationManager.Serialise();
};

serialisationManager.Deserialise();
Routes.OnCollectionChanged((_, _) => serialisationManager.Serialise());
}

public void Start()
Expand Down Expand Up @@ -108,4 +105,4 @@ private void onParameterReceived(VRChatOscMessage message)
sender.Send(OscEncoder.Encode(message));
}
}
}
}
3 changes: 2 additions & 1 deletion VRCOSC.App/Router/Serialisation/RouterManagerSerialiser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace VRCOSC.App.Router.Serialisation;

public class RouterManagerSerialiser : ProfiledSerialiser<RouterManager, SerialisableRouterManager>
{
protected override string Directory => "configuration";
protected override string FileName => "router.json";

public RouterManagerSerialiser(Storage storage, RouterManager reference)
Expand All @@ -21,4 +22,4 @@ protected override bool ExecuteAfterDeserialisation(SerialisableRouterManager da

return false;
}
}
}
4 changes: 1 addition & 3 deletions VRCOSC.App/SDK/Modules/Heartrate/HeartrateModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

namespace VRCOSC.App.SDK.Modules.Heartrate;

[ModuleType(ModuleType.Health)]
[ModulePrefab("VRCOSC-Heartrate", "https://github.com/VolcanicArts/VRCOSC/releases/download/2024.220.1/VRCOSC-Heartrate-2023.629.0.unitypackage")]
public abstract class HeartrateModule<T> : Module where T : HeartrateProvider
{
protected T? HeartrateProvider;
Expand Down Expand Up @@ -332,4 +330,4 @@ private enum HeartrateVariable
Current,
Average
}
}
}
11 changes: 1 addition & 10 deletions VRCOSC.App/SDK/Modules/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -925,16 +925,7 @@ internal void OnParameterReceived(VRChatOscMessage message)
ExceptionHandler.Handle(e, $"Module {FullID} experienced an exception calling {nameof(OnAnyParameterReceived)}");
}

string? parameterName = null;

foreach (var parameter in Parameters.Values)
{
var match = parameterNameRegex[parameter.Name.Value].Match(receivedParameter.Name);
if (!match.Success) continue;

parameterName = match.Groups[1].Captures[0].Value;
}

var parameterName = Parameters.Values.FirstOrDefault(parameter => parameterNameRegex[parameter.Name.Value].Match(receivedParameter.Name).Success)?.Name.Value;
if (parameterName is null) return;

if (!parameterNameEnum.TryGetValue(parameterName, out var lookup)) return;
Expand Down
49 changes: 49 additions & 0 deletions VRCOSC.App/Startup/Serialisation/SerialisableStartupManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using VRCOSC.App.Serialisation;

namespace VRCOSC.App.Startup.Serialisation;

[JsonObject]
public class SerialisableStartupManager : SerialisableVersion
{
[JsonProperty("instances")]
public List<SerialisableStartupInstance> Instances { get; set; } = [];

[JsonConstructor]
public SerialisableStartupManager()
{
}

public SerialisableStartupManager(StartupManager startupManager)
{
Version = 1;

Instances = startupManager.Instances.Select(startupInstance => new SerialisableStartupInstance(startupInstance)).ToList();
}
}

[JsonObject]
public class SerialisableStartupInstance
{
[JsonProperty("file_location")]
public string FileLocation { get; set; } = string.Empty;

[JsonProperty("arguments")]
public string Arguments { get; set; } = string.Empty;

[JsonConstructor]
public SerialisableStartupInstance()
{
}

public SerialisableStartupInstance(StartupInstance startupInstance)
{
FileLocation = startupInstance.FileLocation.Value;
Arguments = startupInstance.Arguments.Value;
}
}
32 changes: 32 additions & 0 deletions VRCOSC.App/Startup/Serialisation/StartupManagerSerialiser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using VRCOSC.App.Serialisation;
using VRCOSC.App.Utils;

namespace VRCOSC.App.Startup.Serialisation;

public class StartupManagerSerialiser : Serialiser<StartupManager, SerialisableStartupManager>
{
protected override string Directory => "configuration";
protected override string FileName => "startup.json";

public StartupManagerSerialiser(Storage storage, StartupManager reference)
: base(storage, reference)
{
}

protected override bool ExecuteAfterDeserialisation(SerialisableStartupManager data)
{
foreach (var serialisableStartupInstance in data.Instances)
{
Reference.Instances.Add(new StartupInstance
{
FileLocation = { Value = serialisableStartupInstance.FileLocation },
Arguments = { Value = serialisableStartupInstance.Arguments }
});
}

return false;
}
}
Loading

0 comments on commit 690427d

Please sign in to comment.