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 23, 2024
2 parents 690427d + d1568fe commit 664858a
Show file tree
Hide file tree
Showing 71 changed files with 1,481 additions and 609 deletions.
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,9 @@ and act as a centralised source for useful OSC programs so that a user will only
Our framework supports developing your own modules to save you the trouble of having to set up everything yourself,
as well as allowing other people to seamlessly use your module on their PC. See how to create a module [here](https://vrcosc.com/docs/v2/sdk/getting-started).

VRCOSC's powerful ChatBox design system allows you to display what you want, when you want, how you want. Check out the ChatBox-Config forum channel of our [Discord Server](https://discord.gg/vj4brHyvT5) to see some of the configs people have created!
VRCOSC's powerful ChatBox design system allows you to display what you want, when you want, how you want. Check out the ChatBox-Config forum channel of our [Discord Server](https://discord.gg/vj4brHyvT5) to see some of the configs people have created or you can make your own by following [the docs](https://vrcosc.com/docs/v2/chatbox)!
The ChatBox uses a [community-created list](https://github.com/cyberkitsune/chatbox-club-blacklist/blob/master/npblacklist.json) to block the ChatBox from being used in certain worlds. You can turn this off in the app settings, but we recommend you keep it on for respect.

Our powerful ChatBox animation system allows you to display what you want, when you want, how you want.
Community configs are available, or you can make your own by following [the docs](https://vrcosc.com/docs/v2/chatbox)!

Featuring:
- Modern GUI
- Automated configuration management
Expand All @@ -44,8 +41,8 @@ Featuring:

## Getting Started
- Download and run `VRCOSCSetup.exe` from the [Releases](https://github.com/VolcanicArts/VRCOSC/releases/latest) page
- Tick the modules you'd like to use
- (Optional) Download any prefabs you want and add them to your avatar (Guides are available inside each prefab)
- Enable the modules you'd like to use
- (Optional) Download any prefabs you want and add them to your avatar from [here](https://vrcosc.com/docs/downloads#prefabs)
- Press the run button!

Check the [FAQ](https://vrcosc.com/docs/faq) if you have any issues with installing, using the app, or using any of the prefabs.
Expand Down Expand Up @@ -84,4 +81,4 @@ The official modules source code is located [here](https://github.com/VolcanicAr
## License
This program is licensed under the [GNU General Public License V3](https://www.gnu.org/licenses/gpl-3.0.en.html). Please see [the license file](LICENSE) for more information.

Other libraries included in this project may contain different licenses. See the license files in their repos for more information.
Other libraries included in this project may contain different licenses. See the license files in their repos for more information.
31 changes: 12 additions & 19 deletions VRCOSC.App/AppManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
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;
using Module = VRCOSC.App.SDK.Modules.Module;
Expand All @@ -59,7 +58,6 @@ public class AppManager
public Observable<AppManagerState> State { get; } = new(AppManagerState.Stopped);
public Observable<Theme> ProxyTheme { get; } = new(Theme.Dark);

public VelopackUpdater VelopackUpdater = null!;
public ConnectionManager ConnectionManager = null!;
public VRChatOscClient VRChatOscClient = null!;
public VRChatClient VRChatClient = null!;
Expand Down Expand Up @@ -87,7 +85,6 @@ public void Initialise()
{
SettingsManager.GetInstance().GetObservable<Theme>(VRCOSCSetting.Theme).Subscribe(theme => ProxyTheme.Value = theme, true);

VelopackUpdater = new VelopackUpdater();
ConnectionManager = new ConnectionManager();
VRChatOscClient = new VRChatOscClient();
VRChatClient = new VRChatClient(VRChatOscClient);
Expand Down Expand Up @@ -155,16 +152,14 @@ public void InitialLoadComplete()
VRChatOscClient.Init(ConnectionManager);
ConnectionManager.Init();

vrchatCheckTask = new Repeater(checkForVRChatAutoStart);
vrchatCheckTask = new Repeater($"{nameof(AppManager)}-{nameof(checkForVRChatAutoStart)}", checkForVRChatAutoStart);
vrchatCheckTask.Start(TimeSpan.FromSeconds(2));

openvrCheckTask = new Repeater(checkForOpenVR);
openvrCheckTask = new Repeater($"{nameof(AppManager)}-{nameof(checkForOpenVR)}", checkForOpenVR);
openvrCheckTask.Start(TimeSpan.FromSeconds(2));

openvrUpdateTask = new Repeater(updateOVRClient);
openvrUpdateTask = new Repeater($"{nameof(AppManager)}-{nameof(updateOVRClient)}", updateOVRClient);
openvrUpdateTask.Start(TimeSpan.FromSeconds(1d / 60d));

VelopackUpdater.ShowUpdateIfAvailable();
}

private void updateOVRClient()
Expand Down Expand Up @@ -241,9 +236,9 @@ private void processOscMessageQueue()
if (wasPlayerUpdated) ModuleManager.GetInstance().PlayerUpdate();

if (message.ParameterName.StartsWith("VRCOSC/Controls")) handleControlParameter(new ReceivedParameter(message.ParameterName, message.ParameterValue));
}

ModuleManager.GetInstance().ParameterReceived(message);
ModuleManager.GetInstance().ParameterReceived(message);
}
}
}

Expand All @@ -262,7 +257,7 @@ private void sendMetadataParameters()

private void handleControlParameter(ReceivedParameter parameter)
{
if (parameter.Name == "VRCOSC/Controls/ChatBox/Enabled" && parameter.IsValueType<bool>())
if (parameter is { Name: "VRCOSC/Controls/ChatBox/Enabled", Type: ParameterType.Bool })
{
ChatBoxManager.GetInstance().SendEnabled = parameter.GetValue<bool>();
}
Expand Down Expand Up @@ -423,14 +418,14 @@ private async Task startAsync()

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

SpeechEngine.Initialise();
}

updateTask = new Repeater(update);
updateTask = new Repeater($"{nameof(AppManager)}-{nameof(update)}", update);
updateTask.Start(TimeSpan.FromSeconds(1d / 60d));

VRChatOscClient.OnParameterReceived += onParameterReceived;
Expand All @@ -442,14 +437,12 @@ private async Task startAsync()
sendControlParameters();
}

private Task installSpeechModel()
public Task InstallSpeechModel() => Application.Current.Dispatcher.Invoke(() =>
{
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"); };

var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin?download=true"), Storage.GetStorageForDirectory("runtime/whisper"), "ggml-small.bin");
action.OnComplete += () => SettingsManager.GetInstance().GetObservable<string>(VRCOSCSetting.SpeechModelPath).Value = Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-small.bin");
return MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action);
}
});

private void initialiseOSCClient(IPAddress sendAddress, int sendPort, IPAddress receiveAddress, int receivePort)
{
Expand Down
60 changes: 36 additions & 24 deletions VRCOSC.App/Audio/Whisper/AudioProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,67 @@ namespace VRCOSC.App.Audio.Whisper;
internal class AudioProcessor
{
private readonly AudioCapture? audioCapture;
private readonly WhisperProcessor? whisper;
private WhisperProcessor? whisper;

private const int default_samples_to_check = 24000; // sample rate is 16000 so check the last 1.5 seconds of audio

private SpeechResult? speechResult;
private bool isProcessing;

public AudioProcessor(MMDevice device)
{
try
{
audioCapture = new AudioCapture(device);
}
catch (Exception e)
{
audioCapture = null;
ExceptionHandler.Handle(e);
}
}

private void buildWhisperProcessor()
{
var modelFilePath = SettingsManager.GetInstance().GetValue<string>(VRCOSCSetting.SpeechModelPath);

try
{
var builder = WhisperFactory.FromPath(modelFilePath);
var builder = WhisperFactory.FromPath(modelFilePath).CreateBuilder();

whisper = builder.CreateBuilder()
.WithProbabilities()
builder = builder.WithProbabilities()
.WithThreads(8)
.WithNoContext()
.WithSingleSegment()
.WithMaxSegmentLength(int.MaxValue)
.Build();
.WithLanguageDetection();

if (SettingsManager.GetInstance().GetValue<bool>(VRCOSCSetting.SpeechTranslate))
{
builder.WithLanguage("en");
}

// translation for any-to-any technically works but is unsupported
// for future reference, to get any-to-any to work you have to do:
// WithLanguageDetection
// WithLanguage(targetLanguage)

whisper = builder.Build();
}
catch (Exception e)
{
whisper = null;
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
{
audioCapture = new AudioCapture(device);
}
catch (Exception e)
{
audioCapture = null;
ExceptionHandler.Handle(e);
}
}

public void Start()
{
speechResult = null;
isProcessing = false;

buildWhisperProcessor();

if (whisper is null || audioCapture is null) return;

audioCapture.ClearBuffer();
Expand All @@ -69,6 +85,8 @@ public void Start()
public void Stop()
{
audioCapture?.StopCapture();
whisper?.Dispose();
whisper = null;
}

public async Task<SpeechResult?> GetResultAsync()
Expand All @@ -95,9 +113,7 @@ public void Stop()

if (finalSpeechResult is not null)
{
#if DEBUG
Logger.Log($"Final result: {finalSpeechResult.Text} - {finalSpeechResult.Confidence}");
#endif
Logger.Log($"Final result: {finalSpeechResult.Text} - {finalSpeechResult.Confidence}", LoggingTarget.Information);
}

speechResult = null;
Expand All @@ -110,9 +126,7 @@ public void Stop()
speechResult = await processWithWhisper(data, false);
isProcessing = false;

#if DEBUG
Logger.Log($"Result: {speechResult?.Text}");
#endif
Logger.Log($"Result: {speechResult?.Text}", LoggingTarget.Information);

return speechResult;
}
Expand Down Expand Up @@ -150,9 +164,7 @@ private bool isSilent(float[] buffer)
}

var rms = Math.Sqrt(sum / samplesToCheck);
#if DEBUG
Logger.Log($"RMS: {rms}");
#endif
Logger.Log($"RMS: {rms}", LoggingTarget.Information);
return rms < SettingsManager.GetInstance().GetValue<float>(VRCOSCSetting.SpeechNoiseCutoff);
}

Expand Down
4 changes: 2 additions & 2 deletions VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private async void onCaptureDeviceIdChanged(string newDeviceId)
audioProcessor = new AudioProcessor(captureDevice);
audioProcessor.Start();

repeater = new Repeater(processResult);
repeater = new Repeater($"{nameof(WhisperSpeechEngine)}-{nameof(processResult)}", processResult);
// Do not change this from 1.5
repeater.Start(TimeSpan.FromSeconds(1.5f));
}
Expand All @@ -81,7 +81,7 @@ private async void processResult()
if (result is null) return;

// filter out things like [BLANK AUDIO]
if (result.Text.StartsWith('[') || result.Text.Contains('*')) return;
if (result.Text.StartsWith('[')) return;

var requiredConfidence = SettingsManager.GetInstance().GetValue<float>(VRCOSCSetting.SpeechConfidence);
if (result.Confidence < requiredConfidence) return;
Expand Down
4 changes: 2 additions & 2 deletions VRCOSC.App/ChatBox/ChatBoxManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ public void Start()
var sendInterval = SettingsManager.GetInstance().GetValue<int>(VRCOSCSetting.ChatBoxSendInterval);

startTime = DateTimeOffset.Now;
sendTask = new Repeater(chatBoxUpdate);
updateTask = new Repeater(update);
sendTask = new Repeater($"{nameof(ChatBoxManager)}-{nameof(chatBoxUpdate)}", chatBoxUpdate);
updateTask = new Repeater($"{nameof(ChatBoxManager)}-{nameof(update)}", update);
SendEnabled = true;
isClear = true;
currentIsTyping = false;
Expand Down
10 changes: 8 additions & 2 deletions VRCOSC.App/ChatBox/Clips/Clip.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using System;
Expand All @@ -10,6 +10,7 @@
using System.Runtime.CompilerServices;
using VRCOSC.App.ChatBox.Clips.Variables;
using VRCOSC.App.Modules;
using VRCOSC.App.Settings;
using VRCOSC.App.Utils;

namespace VRCOSC.App.ChatBox.Clips;
Expand Down Expand Up @@ -47,7 +48,7 @@ public Dictionary<string, List<ClipVariableReference>> UIVariables
ChatBoxManager.GetInstance().VariableReferences.Where(clipVariableReference => clipVariableReference.ModuleID is null).OrderBy(reference => reference.DisplayName.Value).ForEach(clipVariableReference => builtInVariables.Add(clipVariableReference));
finalDict.Add("Built-In", builtInVariables);

var modules = LinkedModules.Select(moduleID => ModuleManager.GetInstance().GetModuleOfID(moduleID)).OrderBy(module => module.Title);
var modules = LinkedModules.Select(moduleID => ModuleManager.GetInstance().GetModuleOfID(moduleID)).Where(module => module.Enabled.Value || !SettingsManager.GetInstance().GetValue<bool>(VRCOSCSetting.FilterByEnabledModules)).OrderBy(module => module.Title);

foreach (var module in modules)
{
Expand All @@ -58,6 +59,11 @@ public Dictionary<string, List<ClipVariableReference>> UIVariables
}
}

