Example #1
0
        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);
            }
        }