-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
{ | ||
"name": "com.mochineko.facial-expressions.extensions.voicevox", | ||
"version": "0.3.2", | ||
"version": "0.3.3", | ||
"displayName": "Facial Expressions / VOICEVOX", | ||
"description": "Facial expressions extension for VOICEVOX.", | ||
"unity": "2021.3", | ||
"author": { | ||
"name": "Mochineko", | ||
"email": "t.o.e.4315@gmail.com" | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 mochineko | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"name": "Mochineko.FacialExpressions.Extensions.uLipSync", | ||
"rootNamespace": "", | ||
"references": [ | ||
"GUID:0e0dbf6ed860b4d57b738a3d1faef12d", | ||
"GUID:d444a9c79d21fec4181e6e12dc2e9706", | ||
"GUID:560b04d1a97f54a4e82edc0cbbb69285" | ||
], | ||
"includePlatforms": [], | ||
"excludePlatforms": [], | ||
"allowUnsafeCode": false, | ||
"overrideReferences": true, | ||
"precompiledReferences": [], | ||
"autoReferenced": false, | ||
"defineConstraints": [], | ||
"versionDefines": [], | ||
"noEngineReferences": false | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
#nullable enable | ||
using System; | ||
using System.Collections.Generic; | ||
using Mochineko.FacialExpressions.LipSync; | ||
using UniRx; | ||
using UnityEngine; | ||
|
||
namespace Mochineko.FacialExpressions.Extensions.uLipSync | ||
{ | ||
public sealed class ULipSyncAnimator : IDisposable | ||
{ | ||
private readonly FollowingLipAnimator followingLipAnimator; | ||
private readonly global::uLipSync.uLipSync uLipSync; | ||
private readonly IDisposable disposable; | ||
|
||
private static readonly IReadOnlyDictionary<string, Viseme> phonomeMap | ||
= new Dictionary<string, Viseme> | ||
{ | ||
["A"] = Viseme.aa, | ||
["I"] = Viseme.ih, | ||
["U"] = Viseme.ou, | ||
["E"] = Viseme.E, | ||
["O"] = Viseme.oh, | ||
}; | ||
|
||
public ULipSyncAnimator( | ||
FollowingLipAnimator followingLipAnimator, | ||
global::uLipSync.uLipSync uLipSync, | ||
float volumeThreshold = 0.01f, | ||
bool verbose = false) | ||
{ | ||
this.followingLipAnimator = followingLipAnimator; | ||
this.uLipSync = uLipSync; | ||
|
||
this.disposable = this.uLipSync | ||
.ObserveEveryValueChanged(lipSync => lipSync.result) | ||
.Subscribe(info => | ||
{ | ||
if (verbose) | ||
{ | ||
Debug.Log($"[FacialExpressions.uLipSync] Update lip sync: {info.phoneme}, {info.volume}"); | ||
} | ||
if (phonomeMap.TryGetValue(info.phoneme, out var viseme) && info.volume > volumeThreshold) | ||
{ | ||
SetTarget(new LipSample(viseme, weight: 1f)); | ||
} | ||
else | ||
{ | ||
SetDefault(); | ||
} | ||
}); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
Reset(); | ||
disposable.Dispose(); | ||
} | ||
|
||
public void Update() | ||
{ | ||
followingLipAnimator.Update(); | ||
} | ||
|
||
public void Reset() | ||
{ | ||
followingLipAnimator.Reset(); | ||
} | ||
|
||
private void SetTarget(LipSample sample) | ||
{ | ||
foreach (var viseme in phonomeMap.Values) | ||
{ | ||
if (viseme == sample.viseme) | ||
{ | ||
followingLipAnimator.SetTarget(sample); | ||
} | ||
else | ||
{ | ||
followingLipAnimator.SetTarget(new LipSample(viseme, weight: 0f)); | ||
} | ||
} | ||
} | ||
|
||
private void SetDefault() | ||
{ | ||
foreach (var viseme in phonomeMap.Values) | ||
{ | ||
followingLipAnimator.SetTarget(new LipSample(viseme, weight: 0f)); | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "com.mochineko.facial-expressions.extensions.ulipsync", | ||
"version": "0.3.3", | ||
"displayName": "Facial Expressions / uLipSync", | ||
"description": "Facial expressions extension for uLipSync.", | ||
"unity": "2021.3", | ||
"author": { | ||
"name": "Mochineko", | ||
"email": "t.o.e.4315@gmail.com" | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#nullable enable | ||
using System; | ||
using System.IO; | ||
using System.Threading; | ||
using Cysharp.Threading.Tasks; | ||
using Mochineko.FacialExpressions.Blink; | ||
using Mochineko.FacialExpressions.Emotion; | ||
using Mochineko.FacialExpressions.Extensions.uLipSync; | ||
using Mochineko.FacialExpressions.Extensions.VRM; | ||
using Mochineko.FacialExpressions.LipSync; | ||
using Mochineko.VOICEVOX_API; | ||
using UnityEngine; | ||
using UniVRM10; | ||
using VRMShaders; | ||
|
||
namespace Mochineko.FacialExpressions.Samples | ||
{ | ||
internal sealed class SampleForULipSyncAndVRM : MonoBehaviour | ||
{ | ||
[SerializeField] private string path = string.Empty; | ||
[SerializeField] private BasicEmotion basicEmotion = BasicEmotion.Neutral; | ||
[SerializeField] private float emotionWeight = 1f; | ||
[SerializeField] private float emotionFollowingTime = 1f; | ||
[SerializeField] private uLipSync.uLipSync? uLipSync = null; | ||
|
||
private ULipSyncAnimator? lipAnimator; | ||
private IEyelidAnimator? eyelidAnimator; | ||
private ExclusiveFollowingEmotionAnimator<BasicEmotion>? emotionAnimator; | ||
|
||
private async void Start() | ||
{ | ||
if (uLipSync == null) | ||
{ | ||
throw new NullReferenceException(nameof(uLipSync)); | ||
} | ||
|
||
VoiceVoxBaseURL.BaseURL = "http://127.0.0.1:50021"; | ||
|
||
var binary = await File.ReadAllBytesAsync( | ||
path, | ||
this.GetCancellationTokenOnDestroy()); | ||
|
||
var instance = await LoadVRMAsync( | ||
binary, | ||
this.GetCancellationTokenOnDestroy()); | ||
|
||
var lipMorpher = new VRMLipMorpher(instance.Runtime.Expression); | ||
var followingLipAnimator = new FollowingLipAnimator(lipMorpher, followingTime: 0.15f); | ||
lipAnimator = new ULipSyncAnimator(followingLipAnimator, uLipSync); | ||
|
||
var eyelidMorpher = new VRMEyelidMorpher(instance.Runtime.Expression); | ||
eyelidAnimator = new SequentialEyelidAnimator(eyelidMorpher); | ||
|
||
var eyelidFrames = ProbabilisticEyelidAnimationGenerator.Generate( | ||
Eyelid.Both, | ||
blinkCount: 20); | ||
|
||
eyelidAnimator.AnimateAsync( | ||
eyelidFrames, | ||
loop: true, | ||
this.GetCancellationTokenOnDestroy()) | ||
.Forget(); | ||
|
||
var emotionMorpher = new VRMEmotionMorpher(instance.Runtime.Expression); | ||
emotionAnimator = new ExclusiveFollowingEmotionAnimator<BasicEmotion>( | ||
emotionMorpher, | ||
followingTime: emotionFollowingTime); | ||
} | ||
|
||
private void Update() | ||
{ | ||
lipAnimator?.Update(); | ||
eyelidAnimator?.Update(); | ||
emotionAnimator?.Update(); | ||
} | ||
|
||
private void OnDestroy() | ||
{ | ||
lipAnimator?.Dispose(); | ||
} | ||
|
||
private static async UniTask<Vrm10Instance> LoadVRMAsync( | ||
byte[] binaryData, | ||
CancellationToken cancellationToken) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
return await Vrm10.LoadBytesAsync( | ||
bytes: binaryData, | ||
canLoadVrm0X: true, | ||
controlRigGenerationOption: ControlRigGenerationOption.None, | ||
showMeshes: true, | ||
awaitCaller: new RuntimeOnlyAwaitCaller(), | ||
materialGenerator: null, | ||
vrmMetaInformationCallback: null, | ||
ct: cancellationToken | ||
); | ||
} | ||
|
||
[ContextMenu(nameof(Emote))] | ||
public void Emote() | ||
{ | ||
emotionAnimator? | ||
.Emote(new EmotionSample<BasicEmotion>( | ||
basicEmotion, | ||
weight: emotionWeight)); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.