yum-slop/TaSTT
Free self-hosted STT for VRChat.
git clone https://git.yummers.dev/yum-slop/TaSTT
f6b93a2
master
1#ifUNITY_EDITOR 2using AnimatorAsCode . V1 ; 3using AnimatorAsCode . V1 . ModularAvatar ; 4using YumTools ; 5using nadena . dev . ndmf ; 6using System . Collections . Generic ; 7using UnityEditor ; 8using UnityEditor . Animations ; 9using UnityEngine ; 10using VRC . SDK3 . Avatars . Components ; 11using VRC . SDKBase ; 12 13// This example uses NDMF. See https://github.com/bdunderscore/ndmf?tab=readme-ov-file#getting-started 14[ assembly: ExportsPlugin ( typeof ( GenerateTextAnimatorPlugin ))] 15namespace YumTools 16{ 17public class GenerateTextAnimator : MonoBehaviour , IEditorOnly 18{ 19// The number of blocks addressable. 20public int numBlocks = 10 ; 21// The number of datums sent per block. 22public int blockWidth = 5 ; 23// The number of bytes per datum. 24public int bytesPerDatum = 2 ; 25 26public string oscPointerParam = "_Unigram_Letter_Grid_OSC_Pointer" ; 27 28// Data sent through OSC uses this pattern. 29public string [] oscParamPerBlockDatumByte = { "_Unigram_Letter_Grid_OSC_Datum{0:00}_Byte{1:00}" }; 30// The pattern of the parameters which this script will animate. 31public string [] matPropPerBlockDatumByte = { "_Unigram_Letter_Grid_Data_Block{0:00}_Datum{1:00}_Byte{2:00}_Animated" }; 32 33public string [] oscParamPerBlockDatum = {}; 34public string [] matPropPerBlockDatum = {}; 35 36public string [] oscParamPerBlock = { "_Unigram_Letter_Grid_OSC_Visual_Pointer" }; 37public string [] matPropPerBlock = { "_Unigram_Letter_Grid_Block_{0:00}_Visual_Pointer_Animated" }; 38} 39 40public class GenerateTextAnimatorPlugin : Plugin < GenerateTextAnimatorPlugin > 41{ 42public override string QualifiedName => "yum.generate_chat_animator" ; 43public override string DisplayName => "Chat Animator" ; 44 45private const string SystemName = "GenerateTextAnimator" ; 46// Direct blendtrees have special semantics with write defaults. We want 47// them on. They will not fuck up the rest of the animator, whether it uses 48// write defaults or not. 49private const bool UseWriteDefaults = true ; 50 51protected override void Configure () 52{ 53InPhase ( BuildPhase . Generating ). Run ( $"Generate { DisplayName } " , Generate ); 54} 55 56private AacFlBlendTreeDirect Add8BitBlendTree ( 57AacFlBase aac , 58AacFlLayer layer , 59GenerateTextAnimator cfg , 60AacFlBlendTreeDirect tree , 61string matProp , string oscParam , string oneParam ) { 62var offAnim = aac . NewClip (). Animating ( clip=> 63{ 64clip . Animates ( cfg . GetComponent < Renderer > (), "material." + matProp ). WithFrameCountUnit ( keyframes=> 65keyframes . Constant ( /*when=*/ 0 , /*value=*/ 0 )); 66}); 67var onAnim = aac . NewClip (). Animating ( clip=> 68{ 69clip . Animates ( cfg . GetComponent < Renderer > (), "material." + matProp ). WithFrameCountUnit ( keyframes=> 70keyframes . Constant ( /*when=*/ 0 , /*value=*/ 255 )); 71}); 72var subtree = aac . NewBlendTree (). Simple1D ( layer . FloatParameter ( oscParam )) 73. WithAnimation ( offAnim , /*threshold=*/ - 1 ) 74. WithAnimation ( onAnim , /*threshold=*/ 1 ); 75return tree . WithAnimation ( subtree , layer . FloatParameter ( "AlwaysOne" )); 76} 77 78private void Generate ( BuildContext ctx ) 79{ 80var components = ctx . AvatarRootTransform . GetComponentsInChildren < GenerateTextAnimator > ( true ); 81if ( components . Length == 0 ) return ; 82 83var aac = AacV1 . Create ( new AacConfiguration 84{ 85SystemName = SystemName , 86AnimatorRoot = ctx . AvatarRootTransform , 87DefaultValueRoot = ctx . AvatarRootTransform , 88AssetKey = GUID . Generate (). ToString (), 89AssetContainer = ctx . AssetContainer , 90ContainerMode = AacConfiguration . Container . OnlyWhenPersistenceRequired , 91AssetContainerProvider = new NDMFContainerProvider ( ctx ), 92DefaultsProvider = new AacDefaultsProvider ( UseWriteDefaults ) 93}); 94 95// Create a new object in the scene. We will add Modular Avatar components inside it. 96var modularAvatar = MaAc . Create ( new GameObject ( SystemName ) 97{ 98transform = { parent = ctx . AvatarRootTransform } 99}); 100 101var ctrl = aac . NewAnimatorController (); 102for ( int component_i = 0 ; component_i < components . Length ; component_i ++ ) { 103GenerateTextAnimator cfg = components [ component_i ]; 104var layer = ctrl . NewLayer ( $"Chatbox Plumbing (component # { component_i } )" ); 105 106var baseState = layer . NewState ( "Entry (noop)" ). WithWriteDefaultsSetTo ( false ); 107 108// Create "always one" value to use in DBT. 109// See https://vrc.school/docs/Other/DBT-Combining/ for details. 110layer . OverrideValue ( layer . FloatParameter ( "AlwaysOne" ), 1.0f ); 111 112// Create blendtrees. One for each block of data. 113var block_trees = new List < AacFlState > (); 114AacFlState last_block_state = null ; 115// For each block. 116for ( int i = 0 ; i < cfg . numBlocks ; i ++ ) { 117var block_tree = aac . NewBlendTree (). Direct (); 118 119// Create block-level animations. 120for ( int ii = 0 ; ii < cfg . oscParamPerBlock . Length ; ii ++ ) { 121string matPropB = string . Format ( cfg . matPropPerBlock [ ii ], i ); 122string oscParamB = cfg . oscParamPerBlock [ ii ]; 123block_tree = Add8BitBlendTree ( aac , layer , cfg , block_tree , matPropB , oscParamB , "AlwaysOne" ); 124} 125 126// For each datum per block. 127for ( int j = 0 ; j < cfg . blockWidth ; j ++ ) { 128// Create (block, datum)-level animations. 129for ( int ii = 0 ; ii < cfg . oscParamPerBlockDatum . Length ; ii ++ ) { 130string matPropBD = string . Format ( cfg . matPropPerBlockDatum [ ii ], i , j ); 131string oscParamBD = string . Format ( cfg . oscParamPerBlockDatum [ ii ], j ); 132block_tree = Add8BitBlendTree ( aac , layer , cfg , block_tree , matPropBD , oscParamBD , "AlwaysOne" ); 133} 134 135// For each byte per datum. 136for ( int k = 0 ; k < cfg . bytesPerDatum ; k ++ ) { 137// Create (block, datum, byte)-level animations. 138for ( int ii = 0 ; ii < cfg . oscParamPerBlockDatumByte . Length ; ii ++ ) { 139string matPropBDB = string . Format ( cfg . matPropPerBlockDatumByte [ ii ], i , j , k ); 140//Debug.Log($"animating property: {matPropBDB}"); 141string oscParamBDB = string . Format ( cfg . oscParamPerBlockDatumByte [ ii ], j , k ); 142block_tree = Add8BitBlendTree ( aac , layer , cfg , block_tree , matPropBDB , oscParamBDB , "AlwaysOne" ); 143} 144} 145} 146 147var cur_block_state = layer . NewState ( $"Block { i } " ). WithAnimation ( block_tree ); 148if ( last_block_state != null ) { 149cur_block_state = cur_block_state . Under ( last_block_state ); 150} 151last_block_state = cur_block_state ; 152block_trees . Add ( cur_block_state ); 153} 154 155// Create transitions to each block's blendtree. 156for ( int i = 0 ; i < cfg . numBlocks ; i ++ ) { 157block_trees [ i ]. TransitionsFromAny () 158//.WithInterruption(TransitionInterruptionSource.Source) 159. When ( layer . IntParameter ( cfg . oscPointerParam ). IsEqualTo ( i )); 160} 161 162// Create sync params (VRCSDK) 163for ( int ii = 0 ; ii < cfg . oscParamPerBlock . Length ; ii ++ ) { 164string bParam = cfg . oscParamPerBlock [ ii ]; 165modularAvatar . NewParameter ( layer . FloatParameter ( bParam )); 166} 167for ( int i = 0 ; i < cfg . blockWidth ; i ++ ) { 168for ( int ii = 0 ; ii < cfg . oscParamPerBlockDatum . Length ; ii ++ ) { 169string bdParam = string . Format ( cfg . oscParamPerBlockDatum [ ii ], i ); 170modularAvatar . NewParameter ( layer . FloatParameter ( bdParam )); 171} 172for ( int j = 0 ; j < cfg . bytesPerDatum ; j ++ ) { 173for ( int ii = 0 ; ii < cfg . oscParamPerBlockDatumByte . Length ; ii ++ ) { 174string bdbParam = string . Format ( cfg . oscParamPerBlockDatumByte [ ii ], i , j ); 175modularAvatar . NewParameter ( layer . FloatParameter ( bdbParam )). WithDefaultValue ( 1.0f ); 176} 177} 178} 179 180modularAvatar . NewParameter ( layer . IntParameter ( cfg . oscPointerParam )); 181} 182 183// By creating a Modular Avatar Merge Animator component, 184// our animator controller will be added to the avatar's FX layer. 185modularAvatar . NewMergeAnimator ( ctrl . AnimatorController , VRCAvatarDescriptor . AnimLayerType . FX ); 186} 187} 188 189// (For AAC 1.2.0 and above) This is recommended starting from NDMF 1.6.0. You only need to define this class once. 190internal class NDMFContainerProvider : IAacAssetContainerProvider 191{ 192private readonly BuildContext _ctx ; 193public NDMFContainerProvider ( BuildContext ctx ) => _ctx = ctx ; 194public void SaveAsPersistenceRequired ( Object objectToAdd ) => _ctx . AssetSaver . SaveAsset ( objectToAdd ); 195public void SaveAsRegular ( Object objectToAdd ) { } // Let NDMF crawl our assets when it finishes 196public void ClearPreviousAssets () { } // ClearPreviousAssets is never used in non-destructive contexts 197} 198} 199#endif 200