節(jié)拍檢測(cè)算法有很多種览爵,比較簡(jiǎn)單的就是聲能檢測(cè)算法康嘉。這里參考了gamedev和另一位大佬的文章
聲能檢測(cè)算法檢測(cè)節(jié)拍
聲能檢測(cè)算法其實(shí)就是根據(jù)頻譜中采樣的值來計(jì)算的赤拒。
上面是一張頻譜圖架曹,其中的一列就是一個(gè)采樣。
聲能檢測(cè)算法公式如下:
其中E代表一個(gè)瞬間(可以理解為一幀)中1024個(gè)采樣的值抵赢,用這個(gè)值和歷史上的43個(gè)瞬間的平均采樣值作比較欺劳,如果這一瞬間的采樣值優(yōu)于歷史上43個(gè)瞬間的采樣值的平均值,那么可以把這一個(gè)瞬間視為節(jié)拍铅鲤。
音樂高潮檢測(cè)
這個(gè)是我根據(jù)聲能檢測(cè)算法一點(diǎn)一點(diǎn)試出來的划提,假設(shè)如果當(dāng)前這一幀的聲能優(yōu)于整首歌的平均聲能,那么就可以把這一幀視為高潮邢享,這個(gè)常數(shù)我猜想的應(yīng)該是同節(jié)拍檢測(cè)的常數(shù)一樣也是1.3鹏往。試驗(yàn)后結(jié)果確實(shí)是這樣。
檢測(cè)聲能大小
其實(shí)就是檢測(cè)當(dāng)前聲能和整首歌中的最大聲能的比例骇塘,用這個(gè)比例來設(shè)置發(fā)射散彈時(shí)子彈的數(shù)量伊履。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioVIsoulization : MonoBehaviour {
AudioSource audio; //聲源
float[] sample = new float[1024]; //存放頻譜數(shù)據(jù)的數(shù)組長(zhǎng)度
LineRenderer lineRenderer; //畫線
public GameObject cube; //預(yù)制體
Transform[] cubeTransform; //cube預(yù)制體的位置
Vector3 cubePos; //中間位置,用以對(duì)比cube位置與此幀的頻譜數(shù)據(jù)
float samplePower = 0; //瞬時(shí)總采樣樣本能量(1024個(gè)采樣樣本的能量和)
List<float> oldSamplePower = new List<float>(); //歷史總采樣樣本能量(統(tǒng)計(jì)43個(gè)采樣樣本能量)
//整首歌全部樣本的總和
List<float> allSample = new List<float>();
//All FAllS DOWN
//最大樣本值
//float MaxSample = 235474.4f;
//Darkside
//最大值樣本
//float MaxSample = 235474.4f;
//DiamondHeart
//平均值
//float avg = 1123.976f;
//float MaxSample = 8572.827f;
//Spectre
//平均值
float avg = 51974.39f;
float MaxSample = 221645.5f;
// Use this for initialization
void Start () {
GameObject tempCube;
audio = GetComponent<AudioSource>(); //獲取聲源組件
lineRenderer = GetComponent<LineRenderer>(); //獲取畫線組件
lineRenderer.positionCount = sample.Length; //設(shè)定線段的片段數(shù)量
cubeTransform = new Transform[sample.Length]; //設(shè)定數(shù)組長(zhǎng)度
//將腳本所掛載的gameobject向左移動(dòng)绪爸,使得生成的物體的中心正對(duì)攝像機(jī)
transform.position=new Vector3(-sample.Length*0.5f,transform.position.y,transform.position.z);
//生成cube湾碎,將其位置信息傳入cubeTransform數(shù)組宙攻,并將其設(shè)置為腳本所掛載的gameobject的子物體
for(int i = 0;i<sample.Length;i++)
{
tempCube=Instantiate(cube,new Vector3(transform.position.x+i,transform.position.y,transform.position.z),Quaternion.identity);
cubeTransform[i] = tempCube.transform;
cubeTransform[i].parent=transform;
}
//StartCoroutine(GetBeat());
}
// Update is called once per frame
void Update() {
//獲取頻譜
audio.GetSpectrumData(sample,0,FFTWindow.BlackmanHarris);
//循環(huán)
for (int i = 0; i < sample.Length; i++)
{
//根據(jù)頻譜數(shù)據(jù)設(shè)置中間位置的y值奠货,根據(jù)對(duì)應(yīng)的cubeTransform的位置,設(shè)置x座掘、z的值
//使用Mathf.Clamp將中間位置的y限制在一定范圍內(nèi),避免過大
//頻譜是越向后越小的递惋,為避后面的數(shù)據(jù)變化不明顯,故擴(kuò)大samples[i]時(shí)溢陪,乘50+i*i*0.5f
//單個(gè)采樣樣本能量
//float y = Mathf.Clamp(sample[i] * (50 + i * i * 0.5f), 0, 200);
float y = sample[i] * (50 + i * i * 0.5f);
//float y = sample[i];
cubePos.Set(cubeTransform[i].position.x, y, cubeTransform[i].position.z);
//累計(jì)瞬時(shí)總采樣樣本能量
samplePower += y;
//畫線萍虽,為使線不會(huì)與cube重合,故高度減一
lineRenderer.SetPosition(i,cubePos-Vector3.up);
//當(dāng)cube的y值小于中間位置cubePos的y值時(shí)形真,cube的位置變?yōu)閏ubePos的位置
if(cubeTransform[i].position.y<cubePos.y)
{
cubeTransform[i].position=cubePos;
}else if(cubeTransform[i].position.y>cubePos.y)
{
cubeTransform[i].position-=new Vector3(0,0.5f,0);
}
}
//累計(jì)總采樣樣本
allSample.Add(samplePower);
//更新最大樣本
MaxSample = MaxSample > samplePower ? MaxSample : samplePower;
if (oldSamplePower.Count == 43)
{
//計(jì)算歷史采樣樣本能量的總值
float OldSamplePowerCount = 0;
for (int i = 0; i < oldSamplePower.Count; i++)
{
OldSamplePowerCount += oldSamplePower[i];
}
//計(jì)算是否為節(jié)拍
float oldSamplePowerAvg = (OldSamplePowerCount / oldSamplePower.Count) * 1.3f;
if (samplePower > oldSamplePowerAvg)
{
//Debug.Log(Time.time + " " + samplePower + " " + oldSamplePowerAvg);
GetComponent<tap>().Tap(samplePower > avg * 1.3 ? true : false, Convert.ToInt32(13 * samplePower / MaxSample));
}
//更新歷史采樣能量
oldSamplePower.RemoveAt(0);
oldSamplePower.Add(samplePower);
}
else
{
//更新歷史采樣能量
oldSamplePower.Add(samplePower);
}
//清空瞬時(shí)采樣能量
samplePower = 0;
if (!audio.isPlaying)
{
GetComponent<tap>().Save();
float allSam = 0;
for (int i = 0; i < allSample.Count; i++)
{
allSam += allSample[i];
}
Debug.Log(allSam / allSample.Count + "平均值");
Debug.Log(MaxSample + "最大值");
}
}
IEnumerator GetBeat()
{
while (true)
{
//Beat();
yield return new WaitForSeconds(0.01f);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LitJson;
using System.IO;
public class tap : MonoBehaviour {
string s = "";
public void Tap(bool isHight,int num)
{
num++;
s += Time.time + " " + isHight + " " + num + "\r\n";
}
public void Save()
{
string path = Application.streamingAssetsPath + "/Spectre.txt";
StreamWriter sw = new StreamWriter(path);
sw.Write(s);
sw.Close();
}
}