internal void Speak(QueuedSpeech utterance, string outputfile) { if (syn == null) { return; } // Got something to say. Initialize temp file. StartSpeech(utterance.voice, outputfile); // Play the beep, if there is one. if (utterance.beep != BeepType.None) { pb.StartSentence(); pb.AppendAudio(BeepNames[(int)utterance.beep]); pb.EndSentence(); } // Say who is talking, if we know. if (utterance.speaker != null) { if (utterance.isAction) { SayPrompt(utterance.speaker); } else { // Speak the name of the person speaking. This is done in a neutral, // softer voice, speaking slightly rapidly. SayPrompt(utterance.speaker + "."); } } // Then synthesize the main part of the message. SaySegment(utterance); // Close the temporary WAV file. FinishSpeech(); }
/// <summary> /// Tell OSX Speech Synthesizer to speak some text. /// </summary> /// <param name="utterance"></param> /// <param name="outputfile"></param> internal void Speak(QueuedSpeech utterance, string outputfile) { string message; // Remove any embedded command delimiters. string sayThis = Regex.Replace(utterance.message, @"\[\[", ""); if (utterance.isAction) { // Action statements are spoken all together. Such as // "/me looks nervous." The "/me" will have been substituted // with the correct name earlier. message = utterance.speaker + " " + sayThis; } else { // Normal speech has the name spoken quickly and slightly softer. message = "[[rate +1.0;volm -10.0]]" + utterance.speaker + "[[rate -1.0;volm +10.0;slnc 200]]" + // 200ms pause after name sayThis; } syn.SetVoice(utterance.voice.root.Name); NSURL fileURL = new NSURL("file://" + outputfile); syn.StartSpeakingStringToURL(message, fileURL); // Wait for it to finish. This proceeds at faster than // speaking speed because output is to a file. // TODO use a callback to detect this. while (syn.IsSpeaking) { Thread.Sleep(200); // Check 5x per second. } }
public void Speak( QueuedSpeech utterance, string filename) { synth.Speak(utterance, filename); }
/// <summary> /// Synthesize a speech with Festival /// </summary> /// <param name="utterance"></param> /// <param name="outputfile"></param> internal void Speak(QueuedSpeech utterance, string outputfile) { string args; // Construct the exact sequence to say in SABLE notation. // SABLE is a simple XML syntax for describing how to speak something. // First set voice name. string message = "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE SABLE PUBLIC \"-//SABLE//DTD SABLE speech mark up//EN\"\n" + "\"Sable.v0_2.dtd\" []>\n" + "<SABLE><SPEAKER NAME=\"" + utterance.voice.root.Name + "\">\n"; // Then pitch and rate variations on that for more variety. if (utterance.voice.pitchModification > 0) { message += "<PITCH BASE=\"+30%\">"; } else if (utterance.voice.pitchModification < 0) { message += "<PITCH BASE=\"-30%\">"; } int effectiveSpeed = utterance.voice.rateModification + rateBias; if (effectiveSpeed != 0) { message += "<RATE SPEED=\"" + effectiveSpeed.ToString("+#;-#;0") + "%\">"; } // For an action it all comes out at a constant speed. // For chat, the name is said quickly, with a break after it. if (utterance.isAction) { message += utterance.speaker + " "; } else { message += "<RATE SPEED=\"+25%\">" + utterance.speaker + " </RATE><BREAK/>\n"; } // Supress any embedded tags which would confuse SABLE. message += Regex.Replace(utterance.message, @"[<>]", "") + "\n"; // Close any modifications, in reverse order. if (utterance.voice.rateModification != 0) { message += "</RATE>"; } if (utterance.voice.pitchModification != 0) { message += "</PITCH>"; } message += "</SPEAKER></SABLE>\n"; // Write this to a temporary file. Filename is the output // filename with ".wav" changed to ".sable". // TODO talk directly to Festival server. string textfilename = Regex.Replace(outputfile, @"\.wav$", ".sable"); FileStream tstream = new FileStream(textfilename, FileMode.Create, FileAccess.Write); byte[] msgBytes = ToASCII.GetBytes(message); tstream.Write(msgBytes, 0, msgBytes.Length); tstream.Close(); // args = Regex.Replace( ActualArgs, @"%T", textfilename ); // Put the desired WAV file name in the command. args = Regex.Replace(ActualArgs, @"%F", outputfile); // Run synthesizer externally // TODO Talk directly to the Festival Server on port 1314. Process proc = new Process( ); proc.StartInfo.FileName = SynthCommand; proc.StartInfo.Arguments = args; proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardInput = true; proc.StartInfo.RedirectStandardError = false; proc.StartInfo.RedirectStandardOutput = false; try { if (proc.Start( )) { // Send the SCHEME command to redirect output back here proc.StandardInput.WriteLine("(tts_return_to_client)"); // Send the SCHEME command to voice the SABLE file. proc.StandardInput.WriteLine("(tts \"" + textfilename + "\" 'sable)"); // proc.StandardInput.WriteLine("(tts_textall \"" + saythis + "\" 'nil)"); proc.StandardInput.Close(); proc.WaitForExit( ); } } catch (Exception e) { System.Console.WriteLine("Festival process error " + e.Message); return; } // All done with the intermediate file. File.Delete(textfilename); }