public void UpdateUI()
{
OnPropertyChanged(nameof(UIVariables));
}

public bool HasStates => States.Any();
public bool HasEvents => Events.Any();

Expand Down
25 changes: 17 additions & 8 deletions VRCOSC.App/ChatBox/Clips/ClipEvent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using System;
Expand Down Expand Up @@ -47,13 +47,22 @@ public override bool ShouldBeVisible
{
get
{
if (!SettingsManager.GetInstance().GetValue<bool>(VRCOSCSetting.ShowRelevantElementsOnly)) return true;

var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip;
Debug.Assert(selectedClip is not null);

var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID);
return enabledModuleIDs.Contains(ModuleID);
if (SettingsManager.GetInstance().GetValue<bool>(VRCOSCSetting.FilterByEnabledModules))
{
var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip;
Debug.Assert(selectedClip is not null);

var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID);
return enabledModuleIDs.Contains(ModuleID);
}
else
{
var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip;
Debug.Assert(selectedClip is not null);

var enabledModuleIDs = ModuleManager.GetInstance().Modules.Values.SelectMany(moduleList => moduleList).Where(module => selectedClip.LinkedModules.Contains(module.FullID)).Select(module => module.FullID).OrderBy(moduleID => moduleID);
return enabledModuleIDs.Contains(ModuleID);
}
}
}

