mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-01 00:08:05 -04:00
// SkinnedModelRenderer - the only new entry point for speaking
SoundHandle SpeakSound( SoundEvent sound, GameObject mouth = null )
SoundHandle SpeakSound( SoundFile sound, float volume = 1, float pitch = 1, GameObject mouth = null )
// SoundFile - the authored lipsync track, read from the compiled vsnd
VisemeTrack Visemes { get; } // null = no track (or not loaded yet, see IsValidForPlayback)
// VisemeTrack - visemes over time, authored in the sound editor
IReadOnlyList<VisemeFrame> Frames { get; }
float Duration { get; }
bool Sample( float time, Span<float> weights )
// VisemeFrame - one viseme span (struct: Viseme, StartTime, EndTime)
// Visemes - the standard 15-viseme table, OVR order
static IReadOnlyList<string> MorphNames { get; }
static int Count { get; }
// MorphCollection - the one shared viseme blend (sounds, voice chat, editor preview)
void ApplyVisemes( ReadOnlySpan<float> visemeWeights, float scale = 1, float smoothTime = 0 )
// ModelMorphs - per-model viseme weight table, cached managed-side (no interop on lookups)
float GetVisemeMorph( int viseme, int morph )
float GetVisemeMorph( string viseme, int morph )
// ResourceCompileContext - inject managed data into natively-compiled resources
void WriteBlock( string blockName, ReadOnlySpan<byte> data ) // four-char block names, eg "LIPS"
void WriteBlockJson( string blockName, object obj )
JsonObject ReadMeta() // the asset's .meta, registers a compile dependency
T ReadMeta<T>( string key, T defaultValue = default )
// ResourceLoadContext - read blocks back when the resource loads (Resource.OnLoaded, engine-internal for now)
// public ref struct, only valid during the load callback
bool Exists( string blockName )
ReadOnlySpan<byte> ReadData( string blockName )
string ReadString( string blockName )
T ReadJson<T>( string blockName, T defaultValue = default )
117 lines
3.0 KiB
C#
117 lines
3.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace AudioTests;
|
|
|
|
[TestClass]
|
|
public class VisemeTrackTest
|
|
{
|
|
// PP over [0.1, 0.3] - viseme 1 in Visemes.MorphNames order
|
|
static VisemeTrack SingleFrameTrack() => new( new List<VisemeFrame>
|
|
{
|
|
new() { Viseme = 1, StartTime = 0.1f, EndTime = 0.3f },
|
|
} );
|
|
|
|
// PP [0.1, 0.2] directly followed by AA (viseme 10) [0.2, 0.3]
|
|
static VisemeTrack AdjacentFramesTrack() => new( new List<VisemeFrame>
|
|
{
|
|
new() { Viseme = 1, StartTime = 0.1f, EndTime = 0.2f },
|
|
new() { Viseme = 10, StartTime = 0.2f, EndTime = 0.3f },
|
|
} );
|
|
|
|
[TestMethod]
|
|
public void Properties()
|
|
{
|
|
var track = AdjacentFramesTrack();
|
|
|
|
Assert.AreEqual( 2, track.Frames.Count );
|
|
Assert.AreEqual( 0.3f, track.Duration, 0.001f );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void FramesGetSortedByStartTime()
|
|
{
|
|
var track = new VisemeTrack( new List<VisemeFrame>
|
|
{
|
|
new() { Viseme = 10, StartTime = 0.5f, EndTime = 0.6f },
|
|
new() { Viseme = 1, StartTime = 0.1f, EndTime = 0.2f },
|
|
} );
|
|
|
|
Assert.AreEqual( 1, track.Frames[0].Viseme );
|
|
Assert.AreEqual( 10, track.Frames[1].Viseme );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SampleInsideFrame()
|
|
{
|
|
var track = SingleFrameTrack();
|
|
Span<float> weights = stackalloc float[Visemes.Count];
|
|
|
|
// Inside the frame the blend window stretches to the frame length (0.2):
|
|
// window [0.15, 0.35] overlaps [0.1, 0.3] for 75% of its length
|
|
Assert.IsTrue( track.Sample( 0.15f, weights ) );
|
|
Assert.AreEqual( 0.75f, weights[1], 0.001f );
|
|
|
|
// Everything else stays zero
|
|
for ( var v = 0; v < Visemes.Count; v++ )
|
|
{
|
|
if ( v == 1 ) continue;
|
|
Assert.AreEqual( 0.0f, weights[v] );
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SampleRampsInBeforeFrame()
|
|
{
|
|
var track = SingleFrameTrack();
|
|
Span<float> weights = stackalloc float[Visemes.Count];
|
|
|
|
// Approaching the frame: window [0.05, 0.13] overlaps [0.1, 0.3] by 37.5%
|
|
Assert.IsTrue( track.Sample( 0.05f, weights ) );
|
|
Assert.AreEqual( 0.375f, weights[1], 0.001f );
|
|
|
|
// Too early - the blend window doesn't reach the frame yet
|
|
Assert.IsFalse( track.Sample( 0.0f, weights ) );
|
|
Assert.AreEqual( 0.0f, weights[1] );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SampleOutsideTrackIsZero()
|
|
{
|
|
var track = SingleFrameTrack();
|
|
Span<float> weights = stackalloc float[Visemes.Count];
|
|
|
|
weights[1] = 999;
|
|
|
|
Assert.IsFalse( track.Sample( 5.0f, weights ) );
|
|
Assert.AreEqual( 0.0f, weights[1] );
|
|
|
|
Assert.IsFalse( track.Sample( -1.0f, weights ) );
|
|
Assert.AreEqual( 0.0f, weights[1] );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SampleCrossfadesAdjacentFrames()
|
|
{
|
|
var track = AdjacentFramesTrack();
|
|
Span<float> weights = stackalloc float[Visemes.Count];
|
|
|
|
// Halfway through PP with AA butting up against it - equal parts of each
|
|
Assert.IsTrue( track.Sample( 0.15f, weights ) );
|
|
Assert.AreEqual( 0.5f, weights[1], 0.001f );
|
|
Assert.AreEqual( 0.5f, weights[10], 0.001f );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SampleThrowsOnSmallBuffer()
|
|
{
|
|
var track = SingleFrameTrack();
|
|
|
|
Assert.ThrowsException<ArgumentException>( () =>
|
|
{
|
|
var weights = new float[3];
|
|
track.Sample( 0.1f, weights );
|
|
} );
|
|
}
|
|
}
|