This repository has no description
1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: 2023 MizunagiKB <mizukb@live.jp>
3using System;
4using Godot;
5
6#pragma warning disable CA1050
7#pragma warning disable IDE1006
8
9
10public partial class demo_effect_custom_03 : Node2D
11{
12 private const String DEFAULT_ASSET = "res://addons/gd_cubism/example/res/live2d/mao_pro_jp/runtime/mao_pro.model3.json";
13
14 private GDCubismUserModelCS cubism_model;
15 private GDCubismEffectCustomCS cubism_efx;
16
17 private String param_mouth_name = "ParamMouthA";
18 [Export]
19 private float min_db = 60.0f;
20 [Export]
21 private int min_voice_freq = 0;
22 [Export]
23 private int max_voice_freq = 3200;
24 [Export]
25 private int history_size = 6;
26
27 [ExportCategory("Audio")]
28 [Export]
29 private String audio_bus_name = "Voice";
30 [Export]
31 private int audio_efx_index = 0;
32
33 private AudioEffectSpectrumAnalyzerInstance spectrum;
34 private GDCubismParameterCS param_mouth;
35 private Godot.Collections.Array<float> ary_volume_history;
36 private int history_position = 0;
37 private bool lipsync_ready = false;
38
39 private void array_rebuild()
40 {
41 if (this.history_size != this.ary_volume_history.Count)
42 {
43 this.ary_volume_history.Resize(this.history_size);
44 this.ary_volume_history.Fill(0.0f);
45 }
46 }
47
48 private void recalc_model_position(GDCubismUserModelCS model)
49 {
50 if(model.Assets == "") return;
51
52 Godot.Collections.Dictionary canvas_info = model.GetCanvasInfo();
53
54 if(canvas_info.Count > 0)
55 {
56 Vector2 vct_viewport_size = new(GetViewportRect().Size.X, GetViewportRect().Size.Y);
57 Vector2 size_in_pixels = (Vector2)canvas_info["size_in_pixels"];
58 float scale = vct_viewport_size.Y / Math.Max(size_in_pixels.X, size_in_pixels.Y);
59 model.GetInternalObject().Position = new(vct_viewport_size.X * 0.5f, vct_viewport_size.Y * 0.5f);
60 model.GetInternalObject().Scale = new(scale, scale);
61 }
62 }
63
64 public override void _Ready()
65 {
66 this.cubism_efx = new(GetNode<Node>("GDCubismUserModel/GDCubismEffectCustom"));
67 this.cubism_efx.CubismPrologue += this._on_cubism_prologue;
68 this.cubism_efx.CubismEpilogue += this._on_cubism_epilogue;
69 this.cubism_efx.CubismInit += this._on_cubism_init;
70 this.cubism_efx.CubismTerm += this._on_cubism_term;
71 this.cubism_efx.CubismProcess += this._on_cubism_process;
72
73 this.cubism_model = new(GetNode<Node2D>("GDCubismUserModel"));
74 if (this.cubism_model.Assets == "") this.cubism_model.Assets = DEFAULT_ASSET;
75 }
76
77 public override void _Process(double delta)
78 {
79 this.recalc_model_position(this.cubism_model);
80 }
81
82 private float array_avg()
83 {
84 float sum_v = 0.0f;
85 foreach (float v in this.ary_volume_history)
86 {
87 sum_v += v;
88 }
89 return sum_v / this.ary_volume_history.Count;
90 }
91
92 private void _on_cubism_prologue(Node2D _model, float v) {}
93 private void _on_cubism_epilogue(Node2D _model, float v) {}
94 private void _on_cubism_init(Node2D _model) {}
95
96 private void _on_cubism_term(Node2D _model)
97 {
98 this.lipsync_ready = false;
99 this.param_mouth = null;
100 this.ary_volume_history.Clear();
101 this.history_position = 0;
102 }
103
104 private void _on_cubism_process(Node2D _model, double delta)
105 {
106 if (this.lipsync_ready == false) return;
107
108 this.array_rebuild();
109
110 float m = this.spectrum.GetMagnitudeForFrequencyRange(
111 this.min_voice_freq,
112 this.max_voice_freq
113 ).Length();
114
115 var v = Math.Clamp((min_db + Mathf.LinearToDb(m)) / this.min_db, 0.0f, 1.0f);
116
117 this.ary_volume_history[this.history_position] = v;
118 this.history_position += 1;
119
120 if (this.history_position >= this.ary_volume_history.Count)
121 {
122 this.history_position = 0;
123 }
124
125 float avg_v = this.array_avg();
126
127 if (this.param_mouth != null)
128 {
129 this.param_mouth.Value = Math.Max(this.param_mouth.Value - 0.2f, 0.0f);
130 if (avg_v > 0.1f)
131 {
132 this.param_mouth.Value = 1.0f;
133 }
134 }
135 }
136}