유니티 Audio Spectrum에서 얻은 데이터에 Buffer를 사용하여 급격하게 변화하는 값 제어하기 #기초

이전 글에서 이어지는 내용이므로 이전 글을 보고 와주세요.

2024.03.26 - [Unity/Study] - 유니티 Audio Spectrum에서 얻은 데이터들을 대역 별로 나누기 #기초

 

Buffer를 이용한 데이터 처리

 

Buffer를 사용하면 위 파란색 큐브처럼 값이 급격하게 변화하는 것을 막을 수 있습니다.

값이 갑자기 튀는 현상을 '약간 해결'해줄 수 있다고 생각하시면 됩니다.


Buffer란?

'Buffer’는 일반적으로 데이터를 일시적으로 저장하는 임시 저장소를 의미합니다. 데이터를 처리하는 두 개의 프로세스 사이에서 임시 데이터로 사용된다고 생각하시면 됩니다.

 

아래 코드에서 사용되는 'Buffer’는 FrequencyBands[i]의 값들을 일시적으로 저장하고, 이전의 값들을 기반으로 새로운 값을 계산하는 데 사용됩니다. 버퍼를 사용하여 데이터의 변화를 부드럽게 만들거나, 빠른 변화에 대응하는 등의 처리를 할 수 있습니다.

 


AudioPeer 수정

using UnityEngine;

public class AudioPeer : MonoBehaviour
{
    public static AudioPeer Instance;
    
    public enum AudioChannel
    {
        Left = 0,      
        Right = 1,       
        Center = 2,     
        LFE = 3,        
        RearLeft = 4,   
        RearRight = 5    
    }
    
    [Header("[Ref]")]
    [SerializeField] private AudioSource audioSource;
    
    [Space(3)]
    [Header("[Value]")]
    [SerializeField, Tooltip("첫 번째 (왼쪽) 스테레오 채널 또는 모노 채널 \n두 번째 (오른쪽) 스테레오 채널 \n센터 채널 \nLFE (저주파 효과) 채널 \n후방 왼쪽 채널 \n후방 오른쪽 채널")] private AudioChannel channel = AudioChannel.Left;
    [Range(3, 12)] public int Pow = 8;
    public int FreqBandPower = 1;
    
    [Space(3)]
    [Header("[OutPut]")]
    public float[] Samples;
    public float[] FrequencyBands;
    public float[] FrequencyBuffer;
    
    private void Awake()
    {
        Instance = this;
        Samples = new float[(int)Mathf.Pow(2,Pow)];
        FrequencyBands = new float[Pow - 1];
        FrequencyBuffer = new float[Pow - 1];
    }
    
    private void Update()
    {
        GetSpectrumAudioSource();
        MakeFrequencyBand();
        SmoothFrequencyBands();
    }

    private void GetSpectrumAudioSource()
    {
        audioSource.GetSpectrumData(samples: Samples, channel: (int)channel, FFTWindow.Blackman);
    }
    
    private void MakeFrequencyBand()
    {
        int count = 0;
        for (int i = 0; i < Pow - 1; i++)
        {
            float average = 0;
            int sampleCount = (int)Mathf.Pow(2, i) * 2;
            if (i == Pow - 2)
            {
                sampleCount += 2;
            }

            for (int j = 0; j < sampleCount; j++)
            {
                average += Samples[count];
                count++;
            }

            average /= sampleCount;
            FrequencyBands[i] = average * FreqBandPower;
        }
    }

    private void SmoothFrequencyBands()
    {
        for (int i = 0; i < Pow - 1; ++i)
        {
            FrequencyBuffer[i] = 0.875f * FrequencyBuffer[i] + 0.125f * FrequencyBands[i];
        }
    }
}

 

FrequencyBuffer 배열을 선언해줍니다.

 

추가된 함수는 아래와 같습니다.

private void SmoothFrequencyBands()
{
    for (int i = 0; i < Pow - 1; ++i)
    {
        FrequencyBuffer[i] = 0.875f * FrequencyBuffer[i] + 0.125f * FrequencyBands[i];
    }
}

 

FrequencyBuffer[i]를 이전 값의 87.5%와 FrequencyBands[i]의 12.5%의 합으로 업데이트하는 방식으로(일종의 가중 평균) FrequencyBuffer[i]를 부드럽게 업데이트하는 방식입니다.

 


ParamCube 스크립트 작성

이전 포스팅까지는 SpawnCube 스크립트에서 큐브를 컨트롤 했는데, 이젠 따로 ParamCube 스크립트를 만들어서 따로 큐브를 컨트롤해주고자 합니다.

 

using UnityEngine;

public class ParamCube : MonoBehaviour
{
    public int index;
    public bool useBuffer;
    [SerializeField] private float sizePower = 10f;
    [SerializeField] private float minYScale = 1f;

    private void Update()
    {
        if (!useBuffer)
        {
            transform.localScale = new Vector3(1f, (AudioPeer.Instance.FrequencyBands[index] * sizePower) + minYScale, 1f);
        }
        else
        {
            transform.localScale = new Vector3(1f, (AudioPeer.Instance.FrequencyBuffer[index] * sizePower) + minYScale, 1f);
        }
    }
}

 

Use Buffer

 

Buffer를 사용하고자 하는 객체에는 인스펙터에서 체크해주면 됩니다.


SpawnCubes 스크립트 수정

using UnityEngine;

public class SpawnCubes : MonoBehaviour
{
    [SerializeField] private GameObject radiusObject;
    private GameObject[] spawnedRadiusObjects;

    public float Radius = 3f;
    
    [SerializeField] private float sizePower = 10f;
    [SerializeField] private float minYScale = 1f;

    private float x = 0.3f;
    private float z = 0.3f;
    private int SampleLength;
    private bool isStart;
    
    private void Start()
    {
        SampleLength = AudioPeer.Instance.Samples.Length;
    }
    
    private void Spawn()
    {
        spawnedRadiusObjects = new GameObject[SampleLength];
        
        for (int i = 0; i < SampleLength; i++)
        {
            float angle = i * 360f / SampleLength;
            transform.rotation = Quaternion.Euler(0, angle, 0);
            
            GameObject go = Instantiate(radiusObject, transform.forward * Radius, Quaternion.identity);
            go.name = "Cube" + i;
            spawnedRadiusObjects[i] = go;
        }

        isStart = true;
    }

    private void Update()
    {
        if (!isStart) return;
        
        for (int i = 0; i < SampleLength; i++)
        {
            spawnedRadiusObjects[i].transform.localScale = new Vector3(x, (AudioPeer.Instance.Samples[i] * sizePower) + minYScale, z);
        }
    }
}

 

 

Update함수를 수정해주었습니다.