Skip to content

Commit b317a4b

Browse files
Fix PySpeechService communicator not combining item tracking
1 parent 1ac281b commit b317a4b

File tree

5 files changed

+81
-38
lines changed

5 files changed

+81
-38
lines changed

src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace TrackerCouncil.Smz3.Tracking.Services;
55
/// <summary>
66
/// Defines a mechanism to communicate with the player.
77
/// </summary>
8-
public interface ICommunicator
8+
public interface ICommunicator : IDisposable
99
{
1010
/// <summary>
1111
/// Communicates the specified text to the player

src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.Runtime.Versioning;
66
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Logging;
78
using PySpeechService.Client;
89
using PySpeechService.TextToSpeech;
910
using TrackerCouncil.Smz3.Data.Options;
@@ -15,6 +16,8 @@ namespace TrackerCouncil.Smz3.Tracking.Services;
1516
internal class PyTextToSpeechCommunicator : ICommunicator
1617
{
1718
private readonly IPySpeechService _pySpeechService;
19+
private readonly ILogger<PyTextToSpeechCommunicator> _logger;
20+
private DateTime? _startSpeakingTime;
1821
private (string onnxPath, string jsonPath)? _defaultPrimaryVoice;
1922
private (string onnxPath, string jsonPath)? _defaultAltVoice;
2023
private (string onnxPath, string jsonPath)? _primaryVoice;
@@ -25,9 +28,10 @@ internal class PyTextToSpeechCommunicator : ICommunicator
2528
private int volume;
2629
private ConcurrentDictionary<string, SpeechRequest> _pendingRequests = [];
2730

28-
public PyTextToSpeechCommunicator(IPySpeechService pySpeechService, TrackerOptionsAccessor trackerOptionsAccessor)
31+
public PyTextToSpeechCommunicator(IPySpeechService pySpeechService, TrackerOptionsAccessor trackerOptionsAccessor, ILogger<PyTextToSpeechCommunicator> logger)
2932
{
3033
_pySpeechService = pySpeechService;
34+
_logger = logger;
3135

3236
// Check to see if the user has the tracker voice files to use
3337
var piperPath = Path.Combine(Directories.AppDataFolder, "PiperModels");
@@ -42,47 +46,70 @@ public PyTextToSpeechCommunicator(IPySpeechService pySpeechService, TrackerOptio
4246
volume = trackerOptionsAccessor.Options?.TextToSpeechVolume ?? 100;
4347
_ = Initialize();
4448

45-
_pySpeechService.Initialized += (_, _) =>
49+
_pySpeechService.Initialized += PySpeechServiceOnInitialized;
50+
_pySpeechService.SpeakCommandResponded += PySpeechServiceOnSpeakCommandResponded;
51+
52+
_isEnabled = trackerOptionsAccessor.Options?.VoiceFrequency != Shared.Enums.TrackerVoiceFrequency.Disabled;
53+
}
54+
55+
private void PySpeechServiceOnSpeakCommandResponded(object? sender, SpeakCommandResponseEventArgs args)
56+
{
57+
SpeechRequest? request = null;
58+
59+
_logger.LogInformation("Response: {Id}", args.Response.MessageId);
60+
61+
if (string.IsNullOrEmpty(args.Response.MessageId) || !_pendingRequests.TryGetValue(args.Response.MessageId, out request))
4662
{
47-
_ = Initialize();
48-
};
63+
_logger.LogError("Received PySpeechService SpeakCommandResponse with no valid message id");
64+
}
4965

50-
_pySpeechService.SpeakCommandResponded += (_, args) =>
66+
if (args.Response.IsStartOfChunk)
67+
{
68+
VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(true, request));
69+
}
70+
else if (args.Response.IsEndOfChunk)
5171
{
52-
_pendingRequests.TryGetValue(args.Response.FullMessage, out var request);
72+
VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(false, request));
73+
}
5374

54-
if (args.Response.IsStartOfChunk)
55-
{
56-
VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(true, request));
57-
}
58-
else if (args.Response.IsEndOfChunk)
75+
if (args.Response.IsStartOfMessage)
76+
{
77+
if (_startSpeakingTime == null)
5978
{
60-
VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(false, request));
79+
_startSpeakingTime = DateTime.Now;
6180
}
6281

63-
if (args.Response.IsStartOfMessage)
82+
_isSpeaking = true;
83+
SpeakStarted?.Invoke(this, EventArgs.Empty);
84+
}
85+
else if (args.Response.IsEndOfMessage)
86+
{
87+
if (request != null)
6488
{
65-
_isSpeaking = true;
66-
SpeakStarted?.Invoke(this, EventArgs.Empty);
89+
_pendingRequests.TryRemove(
90+
new KeyValuePair<string, SpeechRequest>(args.Response.MessageId!, request));
6791
}
68-
else if (args.Response.IsEndOfMessage)
92+
93+
if (!args.Response.HasAnotherRequest)
6994
{
70-
SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(TimeSpan.FromSeconds(3)));
71-
72-
if (request != null)
73-
{
74-
_pendingRequests.TryRemove(
75-
new KeyValuePair<string, SpeechRequest>(args.Response.FullMessage, request));
76-
}
77-
78-
if (!args.Response.HasAnotherRequest)
79-
{
80-
_isSpeaking = false;
81-
}
95+
var duration = DateTime.Now - _startSpeakingTime;
96+
_startSpeakingTime = null;
97+
SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration ?? TimeSpan.Zero, request));
98+
_isSpeaking = false;
8299
}
83-
};
100+
}
101+
}
84102

