diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index df0019849..7c0135a2d 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -2,157 +2,156 @@ name: .NET Build + Test + Publish on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: build: - runs-on: windows-latest steps: - - uses: actions/checkout@v4 - - name: Download config repo - uses: actions/checkout@v4 - with: - repository: TheTrackerCouncil/SMZ3CasConfigs - path: configs - ref: main - - name: Download sprite repo - uses: actions/checkout@v4 - with: - repository: TheTrackerCouncil/SMZ3CasSprites - path: sprites - ref: main - - name: Download tracker sprite repo - uses: actions/checkout@v4 - with: - repository: TheTrackerCouncil/TrackerSprites - path: trackersprites - ref: main - - name: Download git trees - if: ${{ github.event_name != 'pull_request' }} - shell: pwsh - env: + - uses: actions/checkout@v4 + - name: Download config repo + uses: actions/checkout@v4 + with: + repository: TheTrackerCouncil/SMZ3CasConfigs + path: configs + ref: main + - name: Download sprite repo + uses: actions/checkout@v4 + with: + repository: TheTrackerCouncil/SMZ3CasSprites + path: sprites + ref: main + - name: Download tracker sprite repo + uses: actions/checkout@v4 + with: + repository: TheTrackerCouncil/TrackerSprites + path: trackersprites + ref: main + - name: Download git trees + if: ${{ github.event_name != 'pull_request' }} + shell: pwsh + env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - $headers = @{ - Authorization="Bearer $Env:GH_TOKEN" - } - Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/SMZ3CasSprites/git/trees/main?recursive=1 -OutFile sprites/Sprites/sprites.json -Headers $headers - Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/TrackerSprites/git/trees/main?recursive=1 -OutFile trackersprites/tracker-sprites.json -Headers $headers - Remove-Item -LiteralPath "trackersprites/.git" -Force -Recurse - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore -p:PostBuildEvent= - - name: Test - run: dotnet test --no-build --verbosity normal - - name: Publish Windows 64bit - if: ${{ github.event_name != 'pull_request' }} - run: dotnet publish --os win --arch x64 -c Release --self-contained false src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj - - name: Publish Windows 32bit - if: ${{ github.event_name != 'pull_request' }} - run: dotnet publish --os win --arch x86 -c Release --self-contained false src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj - - name: Get version number - if: ${{ github.event_name != 'pull_request' }} - id: version - run: | - $version = (Get-Item "src\TrackerCouncil.Smz3.UI\bin\Release\net8.0\win-x86\publish\SMZ3CasRandomizer.exe").VersionInfo.ProductVersion - $version = $version -replace "\+.*", "" - Write-Output "number=$version" >> $env:GITHUB_OUTPUT - shell: pwsh - - name: Publish Linux 64bit - if: ${{ github.event_name != 'pull_request' }} - run: dotnet publish --os linux --arch x64 -c Release --self-contained false src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj - - name: Publish Multiplayer Server - if: ${{ github.event_name != 'pull_request' }} - run: dotnet publish -c Release --self-contained false src//TrackerCouncil.Smz3.Multiplayer.Server//TrackerCouncil.Smz3.Multiplayer.Server.csproj - - name: Building the Windows installer - if: ${{ github.event_name != 'pull_request' }} - run: "\"%programfiles(x86)%/Inno Setup 6/iscc.exe\" \"setup/randomizer.app.iss\"" - shell: cmd - - name: Building the Linux 64bit package - if: ${{ github.event_name != 'pull_request' }} - working-directory: setup - run: "./LinuxBuildZipper.ps1" - shell: pwsh - - name: Building the Multiplayer Server package - if: ${{ github.event_name != 'pull_request' }} - working-directory: setup - run: "./MultiplayerServerZipper.ps1" - shell: pwsh - - name: Upload artifact - uses: actions/upload-artifact@v4 - if: ${{ github.event_name != 'pull_request' }} - with: - path: "setup/Output/*" - name: SMZ3CasRandomizer_${{ steps.version.outputs.number }} + run: | + $headers = @{ + Authorization="Bearer $Env:GH_TOKEN" + } + Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/SMZ3CasSprites/git/trees/main?recursive=1 -OutFile sprites/Sprites/sprites.json -Headers $headers + Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/TrackerSprites/git/trees/main?recursive=1 -OutFile trackersprites/tracker-sprites.json -Headers $headers + Remove-Item -LiteralPath "trackersprites/.git" -Force -Recurse + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore -p:PostBuildEvent= + - name: Test + run: dotnet test --no-build --verbosity normal + - name: Publish Windows 64bit + if: ${{ github.event_name != 'pull_request' }} + run: dotnet publish --os win --arch x64 -c Release --self-contained false src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj + - name: Publish Windows 32bit + if: ${{ github.event_name != 'pull_request' }} + run: dotnet publish --os win --arch x86 -c Release --self-contained false src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj + - name: Get version number + if: ${{ github.event_name != 'pull_request' }} + id: version + run: | + $version = (Get-Item "src\TrackerCouncil.Smz3.UI\bin\Release\net8.0\win-x86\publish\SMZ3CasRandomizer.exe").VersionInfo.ProductVersion + $version = $version -replace "\+.*", "" + Write-Output "number=$version" >> $env:GITHUB_OUTPUT + shell: pwsh + - name: Publish Linux 64bit + if: ${{ github.event_name != 'pull_request' }} + run: dotnet publish --os linux --arch x64 -c Release --self-contained false src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj + - name: Publish Multiplayer Server + if: ${{ github.event_name != 'pull_request' }} + run: dotnet publish -c Release --self-contained false src//TrackerCouncil.Smz3.Multiplayer.Server//TrackerCouncil.Smz3.Multiplayer.Server.csproj + - name: Building the Windows installer + if: ${{ github.event_name != 'pull_request' }} + run: '"%programfiles(x86)%/Inno Setup 6/iscc.exe" "setup/randomizer.app.iss"' + shell: cmd + - name: Building the Linux 64bit package + if: ${{ github.event_name != 'pull_request' }} + working-directory: setup + run: "./LinuxBuildZipper.ps1" + shell: pwsh + - name: Building the Multiplayer Server package + if: ${{ github.event_name != 'pull_request' }} + working-directory: setup + run: "./MultiplayerServerZipper.ps1" + shell: pwsh + - name: Upload artifact + uses: actions/upload-artifact@v4 + if: ${{ github.event_name != 'pull_request' }} + with: + path: "setup/Output/*" + name: SMZ3CasRandomizer_${{ steps.version.outputs.number }} build-mac: runs-on: macos-latest if: ${{ github.event_name != 'pull_request' }} steps: - - uses: actions/checkout@v4 - - name: Download config repo - uses: actions/checkout@v4 - with: - repository: TheTrackerCouncil/SMZ3CasConfigs - path: configs - ref: main - - name: Download sprite repo - uses: actions/checkout@v4 - with: - repository: TheTrackerCouncil/SMZ3CasSprites - path: sprites - ref: main - - name: Download tracker sprite repo - uses: actions/checkout@v4 - with: - repository: TheTrackerCouncil/TrackerSprites - path: trackersprites - ref: main - - name: Download git trees - if: ${{ github.event_name != 'pull_request' }} - shell: pwsh - env: + - uses: actions/checkout@v4 + - name: Download config repo + uses: actions/checkout@v4 + with: + repository: TheTrackerCouncil/SMZ3CasConfigs + path: configs + ref: main + - name: Download sprite repo + uses: actions/checkout@v4 + with: + repository: TheTrackerCouncil/SMZ3CasSprites + path: sprites + ref: main + - name: Download tracker sprite repo + uses: actions/checkout@v4 + with: + repository: TheTrackerCouncil/TrackerSprites + path: trackersprites + ref: main + - name: Download git trees + if: ${{ github.event_name != 'pull_request' }} + shell: pwsh + env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - $headers = @{ - Authorization="Bearer $Env:GH_TOKEN" - } - Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/SMZ3CasSprites/git/trees/main?recursive=1 -OutFile sprites/Sprites/sprites.json -Headers $headers - Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/TrackerSprites/git/trees/main?recursive=1 -OutFile trackersprites/tracker-sprites.json -Headers $headers - Remove-Item -LiteralPath "trackersprites/.git" -Force -Recurse - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj - - name: Build - run: dotnet build --no-restore -p:PostBuildEvent= src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj - - name: Publish - run: dotnet publish -r osx-arm64 --configuration Release -p:UseAppHost=true src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj - - name: Get version number - id: version - run: | - $version = (Get-Item "src\TrackerCouncil.Smz3.UI\bin\Release\net8.0\osx-arm64\publish\SMZ3CasRandomizer.dll").VersionInfo.ProductVersion - $version = $version -replace "\+.*", "" - $versionShort = $version -replace "\-.*", "" - (Get-Content setup/Info.plist).Replace('%FULL_VERSION%', $version).Replace('%SHORT_VERSION%', $versionShort) | Set-Content setup/Info.plist - Write-Output "number=$version" >> $env:GITHUB_OUTPUT - shell: pwsh - - name: Prepare packaging script - run: | - chmod +x ./setup/package-macos-app.sh - ./setup/package-macos-app.sh "${{ steps.version.outputs.number }}" - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - path: "setup/output/*" - name: SMZ3CasRandomizerMacOS_${{ steps.version.outputs.number }} \ No newline at end of file + run: | + $headers = @{ + Authorization="Bearer $Env:GH_TOKEN" + } + Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/SMZ3CasSprites/git/trees/main?recursive=1 -OutFile sprites/Sprites/sprites.json -Headers $headers + Invoke-RestMethod -Uri https://api.github.com/repos/TheTrackerCouncil/TrackerSprites/git/trees/main?recursive=1 -OutFile trackersprites/tracker-sprites.json -Headers $headers + Remove-Item -LiteralPath "trackersprites/.git" -Force -Recurse + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj + - name: Build + run: dotnet build --no-restore -p:PostBuildEvent= src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj + - name: Publish + run: dotnet publish -r osx-arm64 --configuration Release -p:UseAppHost=true src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj + - name: Get version number + id: version + run: | + $version = (Get-Item "src\TrackerCouncil.Smz3.UI\bin\Release\net8.0\osx-arm64\publish\SMZ3CasRandomizer.dll").VersionInfo.ProductVersion + $version = $version -replace "\+.*", "" + $versionShort = $version -replace "\-.*", "" + (Get-Content setup/Info.plist).Replace('%FULL_VERSION%', $version).Replace('%SHORT_VERSION%', $versionShort) | Set-Content setup/Info.plist + Write-Output "number=$version" >> $env:GITHUB_OUTPUT + shell: pwsh + - name: Prepare packaging script + run: | + chmod +x ./setup/package-macos-app.sh + ./setup/package-macos-app.sh "${{ steps.version.outputs.number }}" + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + path: "setup/output/*" + name: SMZ3CasRandomizerMacOS_${{ steps.version.outputs.number }} diff --git a/README.md b/README.md index ba7854e1d..34b06e899 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,24 @@ In addition to making IBJ completely optional, there is also: - Sprites made by members of [Diabetus’](https://twitch.tv/the_betus) community and others; > [!NOTE] -> Voice recognition and text-to-speech functionality is currently only available on Windows. +> Voice recognition and text-to-speech functionality currently only has native support available on Windows. Linux users can use the [PySpeechService application](https://github.com/MattEqualsCoder/PySpeechService) for voice recognition and text-to-speech. ## Installation ### Windows - Download the latest version from the [GitHub releases] and run the installer + ### Linux - Install [.NET 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) - Download the latest version from the [GitHub releases] and extract into the desired folder - Make the SMZ3CasRandomizer file executable and run it + - (Optional) Download or install the [PySpeechService application](https://github.com/MattEqualsCoder/PySpeechService) for voice recognition and text-to-speech + - Note that if you want custom Piper voices, you will need to add the following [Piper speech files](https://huggingface.co/rhasspy/piper-voices/tree/main/en) to `~/.local/share/SMZ3CasRandomizer/PiperModels/`: + - Tracker_Female.json + - Tracker_Female.onnx + - Tracker_Male.json + - Tracker_Male.onnx + ### Mac - Install [.NET 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) - Download the latest version from the [GitHub releases] and move to the desired folder diff --git a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs index af61394f1..852c74345 100644 --- a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs +++ b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs @@ -35,6 +35,7 @@ public static class Program (typeof(RewardConfig), "rewards.json"), (typeof(RoomConfig), "rooms.json"), (typeof(UIConfig), "ui.json"), + (typeof(MetadataConfig), "metadata.json"), }; private static IServiceProvider? _services; @@ -219,6 +220,12 @@ private static void CreateTemplates(string outputPath) var templateUIConfig = new UIConfig() { new("Layout Name", new List { new() { Identifiers = new List { "" }, Column = 1, Row = 1 } }) }; var exampleUIConfig = UIConfig.Example(); WriteTemplate(templatePath, "ui", templateUIConfig, exampleUIConfig); + + // Metadata Template + var templateMetadataConfig = new MetadataConfig() { }; + PopulateExample(templateResponseConfig, true); + var exampleMetadataConfig = MetadataConfig.Example(); + WriteTemplate(templatePath, "metadata", templateMetadataConfig, exampleMetadataConfig); } private static void WriteTemplate(string templatePath, string type, object data, object? example) diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs index 2851185f6..71314f8b1 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs @@ -37,8 +37,20 @@ public static BossConfig Default() new BossInfo("Ridley") { Type = BossType.Ridley, MemoryAddress = 2, MemoryFlag = 0x1, }, new BossInfo("Mother Brain") { Type = BossType.MotherBrain }, new BossInfo("Bomb Torizo") { MemoryAddress = 0, MemoryFlag = 0x4, }, - new BossInfo("Golden Torizo") { MemoryAddress = 2, MemoryFlag = 0x4, } - + new BossInfo("Golden Torizo") { MemoryAddress = 2, MemoryFlag = 0x4, }, + new BossInfo("Castle Guard") { Type = BossType.CastleGuard }, + new BossInfo("Armos Knights") { Type = BossType.ArmosKnights }, + new BossInfo("Moldorm") { Type = BossType.Moldorm }, + new BossInfo("Lanmolas") { Type = BossType.Lanmolas }, + new BossInfo("Agahnim") { Type = BossType.Agahnim }, + new BossInfo("Helmasaur King") { Type = BossType.HelmasaurKing }, + new BossInfo("Arrghus") { Type = BossType.Arrghus }, + new BossInfo("Blind") { Type = BossType.Blind }, + new BossInfo("Mothula") { Type = BossType.Mothula }, + new BossInfo("Kholdstare") { Type = BossType.Kholdstare }, + new BossInfo("Vitreous") { Type = BossType.Vitreous }, + new BossInfo("Trinexx") { Type = BossType.Trinexx }, + new BossInfo("Ganon") { Type = BossType.Ganon }, ]; } diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/MetadataConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/MetadataConfig.cs new file mode 100644 index 000000000..5328df7c8 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/MetadataConfig.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; + +/// +/// Config file for misc settings that can be updated via config update or +/// don't fit with other configs +/// +[Description("Config file for misc settings that can be updated via config update or " + + "don't fit with other configs")] +public class MetadataConfig : IMergeable, IConfigFile +{ + public Dictionary PySpeechRecognitionReplacements { get; set; } = []; + + /// + /// Returns default config + /// + /// + public static MetadataConfig Default() + { + return new MetadataConfig(); + } + + public static object Example() + { + return new MetadataConfig() + { + PySpeechRecognitionReplacements = new Dictionary { { "brin star", "brinstar" } } + }; + } +} diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs index 7ffd1f7ae..8a04db6cc 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs @@ -206,6 +206,15 @@ public virtual MsuConfig GetMsuConfig(IReadOnlyCollection profiles, stri public virtual HintTileConfig GetHintTileConfig(IReadOnlyCollection profiles, string? mood) => LoadYamlConfigs("hint_tiles.yml", profiles, mood); + /// + /// Returns the configs with misc metadata and other configs + /// + /// The selected tracker profile(s) to load + /// The current tracker mood to pick the specific mood file + /// + public virtual MetadataConfig GetMetadataConfig(IReadOnlyCollection profiles, string? mood) => + LoadYamlConfigs("metadata.yml", profiles, mood); + /// /// Returns a collection of all possible config profiles to /// select from diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs index 83ee500e6..d1492dc2a 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs @@ -95,6 +95,12 @@ public static IServiceCollection AddConfigs(this IServiceCollection services) return configs.HintTileConfig; }); + services.AddScoped(serviceProvider => + { + var configs = serviceProvider.GetRequiredService(); + return configs.MetadataConfig; + }); + return services; } } diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs index e1fdc12e2..37eebf71b 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using Newtonsoft.Json; using TrackerCouncil.Smz3.Shared.Enums; using YamlDotNet.Serialization; @@ -7,10 +8,6 @@ namespace TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; /// /// Represents a boss whose defeat can be tracked. /// -/// -/// This class is typically only used for tracking bosses not already -/// represented by , e.g. Metroid bosses. -/// public class BossInfo : IMergeable { public BossInfo() diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/LocationInfo.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/LocationInfo.cs index b02d4fabb..d3528dad1 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/LocationInfo.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/LocationInfo.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; using TrackerCouncil.Smz3.Shared.Enums; diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/RewardInfo.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/RewardInfo.cs index ac6893c71..51419eeb6 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/RewardInfo.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/RewardInfo.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Shared.Enums; diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs b/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs index 01657569a..33f174de1 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs @@ -51,6 +51,7 @@ public void LoadConfigs() GameLines = _configProvider.GetGameConfig(profiles, CurrentMood); MsuConfig = _configProvider.GetMsuConfig(profiles, CurrentMood); HintTileConfig = _configProvider.GetHintTileConfig(profiles, CurrentMood); + MetadataConfig = _configProvider.GetMetadataConfig(profiles, CurrentMood); } /// @@ -152,4 +153,9 @@ public void LoadConfigs() /// Gets the hint tile config /// public HintTileConfig HintTileConfig { get; private set; } = null!; + + /// + /// Gets the metadata config + /// + public MetadataConfig MetadataConfig { get; private set; } = null!; } diff --git a/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs b/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs index c58a8d12a..fd4a82239 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs @@ -49,6 +49,9 @@ public class GeneralOptions : INotifyPropertyChanged [Range(0.0, 1.0)] public float TrackerConfidenceSassThreshold { get; set; } = 0.92f; + [Range(0, 100)] + public int TextToSpeechVolume { get; set; } = 100; + public byte[] TrackerBGColor { get; set; } = [0xFF, 0x21, 0x21, 0x21]; public bool TrackerShadows { get; set; } = true; @@ -256,6 +259,7 @@ public bool Validate() MinimumRecognitionConfidence = TrackerRecognitionThreshold, MinimumExecutionConfidence = TrackerConfidenceThreshold, MinimumSassConfidence = TrackerConfidenceSassThreshold, + TextToSpeechVolume = TextToSpeechVolume, HintsEnabled = TrackerHintsEnabled, SpoilersEnabled = TrackerSpoilersEnabled, UserName = TwitchChannel, diff --git a/src/TrackerCouncil.Smz3.Data/Options/SpeechRecognitionMode.cs b/src/TrackerCouncil.Smz3.Data/Options/SpeechRecognitionMode.cs index e8fe04730..dbb897b1c 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/SpeechRecognitionMode.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/SpeechRecognitionMode.cs @@ -10,5 +10,8 @@ public enum SpeechRecognitionMode [Description("Push-to-talk")] PushToTalk, + [Description("PySpeechService application")] + PySpeechService, + Disabled } diff --git a/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs b/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs index 228bdf890..08fec2a8e 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs @@ -34,6 +34,12 @@ public record TrackerOptions /// public float MinimumSassConfidence { get; set; } = 0.92f; + /// + /// Gets or sets the volume of the text to speech engine, with 0 + /// being silent and 100 being the default max valume. + /// + public int TextToSpeechVolume { get; set; } = 100; + /// /// Gets or sets a value indicating whether Tracker can give hints when /// asked about an item or location. diff --git a/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs b/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs index 5a9dae786..dc4f002c3 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs @@ -14,6 +14,7 @@ namespace TrackerCouncil.Smz3.Data.Services; /// Service for loading tracker speech sprites /// /// +/// public class TrackerSpriteService(OptionsFactory optionsFactory, ILogger logger) { private List _packs = []; diff --git a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs index 8eaf44902..9ef1460e5 100644 --- a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using DynamicForms.Library.Core; using DynamicForms.Library.Core.Attributes; using MSURandomizerLibrary; @@ -12,36 +13,45 @@ namespace TrackerCouncil.Smz3.Data.ViewModels; [DynamicFormGroupBasic(DynamicFormLayout.TwoColumns, "Bottom")] public class OptionsWindowTrackerOptions { - [DynamicFormFieldColorPicker(label: "Tracker background color:")] + [DynamicFormFieldColorPicker(order: 0, label: "Tracker background color:")] public byte[] TrackerBGColor { get; set; } = [0xFF, 0x21, 0x21, 0x21]; - [DynamicFormFieldCheckBox(checkBoxText: "Render shadows", alignment: DynamicFormAlignment.Right)] + [DynamicFormFieldCheckBox(order: 1, checkBoxText: "Render shadows", alignment: DynamicFormAlignment.Right)] public bool TrackerShadows { get; set; } = true; - [DynamicFormFieldComboBox(label: "Tracker speech window image pack:", - comboBoxOptionsProperty: nameof(TrackerSpeechImagePacks))] + [DynamicFormFieldComboBox(order: 2, label: "Tracker speech window image pack:", + comboBoxOptionsProperty: nameof(TrackerSpeechImagePacks), platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public string TrackerSpeechImagePack { get; set; } = "Default"; - [DynamicFormFieldColorPicker(label: "Tracker speech window color:")] + [DynamicFormFieldColorPicker(order: 3, label: "Tracker speech window color:", platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public byte[] TrackerSpeechBGColor { get; set; } = [0xFF, 0x48, 0x3D, 0x8B]; - [DynamicFormFieldCheckBox(checkBoxText: "Enable speech bounce animation", alignment: DynamicFormAlignment.Right)] + [DynamicFormFieldCheckBox(order: 4, checkBoxText: "Enable speech bounce animation", alignment: DynamicFormAlignment.Right, platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public bool TrackerSpeechEnableBounce { get; set; } = true; - [DynamicFormFieldSlider(minimumValue: 0, maximumValue:100, decimalPlaces:1, incrementAmount:.1, suffix:"%", label: "Tracker recognition threshold:", platforms: DynamicFormPlatform.Windows)] + [DynamicFormFieldSlider(order: 5, minimumValue: 0, maximumValue:100, decimalPlaces:1, incrementAmount:.1, suffix:"%", label: "Tracker recognition threshold:", platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public float TrackerRecognitionThreshold { get; set; } - [DynamicFormFieldSlider(minimumValue: 0, maximumValue:100, decimalPlaces:1, incrementAmount:.1, suffix:"%", label: "Tracker execution threshold:", platforms: DynamicFormPlatform.Windows)] + [DynamicFormFieldSlider(order: 6, minimumValue: 0, maximumValue:100, decimalPlaces:1, incrementAmount:.1, suffix:"%", label: "Tracker execution threshold:", platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public float TrackerConfidenceThreshold { get; set; } - [DynamicFormFieldSlider(minimumValue: 0, maximumValue:100, decimalPlaces:1, incrementAmount:.1, suffix:"%", label: "Tracker spoiler threshold:", platforms: DynamicFormPlatform.Windows)] + [DynamicFormFieldSlider(order: 7, minimumValue: 0, maximumValue:100, decimalPlaces:1, incrementAmount:.1, suffix:"%", label: "Tracker spoiler threshold:", platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public float TrackerConfidenceSassThreshold { get; set; } - [DynamicFormFieldComboBox(label: "Tracker voice frequency:", platforms: DynamicFormPlatform.Windows)] + [DynamicFormFieldSlider(order: 8, minimumValue: 0, maximumValue:100, decimalPlaces:0, incrementAmount:1, suffix:"%", label: "Text to speech volume:", platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] + public int TextToSpeechVolume { get; set; } + +#pragma warning disable CS0067 // Event is never used + [DynamicFormFieldButton(order: 9, buttonText: "Test Tracker Voice", alignment: DynamicFormAlignment.Right, platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] + public event EventHandler? TestTextToSpeechPressed; +#pragma warning restore CS0067 // Event is never used + + [DynamicFormFieldComboBox(label: "Tracker voice frequency:", platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public TrackerVoiceFrequency TrackerVoiceFrequency { get; set; } - [DynamicFormFieldComboBox(label: "Speech recognition mode:", platforms: DynamicFormPlatform.Windows)] - public SpeechRecognitionMode SpeechRecognitionMode { get; set; } + [DynamicFormFieldComboBox(label: "Speech recognition mode:", + comboBoxOptionsProperty: nameof(SpeechRecognitionTypes), platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] + public string SpeechRecognitionMode { get; set; } = ""; [DynamicFormFieldComboBox(label: "Push-to-talk key:", platforms: DynamicFormPlatform.Windows)] public PushToTalkKey PushToTalkKey { get; set; } @@ -49,7 +59,7 @@ public class OptionsWindowTrackerOptions [DynamicFormFieldComboBox(label: "Push-to-talk device:", comboBoxOptionsProperty: nameof(AudioDevices), platforms: DynamicFormPlatform.Windows)] public string PushToTalkDevice { get; set; } = ""; - [DynamicFormFieldNumericUpDown(minValue: 0, label: "Undo expiration time:", platforms: DynamicFormPlatform.Windows)] + [DynamicFormFieldNumericUpDown(minValue: 0, label: "Undo expiration time:", platforms: DynamicFormPlatform.Windows & DynamicFormPlatform.Linux)] public int UndoExpirationTime { get; set; } = 3; [DynamicFormFieldFilePicker(FilePickerType.Folder, label: "Auto tracker scripts folder:", dialogText: "Select auto tracker scripts folder")] @@ -76,10 +86,10 @@ public class OptionsWindowTrackerOptions [DynamicFormFieldCheckBox(checkBoxText: "Auto track viewed events", groupName: "Bottom")] public bool AutoSaveLookAtEvents { get; set; } = true; - [DynamicFormFieldCheckBox(checkBoxText: "Enable hints", groupName: "Bottom", platforms: DynamicFormPlatform.Windows)] + [DynamicFormFieldCheckBox(checkBoxText: "Enable hints", groupName: "Bottom")] public bool TrackerHintsEnabled { get; set; } = true; - [DynamicFormFieldCheckBox(checkBoxText: "Enable spoilers", groupName: "Bottom", platforms: DynamicFormPlatform.Windows)] + [DynamicFormFieldCheckBox(checkBoxText: "Enable spoilers", groupName: "Bottom")] public bool TrackerSpoilersEnabled { get; set; } = true; [DynamicFormFieldCheckBox(checkBoxText: "Enable timer", groupName: "Bottom")] @@ -88,6 +98,7 @@ public class OptionsWindowTrackerOptions [DynamicFormFieldCheckBox(checkBoxText: "Enable MSU Randomizer message server", groupName: "Bottom", toolTipText: "Enables the gRPC server that allows the separate MSU Randomizer application from informing Tracker of when the MSU was shuffled and when the playing track is changed.")] public bool MsuMessageReceiverEnabled { get; set; } = true; + public Dictionary SpeechRecognitionTypes { get; set; } = new(); public Dictionary AudioDevices { get; set; } = new(); public Dictionary TrackerSpeechImagePacks { get; set; } = []; } diff --git a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs index 6c0d9da6f..dcd9339e3 100644 --- a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs +++ b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using DynamicForms.Library.Core; using DynamicForms.Library.Core.Attributes; using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Shared; namespace TrackerCouncil.Smz3.Data.ViewModels; @@ -20,6 +22,12 @@ public OptionsWindowViewModel() public OptionsWindowViewModel(GeneralOptions options, Dictionary trackerImagePacks, Dictionary audioInputDevices, List availableProfiles) { + if (OperatingSystem.IsLinux() && + options.SpeechRecognitionMode is SpeechRecognitionMode.AlwaysOn or SpeechRecognitionMode.PushToTalk) + { + options.SpeechRecognitionMode = SpeechRecognitionMode.PySpeechService; + } + RandomizerOptions.Z3RomPath = options.Z3RomPath; RandomizerOptions.SMRomPath = options.SMRomPath; RandomizerOptions.RomOutputPath = options.RomOutputPath; @@ -40,8 +48,9 @@ public OptionsWindowViewModel(GeneralOptions options, Dictionary TrackerOptions.TrackerRecognitionThreshold = options.TrackerRecognitionThreshold * 100; TrackerOptions.TrackerConfidenceThreshold = options.TrackerConfidenceThreshold * 100; TrackerOptions.TrackerConfidenceSassThreshold = options.TrackerConfidenceSassThreshold * 100; + TrackerOptions.TextToSpeechVolume = options.TextToSpeechVolume; TrackerOptions.TrackerVoiceFrequency = options.TrackerVoiceFrequency; - TrackerOptions.SpeechRecognitionMode = options.SpeechRecognitionMode; + TrackerOptions.SpeechRecognitionMode = options.SpeechRecognitionMode.ToString(); TrackerOptions.PushToTalkKey = options.PushToTalkKey; TrackerOptions.PushToTalkDevice = options.PushToTalkDevice; TrackerOptions.UndoExpirationTime = options.UndoExpirationTime; @@ -59,6 +68,7 @@ public OptionsWindowViewModel(GeneralOptions options, Dictionary TrackerOptions.MsuMessageReceiverEnabled = options.MsuMessageReceiverEnabled; TrackerOptions.TrackerSpeechImagePacks = trackerImagePacks; TrackerOptions.AudioDevices = audioInputDevices; + TrackerOptions.SpeechRecognitionTypes = GetSpeechRecognitionTypes(); TwitchIntegration.TwitchUserName = options.TwitchUserName; TwitchIntegration.TwitchChannel = options.TwitchChannel; @@ -75,6 +85,12 @@ public OptionsWindowViewModel(GeneralOptions options, Dictionary public void UpdateOptions(GeneralOptions options) { + var speechRecognitionMode = SpeechRecognitionMode.AlwaysOn; + if (Enum.TryParse(TrackerOptions.SpeechRecognitionMode, out SpeechRecognitionMode parsedSpeechRecognitionMode)) + { + speechRecognitionMode = parsedSpeechRecognitionMode; + } + options.Z3RomPath = RandomizerOptions.Z3RomPath; options.SMRomPath = RandomizerOptions.SMRomPath; options.RomOutputPath = RandomizerOptions.RomOutputPath; @@ -95,8 +111,9 @@ public void UpdateOptions(GeneralOptions options) options.TrackerRecognitionThreshold = TrackerOptions.TrackerRecognitionThreshold / 100; options.TrackerConfidenceThreshold = TrackerOptions.TrackerConfidenceThreshold / 100; options.TrackerConfidenceSassThreshold = TrackerOptions.TrackerConfidenceSassThreshold / 100; + options.TextToSpeechVolume = TrackerOptions.TextToSpeechVolume; options.TrackerVoiceFrequency = TrackerOptions.TrackerVoiceFrequency; - options.SpeechRecognitionMode = TrackerOptions.SpeechRecognitionMode; + options.SpeechRecognitionMode = speechRecognitionMode; options.PushToTalkKey = TrackerOptions.PushToTalkKey; options.PushToTalkDevice = TrackerOptions.PushToTalkDevice; options.UndoExpirationTime = TrackerOptions.UndoExpirationTime; @@ -136,4 +153,23 @@ public void UpdateOptions(GeneralOptions options) [DynamicFormObject(groupName:"Tracker profiles")] public OptionsWindowTrackerProfiles TrackerProfiles { get; set; } = new(); + + private Dictionary GetSpeechRecognitionTypes() + { + var toReturn = new Dictionary(); + + if (OperatingSystem.IsWindows()) + { + toReturn[SpeechRecognitionMode.AlwaysOn.ToString()] = SpeechRecognitionMode.AlwaysOn.GetDescription(); + toReturn[SpeechRecognitionMode.PushToTalk.ToString()] = SpeechRecognitionMode.PushToTalk.GetDescription(); + } + else if (OperatingSystem.IsLinux()) + { + toReturn[SpeechRecognitionMode.PySpeechService.ToString()] = SpeechRecognitionMode.PySpeechService.GetDescription(); + } + + toReturn[SpeechRecognitionMode.Disabled.ToString()] = SpeechRecognitionMode.Disabled.GetDescription(); + + return toReturn; + } } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs index 9148c1bab..133aa40df 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs @@ -126,6 +126,8 @@ public Playthrough Generate(IReadOnlyCollection worlds, Config config) items.AddRange(newItems); _logger.LogDebug("Sphere {Number}: {ItemCount} new items | {RewardCount} new rewards", spheres.Count + 1, newItems.Count, rewards.Count - prevRewardCount); + _logger.LogDebug("Sphere {Number} Items:{Items}", spheres.Count + 1, string.Join("", newLocations.Select(x => $"\r\n\t{x.RandomName} - {x.Item.Name}"))); + _logger.LogDebug("Sphere {Number} Rewards:{Rewards}", spheres.Count + 1, string.Join("", rewards.Select(x => $"\r\n\t{x.Type}"))); if (!newItems.Any() && prevRewardCount == rewards.Count) { diff --git a/src/TrackerCouncil.Smz3.UI/Directories.cs b/src/TrackerCouncil.Smz3.Shared/Directories.cs similarity index 92% rename from src/TrackerCouncil.Smz3.UI/Directories.cs rename to src/TrackerCouncil.Smz3.Shared/Directories.cs index 9ca9ab742..f6f90ef95 100644 --- a/src/TrackerCouncil.Smz3.UI/Directories.cs +++ b/src/TrackerCouncil.Smz3.Shared/Directories.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace TrackerCouncil.Smz3.UI; +namespace TrackerCouncil.Smz3.Shared; public static class Directories { diff --git a/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj b/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj index 111e67bf1..b88827285 100644 --- a/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj +++ b/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs index 54dea9c92..7fdc15229 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs @@ -1,12 +1,11 @@ using System; -using System.Speech.Synthesis; namespace TrackerCouncil.Smz3.Tracking.Services; /// /// Defines a mechanism to communicate with the player. /// -public interface ICommunicator +public interface ICommunicator : IDisposable { /// /// Communicates the specified text to the player @@ -59,7 +58,13 @@ public void SlowDown() { } /// /// If the TTS is currently speaking /// - public bool IsSpeaking { get; } + public bool IsSpeaking { get; } + + /// + /// Updates the volume of the text to speech engine. + /// + /// New volume between 0-100 with 0 being silent and 100 being the default max value. + public void UpdateVolume(int volume); /// /// Event for when the communicator has started speaking @@ -74,7 +79,7 @@ public void SlowDown() { } /// /// Event for when the communicator has reached a new viseme /// - public event EventHandler VisemeReached; + public event EventHandler VisemeReached; } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs new file mode 100644 index 000000000..5bcd51fc0 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using PySpeechService.Client; +using PySpeechService.TextToSpeech; +using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Shared; + +namespace TrackerCouncil.Smz3.Tracking.Services; + +[SupportedOSPlatform("linux")] +internal class PyTextToSpeechCommunicator : ICommunicator +{ + private readonly IPySpeechService _pySpeechService; + private readonly ILogger _logger; + private DateTime? _startSpeakingTime; + private (string onnxPath, string jsonPath)? _defaultPrimaryVoice; + private (string onnxPath, string jsonPath)? _defaultAltVoice; + private (string onnxPath, string jsonPath)? _primaryVoice; + private (string onnxPath, string jsonPath)? _altVoice; + private double _rate = 1; + private bool _isEnabled; + private bool _isSpeaking; + private int volume; + private ConcurrentDictionary _pendingRequests = []; + + public PyTextToSpeechCommunicator(IPySpeechService pySpeechService, TrackerOptionsAccessor trackerOptionsAccessor, ILogger logger) + { + _pySpeechService = pySpeechService; + _logger = logger; + + // Check to see if the user has the tracker voice files to use + var piperPath = Path.Combine(Directories.AppDataFolder, "PiperModels"); + if (Directory.Exists(piperPath)) + { + var femaleDetails = GetModelPath("Tracker_Female"); + var maleDetails = GetModelPath("Tracker_Male"); + _primaryVoice = _defaultPrimaryVoice = femaleDetails ?? maleDetails; + _altVoice = _defaultAltVoice = maleDetails ?? femaleDetails; + } + + volume = trackerOptionsAccessor.Options?.TextToSpeechVolume ?? 100; + _ = Initialize(); + + _pySpeechService.Initialized += PySpeechServiceOnInitialized; + _pySpeechService.SpeakCommandResponded += PySpeechServiceOnSpeakCommandResponded; + + _isEnabled = trackerOptionsAccessor.Options?.VoiceFrequency != Shared.Enums.TrackerVoiceFrequency.Disabled; + } + + private void PySpeechServiceOnSpeakCommandResponded(object? sender, SpeakCommandResponseEventArgs args) + { + SpeechRequest? request = null; + + if (string.IsNullOrEmpty(args.Response.MessageId) || !_pendingRequests.TryGetValue(args.Response.MessageId, out request)) + { + _logger.LogError("Received PySpeechService SpeakCommandResponse with no valid message id"); + } + + if (args.Response.IsStartOfChunk) + { + VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(true, request)); + } + else if (args.Response.IsEndOfChunk) + { + VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(false, request)); + } + + if (args.Response.IsStartOfMessage) + { + if (_startSpeakingTime == null) + { + _startSpeakingTime = DateTime.Now; + } + + _isSpeaking = true; + SpeakStarted?.Invoke(this, EventArgs.Empty); + } + else if (args.Response.IsEndOfMessage) + { + if (request != null) + { + _pendingRequests.TryRemove( + new KeyValuePair(args.Response.MessageId!, request)); + } + + if (!args.Response.HasAnotherRequest) + { + var duration = DateTime.Now - _startSpeakingTime; + _startSpeakingTime = null; + SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration ?? TimeSpan.Zero, request)); + _isSpeaking = false; + } + } + } + + private async void PySpeechServiceOnInitialized(object? sender, EventArgs args) + { + try + { + await Initialize(); + } + catch (Exception e) + { + _logger.LogError(e, "Error initializing PySpeechService"); + } + } + + public void UseAlternateVoice(bool useAlt = true) + { + if (useAlt) + { + _primaryVoice = _defaultAltVoice; + _altVoice = _defaultPrimaryVoice; + } + else + { + _primaryVoice = _defaultPrimaryVoice; + _altVoice = _defaultAltVoice; + } + } + + private async Task Initialize() + { + await _pySpeechService.SetSpeechSettingsAsync(GetSpeechSettings()); + await _pySpeechService.SetVolumeAsync(volume / 100.0); + } + + private SpeechSettings GetSpeechSettings() + { + return new SpeechSettings + { + OnnxPath = _primaryVoice?.Item1 ?? string.Empty, + ConfigPath = _primaryVoice?.Item2 ?? string.Empty, + AltOnnxPath = _altVoice?.Item1 ?? string.Empty, + AltConfigPath = _altVoice?.Item2 ?? string.Empty, + Speed = _rate, + }; + } + + private (string onnxPath, string jsonPath)? GetModelPath(string modelName) + { + var onnxPath = Path.Combine(Directories.AppDataFolder, "PiperModels", $"{modelName}.onnx"); + var jsonPath = Path.Combine(Directories.AppDataFolder, "PiperModels", $"{modelName}.json"); + if (File.Exists(onnxPath) && File.Exists(jsonPath)) + { + return (onnxPath, jsonPath); + } + + return null; + } + + public void Say(SpeechRequest request) + { + if (!_isEnabled || !_pySpeechService.IsSpeechEnabled) return; + + var messageId = Guid.NewGuid().ToString(); + _pendingRequests.TryAdd(messageId, request); + + if (request.Wait) + { + _pySpeechService.Speak(request.Text, GetSpeechSettings(), messageId); + } + else + { + _pySpeechService.SpeakAsync(request.Text, GetSpeechSettings(), messageId); + } + } + + public bool IsEnabled => _pySpeechService.IsSpeechEnabled && _isEnabled; + + public void Enable() + { + if (!_pySpeechService.IsSpeechEnabled) + { + _ = _pySpeechService.SetSpeechSettingsAsync(GetSpeechSettings()); + } + _isEnabled = true; + } + + public void Disable() + { + _ = _pySpeechService.StopSpeakingAsync(); + _isEnabled = false; + } + + public void SpeedUp() + { + _rate = _rate < 1 ? 1 : 2; + } + + public void SlowDown() + { + _rate = _rate > 1 ? 1 : .5; + } + + public void Abort() + { + _ = _pySpeechService.StopSpeakingAsync(); + } + + public bool IsSpeaking => _isSpeaking; + + public void UpdateVolume(int newVolume) + { + volume = newVolume; + _ = _pySpeechService.SetVolumeAsync(newVolume / 100.0f); + } + + public event EventHandler? SpeakStarted; + public event EventHandler? SpeakCompleted; + public event EventHandler? VisemeReached; + + public void Dispose() + { + _pySpeechService.Initialized -= PySpeechServiceOnInitialized; + _pySpeechService.SpeakCommandResponded -= PySpeechServiceOnSpeakCommandResponded; + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/SpeakCompletedEventArgs.cs b/src/TrackerCouncil.Smz3.Tracking/Services/SpeakCompletedEventArgs.cs index 7634ac1f1..404a1407a 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/SpeakCompletedEventArgs.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/SpeakCompletedEventArgs.cs @@ -6,10 +6,16 @@ namespace TrackerCouncil.Smz3.Tracking.Services; /// Event for when the communicator has finished speaking /// /// How long the speech was going on for -public class SpeakCompletedEventArgs(TimeSpan speechDuration) : EventArgs +/// The speech request that is ending +public class SpeakCompletedEventArgs(TimeSpan speechDuration, SpeechRequest? speechRequest) : EventArgs { /// /// How long the speech was going on for /// public TimeSpan SpeechDuration => speechDuration; + + /// + /// The speech request that is ending + /// + public SpeechRequest? SpeechRequest => speechRequest; } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/SpeakVisemeReachedEventArgs.cs b/src/TrackerCouncil.Smz3.Tracking/Services/SpeakingUpdatedEventArgs.cs similarity index 53% rename from src/TrackerCouncil.Smz3.Tracking/Services/SpeakVisemeReachedEventArgs.cs rename to src/TrackerCouncil.Smz3.Tracking/Services/SpeakingUpdatedEventArgs.cs index 1b2a6c459..beec9bf02 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/SpeakVisemeReachedEventArgs.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/SpeakingUpdatedEventArgs.cs @@ -1,19 +1,18 @@ using System; -using System.Speech.Synthesis; namespace TrackerCouncil.Smz3.Tracking.Services; /// /// Event for when tracker says a new line /// -/// The original viseme details +/// If the TTS service is currently speaking /// The original speech request -public class SpeakVisemeReachedEventArgs(VisemeReachedEventArgs visemeDetails, SpeechRequest? request) : EventArgs +public class SpeakingUpdatedEventArgs(bool isSpeaking, SpeechRequest? request) : EventArgs { /// - /// Original viseme details + /// If the TTS service is currently speaking /// - public VisemeReachedEventArgs VisemeDetails => visemeDetails; + public bool IsSpeaking => isSpeaking; /// /// The original speech request diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/AlwaysOnSpeechRecognitionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/AlwaysOnSpeechRecognitionService.cs index b8f4cef2c..2b1402789 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/AlwaysOnSpeechRecognitionService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/AlwaysOnSpeechRecognitionService.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Speech.Recognition; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; namespace TrackerCouncil.Smz3.Tracking.Services.Speech; @@ -23,7 +25,7 @@ public AlwaysOnSpeechRecognitionService(ILogger _recognizer; + public override SpeechRecognitionEngine RecognitionEngine => _recognizer; public override void ResetInputDevice() => _recognizer.SetInputToDefaultAudioDevice(); @@ -31,7 +33,7 @@ public AlwaysOnSpeechRecognitionService(ILogger _recognizer.RecognizeAsync(RecognizeMode.Multiple); - public override bool Initialize(out bool foundRequestedDevice) + public override bool Initialize(float minRequiredConfidence, out bool foundRequestedDevice) { try { @@ -53,6 +55,14 @@ public override bool Initialize(out bool foundRequestedDevice) } } + public override void AddGrammar(List grammars) + { + foreach (var grammar in grammars) + { + RecognitionEngine.LoadGrammar(grammar.BuildSystemSpeechGrammar()); + } + } + /// /// Dll to get the number of microphones /// diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/ISpeechRecognitionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/ISpeechRecognitionService.cs index 951516f62..713a97b9d 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/ISpeechRecognitionService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/ISpeechRecognitionService.cs @@ -1,5 +1,6 @@ using System; -using System.Speech.Recognition; +using System.Collections.Generic; +using PySpeechService.Recognition; namespace TrackerCouncil.Smz3.Tracking.Services.Speech; @@ -11,7 +12,7 @@ public interface ISpeechRecognitionService /// /// Occurs when speech was successfully understood. /// - event EventHandler? SpeechRecognized; + event EventHandler? SpeechRecognized; /// /// Performs first-time initialization of the speech recognition service. @@ -20,7 +21,7 @@ public interface ISpeechRecognitionService /// if initialization was successful; to disable speech recognition. /// - bool Initialize(out bool foundRequestedDevice); + bool Initialize(float minRequiredConfidence, out bool foundRequestedDevice); /// /// Re-initializes the input device to ensure a valid microphone is being @@ -37,4 +38,10 @@ public interface ISpeechRecognitionService /// Stops speech recognition in the background. /// void StopRecognition(); + + /// + /// Add grammar to the speech recognition service + /// + /// List of created grammar details to add to the speech recognition service + public void AddGrammar(List grammars); } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/NullSpeechRecognitionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/NullSpeechRecognitionService.cs index 2a308dd10..ca1ce4b8a 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/NullSpeechRecognitionService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/NullSpeechRecognitionService.cs @@ -1,5 +1,6 @@ using System; -using System.Speech.Recognition; +using System.Collections.Generic; +using PySpeechService.Recognition; namespace TrackerCouncil.Smz3.Tracking.Services.Speech; @@ -10,7 +11,7 @@ public sealed class NullSpeechRecognitionService : ISpeechRecognitionService { #pragma warning disable CS0067 - public event EventHandler? SpeechRecognized; + public event EventHandler? SpeechRecognized; #pragma warning restore CS0067 public void ResetInputDevice() @@ -21,11 +22,15 @@ public void StopRecognition() { } + public void AddGrammar(List grammars) + { + } + public void StartRecognition() { } - public bool Initialize(out bool foundRequestedDevice) + public bool Initialize(float minRequiredConfidence, out bool foundRequestedDevice) { foundRequestedDevice = true; return false; diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PushToTalkSpeechRecognitionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PushToTalkSpeechRecognitionService.cs index dc40632fb..096c6478e 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PushToTalkSpeechRecognitionService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PushToTalkSpeechRecognitionService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -11,6 +12,7 @@ using Microsoft.Extensions.Logging; using NAudio.Wave; +using PySpeechService.Recognition; using SharpHook; using SharpHook.Native; using TrackerCouncil.Smz3.Data.Options; @@ -103,7 +105,7 @@ private set } /// - public override bool Initialize(out bool foundRequestedDevice) + public override bool Initialize(float minRequiredConfidence, out bool foundRequestedDevice) { if (!_hasInitialized) { @@ -115,6 +117,14 @@ public override bool Initialize(out bool foundRequestedDevice) return _microphoneService.CanRecord(out foundRequestedDevice) && SpeechSupportsWaveFormat(_waveFormat); } + public override void AddGrammar(List grammars) + { + foreach (var grammar in grammars) + { + RecognitionEngine.LoadGrammar(grammar.BuildSystemSpeechGrammar()); + } + } + /// public override void ResetInputDevice() { diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PySpeechRecognitionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PySpeechRecognitionService.cs new file mode 100644 index 000000000..50bb21cf1 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PySpeechRecognitionService.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using System.Speech.Recognition; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using PySpeechService.Client; +using PySpeechService.Recognition; +using TrackerCouncil.Smz3.Data.Configuration; + +namespace TrackerCouncil.Smz3.Tracking.Services.Speech; + +[SupportedOSPlatform("linux")] +public class PySpeechRecognitionService(IPySpeechService pySpeechService, Configs configs, ILogger logger) : SpeechRecognitionServiceBase +{ + private bool _isEnabled; + private float _minRequiredConfidence = 0.8f; + + public override SpeechRecognitionEngine? RecognitionEngine => null; + + public override bool Initialize(float minRequiredConfidence, out bool foundRequestedDevice) + { + _minRequiredConfidence = minRequiredConfidence; + + if (!pySpeechService.IsConnected) + { + _ = pySpeechService.StartAsync(); + } + + pySpeechService.Initialized += async (_, _) => + { + if (_isEnabled) + { + await StartPySpeechRecognition(); + } + }; + + pySpeechService.SpeechRecognized += OnSpeechRecognized; + + foundRequestedDevice = true; + return true; + } + + public override void AddGrammar(List grammars) + { + foreach (var grammar in grammars) + { + pySpeechService.AddSpeechRecognitionCommand(grammar); + } + } + + public override void ResetInputDevice() + { + // Do nothing + } + + public override void StartRecognition() + { + _isEnabled = true; + + pySpeechService.AddSpeechRecognitionReplacements(configs.MetadataConfig.PySpeechRecognitionReplacements); + + if (pySpeechService.IsConnected) + { + _ = StartPySpeechRecognition(); + } + else + { + _ = pySpeechService.StartAsync(); + } + } + + public override void StopRecognition() + { + if (_isEnabled) + { + _isEnabled = false; + pySpeechService.StopSpeechRecognitionAsync(); + } + } + + private async Task StartPySpeechRecognition() + { + try + { + await pySpeechService.StartSpeechRecognitionAsync(requiredConfidence: _minRequiredConfidence * 100); + } + catch (Exception e) + { + logger.LogError(e, "Failed to start PySpeechService SpeechRecognition"); + } + + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/SpeechRecognitionServiceBase.cs b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/SpeechRecognitionServiceBase.cs index 79d44d1fc..1de8dc1a0 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/SpeechRecognitionServiceBase.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/SpeechRecognitionServiceBase.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Speech.Recognition; +using PySpeechService.Recognition; namespace TrackerCouncil.Smz3.Tracking.Services.Speech; @@ -12,7 +15,7 @@ public abstract class SpeechRecognitionServiceBase : ISpeechRecognitionService, /// /// Event fired when speech was successfully understood /// - public event EventHandler? SpeechRecognized; + public event EventHandler? SpeechRecognized; /// /// The internal speech recognition engine used by the service, if @@ -39,7 +42,12 @@ public abstract class SpeechRecognitionServiceBase : ISpeechRecognitionService, /// Initializes the microphone to the default Windows mic /// /// True if successful, false otherwise - public abstract bool Initialize(out bool foundRequestedDevice); + public abstract bool Initialize(float minRequiredConfidence, out bool foundRequestedDevice); + + /// + /// Adds a series of grammar rules to the recognition service + /// + public abstract void AddGrammar(List grammars); /// /// Releases all resources used by the service. @@ -74,7 +82,21 @@ protected virtual void Dispose(bool disposing) /// protected virtual void OnSpeechRecognized(object? sender, SpeechRecognizedEventArgs args) { - if (OperatingSystem.IsWindows()) - SpeechRecognized?.Invoke(sender, args); + if (!OperatingSystem.IsWindows()) return; + + SpeechRecognized?.Invoke(sender, new SpeechRecognitionResultEventArgs(new SpeechRecognitionResult() + { + Text = args.Result.Text ?? string.Empty, + Confidence = args.Result.Confidence, +#pragma warning disable CA1416 + Semantics = args.Result.Semantics.ToDictionary(x => x.Key, x => new SpeechRecognitionSemantic(x.Key.ToString(), x.Value.ToString() ?? string.Empty)), +#pragma warning restore CA1416 + NativeResult = args.Result + })); + } + + protected virtual void OnSpeechRecognized(object? sender, SpeechRecognitionResultEventArgs args) + { + SpeechRecognized?.Invoke(sender, args); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs index 6591b4920..5a93861ba 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs @@ -10,7 +10,7 @@ namespace TrackerCouncil.Smz3.Tracking.Services; /// Facilitates communication with the player using Windows' built-in /// text-to-speech engine. /// -public class TextToSpeechCommunicator : ICommunicator, IDisposable +public class TextToSpeechCommunicator : ICommunicator { private readonly SpeechSynthesizer _tts = null!; private bool _canSpeak; @@ -35,6 +35,8 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, I _tts = new SpeechSynthesizer(); _tts.SelectVoiceByHints(VoiceGender.Female); + _tts.Volume = trackerOptionsAccessor.Options?.TextToSpeechVolume ?? 100; + _tts.SpeakStarted += (sender, args) => { if (IsSpeaking) return; @@ -51,8 +53,9 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, I { IsSpeaking = false; var duration = DateTime.Now - _startSpeakingTime; + var request = _currentSpeechRequest; _currentSpeechRequest = null; - SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration)); + SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration, request)); } else { @@ -63,7 +66,7 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, I _tts.VisemeReached += (sender, args) => { if (!OperatingSystem.IsWindows()) return; - VisemeReached?.Invoke(this, new SpeakVisemeReachedEventArgs(args, _currentSpeechRequest)); + VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(args.Viseme > 0, _currentSpeechRequest)); }; _canSpeak = trackerOptionsAccessor.Options?.VoiceFrequency != Shared.Enums.TrackerVoiceFrequency.Disabled; @@ -149,11 +152,21 @@ public void SlowDown() public bool IsSpeaking { get; private set; } + public void UpdateVolume(int volume) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + _tts.Volume = volume; + } + public event EventHandler? SpeakStarted; public event EventHandler? SpeakCompleted; - public event EventHandler? VisemeReached; + public event EventHandler? VisemeReached; /// public void Dispose() diff --git a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs index 1ccfb4100..40b8c0544 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Speech.Recognition; using System.Text; using System.Threading; using System.Threading.Tasks; using BunLabs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Chat.Integration; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration; @@ -127,15 +127,19 @@ public Tracker(IWorldAccessor worldAccessor, } // Initialize the speech recognition engine - if (_trackerOptions.Options.SpeechRecognitionMode == SpeechRecognitionMode.Disabled || - !OperatingSystem.IsWindows()) + if (_trackerOptions.Options.SpeechRecognitionMode == SpeechRecognitionMode.Disabled || OperatingSystem.IsMacOS()) { _recognizer = serviceProvider.GetRequiredService(); } - else if (_trackerOptions.Options.SpeechRecognitionMode == SpeechRecognitionMode.PushToTalk) + else if (_trackerOptions.Options.SpeechRecognitionMode == SpeechRecognitionMode.PushToTalk && OperatingSystem.IsWindows()) { _recognizer = serviceProvider.GetRequiredService(); } + else if (_trackerOptions.Options.SpeechRecognitionMode == SpeechRecognitionMode.PySpeechService || + OperatingSystem.IsLinux()) + { + _recognizer = serviceProvider.GetRequiredService(); + } else { _recognizer = serviceProvider.GetRequiredService(); @@ -184,7 +188,7 @@ public override string CorrectUserNamePronunciation(string userName) public override bool InitializeMicrophone() { if (MicrophoneInitialized) return true; - MicrophoneInitialized = _recognizer.Initialize(out var foundRequestedDevice); + MicrophoneInitialized = _recognizer.Initialize(_trackerOptions.Options?.MinimumRecognitionConfidence ?? 0.75f, out var foundRequestedDevice); MicrophoneInitializedAsDesiredDevice = foundRequestedDevice; return MicrophoneInitialized; } @@ -321,10 +325,9 @@ public override bool TryStartTracking() bool loadError; try { - var recognitionEngine = _recognizer is SpeechRecognitionServiceBase recognitionBase - ? recognitionBase.RecognitionEngine - : null; - Syntax = _moduleFactory.LoadAll(this, recognitionEngine, out loadError); + var grammars = _moduleFactory.RetrieveGrammar(this, out loadError); + _recognizer.AddGrammar(grammars); + Syntax = _moduleFactory.Syntax; } catch (Exception e) { @@ -459,7 +462,7 @@ public override void StopTracking() /// public override void EnableVoiceRecognition() { - if (MicrophoneInitialized && !VoiceRecognitionEnabled && OperatingSystem.IsWindows()) + if (MicrophoneInitialized && !VoiceRecognitionEnabled && _recognizer is not NullSpeechRecognitionService) { _logger.LogInformation("Starting speech recognition"); _recognizer.ResetInputDevice(); @@ -588,7 +591,7 @@ public override void Repeat() return; } - if (_lastSpokenText == null) + if (string.IsNullOrEmpty(_lastSpokenText)) { Say(text: "I haven't said anything yet."); return; @@ -596,8 +599,10 @@ public override void Repeat() _communicator.Say(new SpeechRequest("I said")); _communicator.SlowDown(); + var prevLastSpokenText = _lastSpokenText; Say(text: _lastSpokenText, wait: true); _communicator.SpeedUp(); + _lastSpokenText = prevLastSpokenText; } /// @@ -703,12 +708,9 @@ private void IdleTimerElapsed(object? state) Say(response: Responses.Idle?[key]); } - private void Recognizer_SpeechRecognized(object? sender, SpeechRecognizedEventArgs e) + private void Recognizer_SpeechRecognized(object? sender, SpeechRecognitionResultEventArgs e) { - if (OperatingSystem.IsWindows()) - { - RestartIdleTimers(); - OnSpeechRecognized(new(e.Result.Confidence, e.Result.Text)); - } + RestartIdleTimers(); + OnSpeechRecognized(new(e.Result.Confidence, e.Result.Text)); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj b/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj index 2a0a6e59c..35f0bf2e2 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj +++ b/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj @@ -8,12 +8,15 @@ - + + + + - + - + diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs index 44d86feb8..987786f91 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs @@ -40,7 +40,6 @@ public static IServiceCollection AddTracker(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -49,12 +48,20 @@ public static IServiceCollection AddTracker(this IServiceCollection services) if (OperatingSystem.IsWindows()) { + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddTransient(); services.AddSingleton(); } + else if (OperatingSystem.IsLinux()) + { + services.AddScoped(); + services.AddScoped(); + services.AddSingleton(); + services.AddScoped(); + } else { services.AddSingleton(); diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs index f129f593f..22c2b5a1b 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs index 71e64d149..83288e96f 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs @@ -1,6 +1,6 @@ using System; -using System.Runtime.Versioning; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Tracking.Services; @@ -29,9 +29,9 @@ public AutoTrackerVoiceModule(TrackerBase tracker, IPlayerProgressionService pla _autoTrackerBase = autoTrackerBase; } - private GrammarBuilder GetLookAtGameRule() + private SpeechRecognitionGrammarBuilder GetLookAtGameRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please", "would you please") .OneOf("look at this", "look here", "record this", "log this", "take a look at this", "get a load of this") @@ -54,7 +54,6 @@ public void Dispose() _autoTrackerBase.Dispose(); } - [SupportedOSPlatform("windows")] public override void AddCommands() { AddCommand("Look at this", GetLookAtGameRule(), (result) => diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs index a8f7c500c..a25f039c7 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs @@ -1,6 +1,6 @@ using System; -using System.Runtime.Versioning; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.Shared; @@ -27,78 +27,74 @@ public BossTrackingModule(TrackerBase tracker, IPlayerProgressionService playerP } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetMarkBossAsDefeatedRule() + private SpeechRecognitionGrammarBuilder GetMarkBossAsDefeatedRule() { var bossNames = GetBossNames(); - var beatBoss = new GrammarBuilder() + var beatBoss = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers) + .Optional(ForceCommandIdentifiers) .OneOf("track", "I beat", "I defeated", "I beat off", "I killed") .Append(BossKey, bossNames); - var markBoss = new GrammarBuilder() + var markBoss = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers) + .Optional(ForceCommandIdentifiers) .Append("mark") .Append(BossKey, bossNames) .Append("as") .OneOf("beaten", "beaten off", "dead", "fucking dead", "defeated"); - var bossIsDead = new GrammarBuilder() + var bossIsDead = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append(BossKey, bossNames) .OneOf("is dead", "is fucking dead"); - return GrammarBuilder.Combine(beatBoss, markBoss, bossIsDead); + return SpeechRecognitionGrammarBuilder.Combine(beatBoss, markBoss, bossIsDead); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetMarkBossAsNotDefeatedRule() + private SpeechRecognitionGrammarBuilder GetMarkBossAsNotDefeatedRule() { var bossNames = GetBossNames(); - var markBoss = new GrammarBuilder() + var markBoss = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers) + .Optional(ForceCommandIdentifiers) .Append("mark") .Append(BossKey, bossNames) .Append("as") .OneOf("alive", "not defeated", "undefeated"); - var untrack = new GrammarBuilder() + var untrack = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers) + .Optional(ForceCommandIdentifiers) .Append("untrack") .Append(BossKey, bossNames); - var bossIsAlive = new GrammarBuilder() + var bossIsAlive = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append(BossKey, bossNames) .OneOf("is alive", "is still alive"); - return GrammarBuilder.Combine(markBoss, untrack, bossIsAlive); + return SpeechRecognitionGrammarBuilder.Combine(markBoss, untrack, bossIsAlive); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetBossDefeatedWithContentRule() + private SpeechRecognitionGrammarBuilder GetBossDefeatedWithContentRule() { var bossNames = GetBossNames(); - var beatBoss = new GrammarBuilder() + var beatBoss = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("I beat", "I defeated", "I beat off", "I killed") .Append(BossKey, bossNames) .OneOf("Without getting hit", "Without taking damage", "And didn't get hit", "And didn't take damage", "In one cycle"); - var oneCycled = new GrammarBuilder() + var oneCycled = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("I one cycled", "I quick killed") .Append(BossKey, bossNames); - return GrammarBuilder.Combine(beatBoss, oneCycled); + return SpeechRecognitionGrammarBuilder.Combine(beatBoss, oneCycled); } - [SupportedOSPlatform("windows")] public override void AddCommands() { AddCommand("Mark boss as defeated", GetMarkBossAsDefeatedRule(), (result) => @@ -109,7 +105,7 @@ public override void AddCommands() var admittedGuilt = result.Text.ContainsAny("killed", "beat", "defeated", "dead") && !result.Text.ContainsAny("beat off", "beaten off"); - var force = result.Text.ContainsAny(GrammarBuilder.ForceCommandIdentifiers); + var force = result.Text.ContainsAny(ForceCommandIdentifiers); if (boss.Region != null) { @@ -126,7 +122,7 @@ public override void AddCommands() { var boss = GetBossFromResult(TrackerBase, result) ?? throw new Exception($"Could not find boss or dungeon in command: '{result.Text}'"); - var force = result.Text.ContainsAny(GrammarBuilder.ForceCommandIdentifiers); + var force = result.Text.ContainsAny(ForceCommandIdentifiers); if (boss.Region != null) { diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs index 08c812b71..2cb7dde1c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Chat.Integration; using TrackerCouncil.Smz3.Chat.Integration.Models; @@ -523,10 +524,9 @@ private void ChatClient_SendMessageFailure(object? sender, EventArgs e) TrackerBase.Error(); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetStartGuessingGameRule() + private SpeechRecognitionGrammarBuilder GetStartGuessingGameRule() { - var commandRule = new GrammarBuilder() + var commandRule = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("initiate", "start", "execute") .OneOf("Ganon's Tower Big Key Guessing Game", @@ -535,18 +535,17 @@ private GrammarBuilder GetStartGuessingGameRule() "GT Guessing Game", "order 66"); - var fromSpeech = new GrammarBuilder() + var fromSpeech = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("It's time for the GT big key guessing game", "The GT big key guessing game is now open for guesses"); - return GrammarBuilder.Combine(commandRule, fromSpeech); + return SpeechRecognitionGrammarBuilder.Combine(commandRule, fromSpeech); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetStopGuessingGameGuessesRule() + private SpeechRecognitionGrammarBuilder GetStopGuessingGameGuessesRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("close the floor", "close the floor for guesses", @@ -554,14 +553,13 @@ private GrammarBuilder GetStopGuessingGameGuessesRule() "the floor is now closed for guesses"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetRevealGuessingGameWinnerRule() + private SpeechRecognitionGrammarBuilder GetRevealGuessingGameWinnerRule() { - var validGuesses = new Choices(); + var validGuesses = new List(); for (var i = 1; i <= 22; i++) - validGuesses.Add(new SemanticResultValue(i.ToString(), i)); + validGuesses.Add(new GrammarKeyValueChoice(i.ToString(), i)); - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("who guessed", "who guessed number", @@ -573,17 +571,15 @@ private GrammarBuilder GetRevealGuessingGameWinnerRule() .Append(WinningGuessKey, validGuesses); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetTrackContent() + private SpeechRecognitionGrammarBuilder GetTrackContent() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("track", "add") .OneOf("content", "con-tent"); } - [SupportedOSPlatform("windows")] public override void AddCommands() { AddCommand("Start Ganon's Tower Big Key Guessing Game", GetStartGuessingGameRule(), async (result) => @@ -598,7 +594,7 @@ public override void AddCommands() AddCommand("Declare Ganon's Tower Big Key Guessing Game Winner", GetRevealGuessingGameWinnerRule(), async (result) => { - var winningNumber = (int)result.Semantics[WinningGuessKey].Value; + var winningNumber = int.Parse(result.Semantics[WinningGuessKey].Value); await DeclareGanonsTowerGuessingGameWinner(winningNumber); }); diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs index 5aec2abef..94a5f0558 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs @@ -1,11 +1,9 @@ using System.Collections.Generic; using System.Linq; -using System.Runtime.Versioning; -using System.Speech.Recognition; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; @@ -124,56 +122,52 @@ private async Task GiveItemAsync(Item? item) } } - [SupportedOSPlatform("windows")] - private static GrammarBuilder GetEnableCheatsRule() + private static SpeechRecognitionGrammarBuilder GetEnableCheatsRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("enable", "turn on") .OneOf("cheats", "cheat codes", "sv_cheats"); } - [SupportedOSPlatform("windows")] - private static GrammarBuilder GetDisableHintsRule() + private static SpeechRecognitionGrammarBuilder GetDisableHintsRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("disable", "turn off") .OneOf("cheats", "cheat codes", "sv_cheats"); } - [SupportedOSPlatform("windows")] - private static GrammarBuilder FillRule() + private static SpeechRecognitionGrammarBuilder FillRule() { - var fillChoices = new Choices(); - fillChoices.Add(s_fillHealthChoices.ToArray()); - fillChoices.Add(s_fillMagicChoices.ToArray()); - fillChoices.Add(s_fillBombsChoices.ToArray()); - fillChoices.Add(s_fillArrowsChoices.ToArray()); - fillChoices.Add(s_fillRupeesChoices.ToArray()); - fillChoices.Add(s_fillMissilesChoices.ToArray()); - fillChoices.Add(s_fillSuperMissileChoices.ToArray()); - fillChoices.Add(s_fillPowerBombsChoices.ToArray()); - var restore = new GrammarBuilder() + var fillChoices = new List(); + fillChoices.AddRange(s_fillHealthChoices.Select(x => new GrammarKeyValueChoice(x))); + fillChoices.AddRange(s_fillMagicChoices.Select(x => new GrammarKeyValueChoice(x))); + fillChoices.AddRange(s_fillBombsChoices.Select(x => new GrammarKeyValueChoice(x))); + fillChoices.AddRange(s_fillArrowsChoices.Select(x => new GrammarKeyValueChoice(x))); + fillChoices.AddRange(s_fillRupeesChoices.Select(x => new GrammarKeyValueChoice(x))); + fillChoices.AddRange(s_fillMissilesChoices.Select(x => new GrammarKeyValueChoice(x))); + fillChoices.AddRange(s_fillSuperMissileChoices.Select(x => new GrammarKeyValueChoice(x))); + fillChoices.AddRange(s_fillPowerBombsChoices.Select(x => new GrammarKeyValueChoice(x))); + var restore = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please", "would you please") .OneOf("restore my", "fill my", "refill my") .Append(s_fillCheatKey, fillChoices); - var heal = new GrammarBuilder() + var heal = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please", "would you please") .OneOf("heal me", "I need healing"); - return GrammarBuilder.Combine(restore, heal); + return SpeechRecognitionGrammarBuilder.Combine(restore, heal); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GiveItemRule() + private SpeechRecognitionGrammarBuilder GiveItemRule() { var itemNames = GetItemNames(x => x.Name != "Content"); - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("give me", "lend me", "donate") @@ -181,28 +175,25 @@ private GrammarBuilder GiveItemRule() .Append(ItemNameKey, itemNames); } - [SupportedOSPlatform("windows")] - private GrammarBuilder KillPlayerRule() + private SpeechRecognitionGrammarBuilder KillPlayerRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("kill me", "give me a tactical reset"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder SetupCrystalFlashRule() + private SpeechRecognitionGrammarBuilder SetupCrystalFlashRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("setup crystal flash requirements", "ready a crystal flash"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder ChargeShinesparkRule() + private SpeechRecognitionGrammarBuilder ChargeShinesparkRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("charge", "ready", "give me", "enable", "activate", "turn on") @@ -210,7 +201,6 @@ private GrammarBuilder ChargeShinesparkRule() .Append("shine spark"); } - [SupportedOSPlatform("windows")] public override void AddCommands() { if (TrackerBase.World.Config.Race || TrackerBase.World.Config.DisableCheats) return; diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs index 06b2839fb..a2a3be61b 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Tracking.Services; @@ -23,9 +24,9 @@ public GoModeModule(TrackerBase tracker, IPlayerProgressionService playerProgres { } - private GrammarBuilder GetGoModeRule(List prompts) + private SpeechRecognitionGrammarBuilder GetGoModeRule(List prompts) { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf(prompts.ToArray()); } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs index 4f13e93c6..86d8a7e06 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Speech.Recognition; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; @@ -28,10 +26,9 @@ public GoalModule(TrackerBase tracker, IPlayerProgressionService playerProgressi { } - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private GrammarBuilder GetGanonsTowerCrystalCountRule() + private SpeechRecognitionGrammarBuilder GetGanonsTowerCrystalCountRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("Ganon's Tower", "G T") .OneOf("requires", "needs") @@ -40,10 +37,9 @@ private GrammarBuilder GetGanonsTowerCrystalCountRule() .Optional("to enter"); } - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private GrammarBuilder GetGanonsCrystalCountRule() + private SpeechRecognitionGrammarBuilder GetGanonsCrystalCountRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("Ganon", "Ganondorf") .OneOf("requires", "needs") @@ -53,9 +49,9 @@ private GrammarBuilder GetGanonsCrystalCountRule() } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private GrammarBuilder GetTourianBossCountRule() + private SpeechRecognitionGrammarBuilder GetTourianBossCountRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("tourian", "the statue room", "the metroid statue room", "the golden statue room") .OneOf("requires", "needs") @@ -63,7 +59,6 @@ private GrammarBuilder GetTourianBossCountRule() .OneOf("bosses", "boss tokens"); } - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public override void AddCommands() { if (TrackerBase.World.Config.RomGenerator == RomGenerator.Cas) @@ -73,19 +68,19 @@ public override void AddCommands() AddCommand("GT Crystal Count", GetGanonsTowerCrystalCountRule(), (result) => { - var count = (int)result.Semantics[ItemCountKey].Value; + var count = int.Parse(result.Semantics[ItemCountKey].Value); TrackerBase.GameStateTracker.UpdateGanonsTowerRequirement(count, false); }); AddCommand("Ganon Crystal Count", GetGanonsCrystalCountRule(), (result) => { - var count = (int)result.Semantics[ItemCountKey].Value; + var count = int.Parse(result.Semantics[ItemCountKey].Value); TrackerBase.GameStateTracker.UpdateGanonRequirement(count, false); }); AddCommand("Tourian Boss Count", GetTourianBossCountRule(), (result) => { - var count = (int)result.Semantics[ItemCountKey].Value; + var count = int.Parse(result.Semantics[ItemCountKey].Value); TrackerBase.GameStateTracker.UpdateTourianRequirement(count, false); }); } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GrammarBuilder.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GrammarBuilder.cs deleted file mode 100644 index 8c1a3238d..000000000 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GrammarBuilder.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Versioning; -using System.Speech.Recognition; - -namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; - -/// -/// Constructs a speech recognition grammar. -/// -public class GrammarBuilder -{ - public static string[] ForceCommandIdentifiers = ["would you please", "please, I'm begging you"]; - - private readonly System.Speech.Recognition.GrammarBuilder _grammar = null!; - private readonly List _elements; - - /// - /// Initializes a new empty instance of the - /// class. - /// - public GrammarBuilder() - { - if (OperatingSystem.IsWindows()) - { - _grammar = new(); - } - _elements = new(); - } - - /// - /// Initializes a new instance of the class - /// that combines the specified grammars into a single choice. - /// - /// The grammars to choose from. - public GrammarBuilder(IEnumerable choices) - : this() - { - if (!OperatingSystem.IsWindows()) - { - return; - } - _grammar.Append(new Choices(choices.Select(x => (System.Speech.Recognition.GrammarBuilder)x).ToArray())); - foreach (var choice in choices) - _elements.Add(choice + "\n"); - } - - /// - /// Converts the grammar builder to the System.Speech grammar builder. - /// - /// The grammar builder to convert. - public static implicit operator System.Speech.Recognition.GrammarBuilder(GrammarBuilder self) => self._grammar; - - /// - /// Combines the specified grammars into a single grammar. - /// - /// - /// The possible grammars to choose from in the new grammar. - /// - /// - /// A new that represents a choice of one - /// of . - /// - public static GrammarBuilder Combine(params GrammarBuilder[] choices) - { - return new GrammarBuilder(choices); - } - - /// - /// Adds the specified text to the end of the grammar. - /// - /// The text to recognize. - /// This instance. - public GrammarBuilder Append(string text) - { - if (!OperatingSystem.IsWindows()) - { - return this; - } - _grammar.Append(text); - _elements.Add(text); - return this; - } - - /// - /// Adds a choice that can be retrieved later using the specified - /// semantic result key. - /// - /// - /// The key used to retrieve the recognized choice. - /// - /// - /// The choices to represent in the grammar. - /// - /// This instance. - public GrammarBuilder Append(string key, Choices choices) - { - if (!OperatingSystem.IsWindows()) - { - return this; - } - _grammar.Append(new SemanticResultKey(key, choices)); - _elements.Add($"<{key}>"); - return this; - } - - /// - /// Adds a choice. - /// - /// - /// The choices to represent in the grammar. - /// - /// This instance. - public GrammarBuilder OneOf(params string[] choices) - { - if (!OperatingSystem.IsWindows()) - { - return this; - } - _grammar.Append(new Choices(choices)); - _elements.Add($"[{string.Join('/', choices)}]"); - return this; - } - - /// - /// Adds the specified optional text to the end of the grammar. - /// - /// - /// The text that may or may not be recognized. - /// - /// This instance. - public GrammarBuilder Optional(string text) - { - if (!OperatingSystem.IsWindows()) - { - return this; - } - _grammar.Append(text, 0, 1); - _elements.Add($"({text})"); - return this; - } - - /// - /// Adds an optional choice. - /// - /// The choices to represent in the grammar. - /// This instance. - public GrammarBuilder Optional(params string[] choices) - { - if (!OperatingSystem.IsWindows()) - { - return this; - } - _grammar.Append(new Choices(choices), 0, 1); - _elements.Add($"({string.Join('/', choices)})"); - return this; - } - - /// - /// Builds the grammar. - /// - /// The name of the grammar rule. - /// A new . - [SupportedOSPlatform("windows")] - public Grammar Build(string name) - { - return new Grammar(this) - { - Name = name - }; - } - - /// - /// Returns a string representing the grammar syntax. - /// - /// A string representing the grammar syntax. - public override string ToString() - => string.Join(' ', _elements); -} diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs index bd5434ebd..e48dd2334 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs @@ -1,14 +1,13 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Runtime.Versioning; -using System.Speech.Recognition; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.SeedGenerator.Contracts; -using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; @@ -40,7 +39,6 @@ public HintTileModule(TrackerBase tracker, IPlayerProgressionService playerProgr /// /// Adds the voice commands /// - [SupportedOSPlatform("windows")] public override void AddCommands() { AddCommand("Hint Tile", GetHintTileRules(), (result) => @@ -60,10 +58,9 @@ public override void AddCommands() }); } - [SupportedOSPlatform("windows")] - private Choices GetHintTileNames() + private List GetHintTileNames() { - var hintTileNames = new Choices(); + var hintTileNames = new List(); if (_hintTileConfig.HintTiles == null) { @@ -77,16 +74,15 @@ private Choices GetHintTileNames() continue; } foreach (var name in hintTile.Name) - hintTileNames.Add(new SemanticResultValue(name.Text, hintTile.HintTileKey)); + hintTileNames.Add(new GrammarKeyValueChoice(name.Text, hintTile.HintTileKey)); } return hintTileNames; } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetHintTileRules() + private SpeechRecognitionGrammarBuilder GetHintTileRules() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("what does the") .Append(_hintTileKey, GetHintTileNames()) @@ -94,8 +90,7 @@ private GrammarBuilder GetHintTileRules() .OneOf("say", "state"); } - [SupportedOSPlatform("windows")] - private (HintTile HintTile, Shared.Models.PlayerHintTile PlayerHintTile) GetHintTileFromResult(RecognitionResult result) + private (HintTile HintTile, Shared.Models.PlayerHintTile PlayerHintTile) GetHintTileFromResult(SpeechRecognitionResult result) { var key = (string)result.Semantics[_hintTileKey].Value; var hintTile = _hintTileConfig.HintTiles?.FirstOrDefault(x => x.HintTileKey == key) ?? diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs index 8be81890f..623b1905a 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs @@ -1,8 +1,7 @@ -using System.Linq; -using System.Runtime.Versioning; -using System.Speech.Recognition; - +using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Tracking.Services; @@ -30,55 +29,53 @@ public ItemTrackingModule(TrackerBase tracker, IPlayerProgressionService playerP } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetTrackDeathRule() + private SpeechRecognitionGrammarBuilder GetTrackDeathRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Append("I just died"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetTrackItemRule(bool isMultiworld) + private SpeechRecognitionGrammarBuilder GetTrackItemRule(bool isMultiworld) { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: true); var itemNames = GetItemNames(x => x.Name != "Content"); var locationNames = GetLocationNames(); var roomNames = GetRoomNames(); - var trackItemNormal = new GrammarBuilder() + var trackItemNormal = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) + .Optional(ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) .OneOf("track", "add") .Append(ItemNameKey, itemNames); if (!isMultiworld) { - var trackItemDungeon = new GrammarBuilder() + var trackItemDungeon = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) + .Optional(ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) .OneOf("track", "add") .Append(ItemNameKey, itemNames) .OneOf("in", "from") .Append(DungeonKey, dungeonNames); - var trackItemLocation = new GrammarBuilder() + var trackItemLocation = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) + .Optional(ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) .OneOf("track", "add") .Append(ItemNameKey, itemNames) .OneOf("in", "from", "in the", "from the") .Append(LocationKey, locationNames); - var trackItemRoom = new GrammarBuilder() + var trackItemRoom = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) + .Optional(ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) .OneOf("track", "add") .Append(ItemNameKey, itemNames) .OneOf("in", "from", "in the", "from the") .Append(RoomKey, roomNames); - return GrammarBuilder.Combine( + return SpeechRecognitionGrammarBuilder.Combine( trackItemNormal, trackItemDungeon, trackItemLocation, trackItemRoom); } @@ -89,13 +86,12 @@ private GrammarBuilder GetTrackItemRule(bool isMultiworld) } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetTrackEverythingRule() + private SpeechRecognitionGrammarBuilder GetTrackEverythingRule() { var roomNames = GetRoomNames(); var regionNames = GetRegionNames(); - var trackAllInRoom = new GrammarBuilder() + var trackAllInRoom = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("track", "add") @@ -103,7 +99,7 @@ private GrammarBuilder GetTrackEverythingRule() .OneOf("in", "from", "in the", "from the") .Append(RoomKey, roomNames); - var trackAllInRegion = new GrammarBuilder() + var trackAllInRegion = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("track", "add") @@ -111,16 +107,15 @@ private GrammarBuilder GetTrackEverythingRule() .OneOf("in", "from") .Append(RegionKey, regionNames); - return GrammarBuilder.Combine(trackAllInRoom, trackAllInRegion); + return SpeechRecognitionGrammarBuilder.Combine(trackAllInRoom, trackAllInRegion); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetTrackEverythingIncludingOutOfLogicRule() + private SpeechRecognitionGrammarBuilder GetTrackEverythingIncludingOutOfLogicRule() { var roomNames = GetRoomNames(); var regionNames = GetRegionNames(); - var trackAllInRoom = new GrammarBuilder() + var trackAllInRoom = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("force", "sudo") .OneOf("track", "add") @@ -128,7 +123,7 @@ private GrammarBuilder GetTrackEverythingIncludingOutOfLogicRule() .OneOf("in", "from", "in the", "from the") .Append(RoomKey, roomNames); - var trackAllInRegion = new GrammarBuilder() + var trackAllInRegion = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("force", "sudo") .OneOf("track", "add") @@ -136,59 +131,56 @@ private GrammarBuilder GetTrackEverythingIncludingOutOfLogicRule() .OneOf("in", "from") .Append(RegionKey, regionNames); - var cheatedRoom = new GrammarBuilder() + var cheatedRoom = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("sequence break", "I sequence broke", "I cheated my way to") .Append(RoomKey, roomNames); - var cheatedRegion = new GrammarBuilder() + var cheatedRegion = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("sequence break", "I sequence broke", "I cheated my way to") .Append(RegionKey, regionNames); - return GrammarBuilder.Combine(trackAllInRoom, trackAllInRegion, + return SpeechRecognitionGrammarBuilder.Combine(trackAllInRoom, trackAllInRegion, cheatedRoom, cheatedRegion); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetUntrackItemRule() + private SpeechRecognitionGrammarBuilder GetUntrackItemRule() { var itemNames = GetItemNames(); - var untrackItem = new GrammarBuilder() + var untrackItem = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) + .Optional(ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) .OneOf("untrack", "remove") .Optional("a", "an", "the") .Append(ItemNameKey, itemNames); - var toggleItemOff = new GrammarBuilder() + var toggleItemOff = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) + .Optional(ForceCommandIdentifiers.Concat(["please", "would you kindly"]).ToArray()) .Append("toggle") .Append(ItemNameKey, itemNames) .Append("off"); - return GrammarBuilder.Combine(untrackItem, toggleItemOff); + return SpeechRecognitionGrammarBuilder.Combine(untrackItem, toggleItemOff); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetSetItemCountRule() + private SpeechRecognitionGrammarBuilder GetSetItemCountRule() { var itemNames = GetPluralItemNames(); - var numbers = new Choices(); + var numbers = new List(); for (var i = 0; i <= 200; i++) - numbers.Add(new SemanticResultValue(i.ToString(), i)); + numbers.Add(new GrammarKeyValueChoice(i.ToString(), i)); - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") - .Optional(GrammarBuilder.ForceCommandIdentifiers) + .Optional(ForceCommandIdentifiers) .OneOf("I have", "I've got", "I possess", "I am in the possession of", "track") .Append(ItemCountKey, numbers) .Append(ItemNameKey, itemNames); } - [SupportedOSPlatform("windows")] public override void AddCommands() { var isMultiworld = WorldQueryService.World.Config.MultiWorld; @@ -197,7 +189,7 @@ public override void AddCommands() { var item = GetItemFromResult(TrackerBase, result, out var itemName); - var force = result.Text.ContainsAny(GrammarBuilder.ForceCommandIdentifiers); + var force = result.Text.ContainsAny(ForceCommandIdentifiers); if (result.Semantics.ContainsKey(DungeonKey)) { @@ -294,15 +286,15 @@ public override void AddCommands() AddCommand("Untrack an item", GetUntrackItemRule(), (result) => { var item = GetItemFromResult(TrackerBase, result, out _); - var force = result.Text.ContainsAny(GrammarBuilder.ForceCommandIdentifiers); + var force = result.Text.ContainsAny(ForceCommandIdentifiers); TrackerBase.ItemTracker.UntrackItem(item, result.Confidence, force: force); }); AddCommand("Set item count", GetSetItemCountRule(), (result) => { var item = GetItemFromResult(TrackerBase, result, out _); - var count = (int)result.Semantics[ItemCountKey].Value; - var force = result.Text.ContainsAny(GrammarBuilder.ForceCommandIdentifiers); + var count = int.Parse(result.Semantics[ItemCountKey].Value); + var force = result.Text.ContainsAny(ForceCommandIdentifiers); TrackerBase.ItemTracker.TrackItemAmount(item, count, result.Confidence, force: force); }); } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs index bd84920fb..1691950ff 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs @@ -1,5 +1,5 @@ -using System.Runtime.Versioning; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Tracking.Services; @@ -24,54 +24,52 @@ public LocationTrackingModule(TrackerBase tracker, IPlayerProgressionService pla } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetMarkItemAtLocationRule() + private SpeechRecognitionGrammarBuilder GetMarkItemAtLocationRule() { var itemNames = GetItemNames(); var locationNames = GetLocationNames(); - var itemIsAtLocation = new GrammarBuilder() + var itemIsAtLocation = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append(ItemNameKey, itemNames) .OneOf("is at", "are at") .Append(LocationKey, locationNames); - var theItemIsAtLocation = new GrammarBuilder() + var theItemIsAtLocation = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("a", "an", "the") .Append(ItemNameKey, itemNames) .OneOf("is at", "are at") .Append(LocationKey, locationNames); - var thereIsItemAtLocation = new GrammarBuilder() + var thereIsItemAtLocation = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("there are", "there is a", "there is an") .Append(ItemNameKey, itemNames) .Append("at") .Append(LocationKey, locationNames); - var locationHasItem = new GrammarBuilder() + var locationHasItem = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append(LocationKey, locationNames) .OneOf("has", "has a", "has an", "has the") .Append(ItemNameKey, itemNames); - var markAtLocation = new GrammarBuilder() + var markAtLocation = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("mark") .Append(ItemNameKey, itemNames) .Append("at") .Append(LocationKey, locationNames); - return GrammarBuilder.Combine( + return SpeechRecognitionGrammarBuilder.Combine( itemIsAtLocation, theItemIsAtLocation, thereIsItemAtLocation, locationHasItem, markAtLocation); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetClearViewedObjectRule() + private SpeechRecognitionGrammarBuilder GetClearViewedObjectRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("clear", "I don't care about", "I don't give a shit about", "I don't give a fuck about") .OneOf("that", "those") @@ -79,71 +77,67 @@ private GrammarBuilder GetClearViewedObjectRule() .OneOf("marked location", "marked locations", "hint tile", "telepathic tile"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetClearLocationRule() + private SpeechRecognitionGrammarBuilder GetClearLocationRule() { var locationNames = GetLocationNames(); - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("clear", "ignore") .Append(LocationKey, locationNames); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetClearAreaRule() + private SpeechRecognitionGrammarBuilder GetClearAreaRule() { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: true); var roomNames = GetRoomNames(); var regionNames = GetRegionNames(excludeDungeons: true); - var clearDungeon = new GrammarBuilder() + var clearDungeon = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("clear", "please clear") .Append(DungeonKey, dungeonNames); - var clearRoom = new GrammarBuilder() + var clearRoom = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("clear", "please clear") .Append(RoomKey, roomNames); - var clearRegion = new GrammarBuilder() + var clearRegion = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("clear", "please clear") .Append(RegionKey, regionNames); - return GrammarBuilder.Combine(clearDungeon, clearRoom, clearRegion); + return SpeechRecognitionGrammarBuilder.Combine(clearDungeon, clearRoom, clearRegion); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetClearAreaIncludingOutOfLogicRule() + private SpeechRecognitionGrammarBuilder GetClearAreaIncludingOutOfLogicRule() { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: true); var roomNames = GetRoomNames(); var regionNames = GetRegionNames(excludeDungeons: true); - var clearDungeon = new GrammarBuilder() + var clearDungeon = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("force", "sudo") .OneOf("clear", "please clear") .Append(DungeonKey, dungeonNames); - var clearRoom = new GrammarBuilder() + var clearRoom = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("force", "sudo") .OneOf("clear", "please clear") .Append(RoomKey, roomNames); - var clearRegion = new GrammarBuilder() + var clearRegion = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("force", "sudo") .OneOf("clear", "please clear") .Append(RegionKey, regionNames); - return GrammarBuilder.Combine(clearDungeon, clearRoom, clearRegion); + return SpeechRecognitionGrammarBuilder.Combine(clearDungeon, clearRoom, clearRegion); } - [SupportedOSPlatform("windows")] public override void AddCommands() { AddCommand("Mark item at specific location", GetMarkItemAtLocationRule(), (result) => diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs index 3b2c51e37..6cb00a1e5 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs @@ -1,7 +1,7 @@ -using System.Linq; -using System.Runtime.Versioning; -using System.Speech.Recognition; +using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Shared.Enums; @@ -33,30 +33,29 @@ public MapModule(TrackerBase tracker, IPlayerProgressionService playerProgressio _config = config; } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetChangeMapRule() + private SpeechRecognitionGrammarBuilder GetChangeMapRule() { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: true); var itemNames = GetItemNames(x => x.Name != "Content"); var locationNames = GetLocationNames(); var roomNames = GetRoomNames(); - var maps = new Choices(); + var maps = new List(); foreach (var map in _config.Maps) { foreach (var name in map.Name) { - maps.Add(new SemanticResultValue(name, map.ToString())); + maps.Add(new GrammarKeyValueChoice(name, map.ToString())); } } - var version1 = new GrammarBuilder() + var version1 = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("update my map to", "change my map to") .Append(MapKey, maps); - var version2 = new GrammarBuilder() + var version2 = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Optional("please", "would you kindly") .OneOf("show me") @@ -64,26 +63,23 @@ private GrammarBuilder GetChangeMapRule() .Append(MapKey, maps) .Optional("map"); - return GrammarBuilder.Combine(version1, version2); + return SpeechRecognitionGrammarBuilder.Combine(version1, version2); } - [SupportedOSPlatform("windows")] - private GrammarBuilder DarkRoomRule() + private SpeechRecognitionGrammarBuilder DarkRoomRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("it's dark in here", "I can't see", "show me this dark room map"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder CanSeeRule() + private SpeechRecognitionGrammarBuilder CanSeeRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("I can see now", "I can see clearly now", "it's no longer dark", "I'm out of the dark room", "stop showing me the dark room map"); } - [SupportedOSPlatform("windows")] public override void AddCommands() { var darkRoomMaps = _config.Maps.Where(x => x.IsDarkRoomMap == true && x.MemoryRoomNumbers?.Count > 0).ToList(); diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs index 38c3ed5e9..77182b494 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Speech.Recognition; - using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Tracking.Services; @@ -36,44 +36,44 @@ public MetaModule(TrackerBase tracker, IPlayerProgressionService playerProgressi } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private static Choices GetIncreaseDecrease() + private static List GetIncreaseDecrease() { - var modifiers = new Choices(); - modifiers.Add(new SemanticResultValue("increase", 1)); - modifiers.Add(new SemanticResultValue("decrease", -1)); + var modifiers = new List(); + modifiers.Add(new GrammarKeyValueChoice("increase", 1)); + modifiers.Add(new GrammarKeyValueChoice("decrease", -1)); return modifiers; } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private static Choices GetOneThroughTenPercent() + private static List GetOneThroughTenPercent() { - var values = new Choices(); - values.Add(new SemanticResultValue("one percent", 0.01)); - values.Add(new SemanticResultValue("two percent", 0.02)); - values.Add(new SemanticResultValue("three percent", 0.03)); - values.Add(new SemanticResultValue("four percent", 0.04)); - values.Add(new SemanticResultValue("five percent", 0.05)); - values.Add(new SemanticResultValue("six percent", 0.06)); - values.Add(new SemanticResultValue("seven percent", 0.07)); - values.Add(new SemanticResultValue("eight percent", 0.08)); - values.Add(new SemanticResultValue("nine percent", 0.09)); - values.Add(new SemanticResultValue("ten percent", 0.10)); + var values = new List(); + values.Add(new GrammarKeyValueChoice("one percent", 0.01)); + values.Add(new GrammarKeyValueChoice("two percent", 0.02)); + values.Add(new GrammarKeyValueChoice("three percent", 0.03)); + values.Add(new GrammarKeyValueChoice("four percent", 0.04)); + values.Add(new GrammarKeyValueChoice("five percent", 0.05)); + values.Add(new GrammarKeyValueChoice("six percent", 0.06)); + values.Add(new GrammarKeyValueChoice("seven percent", 0.07)); + values.Add(new GrammarKeyValueChoice("eight percent", 0.08)); + values.Add(new GrammarKeyValueChoice("nine percent", 0.09)); + values.Add(new GrammarKeyValueChoice("ten percent", 0.10)); return values; } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private static Choices GetThresholdSettings() + private static List GetThresholdSettings() { - var settings = new Choices(); - settings.Add(new SemanticResultValue("recognition", ThresholdSetting_Recognition)); - settings.Add(new SemanticResultValue("execution", ThresholdSetting_Execution)); - settings.Add(new SemanticResultValue("sass", ThresholdSetting_Sass)); + var settings = new List(); + settings.Add(new GrammarKeyValueChoice("recognition", ThresholdSetting_Recognition)); + settings.Add(new GrammarKeyValueChoice("execution", ThresholdSetting_Execution)); + settings.Add(new GrammarKeyValueChoice("sass", ThresholdSetting_Sass)); return settings; } - private GrammarBuilder GetRepeatThatRule() + private SpeechRecognitionGrammarBuilder GetRepeatThatRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("can you repeat that?", "can you please repeat that?", @@ -82,20 +82,20 @@ private GrammarBuilder GetRepeatThatRule() "please repeat that"); } - private GrammarBuilder GetShutUpRule() + private SpeechRecognitionGrammarBuilder GetShutUpRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please") .OneOf("shut up", "be quiet", "stop talking"); } - private GrammarBuilder GetIncreaseThresholdGrammar() + private SpeechRecognitionGrammarBuilder GetIncreaseThresholdGrammar() { var modifiers = GetIncreaseDecrease(); var settings = GetThresholdSettings(); var values = GetOneThroughTenPercent(); - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append(ModifierKey, modifiers) .Optional("minimum") @@ -105,49 +105,49 @@ private GrammarBuilder GetIncreaseThresholdGrammar() .Append(ValueKey, values); } - private GrammarBuilder GetPauseTimerRule() + private SpeechRecognitionGrammarBuilder GetPauseTimerRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please") .OneOf("pause the timer", "stop the timer"); } - private GrammarBuilder GetResumeTimerRule() + private SpeechRecognitionGrammarBuilder GetResumeTimerRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please") .OneOf("resume the timer", "start the timer"); } - private GrammarBuilder GetResetTimerRule() + private SpeechRecognitionGrammarBuilder GetResetTimerRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please") .OneOf("reset the timer"); } - private GrammarBuilder GetMuteRule() + private SpeechRecognitionGrammarBuilder GetMuteRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please") .OneOf("mute yourself", "silence yourself"); } - private GrammarBuilder GetUnmuteRule() + private SpeechRecognitionGrammarBuilder GetUnmuteRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please") .OneOf("unmute yourself", "unsilence yourself"); } - private GrammarBuilder GetBeatGameRule() + private SpeechRecognitionGrammarBuilder GetBeatGameRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("I beat", "I finished") .OneOf("the game", "the seed"); @@ -168,9 +168,9 @@ public override void AddCommands() AddCommand("Temporarily change threshold setting", GetIncreaseThresholdGrammar(), (result) => { - var modifier = (int)result.Semantics[ModifierKey].Value; - var thresholdSetting = (int)result.Semantics[ThresholdSettingKey].Value; - var value = (float)(double)result.Semantics[ValueKey].Value; + var modifier = int.Parse(result.Semantics[ModifierKey].Value); + var thresholdSetting = int.Parse(result.Semantics[ThresholdSettingKey].Value); + var value = float.Parse(result.Semantics[ValueKey].Value); var adjustment = modifier * value; switch (thresholdSetting) @@ -223,7 +223,8 @@ public override void AddCommands() { if (_communicator.IsEnabled) { - TrackerBase.Say(x => x.Muted); + _communicator.Abort(); + TrackerBase.Say(x => x.Muted, wait: true); _communicator.Disable(); TrackerBase.AddUndo(() => { diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs index 450f8579d..96486637c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Speech.Recognition; using Microsoft.Extensions.Logging; using MSURandomizerLibrary; using MSURandomizerLibrary.Configs; using MSURandomizerLibrary.Messenger; using MSURandomizerLibrary.Models; using MSURandomizerLibrary.Services; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; @@ -185,14 +185,14 @@ private void MsuMonitorServiceOnMsuTrackChanged(object? sender, MsuTrackChangedE } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private GrammarBuilder GetLocationSongRules() + private SpeechRecognitionGrammarBuilder GetLocationSongRules() { if (_msuConfig.TrackLocations == null) { - return new GrammarBuilder(); + return new SpeechRecognitionGrammarBuilder(); } - var msuLocations = new Choices(); + var msuLocations = new List(); foreach (var track in _msuConfig.TrackLocations) { @@ -202,35 +202,35 @@ private GrammarBuilder GetLocationSongRules() } foreach (var name in track.Value) { - msuLocations.Add(new SemanticResultValue(name, track.Key)); + msuLocations.Add(new GrammarKeyValueChoice(name, track.Key)); } } - var option1 = new GrammarBuilder() + var option1 = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("what's the current song for", "what's the song for", "what's the current theme for", "what's the theme for") .Optional("the") .Append(_msuKey, msuLocations); - var option2 = new GrammarBuilder() + var option2 = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("what's the current", "what's the") .Append(_msuKey, msuLocations) .OneOf("song", "theme"); - return GrammarBuilder.Combine(option1, option2); + return SpeechRecognitionGrammarBuilder.Combine(option1, option2); } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private GrammarBuilder GetLocationMsuRules() + private SpeechRecognitionGrammarBuilder GetLocationMsuRules() { if (_msuConfig.TrackLocations == null) { - return new GrammarBuilder(); + return new SpeechRecognitionGrammarBuilder(); } - var msuLocations = new Choices(); + var msuLocations = new List(); foreach (var track in _msuConfig.TrackLocations) { @@ -241,11 +241,11 @@ private GrammarBuilder GetLocationMsuRules() foreach (var name in track.Value) { - msuLocations.Add(new SemanticResultValue(name, track.Key)); + msuLocations.Add(new GrammarKeyValueChoice(name, track.Key)); } } - var option1 = new GrammarBuilder() + var option1 = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("what MSU pack is") .OneOf("the current song for", "the song for", "the current theme for", "the theme for") @@ -253,7 +253,7 @@ private GrammarBuilder GetLocationMsuRules() .Append(_msuKey, msuLocations) .Append("from"); - var option2 = new GrammarBuilder() + var option2 = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("what MSU pack is") .OneOf("the current", "the") @@ -261,42 +261,27 @@ private GrammarBuilder GetLocationMsuRules() .OneOf("song", "theme") .Append("from"); - return GrammarBuilder.Combine(option1, option2); + return SpeechRecognitionGrammarBuilder.Combine(option1, option2); } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - private GrammarBuilder GetCurrentSongRules() + private SpeechRecognitionGrammarBuilder GetCurrentSongRules() { if (_msuConfig.TrackLocations == null) { - return new GrammarBuilder(); + return new SpeechRecognitionGrammarBuilder(); } - var msuLocations = new Choices(); - - foreach (var track in _msuConfig.TrackLocations) - { - if (track.Value == null) - { - continue; - } - - foreach (var name in track.Value) - { - msuLocations.Add(new SemanticResultValue(name, track.Key)); - } - } - - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("what's the current song", "what's currently playing", "what's the current track"); } - private GrammarBuilder GetCurrentMsuRules() + private SpeechRecognitionGrammarBuilder GetCurrentMsuRules() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("what MSU pack is") .OneOf("the current song from", "the current track from", "the current theme from"); @@ -328,7 +313,7 @@ public override void AddCommands() return; } - var trackNumber = (int)result.Semantics[_msuKey].Value; + var trackNumber = int.Parse(result.Semantics[_msuKey].Value); var track = _currentMsu.GetTrackFor(trackNumber); if (track != null) { @@ -348,7 +333,7 @@ public override void AddCommands() return; } - var trackNumber = (int)result.Semantics[_msuKey].Value; + var trackNumber = int.Parse(result.Semantics[_msuKey].Value); var track = _currentMsu.GetTrackFor(trackNumber); if (track?.GetMsuName() != null) { diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs index 8ea849071..8f3da69c4 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.Versioning; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Tracking.Services; @@ -27,7 +27,6 @@ public PersonalityModule(TrackerBase tracker, IPlayerProgressionService playerPr } - [SupportedOSPlatform("windows")] public override void AddCommands() { AddCommand("Hey, ya missed pal", GetYaMissedRule(), (_) => @@ -54,21 +53,19 @@ public override void AddCommands() /// public override bool IsSecret => true; - [SupportedOSPlatform("windows")] - private GrammarBuilder GetYaMissedRule() + private SpeechRecognitionGrammarBuilder GetYaMissedRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("what do you know about") .OneOf("your missing steak knives?", "my shoes getting bloody?"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetRequestRule(IEnumerable requests) + private SpeechRecognitionGrammarBuilder GetRequestRule(IEnumerable requests) { - return new GrammarBuilder(requests.Select(x => + return new SpeechRecognitionGrammarBuilder(requests.Select(x => { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Append(x); })); diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs index c608bffcd..ea630a05c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Runtime.Versioning; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.GeneratedData; @@ -907,12 +907,11 @@ private bool GiveItemLocationHint(Item item) return randomLocation; } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetItemSpoilerRule() + private SpeechRecognitionGrammarBuilder GetItemSpoilerRule() { var items = GetItemNames(); - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("where is", "where's", "where are", "where can I find", "where the fuck is", "where the hell is", @@ -921,19 +920,18 @@ private GrammarBuilder GetItemSpoilerRule() .Append(ItemNameKey, items); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetLocationSpoilerRule() + private SpeechRecognitionGrammarBuilder GetLocationSpoilerRule() { var locations = GetLocationNames(); - var whatsAtRule = new GrammarBuilder() + var whatsAtRule = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("what's", "what is") .OneOf("at", "in") .Optional("the") .Append(LocationKey, locations); - var whatDoesLocationHaveRule = new GrammarBuilder() + var whatDoesLocationHaveRule = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Append("what does") .Optional("the") @@ -941,49 +939,44 @@ private GrammarBuilder GetLocationSpoilerRule() .Append("have") .Optional("for me"); - return GrammarBuilder.Combine(whatsAtRule, whatDoesLocationHaveRule); + return SpeechRecognitionGrammarBuilder.Combine(whatsAtRule, whatDoesLocationHaveRule); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetEnableSpoilersRule() + private SpeechRecognitionGrammarBuilder GetEnableSpoilersRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("enable", "turn on") .Append("spoilers"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetDisableSpoilersRule() + private SpeechRecognitionGrammarBuilder GetDisableSpoilersRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("disable", "turn off") .Append("spoilers"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetEnableHintsRule() + private SpeechRecognitionGrammarBuilder GetEnableHintsRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("enable", "turn on") .Append("hints"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetDisableHintsRule() + private SpeechRecognitionGrammarBuilder GetDisableHintsRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("disable", "turn off") .Append("hints"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetProgressionHintRule() + private SpeechRecognitionGrammarBuilder GetProgressionHintRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("give me a hint", "give me a suggestion", @@ -993,11 +986,10 @@ private GrammarBuilder GetProgressionHintRule() "what should I do?"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetLocationUsefulnessHintRule() + private SpeechRecognitionGrammarBuilder GetLocationUsefulnessHintRule() { var regionNames = GetRegionNames(); - var regionGrammar = new GrammarBuilder() + var regionGrammar = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("is there anything useful", "is there anything I need", @@ -1009,7 +1001,7 @@ private GrammarBuilder GetLocationUsefulnessHintRule() .Append(RegionKey, regionNames); var roomNames = GetRoomNames(); - var roomGrammar = new GrammarBuilder() + var roomGrammar = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .OneOf("is there anything useful", "is there anything I need", @@ -1020,10 +1012,9 @@ private GrammarBuilder GetLocationUsefulnessHintRule() .OneOf("in", "at") .Append(RoomKey, roomNames); - return GrammarBuilder.Combine(regionGrammar, roomGrammar); + return SpeechRecognitionGrammarBuilder.Combine(regionGrammar, roomGrammar); } - [SupportedOSPlatform("windows")] public override void AddCommands() { if (TrackerBase.World.Config.Race) return; diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs index 292f128e2..08a3630a2 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs @@ -3,10 +3,10 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Runtime.Versioning; using System.Speech.Recognition; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.WorldData; @@ -21,6 +21,11 @@ namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; /// public abstract class TrackerModule { + /// + /// Phrases for forcing a commmand when using auto tracking + /// + protected static string[] ForceCommandIdentifiers = ["would you please", "please, I'm begging you"]; + /// /// Gets the semantic result key used to identify the name of a dungeon. /// @@ -112,8 +117,8 @@ public IReadOnlyDictionary> Syntax /// Gets a list of speech recognition grammars provided by the module. /// [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - protected IList Grammars { get; } - = new List(); + public IList Grammars { get; } + = new List(); /// /// Gets a logger for the current module. @@ -124,14 +129,12 @@ public IReadOnlyDictionary> Syntax /// Loads the voice commands provided by the module into the specified /// recognition engine. /// - /// - /// The speech recognition engine to initialize. + /// + /// The speech recognition grammar to initialize /// - [SupportedOSPlatform("windows")] - public void LoadInto(SpeechRecognitionEngine engine) + public void LoadInto(List grammar) { - foreach (var grammar in Grammars) - engine.LoadGrammar(grammar); + grammar.AddRange(Grammars); } /// @@ -143,8 +146,7 @@ public void LoadInto(SpeechRecognitionEngine engine) /// /// A from the recognition result. /// - [SupportedOSPlatform("windows")] - protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, RecognitionResult result) + protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, SpeechRecognitionResult result) { var name = (string)result.Semantics[DungeonKey].Value; var dungeon = tracker.World.TreasureRegions.FirstOrDefault(x => x.Name == name); @@ -160,8 +162,7 @@ protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, Recognit /// /// A from the recognition result. /// - [SupportedOSPlatform("windows")] - protected static IHasTreasure? GetBossDungeonFromResult(TrackerBase tracker, RecognitionResult result) + protected static IHasTreasure? GetBossDungeonFromResult(TrackerBase tracker, SpeechRecognitionResult result) { var name = (string)result.Semantics[BossKey].Value; return tracker.World.TreasureRegions.FirstOrDefault(x => x.Name == name); @@ -176,8 +177,7 @@ protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, Recognit /// /// A from the recognition result. /// - [SupportedOSPlatform("windows")] - protected static Boss? GetBossFromResult(TrackerBase tracker, RecognitionResult result) + protected static Boss? GetBossFromResult(TrackerBase tracker, SpeechRecognitionResult result) { var bossName = (string)result.Semantics[BossKey].Value; return tracker.World.AllBosses.SingleOrDefault(x => x.Name.Contains(bossName, StringComparison.Ordinal)); @@ -195,8 +195,7 @@ protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, Recognit /// /// An from the recognition result. /// - [SupportedOSPlatform("windows")] - protected Item GetItemFromResult(TrackerBase tracker, RecognitionResult result, out string itemName) + protected Item GetItemFromResult(TrackerBase tracker, SpeechRecognitionResult result, out string itemName) { itemName = (string)result.Semantics[ItemNameKey].Value; var item = WorldQueryService.FirstOrDefault(itemName); @@ -213,10 +212,9 @@ protected Item GetItemFromResult(TrackerBase tracker, RecognitionResult result, /// /// A from the recognition result. /// - [SupportedOSPlatform("windows")] - protected static Location GetLocationFromResult(TrackerBase tracker, RecognitionResult result) + protected static Location GetLocationFromResult(TrackerBase tracker, SpeechRecognitionResult result) { - var id = (LocationId)result.Semantics[LocationKey].Value; + var id = (LocationId)int.Parse(result.Semantics[LocationKey].Value); var location = tracker.World.Locations.First(x => x.Id == id); return location ?? throw new Exception($"Could not find a location with ID {id} (\"{result.Text}\")"); } @@ -228,10 +226,9 @@ protected static Location GetLocationFromResult(TrackerBase tracker, Recognition /// The tracker instance. /// The speech recognition result. /// A from the recognition result. - [SupportedOSPlatform("windows")] - protected static Room GetRoomFromResult(TrackerBase tracker, RecognitionResult result) + protected static Room GetRoomFromResult(TrackerBase tracker, SpeechRecognitionResult result) { - var roomTypeName = (string)result.Semantics[RoomKey].Value; + var roomTypeName = result.Semantics[RoomKey].Value; var room = tracker.World.Rooms.First(x => x.GetType().FullName == roomTypeName); return room ?? throw new Exception($"Could not find room {roomTypeName} (\"{result.Text}\")."); } @@ -245,10 +242,9 @@ protected static Room GetRoomFromResult(TrackerBase tracker, RecognitionResult r /// /// A from the recognition result. /// - [SupportedOSPlatform("windows")] - protected static Region GetRegionFromResult(TrackerBase tracker, RecognitionResult result) + protected static Region GetRegionFromResult(TrackerBase tracker, SpeechRecognitionResult result) { - var regionTypeName = (string)result.Semantics[RegionKey].Value; + var regionTypeName = result.Semantics[RegionKey].Value; var region = tracker.World.Regions.First(x => x.GetType().FullName == regionTypeName); return region ?? throw new Exception($"Could not find region {regionTypeName} (\"{result.Text}\")."); } @@ -262,8 +258,7 @@ protected static Region GetRegionFromResult(TrackerBase tracker, RecognitionResu /// /// The recognized area from the recognition result. /// - [SupportedOSPlatform("windows")] - protected static IHasLocations GetAreaFromResult(TrackerBase tracker, RecognitionResult result) + protected static IHasLocations GetAreaFromResult(TrackerBase tracker, SpeechRecognitionResult result) { if (result.Semantics.ContainsKey(RegionKey)) return GetRegionFromResult(tracker, result); @@ -281,14 +276,12 @@ protected static IHasLocations GetAreaFromResult(TrackerBase tracker, Recognitio /// /// The command to execute when the phrase is recognized. /// - [SupportedOSPlatform("windows")] protected void AddCommand(string ruleName, string phrase, - Action executeCommand) + Action executeCommand) { - var builder = new GrammarBuilder() + var builder = new SpeechRecognitionGrammarBuilder() .Append(phrase); - _syntax[ruleName] = new[] { phrase }; AddCommand(ruleName, builder, executeCommand); } @@ -300,14 +293,12 @@ protected void AddCommand(string ruleName, string phrase, /// /// The command to execute when any of the phrases is recognized. /// - [SupportedOSPlatform("windows")] protected void AddCommand(string ruleName, string[] phrases, - Action executeCommand) + Action executeCommand) { - var builder = new GrammarBuilder() + var builder = new SpeechRecognitionGrammarBuilder() .OneOf(phrases); - _syntax[ruleName] = phrases; AddCommand(ruleName, builder, executeCommand); } @@ -320,20 +311,13 @@ protected void AddCommand(string ruleName, string[] phrases, /// The command to execute when speech matching the grammar is /// recognized. /// - [SupportedOSPlatform("windows")] - protected void AddCommand(string ruleName, GrammarBuilder grammarBuilder, - Action executeCommand) + protected void AddCommand(string ruleName, SpeechRecognitionGrammarBuilder grammarBuilder, + Action executeCommand) { - if (!OperatingSystem.IsWindows()) - { - return; - } - - _syntax.TryAdd(ruleName, grammarBuilder.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); - try { - var grammar = grammarBuilder.Build(ruleName); + var grammar = grammarBuilder.BuildGrammar(ruleName); + _syntax.TryAdd(ruleName, grammar.HelpText); grammar.SpeechRecognized += (_, e) => { try @@ -385,10 +369,9 @@ protected void AddCommand(string ruleName, GrammarBuilder grammarBuilder, /// A new object representing all possible item /// names. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetPluralItemNames() + protected virtual List GetPluralItemNames() { - var itemNames = new Choices(); + var itemNames = new List(); foreach (var item in WorldQueryService.LocalPlayersItems().Where(x => x.Metadata is { Multiple: true, HasStages: false })) { if (item.Metadata.Plural == null) @@ -398,7 +381,7 @@ protected virtual Choices GetPluralItemNames() } foreach (var name in item.Metadata.Plural) - itemNames.Add(new SemanticResultValue(name.ToString(), item.Name)); + itemNames.Add(new GrammarKeyValueChoice(name.ToString(), item.Name)); } return itemNames; @@ -411,31 +394,43 @@ protected virtual Choices GetPluralItemNames() /// A new object representing all possible item /// names. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetItemNames(Func? where = null) + protected virtual List GetItemNames(Func? where = null) { where ??= _ => true; - var itemNames = new Choices(); + var addedNames = new HashSet(); + var itemNames = new List(); foreach (var item in WorldQueryService.LocalPlayersItems().Where(where)) { if (item.Metadata.Name != null) { foreach (var name in item.Metadata.Name) { - itemNames.Add(new SemanticResultValue(name.ToString(), item.Name)); + if (!addedNames.Contains(name.ToString())) + { + itemNames.Add(new GrammarKeyValueChoice(name.ToString(), item.Name)); + addedNames.Add(name.ToString()); + } } } else { - itemNames.Add(new SemanticResultValue(item.Metadata.Item, item.Name)); + if (!addedNames.Contains(item.Metadata.Item)) + { + itemNames.Add(new GrammarKeyValueChoice(item.Metadata.Item, item.Name)); + addedNames.Add(item.Metadata.Item); + } } if (item.Metadata.Stages != null) { foreach (var stageName in item.Metadata.Stages.SelectMany(x => x.Value)) { - itemNames.Add(new SemanticResultValue(stageName.ToString(), item.Name)); + if (!addedNames.Contains(stageName.ToString())) + { + itemNames.Add(new GrammarKeyValueChoice(stageName.ToString(), item.Name)); + addedNames.Add(stageName.ToString()); + } } } } @@ -450,10 +445,9 @@ protected virtual Choices GetItemNames(Func? where = null) /// A new object representing all possible dungeon /// names. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetDungeonNames(bool includeDungeonsWithoutReward = false) + protected virtual List GetDungeonNames(bool includeDungeonsWithoutReward = false) { - var dungeonNames = new Choices(); + var dungeonNames = new List(); foreach (var dungeon in TrackerBase.World.TreasureRegions) { var rewardRegion = dungeon as IHasReward; @@ -463,11 +457,11 @@ protected virtual Choices GetDungeonNames(bool includeDungeonsWithoutReward = fa if (dungeon.Metadata.Name != null) { foreach (var name in dungeon.Metadata.Name) - dungeonNames.Add(new SemanticResultValue(name.Text, dungeon.Name)); + dungeonNames.Add(new GrammarKeyValueChoice(name.Text, dungeon.Name)); } else { - dungeonNames.Add(new SemanticResultValue(dungeon.Name, dungeon.Name)); + dungeonNames.Add(new GrammarKeyValueChoice(dungeon.Name, dungeon.Name)); } } @@ -481,20 +475,19 @@ protected virtual Choices GetDungeonNames(bool includeDungeonsWithoutReward = fa /// A new object representing all possible boss /// names. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetBossNames() + protected virtual List GetBossNames() { - var bossNames = new Choices(); + var bossNames = new List(); foreach (var boss in TrackerBase.World.AllBosses) { if (boss.Metadata.Name != null) { foreach (var name in boss.Metadata.Name) - bossNames.Add(new SemanticResultValue(name.Text, boss.Name)); + bossNames.Add(new GrammarKeyValueChoice(name.Text, boss.Name)); } else { - bossNames.Add(new SemanticResultValue(boss.Name, boss.Name)); + bossNames.Add(new GrammarKeyValueChoice(boss.Name, boss.Name)); } } return bossNames; @@ -507,21 +500,20 @@ protected virtual Choices GetBossNames() /// A new object representing all possible /// location names mapped to their IDs. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetLocationNames() + protected virtual List GetLocationNames() { - var locationNames = new Choices(); + var locationNames = new List(); foreach (var location in TrackerBase.World.Locations) { if (location.Metadata.Name != null) { foreach (var name in location.Metadata.Name) - locationNames.Add(new SemanticResultValue(name.Text, (int)location.Id)); + locationNames.Add(new GrammarKeyValueChoice(name.Text, (int)location.Id)); } else { - locationNames.Add(new SemanticResultValue(location.Name, (int)location.Id)); + locationNames.Add(new GrammarKeyValueChoice(location.Name, (int)location.Id)); } } @@ -535,17 +527,16 @@ protected virtual Choices GetLocationNames() /// A new object representing all possible room /// names mapped to the primary room name. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetRoomNames() + protected virtual List GetRoomNames() { - var roomNames = new Choices(); + var roomNames = new List(); foreach (var room in TrackerBase.World.Rooms) { var roomName = room.GetType().FullName; if (roomName == null || room.Metadata.Name == null) continue; foreach (var name in room.Metadata.Name) - roomNames.Add(new SemanticResultValue(name.Text, roomName)); + roomNames.Add(new GrammarKeyValueChoice(name.Text, roomName)); } return roomNames; @@ -558,10 +549,9 @@ protected virtual Choices GetRoomNames() /// A new object representing all possible region /// names mapped to the primary region name. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetRegionNames(bool excludeDungeons = false) + protected virtual List GetRegionNames(bool excludeDungeons = false) { - var regionNames = new Choices(); + var regionNames = new List(); foreach (var region in TrackerBase.World.Regions) { @@ -570,7 +560,7 @@ protected virtual Choices GetRegionNames(bool excludeDungeons = false) continue; foreach (var name in region.Metadata.Name) - regionNames.Add(new SemanticResultValue(name.Text, regionName)); + regionNames.Add(new GrammarKeyValueChoice(name.Text, regionName)); } return regionNames; @@ -583,19 +573,18 @@ protected virtual Choices GetRegionNames(bool excludeDungeons = false) /// A new object representing all possible medallion /// names mapped to the primary item name. /// - [SupportedOSPlatform("windows")] - protected virtual Choices GetMedallionNames() + protected virtual List GetMedallionNames() { - var medallions = new Choices(); + var medallions = new List(); var medallionTypes = new List(Enum.GetValues()); foreach (var medallion in medallionTypes.Where(x => x == ItemType.Nothing || x.IsInCategory(ItemCategory.Medallion))) { var item = WorldQueryService.FirstOrDefault(medallion); - if (item?.Metadata?.Name != null) + if (item?.Metadata.Name != null) { foreach (var name in item.Metadata.Name) - medallions.Add(new SemanticResultValue(medallion.ToString(), item.Name)); + medallions.Add(new GrammarKeyValueChoice(medallion.ToString(), item.Name)); } } @@ -608,12 +597,11 @@ protected virtual Choices GetMedallionNames() /// Minimum number choice /// Maximum number choice /// The choices for the user to choose from in speech recognition - [SupportedOSPlatform("windows")] - protected virtual Choices GetNumberChoices(int min, int max) + protected virtual List GetNumberChoices(int min, int max) { - var numbers = new Choices(); + var numbers = new List(); for (var i = min; i <= max; i++) - numbers.Add(new SemanticResultValue(i.ToString(), i)); + numbers.Add(new GrammarKeyValueChoice(i.ToString(), i)); return numbers; } @@ -622,8 +610,7 @@ protected virtual Choices GetNumberChoices(int min, int max) /// /// Maximum number choice /// The choices for the user to choose from in speech recognition - [SupportedOSPlatform("windows")] - protected virtual Choices GetNumberChoices(int max) + protected virtual List GetNumberChoices(int max) { return GetNumberChoices(0, max); diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModuleFactory.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModuleFactory.cs index 8fdc4435c..eabcbfa36 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModuleFactory.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModuleFactory.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; -using System.Speech.Recognition; - using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; @@ -18,6 +16,7 @@ public class TrackerModuleFactory : IDisposable private readonly IServiceProvider _serviceProvider; private IEnumerable? _trackerModules; private ILogger _logger; + private Dictionary> _syntax = []; /// /// Initializes a new instance of the @@ -38,40 +37,40 @@ public TrackerModuleFactory(IServiceProvider serviceProvider, ILogger /// The tracker instance. - /// - /// The speech recognition engine to initialize. - /// /// /// /// A dictionary that contains the loaded speech recognition syntax. /// - public IReadOnlyDictionary> LoadAll(TrackerBase tracker, SpeechRecognitionEngine? engine, out bool moduleLoadError) + public List RetrieveGrammar(TrackerBase tracker, out bool moduleLoadError) { moduleLoadError = false; _trackerModules = _serviceProvider.GetServices().ToList(); - if (engine != null && OperatingSystem.IsWindows()) + var grammar = new List(); + + foreach (var module in _trackerModules) { - foreach (var module in _trackerModules) + try { - try - { - module.AddCommands(); - module.LoadInto(engine); - } - catch (InvalidOperationException e) + module.AddCommands(); + module.LoadInto(grammar); + foreach (var syntax in module.Syntax) { - _logger.LogError(e, $"Error with loading module {module.GetType().Name}"); - moduleLoadError = true; + _syntax.Add(syntax.Key, syntax.Value); } } + catch (InvalidOperationException e) + { + _logger.LogError(e, $"Error with loading module {module.GetType().Name}"); + moduleLoadError = true; + } } - return _trackerModules.Where(x => !x.IsSecret) - .SelectMany(x => x.Syntax) - .ToImmutableSortedDictionary(); + return grammar; } + public Dictionary> Syntax => _syntax; + /// /// Retrieves the created module of the particular type /// diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TreasureTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TreasureTrackingModule.cs index 365fbadc6..982338b02 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TreasureTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TreasureTrackingModule.cs @@ -1,8 +1,7 @@ using System; -using System.Runtime.Versioning; -using System.Speech.Recognition; - +using System.Collections.Generic; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.WorldData.Regions; @@ -35,18 +34,17 @@ public TreasureTrackingModule(TrackerBase tracker, IPlayerProgressionService pla } #region Voice Commands - [SupportedOSPlatform("windows")] - private GrammarBuilder GetMarkDungeonRewardRule() + private SpeechRecognitionGrammarBuilder GetMarkDungeonRewardRule() { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: false); - var rewardNames = new Choices(); + var rewardNames = new List(); foreach (var reward in Enum.GetValues()) { foreach (var name in WorldQueryService?.FirstOrDefault(reward)?.Metadata.Name ?? new SchrodingersString()) - rewardNames.Add(new SemanticResultValue(name, (int)reward)); + rewardNames.Add(new GrammarKeyValueChoice(name, (int)reward)); } - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("mark", "set") .Append(DungeonKey, dungeonNames) @@ -54,10 +52,9 @@ private GrammarBuilder GetMarkDungeonRewardRule() .Append(RewardKey, rewardNames); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetMarkRemainingDungeonRewardsRule() + private SpeechRecognitionGrammarBuilder GetMarkRemainingDungeonRewardsRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("mark", "set") .OneOf("remaining dungeons", "other dungeons", "unmarked dungeons") @@ -65,75 +62,72 @@ private GrammarBuilder GetMarkRemainingDungeonRewardsRule() .OneOf("crystal", "blue crystal"); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetClearDungeonRule() + private SpeechRecognitionGrammarBuilder GetClearDungeonRule() { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: true); - var markDungeon = new GrammarBuilder() + var markDungeon = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("mark") .Append(DungeonKey, dungeonNames) .Append("as") .OneOf("cleared", "beaten"); - return GrammarBuilder.Combine(markDungeon); + return SpeechRecognitionGrammarBuilder.Combine(markDungeon); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetMarkDungeonRequirementRule() + private SpeechRecognitionGrammarBuilder GetMarkDungeonRequirementRule() { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: false); var medallions = GetMedallionNames(); - var dungeonFirst = new GrammarBuilder() + var dungeonFirst = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append(DungeonKey, dungeonNames) .OneOf("requires", "needs") .Append(ItemNameKey, medallions); - var itemFirst = new GrammarBuilder() + var itemFirst = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append(ItemNameKey, medallions) .OneOf("is required for", "is needed for") .Append(DungeonKey, dungeonNames); - var markDungeon = new GrammarBuilder() + var markDungeon = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("mark") .Append(DungeonKey, dungeonNames) .OneOf("as", "as requiring", "as needing") .Append(ItemNameKey, medallions); - var markItem = new GrammarBuilder() + var markItem = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("mark") .Append(ItemNameKey, medallions) .OneOf("as", "as required for", "as needed for") .Append(DungeonKey, dungeonNames); - return GrammarBuilder.Combine( + return SpeechRecognitionGrammarBuilder.Combine( dungeonFirst, itemFirst, markDungeon, markItem); } - [SupportedOSPlatform("windows")] - private GrammarBuilder GetTreasureTrackingRule() + private SpeechRecognitionGrammarBuilder GetTreasureTrackingRule() { var dungeonNames = GetDungeonNames(includeDungeonsWithoutReward: true); - var treasureCount = new Choices(); + var treasureCount = new List(); for (var i = 2; i <= 20; i++) - treasureCount.Add(new SemanticResultValue(i.ToString(), i)); + treasureCount.Add(new GrammarKeyValueChoice(i.ToString(), i)); - var clearOne = new GrammarBuilder() + var clearOne = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("clear") .OneOf("one item", "an item", "one treasure", "a treasure") .OneOf("in", "from") .Append(DungeonKey, dungeonNames); - var clearMultiple = new GrammarBuilder() + var clearMultiple = new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .Append("clear") .Append(TreasureCountKey, treasureCount) @@ -141,16 +135,15 @@ private GrammarBuilder GetTreasureTrackingRule() .OneOf("in", "from") .Append(DungeonKey, dungeonNames); - return GrammarBuilder.Combine(clearOne, clearMultiple); + return SpeechRecognitionGrammarBuilder.Combine(clearOne, clearMultiple); } - [SupportedOSPlatform("windows")] public override void AddCommands() { AddCommand("Mark dungeon pendant/crystal", GetMarkDungeonRewardRule(), (result) => { var dungeon = (IHasReward)GetDungeonFromResult(TrackerBase, result); - var reward = (RewardType)result.Semantics[RewardKey].Value; + var reward = (RewardType)int.Parse(result.Semantics[RewardKey].Value); TrackerBase.RewardTracker.SetAreaReward(dungeon, reward, result.Confidence); }); @@ -179,7 +172,7 @@ public override void AddCommands() AddCommand("Clear dungeon treasure", GetTreasureTrackingRule(), (result) => { var count = result.Semantics.ContainsKey(TreasureCountKey) - ? (int)result.Semantics[TreasureCountKey].Value + ? int.Parse(result.Semantics[TreasureCountKey].Value) : 1; var dungeon = GetDungeonFromResult(TrackerBase, result); TrackerBase.TreasureTracker.TrackDungeonTreasure(dungeon, result.Confidence, amount: count); diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs index 96be924a8..9324d4441 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; +using PySpeechService.Recognition; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Tracking.Services; @@ -23,9 +24,9 @@ public UndoModule(TrackerBase tracker, IPlayerProgressionService playerProgressi } - private GrammarBuilder GetUndoRule() + private SpeechRecognitionGrammarBuilder GetUndoRule() { - return new GrammarBuilder() + return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker,") .OneOf("undo that", "control Z", "that's not what I said", "take backsies"); } diff --git a/src/TrackerCouncil.Smz3.UI/Program.cs b/src/TrackerCouncil.Smz3.UI/Program.cs index e791d2bdf..71b568fe8 100644 --- a/src/TrackerCouncil.Smz3.UI/Program.cs +++ b/src/TrackerCouncil.Smz3.UI/Program.cs @@ -15,6 +15,7 @@ using MSURandomizerLibrary.Models; using MSURandomizerLibrary.Services; using Serilog; +using TrackerCouncil.Smz3.Shared; namespace TrackerCouncil.Smz3.UI; diff --git a/src/TrackerCouncil.Smz3.UI/ServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.UI/ServiceCollectionExtensions.cs index 9e423a3be..7b19cde41 100644 --- a/src/TrackerCouncil.Smz3.UI/ServiceCollectionExtensions.cs +++ b/src/TrackerCouncil.Smz3.UI/ServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Avalonia.Controls; using AvaloniaControls.Extensions; @@ -5,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using MSURandomizer; +using PySpeechService.Client; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Chat.Integration; using TrackerCouncil.Smz3.Data.Configuration; @@ -64,6 +66,11 @@ public static IServiceCollection ConfigureServices(this IServiceCollection servi services.AddTransient(); services.AddTransient(); + if (OperatingSystem.IsLinux()) + { + services.AddPySpeechService(); + } + return services; } diff --git a/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs index 7a7271e52..894846090 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs @@ -11,6 +11,8 @@ using AvaloniaControls.Services; using GitHubReleaseChecker; using Microsoft.Extensions.Logging; +using PySpeechService.Client; +using PySpeechService.TextToSpeech; using TrackerCouncil.Smz3.Chat.Integration; using TrackerCouncil.Smz3.Data; using TrackerCouncil.Smz3.Data.Configuration; @@ -30,6 +32,7 @@ public class MainWindowService( IGitHubFileSynchronizerService gitHubFileSynchronizerService, SpriteService spriteService, TrackerSpriteService trackerSpriteService, + IPySpeechService pySpeechService, Configs configs) : ControlService { private MainWindowViewModel _model = new(); @@ -40,6 +43,7 @@ public MainWindowViewModel InitializeModel(MainWindow window) _options = optionsFactory.Create(); _model.OpenSetupWindow = !_options.GeneralOptions.HasOpenedSetupWindow; ITaskService.Run(CheckForUpdates); + ITaskService.Run(StartPySpeechService); return _model; } @@ -87,6 +91,17 @@ public async Task ValidateTwitchToken() public event EventHandler? SpriteDownloadEnd; + public async Task StartPySpeechService() + { + if (!OperatingSystem.IsLinux()) + { + return; + } + + pySpeechService.AutoReconnect = true; + await pySpeechService.StartAsync(); + await pySpeechService.SetSpeechSettingsAsync(new SpeechSettings()); + } public async Task DownloadConfigsAsync() { var configsUpdated = gitHubConfigDownloaderService.InstallDefaultConfigFolder(); @@ -151,7 +166,7 @@ public async Task DownloadSpritesAsync() DestinationFolder = RandomizerDirectories.TrackerSpritePath, HashPath = RandomizerDirectories.TrackerSpriteHashYamlFilePath, InitialJsonPath = RandomizerDirectories.TrackerSpriteInitialJsonFilePath, - ValidPathCheck = p => p.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || p.EndsWith(".gif", StringComparison.OrdinalIgnoreCase), + ValidPathCheck = p => p.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || p.EndsWith(".gif", StringComparison.OrdinalIgnoreCase) || p.EndsWith(".yml", StringComparison.OrdinalIgnoreCase), DeleteExtraFiles = RandomizerDirectories.DeleteSprites }; diff --git a/src/TrackerCouncil.Smz3.Data/Services/OptionsWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/OptionsWindowService.cs similarity index 94% rename from src/TrackerCouncil.Smz3.Data/Services/OptionsWindowService.cs rename to src/TrackerCouncil.Smz3.UI/Services/OptionsWindowService.cs index 0e7eb1eb8..7b9d31b04 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/OptionsWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/OptionsWindowService.cs @@ -4,14 +4,18 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using AvaloniaControls.ControlServices; using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Chat.Integration; +using TrackerCouncil.Smz3.Data; using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.ViewModels; +using TrackerCouncil.Smz3.Tracking.Services; using ConfigProvider = TrackerCouncil.Smz3.Data.Configuration.ConfigProvider; -namespace TrackerCouncil.Smz3.Data.Services; +namespace TrackerCouncil.Smz3.UI.Services; public class TwitchErrorEventHandler(string error) : EventArgs { @@ -27,7 +31,8 @@ public class OptionsWindowService( IGitHubConfigDownloaderService gitHubConfigDownloaderService, IGitHubFileSynchronizerService gitHubFileSynchronizerService, TrackerSpriteService trackerSpriteService, - Configs configs) + ICommunicator communicator, + Configs configs) : ControlService { private Dictionary _availableInputDevices = new() { { "Default", "Default" } }; private OptionsWindowViewModel _model = new(); @@ -59,6 +64,12 @@ public OptionsWindowViewModel GetViewModel() _ = UpdateSpritesAsync(); }; + _model.TrackerOptions.TestTextToSpeechPressed += (sender, args) => + { + communicator.UpdateVolume(_model.TrackerOptions.TextToSpeechVolume); + communicator.Say(new SpeechRequest("This is a test message", null, true)); + }; + _model.TwitchIntegration.TwitchLoginPressed += (sender, args) => { TwitchLogin(); diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs index b49560864..2bdec3a31 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; using Avalonia; using Avalonia.Media; using Avalonia.Threading; using AvaloniaControls.ControlServices; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.Tracking; using TrackerCouncil.Smz3.Tracking.Services; using TrackerCouncil.Smz3.UI.ViewModels; @@ -27,7 +24,7 @@ public class TrackerSpeechWindowService(ICommunicator communicator, OptionsFacto private int _tickCount; private readonly int _maxTicks = 12; private readonly double _bounceHeight = 6; - private int _prevViseme; + private bool _prevSpeaking; private bool _enableBounce; public TrackerSpeechWindowViewModel GetViewModel() @@ -95,19 +92,17 @@ public void SaveOpenStatus(bool isOpen) options.Save(); } - private void Communicator_VisemeReached(object? sender, SpeakVisemeReachedEventArgs e) + private void Communicator_VisemeReached(object? sender, SpeakingUpdatedEventArgs e) { - if (!OperatingSystem.IsWindows()) return; - SetReactionType(e.Request?.TrackerImage ?? "default"); - if (e.VisemeDetails.Viseme == 0) + if (!e.IsSpeaking) { _model.TrackerImage = _currentSpeechImages?.IdleImage; } else { - if (_enableBounce && _prevViseme == 0 && !_dispatcherTimer.IsEnabled) + if (_enableBounce && !_prevSpeaking&& !_dispatcherTimer.IsEnabled) { _tickCount = 0; _dispatcherTimer.Start(); @@ -115,14 +110,14 @@ private void Communicator_VisemeReached(object? sender, SpeakVisemeReachedEventA _model.TrackerImage = _currentSpeechImages?.TalkingImage; } - _prevViseme = e.VisemeDetails.Viseme; + _prevSpeaking = e.IsSpeaking; } private void Communicator_SpeakCompleted(object? sender, SpeakCompletedEventArgs e) { SetReactionType(); _model.TrackerImage = _currentSpeechImages?.IdleImage; - _prevViseme = 0; + _prevSpeaking = false; } private void SetReactionType(string reaction = "default") diff --git a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj index 75642a0df..d5d0a97c1 100644 --- a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj +++ b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs index f65136d16..710176161 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs @@ -48,7 +48,7 @@ public class TrackerWindowViewModel : ViewModelBase [ReactiveLinkedProperties(nameof(StatusBarBackground), nameof(StatusBarBorder))] public bool IsInGoMode { get; set; } - public bool ShowSpeechRecognition => OperatingSystem.IsWindows(); + public bool ShowSpeechRecognition => !OperatingSystem.IsMacOS(); [Reactive] public string SpeechConfidence { get; set; } = "Voice Disabled"; diff --git a/src/TrackerCouncil.Smz3.UI/Views/CurrentTrackWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/CurrentTrackWindow.axaml.cs index 14f0ae369..6d57dbd35 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/CurrentTrackWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/CurrentTrackWindow.axaml.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using AvaloniaControls.Controls; using AvaloniaControls.Services; +using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.UI.Services; namespace TrackerCouncil.Smz3.UI.Views; diff --git a/src/TrackerCouncil.Smz3.UI/Views/MainWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/MainWindow.axaml.cs index 53196e52d..79e0fbcf9 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/MainWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/MainWindow.axaml.cs @@ -7,6 +7,7 @@ using AvaloniaControls.Services; using Microsoft.Extensions.DependencyInjection; using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.UI.Services; using TrackerCouncil.Smz3.UI.ViewModels; diff --git a/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs index 9e78163b0..b828b8f6f 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/MultiplayerStatusWindow.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Interactivity; using AvaloniaControls.Controls; using AvaloniaControls.Services; +using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Shared.Models; using TrackerCouncil.Smz3.UI.Services; using TrackerCouncil.Smz3.UI.ViewModels; diff --git a/src/TrackerCouncil.Smz3.UI/Views/OptionsWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/OptionsWindow.axaml.cs index eec43c489..2e6ef89ee 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/OptionsWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/OptionsWindow.axaml.cs @@ -3,6 +3,7 @@ using AvaloniaControls.Models; using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.ViewModels; +using TrackerCouncil.Smz3.UI.Services; namespace TrackerCouncil.Smz3.UI.Views; diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerLocationsWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/TrackerLocationsWindow.axaml.cs index 5bbc28a0b..d6d31b0c8 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/TrackerLocationsWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerLocationsWindow.axaml.cs @@ -5,6 +5,7 @@ using Avalonia.Interactivity; using AvaloniaControls.Controls; using AvaloniaControls.Models; +using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.UI.Services; using TrackerCouncil.Smz3.UI.ViewModels; diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerMapWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/TrackerMapWindow.axaml.cs index 97d7b8587..14b2b3ede 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/TrackerMapWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerMapWindow.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Input; using Avalonia.Interactivity; using AvaloniaControls.Controls; +using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.UI.Services; using TrackerCouncil.Smz3.UI.ViewModels; diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml.cs index 3a8a1ea5a..a2eac6133 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Interactivity; using AvaloniaControls.Controls; using AvaloniaControls.Extensions; +using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.UI.Services; using TrackerCouncil.Smz3.UI.ViewModels; diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs index b75ec3bab..a03c5ac4e 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs @@ -13,6 +13,7 @@ using Material.Icons.Avalonia; using SnesConnectorLibrary; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Shared.Models; using TrackerCouncil.Smz3.UI.Services; using TrackerCouncil.Smz3.UI.ViewModels;