1

I have a form and I want to allow the user to receive asynchronous text-to-speech output based on the content of a text box whenever a button is pressed. For context, this form is launched as part of an "internal" C# function within VoiceAttack, and this is a continuation of a previous question. This snippet does the job nicely:

SpeechSynthesizer synth = new SpeechSynthesizer(); // Create new SpeechSynthesizer instance

// Function for asynchronous voicing of text with text-to-speech
public void VoiceText(string text)
{
    string MyVoice = null; // Initialize string for storing requested text-to-speech voice
    string DefaultVoice = null; // Initialize string for storing default Windows text-to-speech voice
    try // Attempt the following code...
    {
        synth.SpeakAsyncCancelAll(); // Cancels all queued, asynchronous, speech synthesis operations
        synth.Volume = 100; // Set the volume level of the text-to-speech voice
        synth.Rate = -2; // Set the rate at which text is spoken by the text-to-speech engine
        MyVoice = VA.GetText(">SDTTextToSpeechVoice") ?? "Not Set"; // Retrieve phonemes for processed text or set to null
        DefaultVoice = synth.Voice.Name; // Store the current default Windows text-to-speech voice
        if (MyVoice == "Default") // Check if requested voice name is "Default"
        {
            MyVoice = DefaultVoice; // Set MyVoice to the DefaultVoice
            VA.SetText(">SDTTextToSpeechVoice", MyVoice); // Redefine VoiceAttack text variable based on redefined MyVoice
        }
        synth.SelectVoice(MyVoice); // Set the voice for this instance of text-to-speech output
    }
    catch // Handle exceptions encountered in "try"
    {
        synth.SelectVoice(DefaultVoice); // Set the voice for this instance of text-to-speech output to the Windows default voice
        string VoiceList = null; // Initialize string variable for storing available text-to-speech voice names
        foreach (InstalledVoice v in synth.GetInstalledVoices()) // Loop through all available text-to-speech voices
            VoiceList += ", " + v.VoiceInfo.Name; // Add text-to-speech voice name to storage variable
        VA.WriteToLog("Text-to-speech voice '" + MyVoice + "' not found", "yellow"); // Output info to event log
        VA.WriteToLog("Valid TTS Voices = " + VoiceList.Trim(',', ' '), "yellow"); // Output info to event log
        VA.WriteToLog("Defaulting to current Windows text-to-speech voice (" + DefaultVoice + ")", "yellow"); // Output info to event log
    }
    synth.SpeakAsync(text); // Generate text-to-speech output asynchronously
}

// Function for disposing SpeechSynthesizer object
public void SpeechDispose()
{
    synth.Dispose(); // Releases resources tied to SpeechSynthesizer object
}

SpeechDispose() is called upon form closing. I can't enclose synth in a using() statement because the voicing is asynchronous (I want the user to not be forced to wait for the voicing to finish before the button can be pressed again). Is there a better way to clean up?

Zer0
  • 7,191
  • 1
  • 20
  • 34
Exergist
  • 157
  • 12
  • You should be using `await` and `async` when calling Async methods. That way you can control the `Task` such as knowing when it's complete. Right now you're just firing a `Task` and forgetting it ever happened with `synth.SpeakAsync(text)`. – Zer0 Mar 14 '19 at 18:08
  • @Zer0 the API being used does not implement async methods (yes the naming is confusing by today's practices). – yv989c Mar 14 '19 at 18:41
  • @yv989c Hm, yeah that is confusing. Then I'd suggest to use [`SpeechSynthesizer.SpeakCompleted`](https://learn.microsoft.com/en-us/dotnet/api/system.speech.synthesis.speechsynthesizer.speakcompleted?view=netframework-4.7.2) – Zer0 Mar 14 '19 at 18:45
  • @Zer0 - I'm trying to avoid creating a new SpeechSynthesizer instance with every button press (which would jive with the SpeechSynthesizer.SpeakCompleted suggestion). – Exergist Mar 14 '19 at 19:01
  • Huh? You don't need to create a new instance with every button press. Just keep track of how many outstanding async operations are going on at once... – Zer0 Mar 14 '19 at 19:03
  • Apologies, my connection got lost while editing my last post. Trying again: @Zer0 - synth gets initialized once the form opens. I was trying to avoid creating a new SpeechSynthesizer instance with every button press (which would jive with the SpeechSynthesizer.SpeakCompleted suggestion). Perhaps my fears concerning this approach are unfounded? – Exergist Mar 14 '19 at 19:11
  • 2
    I would set the voice, rate, etc., *once* when the SpeechSynthesizer is created. Aside from that, disposing on form close is just fine. – Eric Brown Mar 14 '19 at 20:50
  • Seems like I'm on the right track after some minor adjustments. Thank you for the feedback everyone! – Exergist Mar 15 '19 at 00:23

1 Answers1

0
if (synth.State == SynthesizerState.Ready)
{
    synth.SpeakAsync(text);
}
else if (synth.State == SynthesizerState.Speaking)
{
    synth.Pause();
    synth.SpeakAsyncCancelAll();
    synth.Resume();
    synth.SpeakAsync(text);
}
else
{
    //nothing to do - this is condition for Pause state
    //for safety erase comment, enable script below
    //synth.SpeakAsyncCancelAll();
    //synth.Resume();
    //synth.SpeakAsync(text);
}