/************************************************************************************
|
|
Filename : OVRLipSyncContext.cs
|
|
Content : Interface to Oculus Lip-Sync engine
|
|
Created : August 6th, 2015
|
|
Copyright : Copyright Facebook Technologies, LLC and its affiliates.
|
|
All rights reserved.
|
|
|
|
Licensed under the Oculus Audio SDK License Version 3.3 (the "License");
|
|
you may not use the Oculus Audio SDK except in compliance with the License,
|
|
which is provided at the time of installation or download, or which
|
|
otherwise accompanies this software in either electronic or hard copy form.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
https://developer.oculus.com/licenses/audio-3.3/
|
|
|
|
Unless required by applicable law or agreed to in writing, the Oculus Audio SDK
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
************************************************************************************/
|
|
using UnityEngine;
|
|
|
|
[RequireComponent(typeof(AudioSource))]
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// ***** OVRLipSyncContext
|
|
//
|
|
/// <summary>
|
|
/// OVRLipSyncContext interfaces into the Oculus phoneme recognizer.
|
|
/// This component should be added into the scene once for each Audio Source.
|
|
///
|
|
/// </summary>
|
|
public class OVRLipSyncContext : OVRLipSyncContextBase
|
|
{
|
|
// * * * * * * * * * * * * *
|
|
// Public members
|
|
|
|
|
|
[Tooltip("Allow capturing of keyboard input to control operation.")]
|
|
public bool enableKeyboardInput = false;
|
|
[Tooltip("Register a mouse/touch callback to control loopback and gain (requires script restart).")]
|
|
public bool enableTouchInput = false;
|
|
[Tooltip("Play input audio back through audio output.")]
|
|
public bool audioLoopback = false;
|
|
[Tooltip("Key to toggle audio loopback.")]
|
|
public KeyCode loopbackKey = KeyCode.L;
|
|
[Tooltip("Show viseme scores in an OVRLipSyncDebugConsole display.")]
|
|
public bool showVisemes = false;
|
|
[Tooltip("Key to toggle viseme score display.")]
|
|
public KeyCode debugVisemesKey = KeyCode.D;
|
|
[Tooltip("Skip data from the Audio Source. Use if you intend to pass audio data in manually.")]
|
|
public bool skipAudioSource = false;
|
|
[Tooltip("Adjust the linear audio gain multiplier before processing lipsync")]
|
|
public float gain = 1.0f;
|
|
|
|
private bool hasDebugConsole = false;
|
|
|
|
public KeyCode debugLaughterKey = KeyCode.H;
|
|
public bool showLaughter = false;
|
|
public float laughterScore = 0.0f;
|
|
|
|
// * * * * * * * * * * * * *
|
|
// Private members
|
|
|
|
/// <summary>
|
|
/// Start this instance.
|
|
/// Note: make sure to always have a Start function for classes that have editor scripts.
|
|
/// </summary>
|
|
void Start()
|
|
{
|
|
// Add a listener to the OVRTouchpad for touch events
|
|
if (enableTouchInput)
|
|
{
|
|
OVRTouchpad.AddListener(LocalTouchEventCallback);
|
|
}
|
|
|
|
// Find console
|
|
OVRLipSyncDebugConsole[] consoles = FindObjectsOfType<OVRLipSyncDebugConsole>();
|
|
if (consoles.Length > 0)
|
|
{
|
|
hasDebugConsole = consoles[0];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle keyboard input
|
|
/// </summary>
|
|
void HandleKeyboard()
|
|
{
|
|
// Turn loopback on/off
|
|
if (Input.GetKeyDown(loopbackKey))
|
|
{
|
|
ToggleAudioLoopback();
|
|
}
|
|
else if (Input.GetKeyDown(debugVisemesKey))
|
|
{
|
|
showVisemes = !showVisemes;
|
|
|
|
if (showVisemes)
|
|
{
|
|
if (hasDebugConsole)
|
|
{
|
|
Debug.Log("DEBUG SHOW VISEMES: ENABLED");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("Warning: No OVRLipSyncDebugConsole in the scene!");
|
|
showVisemes = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (hasDebugConsole)
|
|
{
|
|
OVRLipSyncDebugConsole.Clear();
|
|
}
|
|
Debug.Log("DEBUG SHOW VISEMES: DISABLED");
|
|
}
|
|
}
|
|
else if (Input.GetKeyDown(debugLaughterKey))
|
|
{
|
|
showLaughter = !showLaughter;
|
|
|
|
if (showLaughter)
|
|
{
|
|
if (hasDebugConsole)
|
|
{
|
|
Debug.Log("DEBUG SHOW LAUGHTER: ENABLED");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("Warning: No OVRLipSyncDebugConsole in the scene!");
|
|
showLaughter = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (hasDebugConsole)
|
|
{
|
|
OVRLipSyncDebugConsole.Clear();
|
|
}
|
|
Debug.Log("DEBUG SHOW LAUGHTER: DISABLED");
|
|
}
|
|
}
|
|
else if (Input.GetKeyDown(KeyCode.LeftArrow))
|
|
{
|
|
gain -= 1.0f;
|
|
if (gain < 1.0f) gain = 1.0f;
|
|
|
|
string g = "LINEAR GAIN: ";
|
|
g += gain;
|
|
|
|
if (hasDebugConsole)
|
|
{
|
|
OVRLipSyncDebugConsole.Clear();
|
|
OVRLipSyncDebugConsole.Log(g);
|
|
OVRLipSyncDebugConsole.ClearTimeout(1.5f);
|
|
}
|
|
}
|
|
else if (Input.GetKeyDown(KeyCode.RightArrow))
|
|
{
|
|
gain += 1.0f;
|
|
if (gain > 15.0f)
|
|
gain = 15.0f;
|
|
|
|
string g = "LINEAR GAIN: ";
|
|
g += gain;
|
|
|
|
if (hasDebugConsole)
|
|
{
|
|
OVRLipSyncDebugConsole.Clear();
|
|
OVRLipSyncDebugConsole.Log(g);
|
|
OVRLipSyncDebugConsole.ClearTimeout(1.5f);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run processes that need to be updated in our game thread
|
|
/// </summary>
|
|
void Update()
|
|
{
|
|
if (enableKeyboardInput)
|
|
{
|
|
HandleKeyboard();
|
|
}
|
|
laughterScore = this.Frame.laughterScore;
|
|
DebugShowVisemesAndLaughter();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Preprocess F32 PCM audio buffer
|
|
/// </summary>
|
|
/// <param name="data">Data.</param>
|
|
/// <param name="channels">Channels.</param>
|
|
public void PreprocessAudioSamples(float[] data, int channels)
|
|
{
|
|
// Increase the gain of the input
|
|
for (int i = 0; i < data.Length; ++i)
|
|
{
|
|
data[i] = data[i] * gain;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Postprocess F32 PCM audio buffer
|
|
/// </summary>
|
|
/// <param name="data">Data.</param>
|
|
/// <param name="channels">Channels.</param>
|
|
public void PostprocessAudioSamples(float[] data, int channels)
|
|
{
|
|
// Turn off output (so that we don't get feedback from mics too close to speakers)
|
|
if (!audioLoopback)
|
|
{
|
|
for (int i = 0; i < data.Length; ++i)
|
|
data[i] = data[i] * 0.0f;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pass F32 PCM audio buffer to the lip sync module
|
|
/// </summary>
|
|
/// <param name="data">Data.</param>
|
|
/// <param name="channels">Channels.</param>
|
|
public void ProcessAudioSamplesRaw(float[] data, int channels)
|
|
{
|
|
// Send data into Phoneme context for processing (if context is not 0)
|
|
lock (this)
|
|
{
|
|
if (Context == 0 || OVRLipSync.IsInitialized() != OVRLipSync.Result.Success)
|
|
{
|
|
return;
|
|
}
|
|
var frame = this.Frame;
|
|
OVRLipSync.ProcessFrame(Context, data, frame, channels == 2);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pass S16 PCM audio buffer to the lip sync module
|
|
/// </summary>
|
|
/// <param name="data">Data.</param>
|
|
/// <param name="channels">Channels.</param>
|
|
public void ProcessAudioSamplesRaw(short[] data, int channels)
|
|
{
|
|
// Send data into Phoneme context for processing (if context is not 0)
|
|
lock (this)
|
|
{
|
|
if (Context == 0 || OVRLipSync.IsInitialized() != OVRLipSync.Result.Success)
|
|
{
|
|
return;
|
|
}
|
|
var frame = this.Frame;
|
|
OVRLipSync.ProcessFrame(Context, data, frame, channels == 2);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Process F32 audio sample and pass it to the lip sync module for computation
|
|
/// </summary>
|
|
/// <param name="data">Data.</param>
|
|
/// <param name="channels">Channels.</param>
|
|
public void ProcessAudioSamples(float[] data, int channels)
|
|
{
|
|
// Do not process if we are not initialized, or if there is no
|
|
// audio source attached to game object
|
|
if ((OVRLipSync.IsInitialized() != OVRLipSync.Result.Success) || audioSource == null)
|
|
{
|
|
return;
|
|
}
|
|
PreprocessAudioSamples(data, channels);
|
|
ProcessAudioSamplesRaw(data, channels);
|
|
PostprocessAudioSamples(data, channels);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the audio filter read event.
|
|
/// </summary>
|
|
/// <param name="data">Data.</param>
|
|
/// <param name="channels">Channels.</param>
|
|
void OnAudioFilterRead(float[] data, int channels)
|
|
{
|
|
if (!skipAudioSource)
|
|
{
|
|
ProcessAudioSamples(data, channels);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Print the visemes and laughter score to game window
|
|
/// </summary>
|
|
void DebugShowVisemesAndLaughter()
|
|
{
|
|
if (hasDebugConsole)
|
|
{
|
|
string seq = "";
|
|
if (showLaughter)
|
|
{
|
|
seq += "Laughter:";
|
|
int count = (int)(50.0f * this.Frame.laughterScore);
|
|
for (int c = 0; c < count; c++)
|
|
seq += "*";
|
|
seq += "\n";
|
|
}
|
|
if (showVisemes)
|
|
{
|
|
for (int i = 0; i < this.Frame.Visemes.Length; i++)
|
|
{
|
|
seq += ((OVRLipSync.Viseme)i).ToString();
|
|
seq += ":";
|
|
|
|
int count = (int)(50.0f * this.Frame.Visemes[i]);
|
|
for (int c = 0; c < count; c++)
|
|
seq += "*";
|
|
|
|
seq += "\n";
|
|
}
|
|
}
|
|
|
|
OVRLipSyncDebugConsole.Clear();
|
|
|
|
if (seq != "")
|
|
{
|
|
OVRLipSyncDebugConsole.Log(seq);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToggleAudioLoopback()
|
|
{
|
|
audioLoopback = !audioLoopback;
|
|
|
|
if (hasDebugConsole)
|
|
{
|
|
OVRLipSyncDebugConsole.Clear();
|
|
OVRLipSyncDebugConsole.ClearTimeout(1.5f);
|
|
|
|
if (audioLoopback)
|
|
OVRLipSyncDebugConsole.Log("LOOPBACK MODE: ENABLED");
|
|
else
|
|
OVRLipSyncDebugConsole.Log("LOOPBACK MODE: DISABLED");
|
|
}
|
|
}
|
|
|
|
// LocalTouchEventCallback
|
|
void LocalTouchEventCallback(OVRTouchpad.TouchEvent touchEvent)
|
|
{
|
|
string g = "LINEAR GAIN: ";
|
|
|
|
switch (touchEvent)
|
|
{
|
|
case (OVRTouchpad.TouchEvent.SingleTap):
|
|
ToggleAudioLoopback();
|
|
break;
|
|
|
|
case (OVRTouchpad.TouchEvent.Up):
|
|
gain += 1.0f;
|
|
if (gain > 15.0f)
|
|
gain = 15.0f;
|
|
|
|
g += gain;
|
|
|
|
if (hasDebugConsole)
|
|
{
|
|
OVRLipSyncDebugConsole.Clear();
|
|
OVRLipSyncDebugConsole.Log(g);
|
|
OVRLipSyncDebugConsole.ClearTimeout(1.5f);
|
|
}
|
|
|
|
break;
|
|
|
|
case (OVRTouchpad.TouchEvent.Down):
|
|
gain -= 1.0f;
|
|
if (gain < 1.0f) gain = 1.0f;
|
|
|
|
g += gain;
|
|
|
|
if (hasDebugConsole)
|
|
{
|
|
OVRLipSyncDebugConsole.Clear();
|
|
OVRLipSyncDebugConsole.Log(g);
|
|
OVRLipSyncDebugConsole.ClearTimeout(1.5f);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|