Expand Down
26 changes: 18 additions & 8 deletions VRCOSC.App/ChatBox/Clips/ClipState.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using System;
Expand Down Expand Up @@ -54,14 +54,24 @@ public override bool ShouldBeVisible
{
if (IsBuiltIn) return true;

if (!SettingsManager.GetInstance().GetValue<bool>(VRCOSCSetting.ShowRelevantElementsOnly)) return true;

var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip;
Debug.Assert(selectedClip is not null);
if (SettingsManager.GetInstance().GetValue<bool>(VRCOSCSetting.FilterByEnabledModules))
{
var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip;
Debug.Assert(selectedClip is not null);

var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID);
var clipStateModuleIDs = States.Select(pair => pair.Key).OrderBy(s => s);
return enabledModuleIDs.SequenceEqual(clipStateModuleIDs);
}
else
{
var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip;
Debug.Assert(selectedClip is not null);

var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID);
var clipStateModuleIDs = States.Select(pair => pair.Key).OrderBy(s => s);
return enabledModuleIDs.SequenceEqual(clipStateModuleIDs);
var enabledModuleIDs = ModuleManager.GetInstance().Modules.Values.SelectMany(moduleList => moduleList).Where(module => selectedClip.LinkedModules.Contains(module.FullID)).Select(module => module.FullID).OrderBy(moduleID => moduleID);
var clipStateModuleIDs = States.Select(pair => pair.Key).OrderBy(s => s);
return enabledModuleIDs.SequenceEqual(clipStateModuleIDs);
}
}
}

Expand Down
Loading

0 comments on commit 664858a

Please sign in to comment.