public IEnumerator ComputeAnimation(AnimationClip clip, Action <BitAnimator> finishCallback = null, Action <BitAnimator> updateCallback = null) { float time = Time.realtimeSinceStartup; if (core == null || core.audioClip != audioClip) { status = "Loading audio"; if (updateCallback != null) { updateCallback(this); } yield return(null); FFTWindow = 1 << FFTWindowLog; if (core == null) { core = new ComputeProgram(computeShader); } core.Initialize(FFTWindowLog, filter, multisamples, windowParam); core.LoadAudio(audioClip); core.resolveFactor = deconvolution; SetMode(ref core.mode, calculateLoudness, energyCorrection); chunks = core.chunks; Debug.LogFormat("[BitAnimator] Setup time = {0:F3}", Time.realtimeSinceStartup - time); time = Time.realtimeSinceStartup; } if (animatorObject == null) { animatorObject = GetComponent <Animator>(); } if (targetObject == null) { targetObject = gameObject; } //Создаем группу задач для вычисления значений ключевых кадров Task[] tasks = new Task[recordSlots.Count]; for (int p = 0; p < recordSlots.Count; p++) { tasks[p].SetRemapCurve(recordSlots[p].minValue, recordSlots[p].maxValue, recordSlots[p].remap, recordSlots[p].channelMask); //Устанавливаем соотношение амплитуды спектра и результирующих значений ключевых кадров if (recordSlots[p].type == RecordSlot.PropertyType.Color) { tasks[p].SetRemapGradient(recordSlots[p].colors, recordSlots[p].channelMask); } tasks[p].values = new ComputeBuffer(chunks, 4); tasks[p].buffer = new ComputeBuffer(chunks, 4); //Резервируем буффер для ключей XYZW|RGBA tasks[p].keyframes = new ComputeBuffer(chunks * tasks[p].Channels, 20); } status = "Calculating spectrum"; if (updateCallback != null) { updateCallback(this); } yield return(null); //разделяем вычисление спектра на блоки int maxUseVRAM = SystemInfo.graphicsMemorySize / 10; // use 10% of total video memory int calculatedMemory = chunks * FFTWindow / 1024 * 21 / 1024; int blocks = (calculatedMemory - 1) / maxUseVRAM + 1; int batches = (chunks - 1) / blocks + 1; float[] temp = new float[1]; for (int block = 0; block < blocks; block++) { int offset = batches * block; //Вычисляем спектрограмму core.FFT_Execute(0, offset, Math.Min(batches, chunks - 1 - offset)); //Преобразуем спектр из комплексных чисел в частотно-амплитудный спектр core.ConvertToSpectrum(); core.ResolveMultisamples(); for (int p = 0; p < recordSlots.Count; p++) { //Интегрируем полосу частот в 1D массив core.MergeBand(recordSlots[p].startFreq, recordSlots[p].endFreq, tasks[p].values, offset); } taskProgress += 0.5f / blocks; if (updateCallback != null) { updateCallback(this); } yield return(null); } Debug.LogFormat("[BitAnimator] Calculating spectrum time = {0:F3}", Time.realtimeSinceStartup - time); time = Time.realtimeSinceStartup; ComputeBuffer mask = new ComputeBuffer(chunks, 4); for (int p = 0; p < recordSlots.Count; p++) { core.Normalize(tasks[p].values); core.ApplyRemap(tasks[p].values, tasks[p].buffer, tasks[p].values.count, tasks[p].remapKeyframes); ComputeProgram.Swap(ref tasks[p].values, ref tasks[p].buffer); //сглаживаем анимацию кривой if (recordSlots[p].ampSmoothness > 0.0f) { core.SmoothSpectrum(tasks[p].values, tasks[p].buffer, recordSlots[p].ampSmoothness); ComputeProgram.Swap(ref tasks[p].values, ref tasks[p].buffer); } if (recordSlots[p].damping >= 0.0001f) { core.ApplyFading(tasks[p].values, tasks[p].buffer, tasks[p].values.count, 1.0f / recordSlots[p].damping - 1.0f); ComputeProgram.Swap(ref tasks[p].values, ref tasks[p].buffer); } //создание неубывающей кривой (каждый пик потихоньку сдвигает позицию к конечному значению) if (recordSlots[p].accumulate) { core.CalculatePrefixSum(tasks[p].values, tasks[p].buffer, tasks[p].values.count); ComputeProgram.Swap(ref tasks[p].values, ref tasks[p].buffer); } int prop = p; if (recordSlots[prop].modificators.Count > 0) { core.Multiply(mask, 0, mask.count, 0.0f); foreach (Modificator mod in recordSlots[prop].modificators) { mod.Apply(core, mask, tasks[prop].buffer); if (mod.useTempBuffer) { ComputeProgram.Swap(ref mask, ref tasks[prop].buffer); } } core.Multiply(tasks[prop].values, mask); } core.Normalize(tasks[p].values); core.Multiply(tasks[p].values, 0, tasks[p].values.count, multiply); if (recordSlots[p].type == RecordSlot.PropertyType.Color) { core.RemapGradient(tasks[p].values, tasks[p].keyframes, tasks[p].gradientKeyframes, tasks[p].Channels, tasks[p].gradientKeys, recordSlots[p].loops); } else { core.CreateKeyframes(tasks[p].values, tasks[p].keyframes, tasks[p].min, tasks[p].max, tasks[p].Channels, recordSlots[p].loops); } //перевод углов Эйлера в кватернионы if (recordSlots[p].type == RecordSlot.PropertyType.Quaternion) { core.ConvertKeyframesRotation(tasks[p].keyframes, tasks[p].values.count); } } mask.Dispose(); tasks[0].values.GetData(temp); status = quality < 1.0f ? "Optimization keyframes" : "Writing animation"; if (updateCallback != null) { updateCallback(this); } yield return(null); string go_path = AnimationUtility.CalculateTransformPath(targetObject.transform, animatorObject.transform); //Получаем результат с GPU и записываем в анимацию for (int p = 0; p < recordSlots.Count; p++) { Type renderType = GetCurveAnimationType(recordSlots[p].typeSet); string[] animationProperties = recordSlots[p].property; for (int c = 0; c < animationProperties.Length; c++) { yield return(null); if (updateCallback != null) { updateCallback(this); } AnimationCurve curve = AnimationCurve.EaseInOut(0, 0, audioClip.length, 0); Keyframe[] keyframes = tasks[p].GetKeyframes(c); if (quality < 1.0f) { float step = 1.0f / 32.0f; float partsTaskProgress = 0; float lastFrame = Time.realtimeSinceStartup; float rmsQuality = Mathf.Pow(10.0f, -6.0f * quality * quality - 1.0f); //quality to RMS [0..1] => [1e-1 .. 1e-7] foreach (Keyframe[] k in core.DecimateAnimationCurve(keyframes, rmsQuality * (tasks[p].max[c] - tasks[p].min[c]))) { partsTaskProgress += step; taskProgress += 0.5f * step / recordSlots.Count / animationProperties.Length; //Делаем паузу если уходит много времени на обработку if (k == null && Time.realtimeSinceStartup - lastFrame >= Time.fixedUnscaledDeltaTime) { lastFrame = Time.realtimeSinceStartup; if (updateCallback != null) { updateCallback(this); } yield return(null); } else { keyframes = k; } } taskProgress += 0.5f * Mathf.Max(0, 1.0f - partsTaskProgress) / recordSlots.Count / animationProperties.Length; } else { taskProgress += 0.5f / recordSlots.Count / animationProperties.Length; } curve.keys = keyframes; clip.SetCurve(go_path, renderType, animationProperties[c], curve); } tasks[p].Release(); } Debug.LogFormat("[BitAnimator] Writing animation time = {0:F3}", Time.realtimeSinceStartup - time); status = "Done"; taskProgress = 0; yield return(null); if (finishCallback != null) { finishCallback(this); } }