ã¯ããã«
ä»åã¯å°ãã¿ã§ããuLipSync ã®ãã£ãªãã¬ã¼ã·ã§ã³ç¨ã«ãªã¼ãã£ãªãã¼ã¿ã®é¸æããç¯å²ã®ã«ã¼ãåçæ©è½ãä½ã£ãã®ã§ããããã®å 容ãã¾ã¨ãã¦ããã¾ããå è¨äºã¯ãã¡ãï¼
æ©è½ã«ã¤ãã¦
ãããªæãã§ãã©ãã°ãã¦åçãããç®æãé¸æãã¯ãã¹ãã§ã¼ãå ·åã調æ´ãã¦ãPlay ãæ¼ãã¨é¸æç¯å²ãã«ã¼ãåçãããã¨ãããã®ã§ããuLipSync ã®ãã£ãªãã¬ã¼ã·ã§ã³ã§ã¯ç¾å¨åçããã¦ããé³ã解æããããããã®ãããªä»çµã¿ãå¿ è¦ã§ããã
解説
é¸æãããé³ãããªãã³ã°ãã AudioClip
ãçæããã©ã³ã¿ã¤ã ã³ã¼ãé¨ã¨ãã©ããããªãã³ã°ãããé¸æãã UI ã®é¨åã®ã¨ãã£ã¿ã³ã¼ãé¨ãããã¾ããã³ã¼ãå
¨æã¯ãªãã¸ããªãããåç
§ãã ããã
ã©ã³ã¿ã¤ã ã³ã¼ã
ã¾ãã¯ã©ã³ã¿ã¤ã ã³ã¼ãé¨ããã§ãããããã»ã©é·ãã¯ç¡ãã®ã§å ¨æãè²¼ã£ã¦ã¿ã¾ãã
using UnityEngine; [RequireComponent(typeof(AudioSource))] public class uLipSyncCalibrationAudioPlayer : MonoBehaviour { public AudioClip clip; public float start = 0f; public float end = 1f; public float crossFadeDuration = 0.05f; AudioClip _tmpClip; float[] _data; int _currentPos = 0; bool _audioReadCalled = false; int _sampleRate = 0; int _sampleCount = 0; int _channels = 1; int _crossFadeDataCount = 0; int playDataSampleCount => _sampleCount - _crossFadeDataCount; public bool isPlaying { get { var source = GetComponent<AudioSource>(); return source ? source.isPlaying : false; } } void OnEnable() { Apply(); } void OnDisable() { Destroy(_tmpClip); } void Update() { if (!_audioReadCalled) return; _sampleRate = AudioSettings.outputSampleRate; } public void Apply() { if (!clip) return; var source = GetComponent<AudioSource>(); if (!source) return; var startPos = (int)(clip.samples * start); var endPos = (int)(clip.samples * end); var freq = clip.frequency; _sampleCount = endPos - startPos; _channels = clip.channels; _crossFadeDataCount = (int)(_sampleRate * crossFadeDuration); _crossFadeDataCount = Mathf.Min(_crossFadeDataCount, _sampleCount / 2 - 1); _data = new float[_sampleCount * _channels]; clip.GetData(_data, startPos); var name = $"{clip.name}-{startPos}-{endPos}"; _tmpClip = AudioClip.Create( name, playDataSampleCount, _channels, freq, true, OnAudioRead, OnAudioSetPosition); source.clip = _tmpClip; source.loop = true; source.Play(); } void OnAudioRead(float[] data) { _audioReadCalled = true; for (int i = 0; i < data.Length / _channels; ++i) { for (int ch = 0; ch < _channels; ++ch) { int index = i * _channels + ch; if (_currentPos < _crossFadeDataCount) { float t = (float)_currentPos / _crossFadeDataCount; float sin = Mathf.Sin(Mathf.PI * 0.5f * t); float cos = Mathf.Cos(Mathf.PI * 0.5f * t); int indexS = _currentPos; int indexE = _sampleCount - (_crossFadeDataCount - _currentPos); float dataS = _data[indexS * _channels + ch]; float dataE = _data[indexE * _channels + ch]; data[index] = dataS * sin + dataE * cos; } else { data[index] = _data[_currentPos * _channels + ch]; } } _currentPos = (_currentPos + 1) % playDataSampleCount; } } void OnAudioSetPosition(int newPosition) { _currentPos = newPosition; } public void Pause() { var source = GetComponent<AudioSource>(); if (!source) return; source.Pause(); } public void UnPause() { var source = GetComponent<AudioSource>(); if (!source) return; source.UnPause(); } }
次ã®ãããªæµãã«ãªã£ã¦ãã¾ãã
start
/end
ã¯ã¨ãã£ã¿å´ããã»ããï¼0.0 ~ 1.0 ã®å¤ï¼- ã¯ãã¹ãã§ã¼ãåãèæ ®ãããã¼ã¿é·ããµã³ãã«ã«ã¦ã³ãããè¨ç®
float[]
ã®ãããã¡ãç¨æããGetData()
ã§å¯¾è±¡ã®ç¯å²ã®ãªã¼ãã£ãªãã¼ã¿ãåå¾- åçã¯
AudioClip.Create()
ã§OnAudioRead()
ãOnAudioSetPosition()
ãæå®ããã¹ããªã¼ãã³ã°å½¢å¼ã§åç- ããã¯äºåã«ãã¼ã¿ãå«ããªã¼ãã£ãªã¯ãªãããä½æãã¦ãã¾ã£ã¦ãè¯ãããããã¾ããã
ã¨ãã£ã¿ã³ã¼ã
波形æç»ã«ã¤ãã¦ã¯ä»¥åè¨äºã§ã触ãã¾ãããã次ã®ããã« AudioUtil.GetMinMaxData()
ããã³ AudioCurveRendering.DrawMinMaxFilledCurve()
ã使ãã¾ãã
public static class EditorUtil { ... public class DrawWaveOption { public System.Func<float, Color> colorFunc; public float waveScale; } public static void DrawWave(Rect rect, AudioClip clip, DrawWaveOption option) { ... var minMaxData = AudioUtil.GetMinMaxData(clip); ... AudioCurveRendering.AudioMinMaxCurveAndColorEvaluator dlg = delegate( float x, out Color col, out float minValue, out float maxValue) { col = option.colorFunc(x); ... minValue = ...; maxValue = ...; ... }; // æç»å¦ç AudioCurveRendering.DrawMinMaxFilledCurve(rect, dlg); } }
ãã®ä¸ã§æ¬¡ã®ãããªã¨ãã£ã¿æ¡å¼µãæ¸ãã¾ãï¼ãã¡ããå°ãé·ãã§ããã»ã¼å ¨æãè¼ãã¾ãï¼ã
using UnityEngine; using UnityEditor; using System.Collections.Generic; [CustomEditor(typeof(uLipSyncCalibrationAudioPlayer))] public class uLipSyncCalibrationAudioPlayerEditor : Editor { uLipSyncCalibrationAudioPlayer player { get { return target as uLipSyncCalibrationAudioPlayer; } } bool _requireRepaint = false; bool _requireApply = false; bool _isDraggingStart = false; bool _isDraggingEnd = false; bool isDragging => _isDraggingStart || _isDraggingEnd; ... public override void OnInspectorGUI() { _requireRepaint = false; serializedObject.Update(); DrawClip(); DrawPlayAndStop(); DrawWave(); EditorGUILayout.Separator(); DrawParameters(); if (Application.isPlaying && _requireApply) { player.Apply(); _requireApply = false; } serializedObject.ApplyModifiedProperties(); if (_requireRepaint) { Repaint(); } else { EditorUtility.SetDirty(target); } if (isDragging) { player.Pause(); } else { player.UnPause(); } ... } ... void DrawClip() { var nextClip = (AudioClip)EditorGUILayout.ObjectField("Clip", player.clip, typeof(AudioClip), true); if (nextClip == player.clip) return; player.clip = nextClip; _requireApply = true; } void DrawPlayAndStop() { EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button(" Play ")) { AudioUtil.PlayClip(player.clip); } if (GUILayout.Button(" Stop ")) { AudioUtil.StopClip(player.clip); } EditorGUILayout.EndHorizontal(); EditorGUILayout.Separator(); } void DrawWave() { var rect = EditorGUILayout.GetControlRect(GUILayout.Height(100)); EditorUtil.DrawBackgroundRect(rect); if (!player.clip) return; EditorUtil.DrawWave(rect, player.clip, new EditorUtil.DrawWaveOption()); var preWaveStart = player.start; var preWaveEnd = player.end; DrawTrimArea(rect, true, ref player.start, ref _isDraggingStart, ref _isDraggingEnd); DrawTrimArea(rect, false, ref player.end, ref _isDraggingEnd, ref _isDraggingStart); DrawCrossFadeArea(rect); player.start = Mathf.Clamp(player.start, 0f, preWaveEnd - 0.001f); player.end = Mathf.Clamp(player.end, preWaveStart + 0.001f, 1f); } void DrawTrimArea(Rect rect, bool isStart, ref float range, ref bool isDraggingSelf, ref bool isDraggingOther) { var trimArea = rect; if (isStart) { trimArea.width *= range; } else { trimArea.width *= 1f - range; trimArea.x += rect.width - trimArea.width; } EditorGUI.DrawRect(trimArea, new Color(0f, 0f, 0f, 0.7f)); var deltaPixels = ProcessDrag(trimArea, ref isDraggingSelf, ref isDraggingOther); range += deltaPixels / rect.width; var borderRect = trimArea; if (isStart) { borderRect.x += trimArea.width; } borderRect.width = 1; var borderColor = isDraggingSelf ? new Color(1f, 0f, 0f, 1f) : new Color(1f, 1f, 1f, 0.5f); EditorGUI.DrawRect(borderRect, borderColor); } float ProcessDrag(Rect dragRect, ref bool isDraggingSelf, ref bool isDraggingOther) { float delta = 0f; var mouseRect = dragRect; mouseRect.x -= 10; mouseRect.width += 20; EditorGUIUtility.AddCursorRect(mouseRect, MouseCursor.SplitResizeLeftRight); if (!isDraggingOther && Event.current.type == EventType.MouseDrag) { if (mouseRect.Contains(Event.current.mousePosition)) { isDraggingSelf = true; } if (isDraggingSelf) { delta = Event.current.delta.x; _requireApply = true; } _requireRepaint = true; } else if (Event.current.type == EventType.MouseUp) { isDraggingSelf = false; } return delta; } void DrawCrossFadeArea(Rect rect) { var range = player.crossFadeDuration / player.clip.length; range = Mathf.Min(range, player.end - player.start); rect.x += (player.end - range) * rect.width; rect.width *= range; EditorGUI.DrawRect(rect, new Color(0f, 1f, 1f, 0.2f)); } void DrawParameters() { float preDuration = player.crossFadeDuration; player.crossFadeDuration = EditorGUILayout.Slider("Cross Fade", preDuration, 0f, 0.1f); if (player.crossFadeDuration != preDuration) { _requireApply = true; } } }
ã³ã¼ãã®å¤§é¨å㯠UI ã®æ§ç¯ã§ããããã¢ã¯ ProcessDrag()
ã§ããEditorGUIUtility.AddCursorRect()
ã§ã¯æå®ããã¨ãªã¢ã®ã«ã¼ã½ã«å½¢ç¶ãå¤æ´ã§ãã¾ãã
ãã®ä¸ã§ãEvent.current.type
ã EventType.MouseDrag
ãã©ããã®ãã§ãã¯ã¨ãEvent.current.mousePosition
ã対象ã®é åã«å«ã¾ãã¦ãããã©ãããæ¤è¨¼ãã¦ãã©ãã°ä¸ãã©ããã®å¤å®ãè¡ã£ã¦ãã¾ããçµæ§ãæç´ãªå®è£
ã«ãªã£ã¦ãã¦ãããå°ãæ½è±¡åããã API ãããã¨è¯ãã®ã§ãã...ãUI Toolkit ã®ä¾ãªã©ãã¿ã¦ãçµæ§ã´ãªãã¨æ¸ãã¦ãã®ã§ç¡ãã®ããªãã¨èãã¦ãã¾ãã
ãããã¦ãã©ãã°åºæ¥ãããã«ããä¸ã§ start
/ end
ãã©ã³ã¿ã¤ã ã³ã¼ãå´ã¸æ¸¡ãã¦å®è¡ããã°ããªãã³ã°ããç¯å²ã®ãªã¼ãã£ãªãã¼ã¿ã®åçãå¯è½ã¨ãªãã¾ããã
ãããã«
æè¿å°ãå¿ãããã¿ãæ¯æ¸ãã¦ããã®ã§ãå°ããã®è¨äºã«ãªãã¾ããã...ãæ¥æããã¯ã¾ãæ°ããå 容ãè²ã æ¸ãã¦ããããã§ãã