85-
_isEnabled = trackerOptionsAccessor.Options?.VoiceFrequency != Shared.Enums.TrackerVoiceFrequency.Disabled;
103+
private async void PySpeechServiceOnInitialized(object? sender, EventArgs args)
104+
{
105+
try
106+
{
107+
await Initialize();
108+
}
109+
catch (Exception e)
110+
{
111+
_logger.LogError(e, "Error initializing PySpeechService");
112+
}
86113
}
87114

88115
public void UseAlternateVoice(bool useAlt = true)
@@ -133,15 +160,18 @@ public void Say(SpeechRequest request)
133160
{
134161
if (!_isEnabled || !_pySpeechService.IsSpeechEnabled) return;
135162

136-
_pendingRequests.TryAdd(request.Text, request);
163+
var messageId = Guid.NewGuid().ToString();
164+
_pendingRequests.TryAdd(messageId, request);
165+
166+
_logger.LogInformation("Request: {Id}", messageId);
137167

138168
if (request.Wait)
139169
{
140-
_pySpeechService.Speak(request.Text, GetSpeechSettings());
170+
_pySpeechService.Speak(request.Text, GetSpeechSettings(), messageId);
141171
}
142172
else
143173
{
144-
_pySpeechService.SpeakAsync(request.Text, GetSpeechSettings());
174+
_pySpeechService.SpeakAsync(request.Text, GetSpeechSettings(), messageId);
145175
}
146176
}
147177

@@ -188,4 +218,10 @@ public void UpdateVolume(int newVolume)
188218
public event EventHandler? SpeakStarted;
189219
public event EventHandler<SpeakCompletedEventArgs>? SpeakCompleted;
190220
public event EventHandler<SpeakingUpdatedEventArgs>? VisemeReached;
221+
222+
public void Dispose()
223+
{
224+
_pySpeechService.Initialized -= PySpeechServiceOnInitialized;
225+
_pySpeechService.SpeakCommandResponded -= PySpeechServiceOnSpeakCommandResponded;
226+
}
191227
}

src/TrackerCouncil.Smz3.Tracking/Services/SpeakCompletedEventArgs.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ namespace TrackerCouncil.Smz3.Tracking.Services;
66
/// Event for when the communicator has finished speaking
77
/// </summary>
88
/// <param name="speechDuration">How long the speech was going on for</param>
9-
public class SpeakCompletedEventArgs(TimeSpan speechDuration) : EventArgs
9+
/// <param name="speechRequest">The speech request that is ending</param>
10+
public class SpeakCompletedEventArgs(TimeSpan speechDuration, SpeechRequest? speechRequest) : EventArgs
1011
{
1112
/// <summary>
1213
/// How long the speech was going on for
1314
/// </summary>
1415
public TimeSpan SpeechDuration => speechDuration;
16+
17+
/// <summary>
18+
/// The speech request that is ending
19+
/// </summary>
20+
public SpeechRequest? SpeechRequest => speechRequest;
1521
}

src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace TrackerCouncil.Smz3.Tracking.Services;
1010
/// Facilitates communication with the player using Windows' built-in
1111
/// text-to-speech engine.
1212
/// </summary>
13-
public class TextToSpeechCommunicator : ICommunicator, IDisposable
13+
public class TextToSpeechCommunicator : ICommunicator
1414
{
1515
private readonly SpeechSynthesizer _tts = null!;
1616
private bool _canSpeak;
@@ -53,8 +53,9 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, I
5353
{
5454
IsSpeaking = false;
5555
var duration = DateTime.Now - _startSpeakingTime;
56+
var request = _currentSpeechRequest;
5657
_currentSpeechRequest = null;
57-
SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration));
58+
SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration, request));
5859
}
5960
else
6061
{

src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="BunLabs.Common" Version="1.0.4" />
1111
<PackageReference Include="MattEqualsCoder.MSURandomizer.Library" Version="3.1.1" />
12-
<PackageReference Include="MattEqualsCoder.PySpeechService.Client" Version="0.1.2" />
12+
<PackageReference Include="MattEqualsCoder.PySpeechService.Client" Version="0.1.4" />
1313
<PackageReference Include="MattEqualsCoder.PySpeechService.Recognition" Version="0.1.0" />
1414
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.3" />
1515
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" />

0 commit comments

Comments
 (0)