From 080688c641de9865883d40fef055d640a7738ac4 Mon Sep 17 00:00:00 2001 From: Marc-Aurel Zent Date: Fri, 24 Mar 2023 15:54:36 +0100 Subject: [PATCH] implement windows TTS --- IINACT/IINACT.csproj | 1 + IINACT/Plugin.cs | 5 +-- IINACT/TextToSpeechProvider.cs | 65 ++++++++++++++++++++++++++++++++++ IINACT/packages.lock.json | 15 +++++++- NotACT/FormActMain.cs | 39 +++----------------- 5 files changed, 88 insertions(+), 37 deletions(-) create mode 100644 IINACT/TextToSpeechProvider.cs diff --git a/IINACT/IINACT.csproj b/IINACT/IINACT.csproj index 22df29e..f8ffe91 100644 --- a/IINACT/IINACT.csproj +++ b/IINACT/IINACT.csproj @@ -36,6 +36,7 @@ + $(DalamudLibPath)FFXIVClientStructs.dll false diff --git a/IINACT/Plugin.cs b/IINACT/Plugin.cs index 36e18f1..5069ebb 100644 --- a/IINACT/Plugin.cs +++ b/IINACT/Plugin.cs @@ -28,7 +28,7 @@ public sealed class Plugin : IDalamudPlugin [PluginService] internal static ChatGui ChatGui { get; private set; } = null!; // ReSharper restore UnusedAutoPropertyAccessor.Local internal Configuration Configuration { get; init; } - + private TextToSpeechProvider TextToSpeechProvider { get; init; } private ConfigWindow ConfigWindow { get; init; } private MainWindow MainWindow { get; init; } internal FileDialogManager FileDialogManager { get; init; } @@ -51,7 +51,8 @@ public Plugin() Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); Configuration.Initialize(PluginInterface); - + + this.TextToSpeechProvider = new TextToSpeechProvider(); Advanced_Combat_Tracker.ActGlobals.oFormActMain.LogFilePath = Configuration.LogFilePath; FfxivActPluginWrapper = new FfxivActPluginWrapper(Configuration, DataManager.Language, ChatGui); diff --git a/IINACT/TextToSpeechProvider.cs b/IINACT/TextToSpeechProvider.cs new file mode 100644 index 0000000..00e8846 --- /dev/null +++ b/IINACT/TextToSpeechProvider.cs @@ -0,0 +1,65 @@ +using System.Diagnostics; +using System.Speech.Synthesis; +using System.Text.RegularExpressions; + +namespace IINACT; + +internal class TextToSpeechProvider +{ + private string binary = "/usr/bin/say"; + private string args = ""; + private readonly object speechLock = new(); + private readonly SpeechSynthesizer speechSynthesizer = new(); + + public TextToSpeechProvider() + { + speechSynthesizer.SetOutputToDefaultAudioDevice(); + Advanced_Combat_Tracker.ActGlobals.oFormActMain.TextToSpeech += Speak; + } + + public void Speak(string message) + { + if (new FileInfo(binary).Exists) + { + try + { + var ttsProcess = new Process + { + StartInfo = + { + FileName = binary, + CreateNoWindow = true, + UseShellExecute = false, + Arguments = args + " \"" + + Regex.Replace(Regex.Replace(message, @"(\\*)" + "\"", @"$1$1\" + "\""), + @"(\\+)$", @"$1$1") + "\"" + } + }; + lock (speechLock) + { + ttsProcess.Start(); + // heuristic pause + Thread.Sleep(500 * message.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length); + } + + return; + } + catch (Exception ex) + { + Trace.WriteLine(ex, $"TTS failed to play back {message} with exception {ex.Message}"); + return; + } + } + + try + { + lock (speechLock) + speechSynthesizer.Speak(message); + } + catch (Exception ex) + { + Trace.WriteLine(ex, $"TTS failed to play back {message} with exception {ex.Message}"); + } + + } +} diff --git a/IINACT/packages.lock.json b/IINACT/packages.lock.json index e541388..9e34705 100644 --- a/IINACT/packages.lock.json +++ b/IINACT/packages.lock.json @@ -8,6 +8,12 @@ "resolved": "2.1.10", "contentHash": "S6NrvvOnLgT4GDdgwuKVJjbFo+8ZEj+JsEYk9ojjOR/MMfv1dIFpT8aRJQfI24rtDcw1uF+GnSSMN4WW1yt7fw==" }, + "System.Speech": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "7E0uB92Cx2sXR67HW9rMKJqDACdLuz9t3I3OwZUFDzAgwKXWuY6CYeRT/NiypHcyZO2be9+0H0w0M6fn7HQtgQ==" + }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.7.0", @@ -64,6 +70,13 @@ } } }, - "net7.0-windows7.0/win-x64": {} + "net7.0-windows7.0/win-x64": { + "System.Speech": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "7E0uB92Cx2sXR67HW9rMKJqDACdLuz9t3I3OwZUFDzAgwKXWuY6CYeRT/NiypHcyZO2be9+0H0w0M6fn7HQtgQ==" + } + } } } \ No newline at end of file diff --git a/NotACT/FormActMain.cs b/NotACT/FormActMain.cs index e668317..2fd2051 100644 --- a/NotACT/FormActMain.cs +++ b/NotACT/FormActMain.cs @@ -14,7 +14,6 @@ public partial class FormActMain : Form, ISynchronizeInvoke public delegate DateTime DateTimeLogParser(string logLine); private readonly ConcurrentQueue afterActionsQueue = new(); - private readonly object ttsLock = new(); private Thread afterActionQueueThread; public DateTimeLogParser GetDateTimeFromLog; private volatile bool inCombat; @@ -114,6 +113,10 @@ public event LogFileChangedDelegate LogFileChanged public event CombatActionDelegate AfterCombatAction; + public delegate void TextToSpeechDelegate(string text); + + public event TextToSpeechDelegate TextToSpeech; + public void WriteExceptionLog(Exception ex, string MoreInfo) { @@ -140,39 +143,7 @@ public void ParseRawLogLine(string logLine) } - public void TTS(string message, string binary = "/usr/bin/say", string args = "") - { - lock (ttsLock) - { - if (new FileInfo(binary).Exists) - { - try - { - var ttsProcess = new Process - { - StartInfo = - { - FileName = binary, - CreateNoWindow = true, - UseShellExecute = false, - Arguments = args + " \"" + - Regex.Replace(Regex.Replace(message, @"(\\*)" + "\"", @"$1$1\" + "\""), - @"(\\+)$", @"$1$1") + "\"" - } - }; - ttsProcess.Start(); - } - catch (Exception ex) - { - WriteExceptionLog(ex, $"TTS failed to play back {message}"); - } - } - else - Trace.WriteLine($"TTS binary {binary} not found"); - - Thread.Sleep(500 * message.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length); - } - } + public void TTS(string message) => TextToSpeech(message); public void ChangeZone(string ZoneName) {