![cat](http://cdn.lcx-blog.top/img/cat.jpg)
# 音乐可视化
**动态演示请见视频链接:https://www.bilibili.com/video/BV1KY4y1974q**
## 一.将MP3音乐文件可视化
步骤如下:
1. 傅里叶变换`audioSource.GetSpectrumData`提取不同频带的振幅,存放在*spectrum*数组中。
2. 将多个频带合并为几个频带(按能量去划分)`makeFrequencyBands()`,将128个频带简化为七个,存放在*_frequencyBands*数组中。
3. 设置buffer更加平滑,`BandBuffer()`,存放在*_frequencyBandsBuffer*数组中。
4. 统计各频段最大值,来归一化.`CreateAudioBands()`,存放在*_audioBandsBuffer*数组中。
tips:
1. 归一化之前,初始化各频段最大值,不要默认为0,可以更快稳定。
2. 0左声道,1右声道,注意音源。
3. 傅里叶变换时启用*FFTWindow*d的更简单的采样方法可以减轻运算负担
## 二.将实时麦克风输入可视化
步骤如下:
1. 使用*_useMicrophone*参数决定是否切换音源。
2. `audioSource.clip = Microphone.Start()`开启麦克风采样,切换clip.
tips:
1. 记得设置loop=true来循环采样。
2. 记得麦克风采样开始后,启用`audioSource.Play()`来获得频谱数据。
3. 为了避免扬声器发声产生循环啸叫,应该与MP3音乐文件可视化相区别,设置两个*AudioMixerGroup*,控制麦克风输入源播放的group音量调到最低
![image-20230310235733042](http://cdn.lcx-blog.top/img/image-20230310235733042.png)
## 三.用到的代码脚本
*MusicVisualizer*,主脚本,采样归一化后存放在public数组。
```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.UI;
[RequireComponent(typeof(AudioSource))]
public class MusicVisualizer : MonoBehaviour
{
public AudioSource audioSource;
public AudioClip _audioClip;
public AudioMixerGroup _mixerGroupMicrophone, _mixerGroupMaster;
//------------------------------
public bool _useMicrophone;
//private AudioClip micRecord;//麦克风
string _selectedDevice;
//----------------------------------
public int channel = 0;//0代表读取左声道
public static float[] spectrum = new float[128];//将频段分为128个,即2^7个
public static float[] spectrum_normalize = new float[128];////归一化之后的128个频段,为了显示那个圆环
public float[] spectrum_Highest = new float[128];
public static float[] _frequencyBands = new float[7];//可视化为七个通道
public static float[] _frequencyBandsBuffer = new float[7];//七个平滑缓冲的通道
public float[] bufferDecrease = new float[7];//平滑用到的衰减系数
public float[] _freqBandHighest = new float[7];
public static float[] _audioBand = new float[7];
public static float[] _audioBandsBuffer= new float[7];
// Start is called before the first frame update
private void Start()
{
//device = Microphone.devices[0];//获取设备麦克风
//micRecord = Microphone.Start(device, true, 10, 44100);//44100音频采样率 固定格式
//Debug.Log(device);
//audioSource.clip = micRecord;
for (int i = 0; i < 128; i++)
{
spectrum_Highest[i] = (float)0.0001;
}
for (int i = 0; i < 7; i++)
{
_freqBandHighest[i] = (float)0.001;
}
if (_useMicrophone)
{
if (Microphone.devices.Length > 0)
{
_selectedDevice = Microphone.devices[0].ToString();
audioSource.outputAudioMixerGroup = _mixerGroupMicrophone;
audioSource.clip = Microphone.Start(_selectedDevice, true, 10, AudioSettings.outputSampleRate);
audioSource.Play();
}
else
{
_useMicrophone = false;
}
}
if (!_useMicrophone)
{
audioSource.clip = _audioClip;
audioSource.outputAudioMixerGroup = _mixerGroupMaster;
audioSource.Play();
}
}
private void Update()
{
audioSource.GetSpectrumData(spectrum, channel, FFTWindow.BlackmanHarris);
//makeFrequencyBands();
// BandBuffer();
// CreateAudioBands();
CreateSpectrumNormalize();
}
void CreateSpectrumNormalize()
{
for (int i = 0; i < 128; i++)
{
if (spectrum[i] > spectrum_Highest[i])//做归一化,让值域固定在0-1之间
{
spectrum_Highest[i] = spectrum[i];
}
spectrum_normalize[i] = (spectrum[i] / spectrum_Highest[i]);
}
}
void CreateAudioBands()
{
for (int i = 0; i < 7; i++)
{
if (_frequencyBands[i] > _freqBandHighest[i])//做归一化,让值域固定在0-1之间
{
_freqBandHighest[i] = _frequencyBands[i];
}
_audioBand[i] = (_frequencyBands[i] / _freqBandHighest[i]);
_audioBandsBuffer[i] = (_frequencyBandsBuffer[i] / _freqBandHighest[i]);
}
}
private void makeFrequencyBands()
{
int count = 0;
for (int i = 0; i < 7; i++)
{
float average = 0;
int sampleCount = (int)Mathf.Pow(2, i);
if (i == 7)
{
sampleCount += 1;
}
for (int j = 0; j < sampleCount; j++)
{
average += spectrum[count] * (count + 1);
count++;
}
average /= count;
_frequencyBands[i] = average * 10;
}
}
private void BandBuffer()
{
for (int g = 0; g < 7; ++g)
{
if (_frequencyBands[g] > _frequencyBandsBuffer[g]) { _frequencyBandsBuffer[g] = _frequencyBands[g]; bufferDecrease[g] = 0.005f; }
if (_frequencyBands[g] < _frequencyBandsBuffer[g])
{
_frequencyBandsBuffer[g] -= bufferDecrease[g];
bufferDecrease[g] *= 1.2f;
}
}
}
public void changebutton()
{
if (_useMicrophone)
{
_useMicrophone = false;
audioSource.clip = _audioClip;
audioSource.outputAudioMixerGroup = _mixerGroupMaster;
audioSource.Play();
}
else
{
_useMicrophone = true;
if (Microphone.devices.Length > 0)
{
_selectedDevice = Microphone.devices[0].ToString();
audioSource.outputAudioMixerGroup = _mixerGroupMicrophone;
audioSource.clip = Microphone.Start(_selectedDevice, true, 10, AudioSettings.outputSampleRate);
audioSource.Play();
}
else
{
_useMicrophone = false;
}
}
}
}
```
*bandcube*,控制七个预制体的的长度变化。
```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bandcube : MonoBehaviour
{
// Start is called before the first frame update
public int _band;
public float _minScale = 1, _maxScale = 3; // Use this for initialization
public bool usebuffer ;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (usebuffer) {
transform.localScale = new Vector3(transform.localScale.x, (MusicVisualizer._audioBandsBuffer[_band] * (_maxScale- _minScale)) + _minScale, transform.localScale.z);
}
else
{
transform.localScale = new Vector3(transform.localScale.x, (MusicVisualizer._audioBand[_band] * (_maxScale - _minScale)) + _minScale, transform.localScale.z);
}
}
}
```
*Instantiate128*,产生120个环形方块,控制长度变化。
```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Instantiate128 : MonoBehaviour
{
public GameObject _samplecubePrefab;
//GameObject[] _samplecube = new GameObject[128];
GameObject[] _samplecube120 = new GameObject[120];
public float _maxscale;
public float _minscale;
// use this for initialization
void Start()
{
for (int i = 0; i < 120; i++)
{
GameObject _instancesamplecube = (GameObject)Instantiate(_samplecubePrefab);
_instancesamplecube.transform.parent = this.transform;
_instancesamplecube.transform.localPosition = new Vector3(0, 0, 0); // this.transform.position;
_instancesamplecube.name = "samplecube" + i;
this.transform.eulerAngles = new Vector3(0, -3f * i, 0);
_instancesamplecube.transform.position = this.transform.position + new Vector3(0, 0, (float)0.03);
_samplecube120[i] = _instancesamplecube;
}
}
// update is called once per frame
void Update()
{
for (int i = 0; i < 120; i++)
{
if (_samplecube120 != null)
{
_samplecube120[i].transform.localScale = new Vector3((float)0.1, (float)0.1, (MusicVisualizer.spectrum_normalize[i] * (_maxscale-_minscale)+_minscale));
}
}
}
}
```
## 四.参考
>PeerPlay频道:https://www.youtube.com/watch?v=rnZ52SlVJj8&list=PL3POsQzaCw50AsSQBoVi9efaz8cP3d8ho
音乐可视化