ISAP/Assets/Plugins/crosstales/RTVoice/Scripts/Tool/TextFileSpeaker.cs

508 lines
14 KiB
C#

using UnityEngine;
namespace Crosstales.RTVoice.Tool
{
/// <summary>Allows to speak text files.</summary>
[ExecuteInEditMode]
[HelpURL("https://www.crosstales.com/media/data/assets/rtvoice/api/class_crosstales_1_1_r_t_voice_1_1_tool_1_1_text_file_speaker.html")]
public class TextFileSpeaker : MonoBehaviour
{
#region Variables
[UnityEngine.Serialization.FormerlySerializedAsAttribute("TextFiles")] [Tooltip("Text files to speak."), SerializeField]
private TextAsset[] textFiles;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("Voices")] [Tooltip("Voices for the speech."), SerializeField]
private Model.VoiceAlias voices;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("Mode")] [Tooltip("Speak mode (default: 'Speak')."), SerializeField]
private Model.Enum.SpeakMode mode = Model.Enum.SpeakMode.Speak;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("Source")] [Header("Optional Settings"), Tooltip("AudioSource for the output (optional)."), SerializeField]
private AudioSource source;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("Rate")] [Tooltip("Speech rate of the speaker in percent (1 = 100%, default: 1, optional)."), Range(0f, 3f), SerializeField]
private float rate = 1f;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("Pitch")] [Tooltip("Speech pitch of the speaker in percent (1 = 100%, default: 1, optional, mobile only)."), Range(0f, 2f), SerializeField]
private float pitch = 1f;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("Volume")] [Tooltip("Volume of the speaker in percent (1 = 100%, default: 1, optional, Windows only)."), Range(0f, 1f), SerializeField]
private float volume = 1f;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("PlayOnStart")] [Header("Behaviour Settings"), Tooltip("Enable speaking of a random text file on start (default: false)."), SerializeField]
private bool playOnStart;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("PlayAllOnStart")] [Tooltip("Enable speaking of a random text file on start (default: false)."), SerializeField]
private bool playAllOnStart;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("SpeakRandom")] [Tooltip("Speaks the text files in random order (default: false)."), SerializeField]
private bool speakRandom;
[UnityEngine.Serialization.FormerlySerializedAsAttribute("Delay")] [Tooltip("Delay until the speech for this text starts (default: 0.1)."), SerializeField]
private float delay = 0.1f;
private string[] texts;
private string[] randomTexts;
private int textIndex = -1;
private int randomTextIndex = -1;
//private Voice voice;
private static readonly System.Random rnd = new System.Random();
private string uid = string.Empty;
private bool played;
private bool playAll;
private float lastSpeaktime = float.MinValue;
private int lastNumberOfTextfiles = -1;
#endregion
#region Properties
/// <summary>Text files to speak.</summary>
public TextAsset[] TextFiles
{
get => textFiles;
set => textFiles = value;
}
/// <summary>Voices for the speech.</summary>
public Model.VoiceAlias Voices
{
get => voices;
set => voices = value;
}
/// <summary>Speak mode.</summary>
public Model.Enum.SpeakMode Mode
{
get => mode;
set => mode = value;
}
/// <summary>AudioSource for the output (optional).</summary>
public AudioSource Source
{
get => source;
set => source = value;
}
/// <summary>Speech rate of the speaker in percent (range: 0-3).</summary>
public float Rate
{
get => rate;
set => rate = Mathf.Clamp(value, 0, 3);
}
/// <summary>Speech pitch of the speaker in percent (range: 0-2).</summary>
public float Pitch
{
get => pitch;
set => pitch = Mathf.Clamp(value, 0, 2);
}
/// <summary>Volume of the speaker in percent (range: 0-1).</summary>
public float Volume
{
get => volume;
set => volume = Mathf.Clamp01(value);
}
/// <summary>Enable speaking of the text on start.</summary>
public bool PlayOnStart
{
get => playOnStart;
set => playOnStart = value;
}
/// <summary>
/// Enable speaking of a all random text files on start (default: false).
/// NOTE: this can only be stopped with the "StopAll"-method
/// </summary>
public bool PlayAllOnStart
{
get => playAllOnStart;
set => playAllOnStart = value;
}
/// <summary>Speaks the text files in random order.</summary>
public bool SpeakRandom
{
get => speakRandom;
set => speakRandom = value;
}
/// <summary>Delay until the speech for this text starts.</summary>
public float Delay
{
get => delay;
set => delay = Mathf.Abs(value);
}
#endregion
#region Events
[Header("Events")] public TextFileSpeakerStartEvent OnStarted;
public TextFileSpeakerCompleteEvent OnCompleted;
/// <summary>An event triggered whenever a TextFileSpeaker 'Speak' is started.</summary>
public event TextFileSpeakerStart OnTextFileSpeakerStart;
/// <summary>An event triggered whenever a TextFileSpeaker 'Speak' is completed.</summary>
public event TextFileSpeakerComplete OnTextFileSpeakerComplete;
#endregion
#region MonoBehaviour methods
private void Start()
{
Speaker.Instance.OnVoicesReady += onVoicesReady;
Speaker.Instance.OnSpeakStart += onSpeakStart;
Speaker.Instance.OnSpeakComplete += onSpeakComplete;
Reload();
play();
}
private void OnDestroy()
{
if (!Util.Helper.isEditorMode && Speaker.Instance != null)
{
Speaker.Instance.OnVoicesReady -= onVoicesReady;
Speaker.Instance.OnSpeakStart -= onSpeakStart;
Speaker.Instance.OnSpeakComplete -= onSpeakComplete;
}
}
private void Update()
{
if (textFiles.Length != lastNumberOfTextfiles)
Reload();
}
private void OnValidate()
{
if (delay < 0f)
delay = 0f;
rate = Mathf.Clamp(rate, 0f, 3f);
pitch = Mathf.Clamp(pitch, 0f, 2f);
volume = Mathf.Clamp01(volume);
}
#endregion
#region Public methods
/// <summary>Speaks all texts until StopAll is called.</summary>
public void SpeakAll()
{
playAll = true;
Next();
}
/// <summary>Stops speaking all texts.</summary>
public void StopAll()
{
playAll = false;
Silence();
}
/// <summary>Speaks the next text (main use for UI).</summary>
public void Next()
{
Next(speakRandom);
}
/// <summary>Speaks the next text.</summary>
/// <param name="random">Speak a random text</param>
public void Next(bool random)
{
int index;
if (random)
{
if (randomTextIndex > -1 && randomTextIndex + 1 < randomTexts.Length)
{
randomTextIndex++;
}
else
{
randomTextIndex = 0;
}
index = randomTextIndex;
}
else
{
if (textIndex > -1 && textIndex + 1 < texts.Length)
{
textIndex++;
}
else
{
textIndex = 0;
}
index = textIndex;
}
SpeakText(index, random);
}
/// <summary>Speaks the previous text (main use for UI).</summary>
public void Previous()
{
Previous(speakRandom);
}
/// <summary>Speaks the previous text.</summary>
/// <param name="random">Speak a random text</param>
public void Previous(bool random)
{
int index;
if (random)
{
if (randomTextIndex > 0 && randomTextIndex < randomTexts.Length)
{
randomTextIndex--;
}
else
{
randomTextIndex = randomTexts.Length - 1;
}
index = randomTextIndex;
}
else
{
if (textIndex > 0 && textIndex < texts.Length)
{
textIndex--;
}
else
{
textIndex = texts.Length - 1;
}
index = textIndex;
}
SpeakText(index, random);
}
/// <summary>Speaks a text (main use for UI).</summary>
public void Speak()
{
Next();
}
/// <summary>Speaks a text with an optional index.</summary>
/// <param name="index">Index of the text (default: -1 (random), optional).</param>
/// <param name="random">Speak a random text (default: false, optional)</param>
/// <returns>UID of the speaker.</returns>
public string SpeakText(int index = -1, bool random = false)
{
float currentTime = Time.realtimeSinceStartup;
if (lastSpeaktime + Util.Constants.SPEAK_CALL_SPEED < currentTime)
{
lastSpeaktime = currentTime;
Silence();
string result = string.Empty;
if (texts.Length > 0)
{
if (random)
{
if (index < 0)
{
result = speak(randomTexts[rnd.Next(randomTexts.Length)]);
}
else
{
if (index < texts.Length)
{
result = speak(randomTexts[index]);
}
else
{
Debug.LogWarning("Text file index is out of bounds: " + index +
" - maximal index is: " + (randomTexts.Length - 1), this);
result = speak(randomTexts[randomTexts.Length - 1]);
}
}
}
else
{
if (index < 0)
{
result = speak(texts[rnd.Next(texts.Length)]);
}
else
{
if (index < texts.Length)
{
result = speak(texts[index]);
}
else
{
Debug.LogWarning("Text file index is out of bounds: " + index +
" - maximal index is: " + (texts.Length - 1), this);
result = speak(texts[texts.Length - 1]);
}
}
}
}
else
{
Debug.LogError("No text files added - speak cancelled!", this);
}
uid = result;
}
else
{
Debug.LogWarning("'SpeakText' called too fast - please slow down!", this);
}
return uid;
}
/// <summary>Silence the speech.</summary>
public void Silence()
{
if (Util.Helper.isEditorMode)
{
Speaker.Instance.Silence();
}
else
{
if (!string.IsNullOrEmpty(uid))
Speaker.Instance.Silence(uid);
}
}
/// <summary>Reloads all text files (e.g. when new text files were added during runtime).</summary>
public void Reload()
{
if (textFiles.Length > 0)
{
texts = new string[textFiles.Length];
randomTexts = new string[textFiles.Length];
lastNumberOfTextfiles = textFiles.Length;
for (int ii = 0; ii < textFiles.Length; ii++)
{
if (textFiles[ii] != null)
{
randomTexts[ii] = texts[ii] = textFiles[ii].text;
}
else
{
randomTexts[ii] = texts[ii] = string.Empty;
}
}
randomTexts.CTShuffle();
textIndex = -1;
randomTextIndex = -1;
}
}
#endregion
#region Private methods
private void play()
{
if (!Util.Helper.isEditorMode)
{
if (!played && Speaker.Instance.Voices.Count > 0)
{
played = true;
if (playOnStart)
{
Invoke(nameof(Next), delay);
}
else if (playAllOnStart)
{
Invoke(nameof(SpeakAll), delay);
}
}
}
}
private string speak(string text)
{
return mode == Model.Enum.SpeakMode.Speak
? Speaker.Instance.Speak(text, source, voices.Voice, true, rate, pitch, volume)
: Speaker.Instance.SpeakNative(text, voices.Voice, rate, pitch, volume);
}
#endregion
#region Callbacks
private void onVoicesReady()
{
play();
}
#endregion
#region Event-trigger methods
private void onSpeakStart(Crosstales.RTVoice.Model.Wrapper wrapper)
{
if (wrapper.Uid.Equals(uid))
{
if (!Util.Helper.isEditorMode)
OnStarted?.Invoke();
OnTextFileSpeakerStart?.Invoke();
}
}
private void onSpeakComplete(Model.Wrapper wrapper)
{
if (wrapper.Uid.Equals(uid))
{
if (!Util.Helper.isEditorMode)
OnCompleted?.Invoke();
OnTextFileSpeakerComplete?.Invoke();
if (playAll)
Invoke(nameof(Next), delay);
}
}
#endregion
}
}
// © 2016-2020 crosstales LLC (https://www.crosstales.com)