Initial Commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8848dd0aaf9c1cd49bd992656eebb954
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64b5e4399ce1894409af2cf7dcab6365
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7913ff34317620543add7ccf91153f48
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Facebook.Wit.Dictation.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:910a956078d2ff4429c717211dcfaecb",
|
||||
"GUID:fa958eb9f0171754fb207d563a15ddfa"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8a301fcccd2a404a9820dc1e9c0d5c3
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce420a201d5144a44b4c15f1b9c4cff5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using Meta.WitAi.Events.Editor;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Meta.WitAi.Dictation.Events.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(DictationEvents))]
|
||||
public class DictationEventPropertyDrawer : EventPropertyDrawer<DictationEvents>
|
||||
{
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b8f297a827717846a390137480216b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7000a4548ba72474f9222567d06a50fc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 110ee5aeb672b914bbc92f92061bcbd4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f0c2fb9674b409c869f53df88912235
|
||||
timeCreated: 1657568993
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Data;
|
||||
|
||||
namespace Meta.WitAi.Dictation.Data
|
||||
{
|
||||
[Serializable]
|
||||
public class DictationSession : VoiceSession
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictation service being used
|
||||
/// </summary>
|
||||
public IDictationService dictationService;
|
||||
/// <summary>
|
||||
/// Collection of Request IDs generated from client for Wit requests
|
||||
/// </summary>
|
||||
public string[] clientRequestId;
|
||||
/// <summary>
|
||||
/// An identifier for the current dictation session
|
||||
/// </summary>
|
||||
public string sessionId = Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9809a25e34ab45d6a522b8f43e4c3703
|
||||
timeCreated: 1657569002
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using Meta.WitAi.Configuration;
|
||||
using Meta.WitAi.Dictation.Events;
|
||||
using Meta.WitAi.Events;
|
||||
using Meta.WitAi.Events.UnityEventListeners;
|
||||
using Meta.WitAi.Interfaces;
|
||||
using Meta.WitAi.Requests;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.Dictation
|
||||
{
|
||||
public abstract class DictationService : MonoBehaviour, IDictationService, IAudioEventProvider, ITranscriptionEventProvider
|
||||
{
|
||||
[Tooltip("Events that will fire before, during and after an activation")]
|
||||
[SerializeField] protected DictationEvents dictationEvents = new DictationEvents();
|
||||
|
||||
///<summary>
|
||||
/// Internal events used to report telemetry. These events are reserved for internal
|
||||
/// use only and should not be used for any other purpose.
|
||||
/// </summary>
|
||||
protected TelemetryEvents telemetryEvents = new TelemetryEvents();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this voice service is currently active and listening with the mic
|
||||
/// </summary>
|
||||
public abstract bool Active { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the service is actively communicating with Wit.ai during an Activation. The mic may or may not still be active while this is true.
|
||||
/// </summary>
|
||||
public abstract bool IsRequestActive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets a custom transcription provider. This can be used to replace any built in asr
|
||||
/// with an on device model or other provided source
|
||||
/// </summary>
|
||||
public abstract ITranscriptionProvider TranscriptionProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this voice service is currently reading data from the microphone
|
||||
/// </summary>
|
||||
public abstract bool MicActive { get; }
|
||||
|
||||
public virtual DictationEvents DictationEvents
|
||||
{
|
||||
get => dictationEvents;
|
||||
set => dictationEvents = value;
|
||||
}
|
||||
|
||||
public TelemetryEvents TelemetryEvents { get => telemetryEvents; set => telemetryEvents = value; }
|
||||
|
||||
/// <summary>
|
||||
/// A subset of events around collection of audio data
|
||||
/// </summary>
|
||||
public IAudioInputEvents AudioEvents => DictationEvents;
|
||||
|
||||
/// <summary>
|
||||
/// A subset of events around receiving transcriptions
|
||||
/// </summary>
|
||||
public ITranscriptionEvent TranscriptionEvents => DictationEvents;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the audio input should be read in an activation
|
||||
/// </summary>
|
||||
protected abstract bool ShouldSendMicData { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
public void Activate() => Activate(new WitRequestOptions(), new VoiceServiceRequestEvents());
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
public void Activate(WitRequestOptions requestOptions) => Activate(requestOptions, new VoiceServiceRequestEvents());
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
public VoiceServiceRequest Activate(VoiceServiceRequestEvents requestEvents) => Activate(new WitRequestOptions(), requestEvents);
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
/// <param name="requestEvents">Events specific to the request's lifecycle</param>
|
||||
public abstract VoiceServiceRequest Activate(WitRequestOptions requestOptions, VoiceServiceRequestEvents requestEvents);
|
||||
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing immediately without waiting for sound/speech from the user to begin. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
public void ActivateImmediately() => ActivateImmediately(new WitRequestOptions(), new VoiceServiceRequestEvents());
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing immediately without waiting for sound/speech from the user to begin. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
public void ActivateImmediately(WitRequestOptions requestOptions) => ActivateImmediately(requestOptions, new VoiceServiceRequestEvents());
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing immediately without waiting for sound/speech from the user to begin. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
public VoiceServiceRequest ActivateImmediately(VoiceServiceRequestEvents requestEvents) => ActivateImmediately(new WitRequestOptions(), requestEvents);
|
||||
/// <summary>
|
||||
/// Activate the microphone and send data for NLU processing immediately without waiting for sound/speech from the user to begin. Includes optional additional request parameters like dynamic entities and maximum results.
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
/// <param name="requestEvents">Events specific to the request's lifecycle</param>
|
||||
public abstract VoiceServiceRequest ActivateImmediately(WitRequestOptions requestOptions, VoiceServiceRequestEvents requestEvents);
|
||||
|
||||
/// <summary>
|
||||
/// Stop listening and submit any remaining buffered microphone data for processing.
|
||||
/// </summary>
|
||||
public abstract void Deactivate();
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the current transcription. No FullTranscription event will fire.
|
||||
/// </summary>
|
||||
public abstract void Cancel();
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
var audioEventListener = GetComponent<AudioEventListener>();
|
||||
if (!audioEventListener)
|
||||
{
|
||||
gameObject.AddComponent<AudioEventListener>();
|
||||
}
|
||||
|
||||
var transcriptionEventListener = GetComponent<TranscriptionEventListener>();
|
||||
if (!transcriptionEventListener)
|
||||
{
|
||||
gameObject.AddComponent<TranscriptionEventListener>();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDictationService: ITelemetryEventsProvider
|
||||
{
|
||||
bool Active { get; }
|
||||
|
||||
bool IsRequestActive { get; }
|
||||
|
||||
bool MicActive { get; }
|
||||
|
||||
ITranscriptionProvider TranscriptionProvider { get; set; }
|
||||
|
||||
DictationEvents DictationEvents { get; set; }
|
||||
|
||||
new TelemetryEvents TelemetryEvents { get; set; }
|
||||
|
||||
VoiceServiceRequest Activate(WitRequestOptions requestOptions, VoiceServiceRequestEvents requestEvents);
|
||||
|
||||
VoiceServiceRequest ActivateImmediately(WitRequestOptions requestOptions, VoiceServiceRequestEvents requestEvents);
|
||||
|
||||
void Deactivate();
|
||||
|
||||
void Cancel();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d481126888a4412939f6940b2fa3950
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b23994c7bbf9462ca7b6939d3d364e7c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Events;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Meta.WitAi.Dictation.Events
|
||||
{
|
||||
[Serializable]
|
||||
public class DictationEvents : SpeechEvents
|
||||
{
|
||||
private const string EVENT_CATEGORY_DICTATION_EVENTS = "Dictation Events";
|
||||
|
||||
/// <summary>
|
||||
/// Called when an individual dictation session has started. This can include multiple server activations if
|
||||
/// dictation is set up to automatically reactivate when the server endpoints an utterance.
|
||||
/// </summary>
|
||||
[Tooltip("Called when an individual dictation session has started. This can include multiple server activations if dictation is set up to automatically reactivate when the server endpoints an utterance.")]
|
||||
[EventCategory(EVENT_CATEGORY_DICTATION_EVENTS)]
|
||||
[FormerlySerializedAs("onDictationSessionStarted")] [SerializeField] [HideInInspector]
|
||||
private DictationSessionEvent _onDictationSessionStarted = new DictationSessionEvent();
|
||||
public DictationSessionEvent OnDictationSessionStarted => _onDictationSessionStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Called when a dictation is completed after Deactivate has been called or auto-reactivate is disabled.
|
||||
/// </summary>
|
||||
[Tooltip("Called when a dictation is completed after Deactivate has been called or auto-reactivate is disabled.")]
|
||||
[EventCategory(EVENT_CATEGORY_DICTATION_EVENTS)]
|
||||
[FormerlySerializedAs("onDictationSessionStopped")] [SerializeField] [HideInInspector]
|
||||
private DictationSessionEvent _onDictationSessionStopped = new DictationSessionEvent();
|
||||
public DictationSessionEvent OnDictationSessionStopped => _onDictationSessionStopped;
|
||||
|
||||
// Deprecated events
|
||||
[Obsolete("Deprecated for 'OnDictationSessionStarted' event")]
|
||||
public DictationSessionEvent onDictationSessionStarted => OnDictationSessionStarted;
|
||||
[Obsolete("Deprecated for 'OnDictationSessionStopped' event")]
|
||||
public DictationSessionEvent onDictationSessionStopped => OnDictationSessionStopped;
|
||||
[Obsolete("Deprecated for 'OnStartListening' event")]
|
||||
public UnityEvent onStart => OnStartListening;
|
||||
[Obsolete("Deprecated for 'OnStoppedListening' event")]
|
||||
public UnityEvent onStopped => OnStoppedListening;
|
||||
[Obsolete("Deprecated for 'OnMicLevelChanged' event")]
|
||||
public WitMicLevelChangedEvent onMicAudioLevel => OnMicLevelChanged;
|
||||
[Obsolete("Deprecated for 'OnError' event")]
|
||||
public WitErrorEvent onError => OnError;
|
||||
[Obsolete("Deprecated for 'OnResponse' event")]
|
||||
public WitResponseEvent onResponse => OnResponse;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 866a0a2cd26dd0c469bb96bf7be7ec27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Dictation.Data;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Meta.WitAi.Dictation.Events
|
||||
{
|
||||
[Serializable]
|
||||
public class DictationSessionEvent : UnityEvent<DictationSession>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8d63aebd47343999a855f5eff93e7a2
|
||||
timeCreated: 1657570491
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Facebook.Wit.Dictation",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:1c28d8b71ced07540b7c271537363cc6",
|
||||
"GUID:4504b1a6e0fdcc3498c30b266e4a63bf"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 910a956078d2ff4429c717211dcfaecb
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Meta.WitAi.Events;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.Dictation
|
||||
{
|
||||
public class MultiRequestTranscription : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private DictationService witDictation;
|
||||
[SerializeField] private int linesBetweenActivations = 2;
|
||||
[Multiline]
|
||||
[SerializeField] private string activationSeparator = String.Empty;
|
||||
|
||||
[Header("Events")]
|
||||
[SerializeField] private WitTranscriptionEvent onTranscriptionUpdated = new
|
||||
WitTranscriptionEvent();
|
||||
|
||||
private StringBuilder _text;
|
||||
private string _activeText;
|
||||
private bool _newSection;
|
||||
|
||||
private StringBuilder _separator;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!witDictation) witDictation = FindObjectOfType<DictationService>();
|
||||
|
||||
_text = new StringBuilder();
|
||||
_separator = new StringBuilder();
|
||||
for (int i = 0; i < linesBetweenActivations; i++)
|
||||
{
|
||||
_separator.AppendLine();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(activationSeparator))
|
||||
{
|
||||
_separator.Append(activationSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
witDictation.DictationEvents.OnFullTranscription.AddListener(OnFullTranscription);
|
||||
witDictation.DictationEvents.OnPartialTranscription.AddListener(OnPartialTranscription);
|
||||
witDictation.DictationEvents.OnAborting.AddListener(OnCancelled);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_activeText = string.Empty;
|
||||
witDictation.DictationEvents.OnFullTranscription.RemoveListener(OnFullTranscription);
|
||||
witDictation.DictationEvents.OnPartialTranscription.RemoveListener(OnPartialTranscription);
|
||||
witDictation.DictationEvents.OnAborting.RemoveListener(OnCancelled);
|
||||
}
|
||||
|
||||
private void OnCancelled()
|
||||
{
|
||||
_activeText = string.Empty;
|
||||
OnTranscriptionUpdated();
|
||||
}
|
||||
|
||||
private void OnFullTranscription(string text)
|
||||
{
|
||||
_activeText = string.Empty;
|
||||
|
||||
if (_text.Length > 0)
|
||||
{
|
||||
_text.Append(_separator);
|
||||
}
|
||||
|
||||
_text.Append(text);
|
||||
|
||||
OnTranscriptionUpdated();
|
||||
}
|
||||
|
||||
private void OnPartialTranscription(string text)
|
||||
{
|
||||
_activeText = text;
|
||||
OnTranscriptionUpdated();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_text.Clear();
|
||||
onTranscriptionUpdated.Invoke(string.Empty);
|
||||
}
|
||||
|
||||
private void OnTranscriptionUpdated()
|
||||
{
|
||||
var transcription = new StringBuilder();
|
||||
transcription.Append(_text);
|
||||
if (!string.IsNullOrEmpty(_activeText))
|
||||
{
|
||||
if (transcription.Length > 0)
|
||||
{
|
||||
transcription.Append(_separator);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_activeText))
|
||||
{
|
||||
transcription.Append(_activeText);
|
||||
}
|
||||
}
|
||||
|
||||
onTranscriptionUpdated.Invoke(transcription.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a960272debaf35b4f8df9292e4191044
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e507c9f16a2497a940845bf47606a99
|
||||
timeCreated: 1656528581
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using Meta.WitAi.Interfaces;
|
||||
using Meta.WitAi.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.ServiceReferences
|
||||
{
|
||||
public class DictationServiceAudioEventReference : AudioInputServiceReference
|
||||
{
|
||||
[SerializeField] private DictationServiceReference _dictationServiceReference;
|
||||
|
||||
public override IAudioInputEvents AudioEvents =>
|
||||
_dictationServiceReference.DictationService.AudioEvents;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2029a881fa07405c941899ca44707e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Dictation;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Meta.WitAi.Utilities
|
||||
{
|
||||
[Serializable]
|
||||
public struct DictationServiceReference
|
||||
{
|
||||
[SerializeField] internal DictationService dictationService;
|
||||
|
||||
public DictationService DictationService
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!dictationService)
|
||||
{
|
||||
DictationService[] services = Resources.FindObjectsOfTypeAll<DictationService>();
|
||||
if (services != null)
|
||||
{
|
||||
// Set as first instance that isn't a prefab
|
||||
dictationService = Array.Find(services, (o) => o.gameObject.scene.rootCount != 0);
|
||||
}
|
||||
}
|
||||
|
||||
return dictationService;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(DictationServiceReference))]
|
||||
public class DictationServiceReferenceDrawer : PropertyDrawer
|
||||
{
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var refProp = property.FindPropertyRelative("DictationService");
|
||||
var reference = refProp.objectReferenceValue as DictationService;
|
||||
var dictationServices = GameObject.FindObjectsOfType<DictationService>();
|
||||
var dictationServiceNames = new string[dictationServices.Length + 1];
|
||||
int index = 0;
|
||||
dictationServiceNames[0] = "Autodetect";
|
||||
if (dictationServices.Length == 1)
|
||||
{
|
||||
dictationServiceNames[0] = $"{dictationServiceNames[0]} - {dictationServices[0].name}";
|
||||
}
|
||||
for (int i = 0; i < dictationServices.Length; i++)
|
||||
{
|
||||
dictationServiceNames[i + 1] = dictationServices[i].name;
|
||||
if (dictationServices[i] == reference)
|
||||
{
|
||||
index = i + 1;
|
||||
}
|
||||
}
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
var updatedIndex = EditorGUI.Popup(position, index, dictationServiceNames);
|
||||
if (index != updatedIndex)
|
||||
{
|
||||
if (updatedIndex > 0)
|
||||
{
|
||||
refProp.objectReferenceValue = dictationServices[updatedIndex - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
refProp.objectReferenceValue = null;
|
||||
}
|
||||
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a487e94faee3e430989a08cd6d770e3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
using Meta.WitAi.Configuration;
|
||||
using Meta.WitAi.Data.Configuration;
|
||||
using Meta.WitAi.Dictation.Events;
|
||||
using Meta.WitAi.Events;
|
||||
using Meta.WitAi.Interfaces;
|
||||
using Meta.WitAi.Requests;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.Dictation
|
||||
{
|
||||
public class WitDictation : DictationService, IWitRuntimeConfigProvider, IVoiceEventProvider, IWitRequestProvider, IWitConfigurationProvider
|
||||
{
|
||||
[SerializeField] private WitRuntimeConfiguration witRuntimeConfiguration;
|
||||
|
||||
private WitService witService;
|
||||
|
||||
public WitRuntimeConfiguration RuntimeConfiguration
|
||||
{
|
||||
get => witRuntimeConfiguration;
|
||||
set => witRuntimeConfiguration = value;
|
||||
}
|
||||
public WitConfiguration Configuration => RuntimeConfiguration?.witConfiguration;
|
||||
|
||||
#region Voice Service Properties
|
||||
|
||||
public override bool Active => null != witService && witService.Active;
|
||||
public override bool IsRequestActive => null != witService && witService.IsRequestActive;
|
||||
|
||||
public override ITranscriptionProvider TranscriptionProvider
|
||||
{
|
||||
get => witService.TranscriptionProvider;
|
||||
set => witService.TranscriptionProvider = value;
|
||||
|
||||
}
|
||||
|
||||
public override bool MicActive => null != witService && witService.MicActive;
|
||||
|
||||
protected override bool ShouldSendMicData => witRuntimeConfiguration.sendAudioToWit ||
|
||||
null == TranscriptionProvider;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Events specific to wit voice activation.
|
||||
/// </summary>
|
||||
public VoiceEvents VoiceEvents => _voiceEvents;
|
||||
private readonly VoiceEvents _voiceEvents = new VoiceEvents();
|
||||
|
||||
public override DictationEvents DictationEvents
|
||||
{
|
||||
get => dictationEvents;
|
||||
set
|
||||
{
|
||||
DictationEvents oldEvents = dictationEvents;
|
||||
dictationEvents = value;
|
||||
if (gameObject.activeSelf)
|
||||
{
|
||||
VoiceEvents.RemoveListener(oldEvents);
|
||||
VoiceEvents.AddListener(dictationEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWitRequestProvider
|
||||
public WitRequest CreateWitRequest(WitConfiguration config, WitRequestOptions requestOptions,
|
||||
VoiceServiceRequestEvents requestEvents,
|
||||
IDynamicEntitiesProvider[] additionalEntityProviders = null)
|
||||
{
|
||||
return config.CreateDictationRequest(requestOptions, requestEvents);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Voice Service Methods
|
||||
/// <summary>
|
||||
/// Activates and waits for the user to exceed the min wake threshold before data is sent to the server.
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
/// <param name="requestEvents">Events specific to the request's lifecycle</param>
|
||||
public override VoiceServiceRequest Activate(WitRequestOptions requestOptions, VoiceServiceRequestEvents requestEvents)
|
||||
{
|
||||
return witService.Activate(requestOptions, requestEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates immediately and starts sending data to the server. This will not wait for min wake threshold
|
||||
/// </summary>
|
||||
/// <param name="requestOptions">Additional options such as custom request id</param>
|
||||
/// <param name="requestEvents">Events specific to the request's lifecycle</param>
|
||||
public override VoiceServiceRequest ActivateImmediately(WitRequestOptions requestOptions, VoiceServiceRequestEvents requestEvents)
|
||||
{
|
||||
return witService.ActivateImmediately(requestOptions, requestEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates. If a transcription is in progress the network request will complete and any additional
|
||||
/// transcription values will be returned.
|
||||
/// </summary>
|
||||
public override void Deactivate()
|
||||
{
|
||||
witService.Deactivate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates and ignores any pending transcription content.
|
||||
/// </summary>
|
||||
public override void Cancel()
|
||||
{
|
||||
witService.DeactivateAndAbortRequest();
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
witService = gameObject.AddComponent<WitService>();
|
||||
witService.VoiceEventProvider = this;
|
||||
witService.ConfigurationProvider = this;
|
||||
witService.WitRequestProvider = this;
|
||||
witService.TelemetryEventsProvider = this;
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
VoiceEvents.AddListener(DictationEvents);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
VoiceEvents.RemoveListener(DictationEvents);
|
||||
}
|
||||
|
||||
public void TranscribeFile(string fileName)
|
||||
{
|
||||
var request = CreateWitRequest(witRuntimeConfiguration.witConfiguration, new WitRequestOptions(), new VoiceServiceRequestEvents());
|
||||
var data = File.ReadAllBytes(fileName);
|
||||
request.postData = data;
|
||||
witService.ExecuteRequest(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4398589986ec92e46a7e1585e63ff2a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cce4aa4005d374a4b94f0b4d6edc298d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e18f1785366987e4886fb11f30d70040
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cde9306012742314db5ada2fe0d9bff2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
+128
@@ -0,0 +1,128 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 701ca4114422ead418e08b1c0bc26348
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
+128
@@ -0,0 +1,128 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3ec408155652024f94168a419eabdcc
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
+128
@@ -0,0 +1,128 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3387a752287aa7845892fd1c1ca0eb8f
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
+128
@@ -0,0 +1,128 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45b126b5c88878b42840b8dd157251b6
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
# Text-to-Speech Overview
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
Voice SDK’s Text-to-Speech (TTS) feature uses a Wit.ai based service to provide audio files for text strings. It’s handled by a single TTSService prefab for all TTS settings and you can use a simple TTSSpeaker script for each scene location in which a TTS clip can be played.
|
||||
|
||||
To keep TTS working smoothly, Voice SDK handles the caching of TTS files during runtime (or when otherwise needed). If streaming TTS audio files is not an option for your application, however, you can set it to use preloaded static TTS files prior to building your app.
|
||||
|
||||
<b>Note:</b> With this initial release of TTS, only a few Wit.ai TTS voice settings are adjustable, but the package will be updated as more voices become available.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Use the following steps to set up TTS for your app once the plugin has been imported:
|
||||
1. Open the scene you want to use TTS within.
|
||||
2. Generate a new Wit Configuration using the Wit Configurations menu within the Oculus > Voice SDK > Voice HUB. Ensure it refreshes successfully and displays all voices available for your configuration.
|
||||
2. Navigate to Assets > Create > Voice SDK > TTS > Add Default TTS Setup
|
||||
3. In your scene heirarchy navigate inside the newly generated TTS GameObject to select the TTSWitService gameobject and adjust the inspector to fit your needs:
|
||||
3a. Use TTSWit > Request Settings > Configuration to select the Wit Configuration asset generated in step 2.
|
||||
3b. Once your configuration is setup, go to 'Preset Voice Settings' and setup any voices that might be shared by multiple TTSSpeakers.
|
||||
For more information, see TTS Voice Customization: https://developer.oculus.com/documentation/unity/voice-sdk-tts-voice-customization
|
||||

|
||||
3c. Under TTS Runtime Cache (Script), adjust the settings to indicate how often clips will be automatically uploaded from memory.
|
||||
For more information, see TTS Cache Options: https://developer.oculus.com/documentation/unity/voice-sdk-tts-cache-options
|
||||
3d. If needed, adjust your disk cache directory location and name in the tree under TTS Disk Cache (Script).
|
||||
For more information, see TTS Cache Options: https://developer.oculus.com/documentation/unity/voice-sdk-tts-cache-options/
|
||||

|
||||
4. Move & duplicate the TTSSpeaker to all the locations in your app where you would like TTS to be played.
|
||||
5. Modify each TTSSpeaker via the Inspector to fit your needs:
|
||||
5a. Under Voice Settings, select the Voice Preset for the specific speaker or select Custom to apply speaker specific settings.
|
||||
5b. Adjust the AudioSource in order to add the TTSSpeaker to a custom audio group or set audio from 2D to 3D.
|
||||
6. Via a script use the following TTSSpeaker methods to load and play text.
|
||||
6a. Use the TTSSpeaker script’s Speak(textToSpeak : string) method to request and say specified text on load.
|
||||
6b. Use the TTSSpeaker script’s SpeakQueued(textToSpeak : string) method to request and say specified text.
|
||||
6c. Send a custom TTSSpeakerClipEvents into any Speak/SpeakQueued method for request specific text load & playback event callbacks.
|
||||
6d. Use the TTSSpeaker script’s Stop() method to immediately stop all loading & playing TTS clips.
|
||||
6e. Use TTSSpeaker's Stop(textToSpeak : string) to immediately stop loading & playing of a specific text string.
|
||||
|
||||
<b>Note:</b> Check out TTS/Samples/Scenes/TTSSample.unity for an example of TTS implementation. Ensure that you set your WitConfiguration to the sample prior to running.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a78e70d61637eb498ee552bab9479ab
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 98bfdca232b84c3db2527e4b5c9d3b9d, type: 3}
|
||||
m_Name: TTS Overview Readme
|
||||
m_EditorClassIdentifier:
|
||||
_displayName: Overview
|
||||
_prefix: Documentation/TTS/
|
||||
_context: {fileID: 11400000, guid: f2e797de12b196c40ad409b92d6394fc, type: 2}
|
||||
_markdownFile: {fileID: 4900000, guid: 0a78e70d61637eb498ee552bab9479ab, type: 3}
|
||||
_priority: 200
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c745949bbb55f7b4dba6eeac61f4a7bd
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5d4d513ffe662a4db92f38c8468357e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68d2ecf49ba62e0408b8f86acb9b103c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 748ff8d1d0d4814429b86058a56a3ee4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e581f7e133ba2048995048327a1ef85
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 466ea4f9ad1762f41a5effc0e1985b21
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b1da9fac71952343b124f3771afe034
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Meta.WitAi.TTS.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:4504b1a6e0fdcc3498c30b266e4a63bf",
|
||||
"GUID:fa958eb9f0171754fb207d563a15ddfa",
|
||||
"GUID:8bbcefc153e1f1b4a98680670797dd16",
|
||||
"GUID:1c28d8b71ced07540b7c271537363cc6",
|
||||
"GUID:5c61c7ae4b0c6f94299e51352f802670"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e31cc0253d8f956459091c800a16d68d
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 716af1288c1a89d44950d38c999fe096
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.TTS.Preload
|
||||
{
|
||||
[Serializable]
|
||||
public class TTSPreloadPhraseData
|
||||
{
|
||||
/// <summary>
|
||||
/// ID used to identify this phrase
|
||||
/// </summary>
|
||||
public string clipID;
|
||||
/// <summary>
|
||||
/// Actual phrase to be spoken
|
||||
/// </summary>
|
||||
public string textToSpeak;
|
||||
|
||||
/// <summary>
|
||||
/// Meta data for whether clip is downloaded or not
|
||||
/// </summary>
|
||||
public bool downloaded;
|
||||
/// <summary>
|
||||
/// Meta data for clip download progress
|
||||
/// </summary>
|
||||
public float downloadProgress;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TTSPreloadVoiceData
|
||||
{
|
||||
/// <summary>
|
||||
/// Specific preset voice settings id to be used with TTSService
|
||||
/// </summary>
|
||||
public string presetVoiceID;
|
||||
/// <summary>
|
||||
/// All data corresponding to text to speak
|
||||
/// </summary>
|
||||
public TTSPreloadPhraseData[] phrases;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TTSPreloadData
|
||||
{
|
||||
public TTSPreloadVoiceData[] voices;
|
||||
}
|
||||
|
||||
public class TTSPreloadSettings : ScriptableObject
|
||||
{
|
||||
[SerializeField] public TTSPreloadData data = new TTSPreloadData();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80be64a601ff65e41b0a8036ba872084
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+481
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using Meta.WitAi.Utilities;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.TTS.Preload
|
||||
{
|
||||
[CustomEditor(typeof(TTSPreloadSettings), true)]
|
||||
public class TTSPreloadSettingsInspector : UnityEditor.Editor
|
||||
{
|
||||
// TTS Settings
|
||||
public TTSPreloadSettings Settings { get; private set; }
|
||||
|
||||
// TTS Service
|
||||
public TTSService TtsService { get; private set; }
|
||||
private List<string> _ttsVoiceIDs;
|
||||
|
||||
// Layout items
|
||||
public const float ACTION_BTN_INDENT = 15f;
|
||||
public virtual Texture2D HeaderIcon => WitTexts.HeaderIcon;
|
||||
public virtual string HeaderUrl => WitTexts.GetAppURL(string.Empty, WitTexts.WitAppEndpointType.Settings);
|
||||
public virtual string DocsUrl => WitTexts.Texts.WitDocsUrl;
|
||||
|
||||
// Layout
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// Get settings
|
||||
if (Settings != target)
|
||||
{
|
||||
Settings = target as TTSPreloadSettings;
|
||||
}
|
||||
|
||||
// Draw header
|
||||
WitEditorUI.LayoutHeaderButton(HeaderIcon, HeaderUrl, DocsUrl);
|
||||
GUILayout.Space(WitStyles.HeaderPaddingBottom);
|
||||
|
||||
// Layout actions
|
||||
LayoutPreloadActions();
|
||||
// Layout data
|
||||
LayoutPreloadData();
|
||||
}
|
||||
// Layout Preload Data
|
||||
protected virtual void LayoutPreloadActions()
|
||||
{
|
||||
// Layout preload actions
|
||||
EditorGUILayout.Space();
|
||||
WitEditorUI.LayoutSubheaderLabel("TTS Preload Actions");
|
||||
|
||||
// Indent
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Hide when playing
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
WitEditorUI.LayoutErrorLabel("TTS preload actions cannot be performed at runtime.");
|
||||
EditorGUI.indentLevel--;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get TTS Service if needed
|
||||
TtsService = EditorGUILayout.ObjectField("TTS Service", TtsService, typeof(TTSService), true) as TTSService;
|
||||
if (TtsService == null)
|
||||
{
|
||||
TtsService = GameObjectSearchUtility.FindSceneObject<TTSService>(true);
|
||||
if (TtsService == null)
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
WitEditorUI.LayoutErrorLabel("You must add a TTS Service to the loaded scene in order perform TTS actions.");
|
||||
EditorGUI.indentLevel--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_ttsVoiceIDs == null)
|
||||
{
|
||||
_ttsVoiceIDs = GetVoiceIDs(TtsService);
|
||||
}
|
||||
|
||||
// Begin buttons
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// Import JSON
|
||||
GUILayout.Space(ACTION_BTN_INDENT * EditorGUI.indentLevel);
|
||||
if (WitEditorUI.LayoutTextButton("Refresh Data"))
|
||||
{
|
||||
RefreshData();
|
||||
}
|
||||
GUILayout.Space(ACTION_BTN_INDENT);
|
||||
if (WitEditorUI.LayoutTextButton("Import JSON"))
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
if (TTSPreloadUtility.ImportData(Settings))
|
||||
{
|
||||
RefreshData();
|
||||
}
|
||||
}
|
||||
GUILayout.Space(ACTION_BTN_INDENT);
|
||||
if (WitEditorUI.LayoutTextButton("Import AutoLoader Data"))
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
if (TTSPreloadUtility.ImportPhrases(Settings))
|
||||
{
|
||||
RefreshData();
|
||||
}
|
||||
}
|
||||
// Clear disk cache
|
||||
GUI.enabled = TtsService != null;
|
||||
EditorGUILayout.Space();
|
||||
Color col = GUI.color;
|
||||
GUI.color = Color.red;
|
||||
if (WitEditorUI.LayoutTextButton("Delete Cache"))
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
TTSPreloadUtility.DeleteData(TtsService);
|
||||
RefreshData();
|
||||
}
|
||||
// Preload disk cache
|
||||
GUILayout.Space(ACTION_BTN_INDENT);
|
||||
GUI.color = Color.green;
|
||||
if (WitEditorUI.LayoutTextButton("Preload Cache"))
|
||||
{
|
||||
DownloadClips();
|
||||
}
|
||||
GUI.color = col;
|
||||
GUI.enabled = true;
|
||||
|
||||
// End buttons
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Indent
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
// Refresh
|
||||
private void RefreshData()
|
||||
{
|
||||
TTSPreloadUtility.RefreshPreloadData(TtsService, Settings.data, (p) =>
|
||||
{
|
||||
EditorUtility.DisplayProgressBar("TTS Preload Utility", "Refreshing Data", p);
|
||||
}, (d, l) =>
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
EditorUtility.SetDirty(Settings);
|
||||
Debug.Log($"TTS Preload Utility - Refresh Complete{l}");
|
||||
});
|
||||
}
|
||||
// Download
|
||||
private void DownloadClips()
|
||||
{
|
||||
TTSPreloadUtility.PreloadData(TtsService, Settings.data, (p) =>
|
||||
{
|
||||
EditorUtility.DisplayProgressBar("TTS Preload Utility", "Downloading Clips", p);
|
||||
}, (d, l) =>
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
EditorUtility.SetDirty(Settings);
|
||||
AssetDatabase.Refresh();
|
||||
Debug.Log($"TTS Preload Utility - Preload Complete{l}");
|
||||
});
|
||||
}
|
||||
// Layout Preload Data
|
||||
protected virtual void LayoutPreloadData()
|
||||
{
|
||||
// For updates
|
||||
bool updated = false;
|
||||
|
||||
// Layout preload items
|
||||
GUILayout.Space(WitStyles.WindowPaddingBottom);
|
||||
GUILayout.BeginHorizontal();
|
||||
WitEditorUI.LayoutSubheaderLabel("TTS Preload Data");
|
||||
if (WitEditorUI.LayoutTextButton("Add Voice"))
|
||||
{
|
||||
AddVoice();
|
||||
updated = true;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Indent
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Generate
|
||||
if (Settings.data == null)
|
||||
{
|
||||
Settings.data = new TTSPreloadData();
|
||||
}
|
||||
if (Settings.data.voices == null)
|
||||
{
|
||||
Settings.data.voices = new TTSPreloadVoiceData[] {new TTSPreloadVoiceData()};
|
||||
}
|
||||
|
||||
// Begin scroll
|
||||
for (int v = 0; v < Settings.data.voices.Length; v++)
|
||||
{
|
||||
if (!LayoutVoiceData(Settings.data, v, ref updated))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set dirty
|
||||
if (updated)
|
||||
{
|
||||
EditorUtility.SetDirty(Settings);
|
||||
}
|
||||
|
||||
// Indent
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
// Layout
|
||||
private bool LayoutVoiceData(TTSPreloadData preloadData, int voiceIndex, ref bool updated)
|
||||
{
|
||||
// Indent
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Get data
|
||||
TTSPreloadVoiceData voiceData = preloadData.voices[voiceIndex];
|
||||
string voiceID = voiceData.presetVoiceID;
|
||||
if (string.IsNullOrEmpty(voiceID))
|
||||
{
|
||||
voiceID = "No Voice Selected";
|
||||
}
|
||||
voiceID = $"{(voiceIndex+1)} - {voiceID}";
|
||||
|
||||
// Foldout
|
||||
GUILayout.BeginHorizontal();
|
||||
bool show = WitEditorUI.LayoutFoldout(new GUIContent(voiceID), voiceData);
|
||||
if (!show)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel--;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Delete
|
||||
if (WitEditorUI.LayoutTextButton("Delete Voice"))
|
||||
{
|
||||
DeleteVoice(voiceIndex);
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel--;
|
||||
updated = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin Voice Data
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Voice Text Field
|
||||
if (TtsService == null || _ttsVoiceIDs == null || _ttsVoiceIDs.Count == 0)
|
||||
{
|
||||
WitEditorUI.LayoutTextField(new GUIContent("Voice ID"), ref voiceData.presetVoiceID, ref updated);
|
||||
}
|
||||
// Voice Preset Select
|
||||
else
|
||||
{
|
||||
int presetIndex = _ttsVoiceIDs.IndexOf(voiceData.presetVoiceID);
|
||||
bool presetUpdated = false;
|
||||
WitEditorUI.LayoutPopup("Voice ID", _ttsVoiceIDs.ToArray(), ref presetIndex, ref presetUpdated);
|
||||
if (presetUpdated)
|
||||
{
|
||||
voiceData.presetVoiceID = _ttsVoiceIDs[presetIndex];
|
||||
string l = string.Empty;
|
||||
TTSPreloadUtility.RefreshVoiceData(TtsService, voiceData, null, ref l);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure phrases exist
|
||||
if (voiceData.phrases == null)
|
||||
{
|
||||
voiceData.phrases = new TTSPreloadPhraseData[] { };
|
||||
}
|
||||
|
||||
// Phrase Foldout
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
bool isLayout = WitEditorUI.LayoutFoldout(new GUIContent($"Phrases ({voiceData.phrases.Length})"),
|
||||
voiceData.phrases);
|
||||
if (WitEditorUI.LayoutTextButton("Add Phrase"))
|
||||
{
|
||||
TTSPreloadPhraseData lastPhrase = voiceData.phrases.Length == 0 ? null : voiceData.phrases[voiceData.phrases.Length - 1];
|
||||
voiceData.phrases = AddArrayItem<TTSPreloadPhraseData>(voiceData.phrases, new TTSPreloadPhraseData()
|
||||
{
|
||||
textToSpeak = lastPhrase?.textToSpeak,
|
||||
clipID = lastPhrase?.clipID
|
||||
});
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel--;
|
||||
updated = true;
|
||||
return false;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
if (isLayout)
|
||||
{
|
||||
for (int p = 0; p < voiceData.phrases.Length; p++)
|
||||
{
|
||||
if (!LayoutPhraseData(voiceData, p, ref updated))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End Voice Data
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.indentLevel--;
|
||||
return true;
|
||||
}
|
||||
// Layout phrase data
|
||||
private bool LayoutPhraseData(TTSPreloadVoiceData voiceData, int phraseIndex, ref bool updated)
|
||||
{
|
||||
// Begin Phrase
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Get data
|
||||
TTSPreloadPhraseData phraseData = voiceData.phrases[phraseIndex];
|
||||
string title = $"{(phraseIndex+1)} - {phraseData.textToSpeak}";
|
||||
|
||||
// Foldout
|
||||
GUILayout.BeginHorizontal();
|
||||
bool show = WitEditorUI.LayoutFoldout(new GUIContent(title), phraseData);
|
||||
if (!show)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel--;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Delete
|
||||
if (WitEditorUI.LayoutTextButton("Delete Phrase"))
|
||||
{
|
||||
voiceData.phrases = DeleteArrayItem<TTSPreloadPhraseData>(voiceData.phrases, phraseIndex);
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel--;
|
||||
updated = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin phrase Data
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Phrase
|
||||
bool phraseChange = false;
|
||||
WitEditorUI.LayoutTextField(new GUIContent("Phrase"), ref phraseData.textToSpeak, ref phraseChange);
|
||||
if (phraseChange)
|
||||
{
|
||||
TTSPreloadUtility.RefreshPhraseData(TtsService, new TTSDiskCacheSettings()
|
||||
{
|
||||
DiskCacheLocation = TTSDiskCacheLocation.Preload
|
||||
}, TtsService?.GetPresetVoiceSettings(voiceData.presetVoiceID), phraseData);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
// Clip
|
||||
string clipID = phraseData.clipID;
|
||||
WitEditorUI.LayoutTextField(new GUIContent("Clip ID"), ref clipID, ref phraseChange);
|
||||
|
||||
// State
|
||||
Color col = GUI.color;
|
||||
Color stateColor = Color.green;
|
||||
string stateValue = "Downloaded";
|
||||
if (!phraseData.downloaded)
|
||||
{
|
||||
if (phraseData.downloadProgress <= 0f)
|
||||
{
|
||||
stateColor = Color.red;
|
||||
stateValue = "Missing";
|
||||
}
|
||||
else
|
||||
{
|
||||
stateColor = Color.yellow;
|
||||
stateValue = $"Downloading {(phraseData.downloadProgress * 100f):00.0}%";
|
||||
}
|
||||
}
|
||||
GUI.color = stateColor;
|
||||
WitEditorUI.LayoutKeyLabel("State", stateValue);
|
||||
GUI.color = col;
|
||||
|
||||
// End Phrase
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.indentLevel--;
|
||||
return true;
|
||||
}
|
||||
// Add
|
||||
private T[] AddArrayItem<T>(T[] array, T item) => EditArray<T>(array, (l) => l.Add(item));
|
||||
// Delete
|
||||
private T[] DeleteArrayItem<T>(T[] array, int index) => EditArray<T>(array, (l) => l.RemoveAt(index));
|
||||
// Edit array
|
||||
private T[] EditArray<T>(T[] array, Action<List<T>> edit)
|
||||
{
|
||||
// Generate list
|
||||
List<T> list = new List<T>();
|
||||
|
||||
// Add array to list
|
||||
if (array != null)
|
||||
{
|
||||
list.AddRange(array);
|
||||
}
|
||||
|
||||
// Call edit action
|
||||
edit(list);
|
||||
|
||||
// Set to array
|
||||
T[] result = list.ToArray();
|
||||
|
||||
// Refresh foldout value
|
||||
WitEditorUI.SetFoldoutValue(result, WitEditorUI.GetFoldoutValue(array));
|
||||
|
||||
// Return array
|
||||
return result;
|
||||
}
|
||||
//
|
||||
private void AddVoice()
|
||||
{
|
||||
List<TTSPreloadVoiceData> voices = new List<TTSPreloadVoiceData>();
|
||||
if (Settings?.data?.voices != null)
|
||||
{
|
||||
voices.AddRange(Settings.data.voices);
|
||||
}
|
||||
voices.Add(new TTSPreloadVoiceData()
|
||||
{
|
||||
presetVoiceID = _ttsVoiceIDs == null || _ttsVoiceIDs.Count == 0 ? "" : _ttsVoiceIDs[0],
|
||||
phrases = new TTSPreloadPhraseData[] { new TTSPreloadPhraseData() }
|
||||
});
|
||||
Settings.data.voices = voices.ToArray();
|
||||
}
|
||||
// Delete voice
|
||||
private void DeleteVoice(int index)
|
||||
{
|
||||
// Invalid
|
||||
if (Settings?.data?.voices == null || index < 0 || index >= Settings.data.voices.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Cancelled
|
||||
if (!EditorUtility.DisplayDialog("Delete Voice?",
|
||||
$"Are you sure you would like to remove voice data:\n#{(index + 1)} - {Settings.data.voices[index].presetVoiceID}?",
|
||||
"Okay", "Cancel"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove
|
||||
List<TTSPreloadVoiceData> voices = new List<TTSPreloadVoiceData>(Settings.data.voices);
|
||||
voices.RemoveAt(index);
|
||||
Settings.data.voices = voices.ToArray();
|
||||
}
|
||||
// Get voice ids
|
||||
private List<string> GetVoiceIDs(TTSService service)
|
||||
{
|
||||
List<string> results = new List<string>();
|
||||
if (service != null)
|
||||
{
|
||||
foreach (var voiceSetting in service.GetAllPresetVoiceSettings())
|
||||
{
|
||||
if (voiceSetting != null && !string.IsNullOrEmpty(voiceSetting.SettingsId) &&
|
||||
!results.Contains(voiceSetting.SettingsId))
|
||||
{
|
||||
results.Add(voiceSetting.SettingsId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af9c44712a1f27646a9538dbb053e961
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+632
@@ -0,0 +1,632 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using Meta.WitAi.Data.Configuration;
|
||||
using Meta.WitAi.Json;
|
||||
using Meta.WitAi.TTS.Utilities;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Meta.WitAi.TTS.Preload
|
||||
{
|
||||
public static class TTSPreloadUtility
|
||||
{
|
||||
#region MANAGEMENT
|
||||
/// <summary>
|
||||
/// Create a new preload settings asset by prompting a save location
|
||||
/// </summary>
|
||||
public static TTSPreloadSettings CreatePreloadSettings()
|
||||
{
|
||||
string savePath = WitConfigurationUtility.GetFileSaveDirectory("Save TTS Preload Settings", "TTSPreloadSettings", "asset");
|
||||
return CreatePreloadSettings(savePath);
|
||||
}
|
||||
/// <summary>
|
||||
/// Create a new preload settings asset at specified location
|
||||
/// </summary>
|
||||
public static TTSPreloadSettings CreatePreloadSettings(string savePath)
|
||||
{
|
||||
// Ignore if empty
|
||||
if (string.IsNullOrEmpty(savePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get asset path
|
||||
string assetPath = savePath.Replace("\\", "/");
|
||||
if (!assetPath.StartsWith(Application.dataPath))
|
||||
{
|
||||
VLog.E(
|
||||
$"TTS Preload Utility - Cannot Create Setting Outside of Assets Directory\nPath: {assetPath}");
|
||||
return null;
|
||||
}
|
||||
assetPath = assetPath.Replace(Application.dataPath, "Assets");
|
||||
|
||||
// Generate & save
|
||||
TTSPreloadSettings settings = ScriptableObject.CreateInstance<TTSPreloadSettings>();
|
||||
AssetDatabase.CreateAsset(settings, assetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
// Reload & return
|
||||
return AssetDatabase.LoadAssetAtPath<TTSPreloadSettings>(assetPath);
|
||||
}
|
||||
/// <summary>
|
||||
/// Find all preload settings currently in the Assets directory
|
||||
/// </summary>
|
||||
public static TTSPreloadSettings[] GetPreloadSettings()
|
||||
{
|
||||
List<TTSPreloadSettings> results = new List<TTSPreloadSettings>();
|
||||
string[] guids = AssetDatabase.FindAssets("t:TTSPreloadSettings");
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
TTSPreloadSettings settings = AssetDatabase.LoadAssetAtPath<TTSPreloadSettings>(path);
|
||||
results.Add(settings);
|
||||
}
|
||||
return results.ToArray();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ITERATE
|
||||
// Performer
|
||||
public static CoroutineUtility.CoroutinePerformer _performer;
|
||||
//
|
||||
public delegate IEnumerator TTSPreloadIterateDelegate(TTSService service, TTSDiskCacheSettings cacheSettings, TTSVoiceSettings voiceSettings, TTSPreloadPhraseData phraseData, Action<float> onProgress, Action<string> onComplete);
|
||||
// Iterating
|
||||
public static bool IsIterating()
|
||||
{
|
||||
return _performer != null;
|
||||
}
|
||||
// Perform a check of all data
|
||||
private static bool CheckIterateData(TTSService service, TTSPreloadData preloadData, TTSPreloadIterateDelegate onIterate, Action<float> onProgress, Action<string> onComplete)
|
||||
{
|
||||
// No service
|
||||
if (service == null)
|
||||
{
|
||||
onProgress?.Invoke(1f);
|
||||
onComplete?.Invoke("\nNo TTSService found in current scene");
|
||||
return false;
|
||||
}
|
||||
// No preload data
|
||||
if (preloadData == null)
|
||||
{
|
||||
onProgress?.Invoke(1f);
|
||||
onComplete?.Invoke("\nTTS Preload Data Not Found");
|
||||
return false;
|
||||
}
|
||||
// No preload data
|
||||
if (preloadData.voices == null)
|
||||
{
|
||||
onProgress?.Invoke(1f);
|
||||
onComplete?.Invoke("\nTTS Preload Data Voices Not Found");
|
||||
return false;
|
||||
}
|
||||
// Ignore if running
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
onProgress?.Invoke(1f);
|
||||
onComplete?.Invoke("Cannot preload while running");
|
||||
return false;
|
||||
}
|
||||
// Ignore if running
|
||||
if (onIterate == null)
|
||||
{
|
||||
onProgress?.Invoke(1f);
|
||||
onComplete?.Invoke("Code recompiled mid update");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Iterate phrases
|
||||
private static void IteratePhrases(TTSService service, TTSPreloadData preloadData, TTSPreloadIterateDelegate onIterate, Action<float> onProgress, Action<string> onComplete)
|
||||
{
|
||||
// Skip if check fails
|
||||
if (!CheckIterateData(service, preloadData, onIterate, onProgress, onComplete))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Unload previous coroutine performer
|
||||
if (_performer != null)
|
||||
{
|
||||
_performer.gameObject.DestroySafely();
|
||||
_performer = null;
|
||||
}
|
||||
|
||||
// Run new coroutine
|
||||
_performer = CoroutineUtility.StartCoroutine(PerformIteratePhrases(service, preloadData, onIterate, onProgress, onComplete));
|
||||
}
|
||||
// Perform iterate
|
||||
private static IEnumerator PerformIteratePhrases(TTSService service, TTSPreloadData preloadData, TTSPreloadIterateDelegate onIterate, Action<float> onProgress, Action<string> onComplete)
|
||||
{
|
||||
// Get cache settings
|
||||
TTSDiskCacheSettings cacheSettings = new TTSDiskCacheSettings()
|
||||
{
|
||||
DiskCacheLocation = TTSDiskCacheLocation.Preload
|
||||
};
|
||||
|
||||
// Get total phrases
|
||||
int phraseTotal = 0;
|
||||
foreach (var voice in preloadData.voices)
|
||||
{
|
||||
if (voice.phrases == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var phrase in voice.phrases)
|
||||
{
|
||||
phraseTotal++;
|
||||
}
|
||||
}
|
||||
|
||||
// Begin
|
||||
onProgress?.Invoke(0f);
|
||||
|
||||
// Iterate
|
||||
int phraseCount = 0;
|
||||
float phraseInc = 1f / (float)phraseTotal;
|
||||
string log = string.Empty;
|
||||
for (int v = 0; v < preloadData.voices.Length; v++)
|
||||
{
|
||||
// Get voice data
|
||||
TTSPreloadVoiceData voiceData = preloadData.voices[v];
|
||||
if (voiceData.phrases == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get voice
|
||||
TTSVoiceSettings voiceSettings = service.GetPresetVoiceSettings(voiceData.presetVoiceID);
|
||||
if (voiceSettings == null)
|
||||
{
|
||||
log += "\n-Missing Voice Setting: " + voiceData.presetVoiceID;
|
||||
phraseCount += voiceData.phrases.Length;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate phrases
|
||||
for (int p = 0; p < voiceData.phrases.Length; p++)
|
||||
{
|
||||
// Iterate progress
|
||||
float progress = (float) phraseCount / (float) phraseTotal;
|
||||
onProgress?.Invoke(progress);
|
||||
phraseCount++;
|
||||
|
||||
// Iterate Load
|
||||
yield return onIterate(service, cacheSettings, voiceSettings, voiceData.phrases[p],
|
||||
(p2) => onProgress?.Invoke(progress + p2 * phraseInc), (l) => log += l);
|
||||
|
||||
// Skip if check fails
|
||||
if (!CheckIterateData(service, preloadData, onIterate, onProgress, onComplete))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Complete
|
||||
onProgress?.Invoke(1f);
|
||||
onComplete?.Invoke(log);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PRELOAD
|
||||
// Can preload data
|
||||
public static bool CanPreloadData()
|
||||
{
|
||||
return TTSService.Instance != null;
|
||||
}
|
||||
// Preload from data
|
||||
public static void PreloadData(TTSService service, TTSPreloadData preloadData, Action<float> onProgress, Action<TTSPreloadData, string> onComplete)
|
||||
{
|
||||
IteratePhrases(service, preloadData, PreloadPhraseData, onProgress, (l) => onComplete?.Invoke(preloadData, l));
|
||||
}
|
||||
// Preload voice text
|
||||
private static IEnumerator PreloadPhraseData(TTSService service, TTSDiskCacheSettings cacheSettings, TTSVoiceSettings voiceSettings, TTSPreloadPhraseData phraseData, Action<float> onProgress, Action<string> onComplete)
|
||||
{
|
||||
// Begin running
|
||||
bool running = true;
|
||||
|
||||
// Download
|
||||
string log = string.Empty;
|
||||
service.DownloadToDiskCache(phraseData.textToSpeak, string.Empty, voiceSettings, cacheSettings, delegate(TTSClipData data, string path, string error)
|
||||
{
|
||||
// Set phrase data
|
||||
phraseData.clipID = data.clipID;
|
||||
phraseData.downloaded = string.IsNullOrEmpty(error);
|
||||
// Failed
|
||||
if (!phraseData.downloaded)
|
||||
{
|
||||
log += $"\n-{voiceSettings.SettingsId} Preload Failed: {phraseData.textToSpeak}";
|
||||
}
|
||||
// Next
|
||||
running = false;
|
||||
});
|
||||
|
||||
// Wait for running to complete
|
||||
while (running)
|
||||
{
|
||||
//Debug.Log($"Preload Wait: {voiceSettings.SettingsId} - {phraseData.textToSpeak}");
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Invoke
|
||||
onComplete?.Invoke(log);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region REFRESH
|
||||
// Refresh
|
||||
public static void RefreshPreloadData(TTSService service, TTSPreloadData preloadData, Action<float> onProgress, Action<TTSPreloadData, string> onComplete)
|
||||
{
|
||||
IteratePhrases(service, preloadData, RefreshPhraseData, onProgress, (l) => onComplete?.Invoke(preloadData, l));
|
||||
}
|
||||
// Refresh
|
||||
private static IEnumerator RefreshPhraseData(TTSService service, TTSDiskCacheSettings cacheSettings, TTSVoiceSettings voiceSettings, TTSPreloadPhraseData phraseData, Action<float> onProgress, Action<string> onComplete)
|
||||
{
|
||||
RefreshPhraseData(service, cacheSettings, voiceSettings, phraseData);
|
||||
yield return null;
|
||||
onComplete?.Invoke(string.Empty);
|
||||
}
|
||||
// Refresh phrase data
|
||||
public static void RefreshVoiceData(TTSService service, TTSPreloadVoiceData voiceData, TTSDiskCacheSettings cacheSettings, ref string log)
|
||||
{
|
||||
// Get voice settings
|
||||
if (service == null)
|
||||
{
|
||||
log += "\n-No TTS service found";
|
||||
return;
|
||||
}
|
||||
// No voice data
|
||||
if (voiceData == null)
|
||||
{
|
||||
log += "\n-No voice data provided";
|
||||
return;
|
||||
}
|
||||
// Get voice
|
||||
TTSVoiceSettings voiceSettings = service.GetPresetVoiceSettings(voiceData.presetVoiceID);
|
||||
if (voiceSettings == null)
|
||||
{
|
||||
log += "\n-Missing Voice Setting: " + voiceData.presetVoiceID;
|
||||
return;
|
||||
}
|
||||
// Generate
|
||||
if (cacheSettings == null)
|
||||
{
|
||||
cacheSettings = new TTSDiskCacheSettings()
|
||||
{
|
||||
DiskCacheLocation = TTSDiskCacheLocation.Preload
|
||||
};
|
||||
}
|
||||
|
||||
// Iterate phrases
|
||||
for (int p = 0; p < voiceData.phrases.Length; p++)
|
||||
{
|
||||
RefreshPhraseData(service, cacheSettings, voiceSettings, voiceData.phrases[p]);
|
||||
}
|
||||
}
|
||||
// Refresh phrase data
|
||||
public static void RefreshPhraseData(TTSService service, TTSDiskCacheSettings cacheSettings, TTSVoiceSettings voiceSettings, TTSPreloadPhraseData phraseData)
|
||||
{
|
||||
// Get voice settings
|
||||
if (service == null || voiceSettings == null || string.IsNullOrEmpty(phraseData.textToSpeak))
|
||||
{
|
||||
phraseData.clipID = string.Empty;
|
||||
phraseData.downloaded = false;
|
||||
phraseData.downloadProgress = 0f;
|
||||
return;
|
||||
}
|
||||
if (cacheSettings == null)
|
||||
{
|
||||
cacheSettings = new TTSDiskCacheSettings()
|
||||
{
|
||||
DiskCacheLocation = TTSDiskCacheLocation.Preload
|
||||
};
|
||||
}
|
||||
|
||||
// Get phrase data
|
||||
phraseData.clipID = service.GetClipID(phraseData.textToSpeak, voiceSettings);
|
||||
|
||||
// Check if file exists
|
||||
string path = service.GetDiskCachePath(phraseData.textToSpeak, phraseData.clipID, voiceSettings, cacheSettings);
|
||||
phraseData.downloaded = File.Exists(path);
|
||||
phraseData.downloadProgress = phraseData.downloaded ? 1f : 0f;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region DELETE
|
||||
// Clear all clips in a tts preload file
|
||||
public static void DeleteData(TTSService service)
|
||||
{
|
||||
// Get test file path
|
||||
string path = service.GetDiskCachePath(string.Empty, "TEST", null, new TTSDiskCacheSettings()
|
||||
{
|
||||
DiskCacheLocation = TTSDiskCacheLocation.Preload
|
||||
});
|
||||
// Get directory
|
||||
string directory = new FileInfo(path).DirectoryName;
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask
|
||||
if (!EditorUtility.DisplayDialog("Delete Preload Cache",
|
||||
$"Are you sure you would like to delete the TTS Preload directory at:\n{directory}?", "Okay", "Cancel"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete recursively
|
||||
Directory.Delete(directory, true);
|
||||
// Delete meta
|
||||
string meta = directory + ".meta";
|
||||
if (File.Exists(meta))
|
||||
{
|
||||
File.Delete(meta);
|
||||
}
|
||||
// Refresh assets
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IMPORT
|
||||
/// <summary>
|
||||
/// Prompt user for a json file to be imported into an existing TTSPreloadSettings asset
|
||||
/// </summary>
|
||||
public static bool ImportData(TTSPreloadSettings preloadSettings)
|
||||
{
|
||||
// Select a file
|
||||
string textFilePath = EditorUtility.OpenFilePanel("Select TTS Preload Json", Application.dataPath, "json");
|
||||
if (string.IsNullOrEmpty(textFilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Import with selected file path
|
||||
return ImportData(preloadSettings, textFilePath);
|
||||
}
|
||||
/// <summary>
|
||||
/// Imported json data into an existing TTSPreloadSettings asset
|
||||
/// </summary>
|
||||
public static bool ImportData(TTSPreloadSettings preloadSettings, string textFilePath)
|
||||
{
|
||||
// Check for file
|
||||
if (!File.Exists(textFilePath))
|
||||
{
|
||||
VLog.E($"TTS Preload Utility - Preload file does not exist\nPath: {textFilePath}");
|
||||
return false;
|
||||
}
|
||||
// Load file
|
||||
string textFileContents = File.ReadAllText(textFilePath);
|
||||
if (string.IsNullOrEmpty(textFileContents))
|
||||
{
|
||||
VLog.E($"TTS Preload Utility - Preload file load failed\nPath: {textFilePath}");
|
||||
return false;
|
||||
}
|
||||
// Parse file
|
||||
WitResponseNode node = WitResponseNode.Parse(textFileContents);
|
||||
if (node == null)
|
||||
{
|
||||
VLog.E($"TTS Preload Utility - Preload file parse failed\nPath: {textFilePath}");
|
||||
return false;
|
||||
}
|
||||
// Iterate children for texts
|
||||
WitResponseClass data = node.AsObject;
|
||||
Dictionary<string, List<string>> textsByVoice = new Dictionary<string, List<string>>();
|
||||
foreach (var voiceName in data.ChildNodeNames)
|
||||
{
|
||||
// Get texts list
|
||||
List<string> texts;
|
||||
if (textsByVoice.ContainsKey(voiceName))
|
||||
{
|
||||
texts = textsByVoice[voiceName];
|
||||
}
|
||||
else
|
||||
{
|
||||
texts = new List<string>();
|
||||
}
|
||||
|
||||
// Add text phrases
|
||||
string[] voicePhrases = data[voiceName].AsStringArray;
|
||||
if (voicePhrases != null)
|
||||
{
|
||||
foreach (var phrase in voicePhrases)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(phrase) && !texts.Contains(phrase))
|
||||
{
|
||||
texts.Add(phrase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply
|
||||
textsByVoice[voiceName] = texts;
|
||||
}
|
||||
// Import
|
||||
return ImportData(preloadSettings, textsByVoice);
|
||||
}
|
||||
/// <summary>
|
||||
/// Find all ITTSPhraseProviders loaded in scenes & generate
|
||||
/// data file to import all phrases associated with the files.
|
||||
/// </summary>
|
||||
public static bool ImportPhrases(TTSPreloadSettings preloadSettings)
|
||||
{
|
||||
// Find phrase providers in all scenes
|
||||
List<ITTSPhraseProvider> phraseProviders = new List<ITTSPhraseProvider>();
|
||||
for (int s = 0; s < SceneManager.sceneCount; s++)
|
||||
{
|
||||
Scene scene = SceneManager.GetSceneAt(s);
|
||||
foreach (var root in scene.GetRootGameObjects())
|
||||
{
|
||||
ITTSPhraseProvider[] found = root.GetComponentsInChildren<ITTSPhraseProvider>(true);
|
||||
if (found != null)
|
||||
{
|
||||
phraseProviders.AddRange(found);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get all phrases by voice id
|
||||
Dictionary<string, List<string>> textsByVoice = new Dictionary<string, List<string>>();
|
||||
foreach (var phraseProvider in phraseProviders)
|
||||
{
|
||||
// Ignore if no voices are found
|
||||
List<string> voiceIds = phraseProvider.GetVoiceIds();
|
||||
if (voiceIds == null || voiceIds.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate voice ids
|
||||
foreach (var voiceId in voiceIds)
|
||||
{
|
||||
// Ignore empty voice id
|
||||
if (string.IsNullOrEmpty(voiceId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore if phrases are null
|
||||
List<string> phrases = phraseProvider.GetVoicePhrases(voiceId);
|
||||
if (phrases == null || phrases.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get phrase list
|
||||
List<string> voicePhrases;
|
||||
if (textsByVoice.ContainsKey(voiceId))
|
||||
{
|
||||
voicePhrases = textsByVoice[voiceId];
|
||||
}
|
||||
else
|
||||
{
|
||||
voicePhrases = new List<string>();
|
||||
}
|
||||
|
||||
// Append unique phrases
|
||||
foreach (var phrase in phrases)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(phrase) && !voicePhrases.Contains(phrase))
|
||||
{
|
||||
voicePhrases.Add(phrase);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply phrase list
|
||||
textsByVoice[voiceId] = voicePhrases;
|
||||
}
|
||||
}
|
||||
// Import with data
|
||||
return ImportData(preloadSettings, textsByVoice);
|
||||
}
|
||||
/// <summary>
|
||||
/// Imported dictionary data into an existing TTSPreloadSettings asset
|
||||
/// </summary>
|
||||
public static bool ImportData(TTSPreloadSettings preloadSettings, Dictionary<string, List<string>> textsByVoice)
|
||||
{
|
||||
// Import
|
||||
if (preloadSettings == null)
|
||||
{
|
||||
VLog.E("TTS Preload Utility - Import Failed - Null Preload Settings");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Whether or not changed
|
||||
bool changed = false;
|
||||
|
||||
// Generate if needed
|
||||
if (preloadSettings.data == null)
|
||||
{
|
||||
preloadSettings.data = new TTSPreloadData();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Begin voice list
|
||||
List<TTSPreloadVoiceData> voices = new List<TTSPreloadVoiceData>();
|
||||
if (preloadSettings.data.voices != null)
|
||||
{
|
||||
voices.AddRange(preloadSettings.data.voices);
|
||||
}
|
||||
|
||||
// Iterate voice names
|
||||
foreach (var voiceName in textsByVoice.Keys)
|
||||
{
|
||||
// Get voice index if possible
|
||||
int voiceIndex = voices.FindIndex((v) => string.Equals(v.presetVoiceID, voiceName));
|
||||
|
||||
// Generate voice
|
||||
TTSPreloadVoiceData voice;
|
||||
if (voiceIndex == -1)
|
||||
{
|
||||
voice = new TTSPreloadVoiceData();
|
||||
voice.presetVoiceID = voiceName;
|
||||
voiceIndex = voices.Count;
|
||||
voices.Add(voice);
|
||||
}
|
||||
// Use existing
|
||||
else
|
||||
{
|
||||
voice = voices[voiceIndex];
|
||||
}
|
||||
|
||||
// Get texts & phrases for current voice
|
||||
List<string> texts = new List<string>();
|
||||
List<TTSPreloadPhraseData> phrases = new List<TTSPreloadPhraseData>();
|
||||
if (voice.phrases != null)
|
||||
{
|
||||
foreach (var phrase in voice.phrases)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(phrase.textToSpeak) && !texts.Contains(phrase.textToSpeak))
|
||||
{
|
||||
texts.Add(phrase.textToSpeak);
|
||||
phrases.Add(phrase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get data
|
||||
List<string> newTexts = textsByVoice[voiceName];
|
||||
if (newTexts != null)
|
||||
{
|
||||
foreach (var newText in newTexts)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(newText) && !texts.Contains(newText))
|
||||
{
|
||||
changed = true;
|
||||
texts.Add(newText);
|
||||
phrases.Add(new TTSPreloadPhraseData()
|
||||
{
|
||||
textToSpeak = newText
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply voice
|
||||
voice.phrases = phrases.ToArray();
|
||||
voices[voiceIndex] = voice;
|
||||
}
|
||||
|
||||
// Apply data
|
||||
if (changed)
|
||||
{
|
||||
preloadSettings.data.voices = voices.ToArray();
|
||||
EditorUtility.SetDirty(preloadSettings);
|
||||
}
|
||||
|
||||
// Return changed
|
||||
return changed;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6428a97eb23d27b48bff4ecaa464004e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Meta.WitAi.Data.Configuration;
|
||||
using Meta.WitAi.TTS.Integrations;
|
||||
using Meta.WitAi.TTS.Utilities;
|
||||
using Meta.WitAi.Data.Info;
|
||||
using Meta.WitAi.Lib;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.TTS
|
||||
{
|
||||
public static class TTSEditorUtilities
|
||||
{
|
||||
// Default TTS Setup
|
||||
public static Transform CreateDefaultSetup()
|
||||
{
|
||||
// Generate parent
|
||||
Transform parent = GenerateGameObject("TTS").transform;
|
||||
|
||||
// Add TTS Service
|
||||
TTSService service = CreateService(parent);
|
||||
|
||||
// Add TTS Speaker
|
||||
CreateSpeaker(parent, service);
|
||||
|
||||
// Select parent
|
||||
Selection.activeObject = parent.gameObject;
|
||||
return parent;
|
||||
}
|
||||
|
||||
// Default TTS Service
|
||||
public static TTSService CreateService(Transform parent = null, bool ignoreErrors = false)
|
||||
{
|
||||
// Get parent
|
||||
if (parent == null)
|
||||
{
|
||||
Transform selected = Selection.activeTransform;
|
||||
if (selected != null && selected.gameObject.scene.rootCount > 0)
|
||||
{
|
||||
parent = Selection.activeTransform;
|
||||
}
|
||||
}
|
||||
// Ignore if found
|
||||
TTSService instance = GameObject.FindObjectOfType<TTSService>();
|
||||
if (instance != null)
|
||||
{
|
||||
// Log
|
||||
if (!ignoreErrors)
|
||||
{
|
||||
VLog.W($"TTS Service - A TTSService is already in scene\nGameObject: {instance.gameObject.name}");
|
||||
}
|
||||
|
||||
// Move into parent
|
||||
if (parent != null)
|
||||
{
|
||||
instance.transform.SetParent(parent, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate TTSWit
|
||||
else
|
||||
{
|
||||
instance = CreateWitService(parent);
|
||||
}
|
||||
|
||||
// Select & return instance
|
||||
Selection.activeObject = instance.gameObject;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Default TTS Service
|
||||
private static TTSWit CreateWitService(Transform parent = null)
|
||||
{
|
||||
// Generate new TTSWit & add caches
|
||||
TTSWit ttsWit = GenerateGameObject("TTSWitService", parent).AddComponent<TTSWit>();
|
||||
ttsWit.gameObject.AddComponent<TTSRuntimeCache>();
|
||||
ttsWit.gameObject.AddComponent<TTSDiskCache>();
|
||||
VLog.D($"TTS Service - Instantiated Service {ttsWit.gameObject.name}");
|
||||
|
||||
// Refresh configuration
|
||||
WitConfiguration configuration = SetupConfiguration(ttsWit);
|
||||
if (configuration != null)
|
||||
{
|
||||
RefreshAvailableVoices(ttsWit);
|
||||
}
|
||||
|
||||
// Log
|
||||
return ttsWit;
|
||||
}
|
||||
|
||||
// Wit configuration
|
||||
private static WitConfiguration SetupConfiguration(TTSService instance)
|
||||
{
|
||||
// Ignore non-tts wit
|
||||
if (instance.GetType() != typeof(TTSWit))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Already setup
|
||||
TTSWit ttsWit = instance as TTSWit;
|
||||
if (ttsWit.RequestSettings.configuration != null)
|
||||
{
|
||||
return ttsWit.RequestSettings.configuration;
|
||||
}
|
||||
|
||||
// Refresh configuration list
|
||||
if (WitConfigurationUtility.WitConfigs == null)
|
||||
{
|
||||
WitConfigurationUtility.ReloadConfigurationData();
|
||||
}
|
||||
|
||||
// Assign first wit configuration found
|
||||
if (WitConfigurationUtility.WitConfigs != null && WitConfigurationUtility.WitConfigs.Length > 0)
|
||||
{
|
||||
ttsWit.RequestSettings.configuration = WitConfigurationUtility.WitConfigs[0];
|
||||
VLog.D($"TTS Service - Assigned Wit Configuration {ttsWit.RequestSettings.configuration.name}");
|
||||
}
|
||||
|
||||
// Warning
|
||||
if (ttsWit.RequestSettings.configuration == null)
|
||||
{
|
||||
VLog.W($"TTS Service - Please create and assign a WitConfiguration to TTSWit");
|
||||
}
|
||||
|
||||
// Return configuration
|
||||
return ttsWit.RequestSettings.configuration;
|
||||
}
|
||||
|
||||
// Refresh available voices
|
||||
internal static void RefreshAvailableVoices(TTSWit ttsWit, Action<WitAppInfo> onUpdateComplete = null)
|
||||
{
|
||||
// Fail without configuration
|
||||
if (ttsWit == null)
|
||||
{
|
||||
VLog.W($"TTS Service - Cannot refresh voices without TTS Wit Service");
|
||||
return;
|
||||
}
|
||||
IWitRequestConfiguration configuration = ttsWit.RequestSettings.configuration;
|
||||
if (configuration == null)
|
||||
{
|
||||
VLog.W($"TTS Service - Cannot refresh voices without TTS Wit Configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update app info
|
||||
WitAppInfoUtility.Update(ttsWit.RequestSettings.configuration, (newInfo, r) =>
|
||||
{
|
||||
configuration.SetApplicationInfo(newInfo);
|
||||
UpdatePresets(ttsWit, newInfo);
|
||||
onUpdateComplete?.Invoke(newInfo);
|
||||
});
|
||||
}
|
||||
|
||||
// Add all presets
|
||||
private static void UpdatePresets(TTSWit ttsWit, WitAppInfo appInfo)
|
||||
{
|
||||
// Cannot update presets without voices
|
||||
if (appInfo.voices == null || appInfo.voices.Length == 0)
|
||||
{
|
||||
VLog.W("TTS Refresh failed to find voices");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add all voices to preset voice list
|
||||
if (ttsWit.PresetWitVoiceSettings == null || ttsWit.PresetWitVoiceSettings.Length == 0)
|
||||
{
|
||||
AddPresetsForInfo(ttsWit, appInfo.voices);
|
||||
}
|
||||
|
||||
// Refresh speakers
|
||||
RefreshEmptySpeakers(ttsWit);
|
||||
}
|
||||
|
||||
// Adds a list of voice infos
|
||||
internal static void AddPresetsForInfo(TTSWit ttsWit, WitVoiceInfo[] voiceInfos)
|
||||
{
|
||||
// Ignore without infos
|
||||
if (voiceInfos == null || voiceInfos.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Add all voices to preset voice list
|
||||
List<TTSWitVoiceSettings> voices = new List<TTSWitVoiceSettings>();
|
||||
if (ttsWit.PresetWitVoiceSettings != null)
|
||||
{
|
||||
voices.AddRange(ttsWit.PresetWitVoiceSettings);
|
||||
}
|
||||
foreach (var voiceData in voiceInfos)
|
||||
{
|
||||
voices.Add(GetDefaultVoiceSetting(voiceData));
|
||||
}
|
||||
ttsWit.SetVoiceSettings(voices.ToArray());
|
||||
}
|
||||
|
||||
// Adds a preset for a specific voice
|
||||
internal static void AddPresetForInfo(TTSWit ttsWit, WitVoiceInfo voiceData)
|
||||
{
|
||||
List<TTSWitVoiceSettings> voices = new List<TTSWitVoiceSettings>();
|
||||
if (ttsWit.PresetWitVoiceSettings != null)
|
||||
{
|
||||
voices.AddRange(ttsWit.PresetWitVoiceSettings);
|
||||
}
|
||||
voices.Add(GetDefaultVoiceSetting(voiceData));
|
||||
ttsWit.SetVoiceSettings(voices.ToArray());
|
||||
}
|
||||
|
||||
// Get default voice settings
|
||||
private static TTSWitVoiceSettings GetDefaultVoiceSetting(WitVoiceInfo voiceData)
|
||||
{
|
||||
TTSWitVoiceSettings result = new TTSWitVoiceSettings()
|
||||
{
|
||||
SettingsId = voiceData.name.ToUpper(),
|
||||
voice = voiceData.name
|
||||
};
|
||||
// Use first style provided
|
||||
if (voiceData.styles != null && voiceData.styles.Length > 0)
|
||||
{
|
||||
result.style = voiceData.styles[0];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// Set all blank IDs to default voice id
|
||||
private static void RefreshEmptySpeakers(TTSService service)
|
||||
{
|
||||
string defaultVoiceID = service.VoiceProvider.VoiceDefaultSettings?.SettingsId;
|
||||
foreach (var speaker in GameObject.FindObjectsOfType<TTSSpeaker>())
|
||||
{
|
||||
if (string.IsNullOrEmpty(speaker.presetVoiceID))
|
||||
{
|
||||
speaker.presetVoiceID = defaultVoiceID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default TTS Speaker
|
||||
public static TTSSpeaker CreateSpeaker(Transform parent = null, TTSService service = null)
|
||||
{
|
||||
// Get parent
|
||||
if (parent == null)
|
||||
{
|
||||
Transform selected = Selection.activeTransform;
|
||||
if (selected != null && selected.gameObject.scene.rootCount > 0)
|
||||
{
|
||||
parent = Selection.activeTransform;
|
||||
}
|
||||
}
|
||||
// Generate service if possible
|
||||
if (service == null)
|
||||
{
|
||||
service = CreateService(parent);
|
||||
}
|
||||
|
||||
// TTS Speaker
|
||||
string goName = typeof(TTSSpeaker).Name;
|
||||
TTSSpeaker speaker = GenerateGameObject(goName, parent).AddComponent<TTSSpeaker>();
|
||||
speaker.presetVoiceID = string.Empty;
|
||||
|
||||
// Audio Source
|
||||
AudioSource audio = GenerateGameObject($"{goName}Audio", speaker.transform).AddComponent<AudioSource>();
|
||||
audio.playOnAwake = false;
|
||||
audio.loop = false;
|
||||
audio.spatialBlend = 0f; // Default to 2D
|
||||
|
||||
// Return speaker
|
||||
VLog.D($"TTS Service - Instantiated Speaker {speaker.gameObject.name}");
|
||||
Selection.activeObject = speaker.gameObject;
|
||||
return speaker;
|
||||
}
|
||||
|
||||
// Generate with specified name
|
||||
private static GameObject GenerateGameObject(string name, Transform parent = null)
|
||||
{
|
||||
Transform result = new GameObject(name).transform;
|
||||
result.SetParent(parent);
|
||||
result.localPosition = Vector3.zero;
|
||||
result.localRotation = Quaternion.identity;
|
||||
result.localScale = Vector3.one;
|
||||
return result.gameObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c590c0f8426e4194db6efc14c68db75c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.TTS
|
||||
{
|
||||
[CustomEditor(typeof(TTSService), true)]
|
||||
public class TTSServiceInspector : UnityEditor.Editor
|
||||
{
|
||||
// Service
|
||||
private TTSService _service;
|
||||
// Dropdown
|
||||
private bool _clipFoldout = false;
|
||||
// Maximum text for abbreviated
|
||||
private const int MAX_DISPLAY_TEXT = 20;
|
||||
|
||||
// GUI
|
||||
public sealed override void OnInspectorGUI()
|
||||
{
|
||||
// Display default ui
|
||||
OnEditTimeGUI();
|
||||
OnPlaytimeGUI();
|
||||
}
|
||||
|
||||
protected virtual void OnEditTimeGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
}
|
||||
|
||||
protected virtual void OnPlaytimeGUI()
|
||||
{
|
||||
// Ignore if in editor
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get service
|
||||
if (!_service)
|
||||
{
|
||||
_service = target as TTSService;
|
||||
}
|
||||
|
||||
// Add spaces
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Runtime Clip Cache", EditorStyles.boldLabel);
|
||||
|
||||
// No clips
|
||||
TTSClipData[] clips = _service.GetAllRuntimeCachedClips();
|
||||
if (clips == null || clips.Length == 0)
|
||||
{
|
||||
WitEditorUI.LayoutErrorLabel("No clips found");
|
||||
return;
|
||||
}
|
||||
// Has clips
|
||||
_clipFoldout = WitEditorUI.LayoutFoldout(new GUIContent($"Clips: {clips.Length}"), _clipFoldout);
|
||||
if (_clipFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
// Iterate clips
|
||||
foreach (TTSClipData clip in clips)
|
||||
{
|
||||
// Get display name
|
||||
string displayName = clip.textToSpeak;
|
||||
// Crop if too long
|
||||
if (displayName.Length > MAX_DISPLAY_TEXT)
|
||||
{
|
||||
displayName = displayName.Substring(0, MAX_DISPLAY_TEXT);
|
||||
}
|
||||
// Add voice setting id
|
||||
if (clip.voiceSettings != null)
|
||||
{
|
||||
displayName = $"{clip.voiceSettings.SettingsId} - {displayName}";
|
||||
}
|
||||
// Foldout if desired
|
||||
bool foldout = WitEditorUI.LayoutFoldout(new GUIContent(displayName), clip);
|
||||
if (foldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
DrawClipGUI(clip);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
// Clip data
|
||||
public static void DrawClipGUI(TTSClipData clip)
|
||||
{
|
||||
// Generation Settings
|
||||
WitEditorUI.LayoutKeyLabel("Text", clip.textToSpeak);
|
||||
EditorGUILayout.TextField("Clip ID", clip.clipID);
|
||||
EditorGUILayout.ObjectField("Clip", clip.clip, typeof(AudioClip), true);
|
||||
|
||||
// Loaded
|
||||
TTSClipLoadState loadState = clip.loadState;
|
||||
if (loadState != TTSClipLoadState.Preparing)
|
||||
{
|
||||
WitEditorUI.LayoutKeyLabel("Load State", loadState.ToString());
|
||||
}
|
||||
// Loading with progress
|
||||
else
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
int loadProgress = Mathf.FloorToInt(clip.loadProgress * 100f);
|
||||
WitEditorUI.LayoutKeyLabel("Load State", $"{loadState} ({loadProgress}%)");
|
||||
GUILayout.HorizontalSlider(loadProgress, 0, 100);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
// Additional Settings
|
||||
WitEditorUI.LayoutKeyObjectLabels("Voice Settings", clip.voiceSettings);
|
||||
WitEditorUI.LayoutKeyObjectLabels("Cache Settings", clip.diskCacheSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7b031cd5a557e14fa08e59b869a2e78
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Meta.WitAi.TTS.Utilities;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
|
||||
namespace Meta.WitAi.TTS
|
||||
{
|
||||
[CustomEditor(typeof(TTSSpeaker), true)]
|
||||
public class TTSSpeakerInspector : Editor
|
||||
{
|
||||
// Speaker
|
||||
private TTSSpeaker _speaker;
|
||||
private SerializedProperty _presetVoiceProperty;
|
||||
private SerializedProperty _customVoiceProperty;
|
||||
|
||||
// Voices
|
||||
private int _voiceIndex = -1;
|
||||
private string[] _voicePresetIds = null;
|
||||
|
||||
// Voice text
|
||||
private const string UI_PRESET_VOICE_KEY = "Voice Preset";
|
||||
private const string UI_CUSTOM_VOICE_KEY = "Custom Voice";
|
||||
private const string UI_CUSTOM_KEY = "CUSTOM";
|
||||
|
||||
//
|
||||
void OnEnable()
|
||||
{
|
||||
_speaker = target as TTSSpeaker;
|
||||
_presetVoiceProperty = serializedObject.FindProperty("presetVoiceID");
|
||||
_customVoiceProperty = serializedObject.FindProperty("customWitVoiceSettings");
|
||||
}
|
||||
|
||||
// GUI
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// Display default ui
|
||||
base.OnInspectorGUI();
|
||||
|
||||
// Check voices
|
||||
TTSService tts = _speaker.TTSService;
|
||||
TTSVoiceSettings[] settings = tts?.GetAllPresetVoiceSettings();
|
||||
if (_voicePresetIds == null
|
||||
|| (settings != null && _voicePresetIds.Length != settings.Length + 1)
|
||||
|| (_voiceIndex >= 0 && _voiceIndex < _voicePresetIds.Length - 1 && !string.Equals(_speaker.presetVoiceID, _voicePresetIds[_voiceIndex])))
|
||||
{
|
||||
RefreshVoices(settings);
|
||||
}
|
||||
|
||||
// No preset voices found, assume custom
|
||||
if (_voicePresetIds == null || _voicePresetIds.Length == 0)
|
||||
{
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.TextField(UI_PRESET_VOICE_KEY, UI_CUSTOM_KEY);
|
||||
GUI.enabled = true;
|
||||
}
|
||||
// Voice dropdown
|
||||
else
|
||||
{
|
||||
bool updated = false;
|
||||
WitEditorUI.LayoutPopup(UI_PRESET_VOICE_KEY, _voicePresetIds, ref _voiceIndex, ref updated);
|
||||
if (updated)
|
||||
{
|
||||
if (_voiceIndex >= 0 && _voiceIndex < _voicePresetIds.Length - 1)
|
||||
{
|
||||
_presetVoiceProperty.stringValue = _voicePresetIds[_voiceIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
_presetVoiceProperty.stringValue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom layout
|
||||
if (_voicePresetIds == null || _voiceIndex < 0 || _voiceIndex >= _voicePresetIds.Length - 1)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_customVoiceProperty, new GUIContent(UI_CUSTOM_VOICE_KEY));
|
||||
}
|
||||
|
||||
// Apply all modified properties
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
// Layout TTS clip queue
|
||||
LayoutClipQueue();
|
||||
}
|
||||
|
||||
// Refresh voices
|
||||
private void RefreshVoices(TTSVoiceSettings[] settings)
|
||||
{
|
||||
// Reset voice data
|
||||
_voiceIndex = -1;
|
||||
_voicePresetIds = null;
|
||||
if (settings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all ids
|
||||
List<string> presetIds = settings.Select(s => s.SettingsId).ToList();
|
||||
// Get voice index
|
||||
_voiceIndex = presetIds.IndexOf(_speaker.presetVoiceID);
|
||||
if (_voiceIndex == -1)
|
||||
{
|
||||
_voiceIndex = presetIds.Count;
|
||||
}
|
||||
// Add custom key
|
||||
presetIds.Add(UI_CUSTOM_KEY);
|
||||
// Apply preset ids
|
||||
_voicePresetIds = presetIds.ToArray();
|
||||
}
|
||||
|
||||
// Layout clip queue
|
||||
private const string UI_CLIP_HEADER_TEXT = "Clip Queue";
|
||||
private const string UI_CLIP_SPEAKER_TEXT = "Speaker Clip:";
|
||||
private const string UI_CLIP_QUEUE_TEXT = "Loading Clips:";
|
||||
private bool _speakerFoldout = false;
|
||||
private bool _queueFoldout = false;
|
||||
private void LayoutClipQueue()
|
||||
{
|
||||
// Ignore unless playing
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Add header
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(UI_CLIP_HEADER_TEXT, EditorStyles.boldLabel);
|
||||
|
||||
// Speaker Foldout
|
||||
_speakerFoldout = EditorGUILayout.Foldout(_speakerFoldout, UI_CLIP_SPEAKER_TEXT);
|
||||
if (_speakerFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
if (!_speaker.IsSpeaking)
|
||||
{
|
||||
EditorGUILayout.LabelField("None");
|
||||
}
|
||||
else
|
||||
{
|
||||
TTSServiceInspector.DrawClipGUI(_speaker.SpeakingClip);
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
// Queue Foldout
|
||||
List<TTSClipData> queuedClips = _speaker.QueuedClips;
|
||||
_queueFoldout = EditorGUILayout.Foldout(_queueFoldout, $"{UI_CLIP_QUEUE_TEXT} {(queuedClips == null ? 0 : queuedClips.Count)}");
|
||||
if (_queueFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
if (queuedClips == null || queuedClips.Count == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("None");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < queuedClips.Count; i++)
|
||||
{
|
||||
TTSClipData clipData = queuedClips[i];
|
||||
bool oldFoldout = WitEditorUI.GetFoldoutValue(clipData);
|
||||
bool newFoldout = EditorGUILayout.Foldout(oldFoldout, $"Clip[{i}]");
|
||||
if (oldFoldout != newFoldout)
|
||||
{
|
||||
WitEditorUI.SetFoldoutValue(clipData, newFoldout);
|
||||
}
|
||||
if (newFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
TTSServiceInspector.DrawClipGUI(clipData);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3835212f72d4d5149bed0f07915f204e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Meta.WitAi.TTS.Integrations;
|
||||
|
||||
namespace Meta.WitAi.TTS
|
||||
{
|
||||
[CustomEditor(typeof(TTSWit), true)]
|
||||
public class TTSWitInspector : TTSServiceInspector
|
||||
{
|
||||
private int selectedBaseVoice;
|
||||
|
||||
protected override void OnEditTimeGUI()
|
||||
{
|
||||
base.OnEditTimeGUI();
|
||||
|
||||
var ttsWit = (TTSWit)target;
|
||||
var config = ttsWit.RequestSettings.configuration;
|
||||
if (!config) return;
|
||||
|
||||
// Get app info for voices
|
||||
var appInfo = config.GetApplicationInfo();
|
||||
if (null != appInfo.voices && appInfo.voices.Length > 0)
|
||||
{
|
||||
// Get all voice names from wit
|
||||
string[] voiceNames = appInfo.voices.Select(v => v.name).ToArray();
|
||||
|
||||
// Add a selected preset
|
||||
GUILayout.BeginHorizontal();
|
||||
selectedBaseVoice = EditorGUILayout.Popup(selectedBaseVoice, voiceNames);
|
||||
GUI.enabled = selectedBaseVoice >= 0 && selectedBaseVoice < appInfo.voices.Length;
|
||||
if (WitEditorUI.LayoutTextButton("Add Preset"))
|
||||
{
|
||||
TTSEditorUtilities.AddPresetForInfo(ttsWit, appInfo.voices[selectedBaseVoice]);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Add all unused presets
|
||||
GUI.enabled = true;
|
||||
if (GUILayout.Button("Add Unused Voices as Presets"))
|
||||
{
|
||||
// Get used voices
|
||||
List<string> usedVoiceNames = ttsWit.PresetWitVoiceSettings.Select(v => v.voice).ToList();
|
||||
|
||||
// Get unused voices
|
||||
var unusedVoices = appInfo.voices.Where(v => !usedVoiceNames.Contains(v.name)).ToArray();
|
||||
|
||||
// Add all unused presets
|
||||
TTSEditorUtilities.AddPresetsForInfo(ttsWit, unusedVoices);
|
||||
}
|
||||
}
|
||||
// Log warning
|
||||
else
|
||||
{
|
||||
GUILayout.Label("There are currently no base presets available. Click refresh to check for updates.", EditorStyles.helpBox);
|
||||
}
|
||||
|
||||
// Refresh button
|
||||
if (GUILayout.Button("Refresh Presets"))
|
||||
{
|
||||
TTSEditorUtilities.RefreshAvailableVoices(ttsWit, info =>
|
||||
{
|
||||
Repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f6696f09b6647cda45870d07c064cab
|
||||
timeCreated: 1681499481
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69fad6077e2eeef4a812dc81d313fc2a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using Meta.WitAi.TTS.Integrations;
|
||||
using Meta.WitAi.Windows;
|
||||
using Meta.WitAi.Data.Info;
|
||||
using Meta.WitAi.Lib;
|
||||
using Meta.WitAi.Data.Configuration;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using Meta.WitAi.TTS.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.TTS.Voices
|
||||
{
|
||||
[CustomPropertyDrawer(typeof( TTSWitVoiceSettings))]
|
||||
public class TTSWitVoiceSettingsDrawer : PropertyDrawer
|
||||
{
|
||||
// Constants for var layout
|
||||
private const float VAR_HEIGHT = 20f;
|
||||
private const float VAR_MARGIN = 4f;
|
||||
private const float FIELD_HEIGHT = 60f;
|
||||
|
||||
// Constants for var lookup
|
||||
private const string VAR_SETTINGS = "SettingsId";
|
||||
private const string VAR_VOICE = "voice";
|
||||
private const string VAR_STYLE = "style";
|
||||
|
||||
// Voice data
|
||||
private TTSService _ttsService;
|
||||
private bool _onSpeaker;
|
||||
private IWitRequestConfiguration _configuration;
|
||||
private bool _configUpdating;
|
||||
private WitVoiceInfo[] _voices;
|
||||
private string[] _voiceNames;
|
||||
|
||||
// Subfields
|
||||
private static readonly FieldInfo[] _fields = FieldGUI.GetFields(typeof( TTSWitVoiceSettings));
|
||||
|
||||
// Determine height
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Get service
|
||||
if (!_ttsService)
|
||||
{
|
||||
GetService(property, out _ttsService, out _onSpeaker);
|
||||
}
|
||||
// Property
|
||||
if (!property.isExpanded)
|
||||
{
|
||||
return VAR_HEIGHT;
|
||||
}
|
||||
// Add each field + 1 for dropdown
|
||||
int total = _fields.Length + 1;
|
||||
// Remove 3 on speaker
|
||||
if (_onSpeaker)
|
||||
{
|
||||
total -= 3;
|
||||
}
|
||||
// Add 2 for voice properties
|
||||
int voiceIndex = GetVoiceIndex(property);
|
||||
if (voiceIndex != -1)
|
||||
{
|
||||
total += 2;
|
||||
}
|
||||
// Get height
|
||||
float height = total * VAR_HEIGHT + Mathf.Max(0, total - 1) * VAR_MARGIN;
|
||||
// Add fields
|
||||
if (!_onSpeaker)
|
||||
{
|
||||
height += 2 * (FIELD_HEIGHT - VAR_HEIGHT);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
// Handles gui layout
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Get service if needed
|
||||
if (!_ttsService)
|
||||
{
|
||||
GetService(property, out _ttsService, out _onSpeaker);
|
||||
}
|
||||
|
||||
// On gui
|
||||
float y = position.y;
|
||||
string voiceName = property.FindPropertyRelative(VAR_SETTINGS).stringValue;
|
||||
if (string.IsNullOrEmpty(voiceName))
|
||||
{
|
||||
voiceName = property.displayName;
|
||||
}
|
||||
property.isExpanded =
|
||||
EditorGUI.Foldout(new Rect(position.x, y, position.width, VAR_HEIGHT), property.isExpanded, voiceName);
|
||||
if (!property.isExpanded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
y += VAR_HEIGHT + VAR_MARGIN;
|
||||
|
||||
// Increment
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Refresh voices if needed
|
||||
RefreshVoices(_ttsService, property);
|
||||
// Get voice index
|
||||
int voiceIndex = GetVoiceIndex(property);
|
||||
|
||||
// Iterate subfields
|
||||
for (int s = 0; s < _fields.Length; s++)
|
||||
{
|
||||
// Get subfield
|
||||
FieldInfo subfield = _fields[s];
|
||||
|
||||
// Ignore base fields on speaker
|
||||
if (_onSpeaker && subfield.DeclaringType == typeof(TTSVoiceSettings))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get subfield property
|
||||
SerializedProperty subfieldProperty = property.FindPropertyRelative(subfield.Name);
|
||||
Rect subfieldRect = new Rect(position.x, y, position.width, VAR_HEIGHT);
|
||||
if (string.Equals(subfield.Name, VAR_VOICE) && voiceIndex != -1)
|
||||
{
|
||||
int newVoiceIndex = EditorGUI.Popup(subfieldRect, subfieldProperty.displayName, voiceIndex,
|
||||
_voiceNames);
|
||||
newVoiceIndex = Mathf.Clamp(newVoiceIndex, 0, _voiceNames.Length);
|
||||
if (voiceIndex != newVoiceIndex)
|
||||
{
|
||||
voiceIndex = newVoiceIndex;
|
||||
subfieldProperty.stringValue = _voiceNames[voiceIndex];
|
||||
GUI.FocusControl(null);
|
||||
}
|
||||
y += VAR_HEIGHT + VAR_MARGIN;
|
||||
continue;
|
||||
}
|
||||
if (string.Equals(subfield.Name, VAR_STYLE) && voiceIndex >= 0 && voiceIndex < _voices.Length)
|
||||
{
|
||||
// Get voice data
|
||||
WitVoiceInfo voiceInfo = _voices[voiceIndex];
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Locale layout
|
||||
EditorGUI.LabelField(subfieldRect, "Locale", voiceInfo.locale);
|
||||
y += VAR_HEIGHT + VAR_MARGIN;
|
||||
|
||||
// Gender layout
|
||||
subfieldRect = new Rect(position.x, y, position.width, VAR_HEIGHT);
|
||||
EditorGUI.LabelField(subfieldRect, "Gender", voiceInfo.gender);
|
||||
y += VAR_HEIGHT + VAR_MARGIN;
|
||||
|
||||
// Style layout/select
|
||||
subfieldRect = new Rect(position.x, y, position.width, VAR_HEIGHT);
|
||||
if (voiceInfo.styles != null && voiceInfo.styles.Length > 0)
|
||||
{
|
||||
// Get style index
|
||||
string style = subfieldProperty.stringValue;
|
||||
int styleIndex = new List<string>(voiceInfo.styles).IndexOf(style);
|
||||
|
||||
// Show style select
|
||||
int newStyleIndex = EditorGUI.Popup(subfieldRect, subfieldProperty.displayName, styleIndex,
|
||||
voiceInfo.styles);
|
||||
newStyleIndex = Mathf.Clamp(newStyleIndex, 0, voiceInfo.styles.Length);
|
||||
if (styleIndex != newStyleIndex)
|
||||
{
|
||||
// Apply style
|
||||
styleIndex = newStyleIndex;
|
||||
subfieldProperty.stringValue = voiceInfo.styles[styleIndex];
|
||||
GUI.FocusControl(null);
|
||||
}
|
||||
|
||||
// Move down
|
||||
y += VAR_HEIGHT + VAR_MARGIN;
|
||||
EditorGUI.indentLevel--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Undent
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
//
|
||||
TextAreaAttribute area = subfield.GetCustomAttribute<TextAreaAttribute>();
|
||||
if (area != null)
|
||||
{
|
||||
subfieldRect.height = FIELD_HEIGHT;
|
||||
y += FIELD_HEIGHT - VAR_HEIGHT;
|
||||
}
|
||||
|
||||
// Default layout
|
||||
EditorGUI.PropertyField(subfieldRect, subfieldProperty, new GUIContent(subfieldProperty.displayName));
|
||||
|
||||
// Clamp in between range
|
||||
RangeAttribute range = subfield.GetCustomAttribute<RangeAttribute>();
|
||||
if (range != null)
|
||||
{
|
||||
int newValue = Mathf.Clamp(subfieldProperty.intValue, (int)range.min, (int)range.max);
|
||||
if (subfieldProperty.intValue != newValue)
|
||||
{
|
||||
subfieldProperty.intValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment
|
||||
y += VAR_HEIGHT + VAR_MARGIN;
|
||||
}
|
||||
|
||||
// Undent
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
// Get service & if on a speaker
|
||||
private void GetService(SerializedProperty property, out TTSService ttsService, out bool onSpeaker)
|
||||
{
|
||||
// Get tts wit if possible
|
||||
object targetObject = property.serializedObject.targetObject;
|
||||
if (targetObject != null)
|
||||
{
|
||||
// Currently on TTSService
|
||||
if (targetObject is TTSService service)
|
||||
{
|
||||
ttsService = service;
|
||||
onSpeaker = false;
|
||||
return;
|
||||
}
|
||||
// Currently on TTSSpeaker
|
||||
if (targetObject is TTSSpeaker speaker)
|
||||
{
|
||||
ttsService = speaker.TTSService;
|
||||
onSpeaker = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Speaker not found
|
||||
ttsService = null;
|
||||
onSpeaker = false;
|
||||
}
|
||||
// Refresh voices
|
||||
private void RefreshVoices(TTSService ttsService, SerializedProperty property)
|
||||
{
|
||||
// Ensure service exists
|
||||
if (!(ttsService is TTSWit))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Get configuration
|
||||
IWitRequestConfiguration configuration = (ttsService as TTSWit).RequestSettings.configuration;
|
||||
// Set configuration
|
||||
if (_configuration != configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_voices = null;
|
||||
_voiceNames = null;
|
||||
_configUpdating = false;
|
||||
}
|
||||
// Ignore if null
|
||||
if (configuration == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Ignore if already set up
|
||||
if (_voices != null && _voiceNames != null && !_configUpdating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Get voices
|
||||
_voices = configuration.GetApplicationInfo().voices;
|
||||
_voiceNames = _voices?.Select(voice => voice.name).ToArray();
|
||||
|
||||
// Voices found!
|
||||
if (_voices != null && _voices.Length > 0)
|
||||
{
|
||||
_configUpdating = false;
|
||||
}
|
||||
// Configuration needs voices, perform update
|
||||
else if (!_configUpdating)
|
||||
{
|
||||
// Perform update if possible
|
||||
if (_configuration is WitConfiguration witConfig && !witConfig.IsUpdatingData())
|
||||
{
|
||||
witConfig.RefreshAppInfo();
|
||||
}
|
||||
// Now updating
|
||||
_configUpdating = true;
|
||||
}
|
||||
}
|
||||
// Get voice index
|
||||
private int GetVoiceIndex(SerializedProperty property)
|
||||
{
|
||||
SerializedProperty voiceProperty = property.FindPropertyRelative(VAR_VOICE);
|
||||
string voiceID = voiceProperty.stringValue;
|
||||
int voiceIndex = -1;
|
||||
List<string> voiceNames = new List<string>();
|
||||
if (_voiceNames != null)
|
||||
{
|
||||
voiceNames.AddRange(_voiceNames);
|
||||
}
|
||||
if (voiceNames.Count > 0)
|
||||
{
|
||||
if (string.IsNullOrEmpty(voiceID))
|
||||
{
|
||||
voiceIndex = 0;
|
||||
voiceID = voiceNames[0];
|
||||
voiceProperty.stringValue = voiceID;
|
||||
GUI.FocusControl(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
voiceIndex = voiceNames.IndexOf(voiceID);
|
||||
}
|
||||
}
|
||||
return voiceIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bf9132926065a54da619451f5258d3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c945bb3fb3322b4eb73aba670cad74a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4d30ee47fb6e7d4a888e9ee4b707391
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Meta.Voice.Audio;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.TTS.Data
|
||||
{
|
||||
// Various request load states
|
||||
public enum TTSClipLoadState
|
||||
{
|
||||
Unloaded,
|
||||
Preparing,
|
||||
Loaded,
|
||||
Error
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TTSClipData
|
||||
{
|
||||
// Text to be spoken
|
||||
public string textToSpeak;
|
||||
// Unique identifier
|
||||
public string clipID;
|
||||
// Audio type
|
||||
public AudioType audioType;
|
||||
// Voice settings for request
|
||||
public TTSVoiceSettings voiceSettings;
|
||||
// Cache settings for request
|
||||
public TTSDiskCacheSettings diskCacheSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Unique request id used for tracking & logging
|
||||
/// </summary>
|
||||
public string queryRequestId => _queryRequestId;
|
||||
private string _queryRequestId = Guid.NewGuid().ToString();
|
||||
// Whether service should stream audio or just provide all at once
|
||||
public bool queryStream;
|
||||
// Request data
|
||||
public Dictionary<string, string> queryParameters;
|
||||
|
||||
// Clip stream
|
||||
public IAudioClipStream clipStream
|
||||
{
|
||||
get => _clipStream;
|
||||
set
|
||||
{
|
||||
// Unload previous clip stream
|
||||
IAudioClipStream v = value;
|
||||
if (_clipStream != null && _clipStream != v)
|
||||
{
|
||||
clipStream.OnStreamReady = null;
|
||||
clipStream.OnStreamUpdated = null;
|
||||
clipStream.OnStreamComplete = null;
|
||||
_clipStream.Unload();
|
||||
}
|
||||
// Apply new clip stream
|
||||
_clipStream = v;
|
||||
}
|
||||
}
|
||||
private IAudioClipStream _clipStream;
|
||||
public AudioClip clip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (clipStream is IAudioClipProvider uacs)
|
||||
{
|
||||
return uacs.Clip;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Clip load state
|
||||
[NonSerialized] public TTSClipLoadState loadState;
|
||||
// Clip load progress
|
||||
[NonSerialized] public float loadProgress;
|
||||
|
||||
// On clip state change
|
||||
public Action<TTSClipData, TTSClipLoadState> onStateChange;
|
||||
|
||||
/// <summary>
|
||||
/// A callback when clip stream is ready
|
||||
/// Returns an error if there was an issue
|
||||
/// </summary>
|
||||
public Action<string> onPlaybackReady;
|
||||
/// <summary>
|
||||
/// A callback when clip has downloaded successfully
|
||||
/// Returns an error if there was an issue
|
||||
/// </summary>
|
||||
public Action<string> onDownloadComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Compare clips if possible
|
||||
/// </summary>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is TTSClipData other)
|
||||
{
|
||||
return Equals(other);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Compare clip ids
|
||||
/// </summary>
|
||||
public bool Equals(TTSClipData other)
|
||||
{
|
||||
return HasClipId(other?.clipID);
|
||||
}
|
||||
/// <summary>
|
||||
/// Compare clip ids
|
||||
/// </summary>
|
||||
public bool HasClipId(string clipId)
|
||||
{
|
||||
return string.Equals(clipID, clipId, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
/// <summary>
|
||||
/// Get hash code
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + clipID.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef626b8cea4f59646a5076430a0e14aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Meta.WitAi.TTS.Data
|
||||
{
|
||||
// TTS Cache disk location
|
||||
public enum TTSDiskCacheLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// Does not cache
|
||||
/// </summary>
|
||||
Stream,
|
||||
/// <summary>
|
||||
/// Stores files in editor only & loads files from internal project location (Application.streamingAssetsPath)
|
||||
/// </summary>
|
||||
Preload,
|
||||
/// <summary>
|
||||
/// Stores files at persistent location (Application.persistentDataPath)
|
||||
/// </summary>
|
||||
Persistent,
|
||||
/// <summary>
|
||||
/// Stores files at temporary cache location (Application.temporaryCachePath)
|
||||
/// </summary>
|
||||
Temporary
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TTSDiskCacheSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Where the TTS clip should be cached
|
||||
/// </summary>
|
||||
public TTSDiskCacheLocation DiskCacheLocation = TTSDiskCacheLocation.Stream;
|
||||
|
||||
/// <summary>
|
||||
/// Where the TTS clip should streamed from cache
|
||||
/// </summary>
|
||||
public bool StreamFromDisk = false;
|
||||
/// <summary>
|
||||
/// Length of a streamed clip buffer in seconds
|
||||
/// </summary>
|
||||
public float StreamBufferLength = 5f;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4d1170a24dd77d49bf3cd610dd1c9a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Meta.WitAi.TTS.Data
|
||||
{
|
||||
public abstract class TTSVoiceSettings
|
||||
{
|
||||
[Tooltip("A unique id used for linking these voice settings to a TTS Speaker")]
|
||||
[FormerlySerializedAs("settingsID")]
|
||||
public string SettingsId;
|
||||
|
||||
[Tooltip("Text that is added to the front of any TTS request using this voice setting")]
|
||||
[TextArea]
|
||||
public string PrependedText;
|
||||
|
||||
[TextArea]
|
||||
[Tooltip("Text that is added to the end of any TTS request using this voice setting")]
|
||||
public string AppendedText;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dbbd0a6d06807d4f8a3190785a267f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a7b8f23689f0b94dbe6a9aae4811de6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Meta.WitAi.TTS.Events
|
||||
{
|
||||
[Serializable]
|
||||
public class TTSClipDownloadEvent : UnityEvent<TTSClipData, string>
|
||||
{
|
||||
}
|
||||
[Serializable]
|
||||
public class TTSClipDownloadErrorEvent : UnityEvent<TTSClipData, string, string>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TTSDownloadEvents
|
||||
{
|
||||
[Tooltip("Called when a audio clip download begins")]
|
||||
public TTSClipDownloadEvent OnDownloadBegin = new TTSClipDownloadEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip is downloaded successfully")]
|
||||
public TTSClipDownloadEvent OnDownloadSuccess = new TTSClipDownloadEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip downloaded has been cancelled")]
|
||||
public TTSClipDownloadEvent OnDownloadCancel = new TTSClipDownloadEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip downloaded has failed")]
|
||||
public TTSClipDownloadErrorEvent OnDownloadError = new TTSClipDownloadErrorEvent();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8c6f2c6a5fdba344b75e8f613c5dc09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.TTS.Events
|
||||
{
|
||||
[Serializable]
|
||||
public class TTSServiceEvents
|
||||
{
|
||||
[Tooltip("Called when a audio clip has been added to the runtime cache")]
|
||||
public TTSClipEvent OnClipCreated = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip has been removed from the runtime cache")]
|
||||
public TTSClipEvent OnClipUnloaded = new TTSClipEvent();
|
||||
|
||||
/// <summary>
|
||||
/// All events related to web requests
|
||||
/// </summary>
|
||||
public TTSWebRequestEvents WebRequest = new TTSWebRequestEvents();
|
||||
|
||||
/// <summary>
|
||||
/// All events related to streaming from web or disk
|
||||
/// </summary>
|
||||
public TTSStreamEvents Stream = new TTSStreamEvents();
|
||||
|
||||
/// <summary>
|
||||
/// All events related to downloading from the web
|
||||
/// </summary>
|
||||
public TTSDownloadEvents Download = new TTSDownloadEvents();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a41b87319719e004da4ad59b6a70358d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Speech;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
|
||||
namespace Meta.WitAi.TTS.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A unity event that returns a TTSSpeaker & TTSClipData
|
||||
/// for a specific speaker playback request.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TTSSpeakerClipEvent : UnityEvent<TTSSpeaker, TTSClipData> { }
|
||||
/// <summary>
|
||||
/// A unity event that returns a TTSSpeaker, TTSClipData & text
|
||||
/// for a specific speaker playback request
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TTSSpeakerClipMessageEvent : UnityEvent<TTSSpeaker, TTSClipData, string> { }
|
||||
|
||||
/// <summary>
|
||||
/// A collection of events used for speaker tts playback.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TTSSpeakerClipEvents : VoiceSpeechEvents
|
||||
{
|
||||
[Header("Speaker Lifecycle Events")]
|
||||
[SerializeField] [Tooltip("Initial callback as soon as the audio clip speak request is generated")]
|
||||
private TTSSpeakerClipEvent _onInit = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Initial callback as soon as the audio clip speak request is generated
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnInit => _onInit;
|
||||
|
||||
[SerializeField] [Tooltip("Final call for a 'Speak' request that is called following a load failure, load abort, playback cancellation or playback completion")]
|
||||
private TTSSpeakerClipEvent _onComplete = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Final call for a 'Speak' request that is called following a load failure,
|
||||
/// load abort, playback cancellation or playback completion
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnComplete => _onComplete;
|
||||
|
||||
[Header("Speaker Loading Events")]
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip load begins")]
|
||||
private TTSSpeakerClipEvent _onLoadBegin = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip load begins
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnLoadBegin => _onLoadBegin;
|
||||
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip load is cancelled")]
|
||||
private TTSSpeakerClipEvent _onLoadAbort = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip load is cancelled
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnLoadAbort => _onLoadAbort;
|
||||
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip load fails due to a network or load error")]
|
||||
private TTSSpeakerClipMessageEvent _onLoadFailed = new TTSSpeakerClipMessageEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip load fails due to a network or load error
|
||||
/// </summary>
|
||||
public TTSSpeakerClipMessageEvent OnLoadFailed => _onLoadFailed;
|
||||
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip load successfully")]
|
||||
private TTSSpeakerClipEvent _onLoadSuccess = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip load successfully
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnLoadSuccess => _onLoadSuccess;
|
||||
|
||||
[Header("Speaker Playback Events")]
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip playback is ready")]
|
||||
private TTSSpeakerClipEvent _onPlaybackReady = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip playback is ready
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnPlaybackReady => _onPlaybackReady;
|
||||
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip playback has begun")]
|
||||
private TTSSpeakerClipEvent _onPlaybackStart = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip playback has begun
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnPlaybackStart => _onPlaybackStart;
|
||||
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip playback been cancelled")]
|
||||
private TTSSpeakerClipMessageEvent _onPlaybackCancelled = new TTSSpeakerClipMessageEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip playback been cancelled
|
||||
/// </summary>
|
||||
public TTSSpeakerClipMessageEvent OnPlaybackCancelled => _onPlaybackCancelled;
|
||||
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip is updated during streamed playback")]
|
||||
private TTSSpeakerClipEvent _onPlaybackClipUpdated = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip is updated during streamed playback
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnPlaybackClipUpdated => _onPlaybackClipUpdated;
|
||||
|
||||
[SerializeField] [Tooltip("Called when TTS audio clip playback completed successfully")]
|
||||
private TTSSpeakerClipEvent _onPlaybackComplete = new TTSSpeakerClipEvent();
|
||||
/// <summary>
|
||||
/// Called when TTS audio clip playback completed successfully
|
||||
/// </summary>
|
||||
public TTSSpeakerClipEvent OnPlaybackComplete => _onPlaybackComplete;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c303e406a42b8b4d9853bfd74e434ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Speech;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Meta.WitAi.TTS.Utilities
|
||||
{
|
||||
[Serializable]
|
||||
public class TTSSpeakerEvent : UnityEvent<TTSSpeaker, string> { }
|
||||
[Serializable]
|
||||
public class TTSSpeakerClipDataEvent : UnityEvent<TTSClipData> { }
|
||||
[Serializable]
|
||||
public class TTSSpeakerEvents : TTSSpeakerClipEvents
|
||||
{
|
||||
[Header("Queue Events")]
|
||||
[Tooltip("Called when a tts request is added to an empty queue")]
|
||||
[SerializeField] [FormerlySerializedAs("OnPlaybackQueueBegin")]
|
||||
private UnityEvent _onPlaybackQueueBegin = new UnityEvent();
|
||||
public UnityEvent OnPlaybackQueueBegin => _onPlaybackQueueBegin;
|
||||
|
||||
[Tooltip("Called the final request is removed from a queue")]
|
||||
[SerializeField] [FormerlySerializedAs("OnPlaybackQueueComplete")]
|
||||
private UnityEvent _onPlaybackQueueComplete = new UnityEvent();
|
||||
public UnityEvent OnPlaybackQueueComplete => _onPlaybackQueueComplete;
|
||||
|
||||
[Header("Deprecated Events")]
|
||||
[Obsolete("Use 'OnLoadBegin' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataQueued;
|
||||
[Obsolete("Use 'OnLoadBegin' event")]
|
||||
public TTSSpeakerEvent OnClipLoadBegin;
|
||||
[Obsolete("Use 'OnLoadBegin' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataLoadBegin;
|
||||
[Obsolete("Use 'OnLoadAbort' event")]
|
||||
public TTSSpeakerEvent OnClipLoadAbort;
|
||||
[Obsolete("Use 'OnLoadAbort' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataLoadAbort;
|
||||
[Obsolete("Use 'OnLoadFailed' event")]
|
||||
public TTSSpeakerEvent OnClipLoadFailed;
|
||||
[Obsolete("Use 'OnLoadFailed' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataLoadFailed;
|
||||
[Obsolete("Use 'OnLoadSuccess' event")]
|
||||
public TTSSpeakerEvent OnClipLoadSuccess;
|
||||
[Obsolete("Use 'OnLoadSuccess' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataLoadSuccess;
|
||||
[Obsolete("Use 'OnPlaybackReady' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataPlaybackReady;
|
||||
[Obsolete("Use 'OnPlaybackStart' event")]
|
||||
public TTSSpeakerEvent OnStartSpeaking;
|
||||
[Obsolete("Use 'OnPlaybackStart' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataPlaybackStart;
|
||||
[Obsolete("Use 'OnPlaybackCancelled' event")]
|
||||
public TTSSpeakerEvent OnCancelledSpeaking;
|
||||
[Obsolete("Use 'OnPlaybackCancelled' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataPlaybackCancelled;
|
||||
[Obsolete("Use 'OnPlaybackComplete' event")]
|
||||
public TTSSpeakerEvent OnFinishedSpeaking;
|
||||
[Obsolete("Use 'OnPlaybackComplete' event")]
|
||||
public TTSSpeakerClipDataEvent OnClipDataPlaybackFinished;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8f392c4b8438cd4e8a5318177de7a23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Meta.WitAi.TTS.Events
|
||||
{
|
||||
[Serializable]
|
||||
public class TTSClipEvent : UnityEvent<TTSClipData>
|
||||
{
|
||||
}
|
||||
[Serializable]
|
||||
public class TTSClipErrorEvent : UnityEvent<TTSClipData, string>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TTSStreamEvents
|
||||
{
|
||||
[Tooltip("Called when a audio clip stream begins")]
|
||||
public TTSClipEvent OnStreamBegin = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip is ready for playback")]
|
||||
public TTSClipEvent OnStreamReady = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called if/when an audio clip is adjusted")]
|
||||
public TTSClipEvent OnStreamClipUpdate = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip is completely loaded")]
|
||||
public TTSClipEvent OnStreamComplete = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip stream has been cancelled")]
|
||||
public TTSClipEvent OnStreamCancel = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a audio clip stream has failed")]
|
||||
public TTSClipErrorEvent OnStreamError = new TTSClipErrorEvent();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc1209a088b657247b1f0c645ae7ee93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Meta.WitAi.TTS.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Events related to web requests
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TTSWebRequestEvents
|
||||
{
|
||||
[Tooltip("Called when a web request begins transmission")]
|
||||
public TTSClipEvent OnRequestBegin = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a web request is cancelled")]
|
||||
public TTSClipEvent OnRequestCancel = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a web request fails")]
|
||||
public TTSClipErrorEvent OnRequestError = new TTSClipErrorEvent();
|
||||
|
||||
[Tooltip("Called when a web request receives first data")]
|
||||
public TTSClipEvent OnRequestFirstResponse = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a web request is ready for playback")]
|
||||
public TTSClipEvent OnRequestReady = new TTSClipEvent();
|
||||
|
||||
[Tooltip("Called when a web request is completed via success, cancellation or failure")]
|
||||
public TTSClipEvent OnRequestComplete = new TTSClipEvent();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59d937d620dd3ea45abde93fe056c240
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Meta.WitAi.TTS",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:1c28d8b71ced07540b7c271537363cc6",
|
||||
"GUID:4504b1a6e0fdcc3498c30b266e4a63bf"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bbcefc153e1f1b4a98680670797dd16
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8e61f36a843a8e4f92bb0985d1267d3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using Meta.WitAi.TTS.Events;
|
||||
using Meta.WitAi.TTS.Interfaces;
|
||||
using Meta.WitAi.Utilities;
|
||||
using Meta.WitAi.Requests;
|
||||
using Meta.Voice.Audio;
|
||||
|
||||
namespace Meta.WitAi.TTS.Integrations
|
||||
{
|
||||
public class TTSDiskCache : MonoBehaviour, ITTSDiskCacheHandler
|
||||
{
|
||||
[Header("Disk Cache Settings")]
|
||||
/// <summary>
|
||||
/// The relative path from the DiskCacheLocation in TTSDiskCacheSettings
|
||||
/// </summary>
|
||||
[SerializeField] private string _diskPath = "TTS/";
|
||||
public string DiskPath => _diskPath;
|
||||
|
||||
/// <summary>
|
||||
/// The cache default settings
|
||||
/// </summary>
|
||||
[SerializeField] private TTSDiskCacheSettings _defaultSettings = new TTSDiskCacheSettings();
|
||||
public TTSDiskCacheSettings DiskCacheDefaultSettings => _defaultSettings;
|
||||
|
||||
/// <summary>
|
||||
/// The cache streaming events
|
||||
/// </summary>
|
||||
[SerializeField] private TTSStreamEvents _events = new TTSStreamEvents();
|
||||
public TTSStreamEvents DiskStreamEvents
|
||||
{
|
||||
get => _events;
|
||||
set { _events = value; }
|
||||
}
|
||||
|
||||
// All currently performing stream requests
|
||||
private Dictionary<string, VRequest> _streamRequests = new Dictionary<string, VRequest>();
|
||||
|
||||
// Cancel all requests
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
Dictionary<string, VRequest> requests = _streamRequests;
|
||||
_streamRequests.Clear();
|
||||
foreach (var request in requests.Values)
|
||||
{
|
||||
request.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds full cache path
|
||||
/// </summary>
|
||||
/// <param name="clipData"></param>
|
||||
/// <returns></returns>
|
||||
public string GetDiskCachePath(TTSClipData clipData)
|
||||
{
|
||||
// Disabled
|
||||
if (!ShouldCacheToDisk(clipData))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Get directory path
|
||||
TTSDiskCacheLocation location = clipData.diskCacheSettings.DiskCacheLocation;
|
||||
string directory = string.Empty;
|
||||
switch (location)
|
||||
{
|
||||
case TTSDiskCacheLocation.Persistent:
|
||||
directory = Application.persistentDataPath;
|
||||
break;
|
||||
case TTSDiskCacheLocation.Temporary:
|
||||
directory = Application.temporaryCachePath;
|
||||
break;
|
||||
case TTSDiskCacheLocation.Preload:
|
||||
directory = Application.streamingAssetsPath;
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(directory))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Add tts cache path & clean
|
||||
directory = Path.Combine(directory, DiskPath);
|
||||
|
||||
// Generate tts directory if possible
|
||||
if (location != TTSDiskCacheLocation.Preload || !Application.isPlaying)
|
||||
{
|
||||
if (!IOUtility.CreateDirectory(directory, true))
|
||||
{
|
||||
VLog.E($"Failed to create tts directory\nPath: {directory}\nLocation: {location}");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Return clip path
|
||||
return Path.Combine(directory, clipData.clipID + "." + WitTTSVRequest.GetAudioExtension(clipData.audioType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if should cache to disk or not
|
||||
/// </summary>
|
||||
/// <param name="clipData">All clip data</param>
|
||||
/// <returns>Returns true if should cache to disk</returns>
|
||||
public bool ShouldCacheToDisk(TTSClipData clipData)
|
||||
{
|
||||
return clipData != null && clipData.diskCacheSettings.DiskCacheLocation != TTSDiskCacheLocation.Stream && !string.IsNullOrEmpty(clipData.clipID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if file is cached on disk
|
||||
/// </summary>
|
||||
/// <param name="clipData">Request data</param>
|
||||
/// <returns>True if file is on disk</returns>
|
||||
public void CheckCachedToDisk(TTSClipData clipData, Action<TTSClipData, bool> onCheckComplete)
|
||||
{
|
||||
// Get path
|
||||
string cachePath = GetDiskCachePath(clipData);
|
||||
if (string.IsNullOrEmpty(cachePath))
|
||||
{
|
||||
onCheckComplete?.Invoke(clipData, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
VRequest request = new VRequest();
|
||||
bool canPerform = request.RequestFileExists(cachePath, (success, error) =>
|
||||
{
|
||||
// Remove
|
||||
if (_streamRequests.ContainsKey(clipData.clipID))
|
||||
{
|
||||
_streamRequests.Remove(clipData.clipID);
|
||||
}
|
||||
// Complete
|
||||
onCheckComplete(clipData, success);
|
||||
});
|
||||
if (canPerform)
|
||||
{
|
||||
_streamRequests[clipData.clipID] = request;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs async load request
|
||||
/// </summary>
|
||||
public void StreamFromDiskCache(TTSClipData clipData)
|
||||
{
|
||||
// Invoke begin
|
||||
DiskStreamEvents?.OnStreamBegin?.Invoke(clipData);
|
||||
|
||||
// Get file path
|
||||
string filePath = GetDiskCachePath(clipData);
|
||||
|
||||
// Load clip async
|
||||
VRequest request = new VRequest((progress) => clipData.loadProgress = progress);
|
||||
bool canPerform = request.RequestAudioStream(clipData.clipStream, new Uri(request.CleanUrl(filePath)),
|
||||
(clipStream, error) =>
|
||||
{
|
||||
clipData.clipStream = clipStream;
|
||||
OnStreamComplete(clipData, error);
|
||||
}, clipData.audioType, clipData.diskCacheSettings.StreamFromDisk);
|
||||
if (canPerform)
|
||||
{
|
||||
_streamRequests[clipData.clipID] = request;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Cancels unity request
|
||||
/// </summary>
|
||||
public void CancelDiskCacheStream(TTSClipData clipData)
|
||||
{
|
||||
// Ignore if not currently streaming
|
||||
if (!_streamRequests.ContainsKey(clipData.clipID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get request
|
||||
VRequest request = _streamRequests[clipData.clipID];
|
||||
_streamRequests.Remove(clipData.clipID);
|
||||
|
||||
// Cancel immediately
|
||||
request?.Cancel();
|
||||
request = null;
|
||||
|
||||
// Call cancel
|
||||
DiskStreamEvents?.OnStreamCancel?.Invoke(clipData);
|
||||
}
|
||||
// On stream completion
|
||||
protected virtual void OnStreamComplete(TTSClipData clipData, string error)
|
||||
{
|
||||
// Ignore if not currently streaming
|
||||
if (!_streamRequests.ContainsKey(clipData.clipID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from list
|
||||
_streamRequests.Remove(clipData.clipID);
|
||||
|
||||
// Error
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
DiskStreamEvents?.OnStreamError?.Invoke(clipData, error);
|
||||
}
|
||||
// Success
|
||||
else
|
||||
{
|
||||
DiskStreamEvents?.OnStreamReady?.Invoke(clipData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0ffdd015bcb8ea41bb96f19a723bf7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using Meta.WitAi.TTS.Data;
|
||||
using Meta.WitAi.TTS.Interfaces;
|
||||
using Meta.WitAi.TTS.Events;
|
||||
|
||||
namespace Meta.WitAi.TTS.Integrations
|
||||
{
|
||||
// A simple LRU Cache
|
||||
public class TTSRuntimeCache : MonoBehaviour, ITTSRuntimeCacheHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not to unload clip data after the clip capacity is hit
|
||||
/// </summary>
|
||||
[Header("Runtime Cache Settings")]
|
||||
[Tooltip("Whether or not to unload clip data after the clip capacity is hit")]
|
||||
[FormerlySerializedAs("_clipLimit")]
|
||||
public bool ClipLimit = true;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum clips allowed in the runtime cache
|
||||
/// </summary>
|
||||
[Tooltip("The maximum clips allowed in the runtime cache")]
|
||||
[FormerlySerializedAs("_clipCapacity")]
|
||||
[Min(1)] public int ClipCapacity = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to unload clip data after the ram capacity is hit
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not to unload clip data after the ram capacity is hit")]
|
||||
[FormerlySerializedAs("_ramLimit")]
|
||||
public bool RamLimit = true;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of RAM allowed in the runtime cache. In KBs
|
||||
/// </summary>
|
||||
[Tooltip("The maximum amount of RAM allowed in the runtime cache. In KBs")]
|
||||
[FormerlySerializedAs("_ramCapacity")]
|
||||
[Min(1)] public int RamCapacity = 32768;
|
||||
|
||||
/// <summary>
|
||||
/// On clip added callback
|
||||
/// </summary>
|
||||
public TTSClipEvent OnClipAdded { get; set; } = new TTSClipEvent();
|
||||
/// <summary>
|
||||
/// On clip removed callback
|
||||
/// </summary>
|
||||
public TTSClipEvent OnClipRemoved { get; set; } = new TTSClipEvent();
|
||||
|
||||
// Clips & their ids
|
||||
private Dictionary<string, TTSClipData> _clips = new Dictionary<string, TTSClipData>();
|
||||
private List<string> _clipOrder = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Simple getter for all clips
|
||||
/// </summary>
|
||||
public TTSClipData[] GetClips() => _clips.Values.ToArray();
|
||||
|
||||
// Remove all
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
_clips.Clear();
|
||||
_clipOrder.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getter for a clip that also moves clip to the back of the queue
|
||||
/// </summary>
|
||||
public TTSClipData GetClip(string clipID)
|
||||
{
|
||||
// Id not found
|
||||
if (!_clips.ContainsKey(clipID))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort to end
|
||||
int clipIndex = _clipOrder.IndexOf(clipID);
|
||||
_clipOrder.RemoveAt(clipIndex);
|
||||
_clipOrder.Add(clipID);
|
||||
|
||||
// Return clip
|
||||
return _clips[clipID];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add clip to cache and ensure it is most recently referenced
|
||||
/// </summary>
|
||||
/// <param name="clipData"></param>
|
||||
public bool AddClip(TTSClipData clipData)
|
||||
{
|
||||
// Do not add null
|
||||
if (clipData == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Remove from order
|
||||
bool wasAdded = true;
|
||||
int clipIndex = _clipOrder.IndexOf(clipData.clipID);
|
||||
if (clipIndex != -1)
|
||||
{
|
||||
wasAdded = false;
|
||||
_clipOrder.RemoveAt(clipIndex);
|
||||
}
|
||||
|
||||
// Add clip
|
||||
_clips[clipData.clipID] = clipData;
|
||||
// Add to end of order
|
||||
_clipOrder.Add(clipData.clipID);
|
||||
|
||||
// Evict least recently used clips
|
||||
while (IsCacheFull() && _clipOrder.Count > 0)
|
||||
{
|
||||
// Remove clip
|
||||
RemoveClip(_clipOrder[0]);
|
||||
}
|
||||
|
||||
// Call add delegate even if removed
|
||||
if (wasAdded && _clips.Keys.Count > 0)
|
||||
{
|
||||
OnClipAdded?.Invoke(clipData);
|
||||
}
|
||||
|
||||
// True if successfully added
|
||||
return _clips.Keys.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove clip from cache immediately
|
||||
/// </summary>
|
||||
/// <param name="clipID"></param>
|
||||
public void RemoveClip(string clipID)
|
||||
{
|
||||
// Id not found
|
||||
if (!_clips.ContainsKey(clipID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from dictionary
|
||||
TTSClipData clipData = _clips[clipID];
|
||||
_clips.Remove(clipID);
|
||||
|
||||
// Remove from order list
|
||||
int clipIndex = _clipOrder.IndexOf(clipID);
|
||||
_clipOrder.RemoveAt(clipIndex);
|
||||
|
||||
// Call remove delegate
|
||||
OnClipRemoved?.Invoke(clipData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if cache is full
|
||||
/// </summary>
|
||||
protected bool IsCacheFull()
|
||||
{
|
||||
// Capacity full
|
||||
if (ClipLimit && _clipOrder.Count > ClipCapacity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Ram full
|
||||
if (RamLimit && GetCacheDiskSize() > RamCapacity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Free
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Get RAM size of cache in KBs
|
||||
/// </summary>
|
||||
/// <returns>Returns size in KBs rounded up</returns>
|
||||
public int GetCacheDiskSize()
|
||||
{
|
||||
long total = 0;
|
||||
foreach (var key in _clips.Keys)
|
||||
{
|
||||
total += GetClipBytes(_clips[key].clipStream.Channels, _clips[key].clipStream.TotalSamples);
|
||||
}
|
||||
return (int)(total / (long)1024) + 1;
|
||||
}
|
||||
// Return bytes occupied by clip
|
||||
public static long GetClipBytes(AudioClip clip)
|
||||
{
|
||||
if (clip != null)
|
||||
{
|
||||
return GetClipBytes(clip.channels, clip.samples);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// Return bytes occupied by clip
|
||||
public static long GetClipBytes(int channels, int samples)
|
||||
{
|
||||
return channels * samples * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d60dcb6d02034b4b96284db469db5e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user