用Unity的GetSpectrumData方法识别钢琴曲中的钢琴琴键

目录

要解决的问题

我在上一篇系列文章的结尾段提到了一个可以利用GetSpectrumData方法检测乐曲中的音高的思路,本文既是对此进行尝试与验证。

那篇文章中已经写过,从物理的角度看,声音是一种波动,音乐则是一种有规则的声音波动(无规则的被称为噪音)。不管是旋律(一个声波接着另一个声波)还是声(声波的叠加),人类听觉总是偏喜欢“更和谐”的音高组合,从数学上看,这个音高组合问题其实是一个相对比率问题,既是C(do) D(re) E(mi) F(fa) G(sol) A(la) B(si) C(do) #C(升do) #D(升re) #F(升fa) #G(升sol) #A(升la)之间的相对音高比率。在古代,这些相关的知识就已经形成了音乐领域中的一种学科,设计这些音高比率的过程叫做定律,从中总结出的规则叫做音律。

音律

五度相生律与 3 2 \frac{3}{2} 23

世界上很多文明从不同时期都独立的发现了类似的音律思想,在中国叫五度相生律,它的核心是一个3:2的比率。假如我们定一个基准比率C4=1,通过3:2找到它的上方五度音(度可以理解为五线谱上的线和间,例如do到mi一共经过两线一间,所以它们是三度)G4=3/2,继续通过3:2找到G4的上方五度D5=9/4,任何一个音的八度都是二倍比率,降一个八度找到D4=9/8,D4的五度A4=27/16,A4的五度E5=81/32,降八度E4=81/64,E4的五度B4=243/128…以此类推找出所有音高的比率,再通过一个标准音高值进行相乘即可得出所有音高值。五度相生律的优点是由于所有五度(例如do和so)的频率比都是3比2,所以五度听起来非常协和。

纯率与 5 4 \frac{5}{4} 45

在五度相生律的基础上引入一个5:4的比率去找一个音的大三度音。例如已知标准c4=1,通过3/2比率找出纯五度g4=3/2,再通过c4的5/4比率找出大三度e4=5/4,通过c4的3/2比率找出纯五度f3=2/3,f4=4/3,再通过f3的5/4比率找出大三度a3=5/6,a4=5/3,通过g4的3/2比率找出d5=9/4,d4=9/8,通过g4的5/4比率找出b4=15/8…纯律的优点是和弦中比较重要的三度音听起来更协和。

纯律和五度相生律最大的问题是不可转调,(转调简单来说就是原曲是C->D->E,转某个调后变成F->G->A,由于每个音之间的音程(相对距离关系)没变,所以听着旋律还是对的)。例如现在的流行音乐(使用十二平均律)有时用C调或降E调都能唱不会有什么问题,但是用纯律或五度相生率写的歌转调后是有可能会跑调的(转调后音的相对关系无法保持一致导致旋律被破坏了)。

十二平均律与 2 1 12 2^\frac{1}{12} 2121

十二平均律是将一个八度(从声波频率倍数1到2的距离)分为了十二个半音,这十二个半音的音高的比率从数学上看是一个等比队列,每个半音之间的比率是2的1/12次方,例如以A4=440Hz为标准,那么#A4=440* 2 1 12 2^\frac{1}{12} 2121=466.164Hz。代入所有键的位置参数,其他键的音高频率可通过一个通用公式 f ( n ) = ( 2 1 12 ) n − 49 ∗ 440 H z f(n)={(2^\frac{1}{12})}^{n-49}*440Hz f(n)=(2121)n49440Hz得出,其中n是半音的键位。

十二平均律的优点是可以灵活转调,缺点是从数值上看关键音程的协和度没有上面两种更纯粹,比如某些音程比率在五度相生率中比例是3:2,但在十二平均律中可能是3:2.1,但是这些差距对于人类听觉来讲影响不大。

88键钢琴各键键位与音高

钢琴各键的音高既是通过十二平均律计算出来的。
在这里插入图片描述
(图1:88键钢琴键位与音高,原图地址)

Unity GetSpectrumData获取的音频数据与88键钢琴各键的映射

理解好了理论后,我们接下来进行实践。

第一步将图1中的钢琴各键标准频率保存在一个数组中,可利用上面提到的公式“ f ( n ) = ( 2 1 12 ) n − 49 ∗ 440 H z f(n)={(2^\frac{1}{12})}^{n-49}*440Hz f(n)=(2121)n49440Hz”进行计算:

    float[] Herz_PianoKeys = new float[88];void InitAllPianoKeysHerz(){for(int i = 0; i < Herz_PianoKeys.Length; i++){Herz_PianoKeys[i] = Mathf.Pow(Mathf.Pow(2, ET12), ((i + 1) - Pos_A4))*Herz_A4;}}

第二步,将每个钢琴键标准频率数组的index与GetSpectrumData获取的频谱数组中的最接近它的频率的index,一同插入到一个字典中:

    private float[] spectrumData = new float[8192];private Dictionary<int, int> KeysDataMap = new Dictionary<int, int>();void BindKeysAndSpectrumData(){//由于官网上缺乏说明,其实这里对GetSpectrumData的返回数组中的每个成员的所代表的频率只是猜测,不过从测试结果来看应该是猜对了float interval = MaxHerzOfSpectrumData / spectrumData.Length;//尝试找到精确的映射。与88和8192两个参数有关。//try to find the precise mapping.the algorithm depending on the parameters of 88 and 8192for (int i = 0; i < spectrumData.Length; i++){for (int j = 0; j < Herz_PianoKeys.Length; j++) {if (Mathf.Abs((i + 1) * interval - Herz_PianoKeys[j]) <= 0.05f){KeysDataMap[j] = i;}else if (KeysDataMap.ContainsKey(j) == false && Mathf.Abs((i + 1) * interval - Herz_PianoKeys[j]) <= 1f){KeysDataMap[j] = i;}else if (KeysDataMap.ContainsKey(j) == false && Mathf.Abs((i + 1) * interval - Herz_PianoKeys[j]) <= 2f){KeysDataMap[j] = i;}}}}

最后一步,在update()中实时分析spectrumData中对应钢琴键的频率的成员,找出在当前帧它们之中最大音量的频率:

    void AnalyzeMusic() {float maxValue = 0;int maxKey = 0;foreach (var key in KeysDataMap.Keys){//find maxif (spectrumData[KeysDataMap[key]] > maxValue && spectrumData[KeysDataMap[key]] > threadshold){maxValue = spectrumData[KeysDataMap[key]];maxKey = key;}}if (maxValue>0){Debug.Log(maxKey + 1);Debug.Log(spectrumData[KeysDataMap[maxKey]]);//testTestResult(maxKey+1);}}

全部代码与测试结果

分析一个从中央C爬音到B再降回C的一个简单音频(
C4(键位40)->C#4(键位41)->D4(键位42)->D#4(键位43)->E4(键位44)->F4(键位45)->F#4(键位46)->G4(键位47)->G#4(键位48)->A4(键位49)->A#4(键位50)->B4(键位51)->A5(键位52)->B4(键位51)->A#4(键位50)->A4(键位49)->G#4(键位48)->G4(键位47)->F#4(键位46)->F4(键位45)->E4(键位44)->D#4(键位43)->D4(键位42)->C#4(键位41)->C4(键位40)

结果(Debug打印AnalyzeMusic方法检测出的每个键的键位):
在这里插入图片描述
(图2:爬音音频测试结果正确)

全部代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PianoKeysDetector : MonoBehaviour
{//88键钢琴//88 keys pianofloat[] Herz_PianoKeys = new float[88];//A4键440标准赫兹,键位49//A4 at key 49 with 440Hzfloat Herz_A4 = 440f;int Pos_A4 = 49;//12平均律//twelve-tone equal temperamentfloat ET12 = 1f / 12f;//过滤当前音乐杂音阈值//filter threadshold of current music cliipfloat threadshold = 0.002f;//被分析的音乐来自:https://upload.wikimedia.org/wikipedia/commons/f/f0/ChromaticScaleUpDown.ogg//music clip from:https://upload.wikimedia.org/wikipedia/commons/f/f0/ChromaticScaleUpDown.oggprivate AudioSource thisAudioSource;private float[] spectrumData = new float[8192];//the value denpended on pc, sould be updated in runtimeprivate float MaxHerzOfSpectrumData = 22050;//钢琴键位<-->spectrumData位置//piano key position<-->spectrumData positionprivate Dictionary<int, int> KeysDataMap = new Dictionary<int, int>();void InitAllPianoKeysHerz(){for(int i = 0; i < Herz_PianoKeys.Length; i++){Herz_PianoKeys[i] = Mathf.Pow(Mathf.Pow(2, ET12), ((i + 1) - Pos_A4))*Herz_A4;}}void BindKeysAndSpectrumData(){float interval = MaxHerzOfSpectrumData / spectrumData.Length;//尝试找到精确的映射。与88和8192两个参数有关。//try to find the precise mapping.the algorithm depending on the parameters of 88 and 8192for (int i = 0; i < spectrumData.Length; i++){for (int j = 0; j < Herz_PianoKeys.Length; j++) {if (Mathf.Abs((i + 1) * interval - Herz_PianoKeys[j]) <= 0.05f){KeysDataMap[j] = i;}else if (KeysDataMap.ContainsKey(j) == false && Mathf.Abs((i + 1) * interval - Herz_PianoKeys[j]) <= 1f){KeysDataMap[j] = i;}else if (KeysDataMap.ContainsKey(j) == false && Mathf.Abs((i + 1) * interval - Herz_PianoKeys[j]) <= 2f){KeysDataMap[j] = i;}}}}void AnalyzeMusic() {float maxValue = 0;int maxKey = 0;foreach (var key in KeysDataMap.Keys){//find maxif (spectrumData[KeysDataMap[key]] > maxValue && spectrumData[KeysDataMap[key]] > threadshold){maxValue = spectrumData[KeysDataMap[key]];maxKey = key;}}if (maxValue>0){Debug.Log(maxKey + 1);Debug.Log(spectrumData[KeysDataMap[maxKey]]);//testTestResult(maxKey+1);}}//should be:C4 C#4 D4 D#4 E4 F4 F#4 G4 G#4 A4 A#4 B4 C5 B4 A#4 A4 G#4 G4 F#4 F4 E4 D#4 D4 C#4 C4//          40 41  42 43  44 45 46  47 48  49 50  51 52 51 50  49 48  47 46  45 44 43  42 41  40List<int> testResults = new List<int>();void TestResult(int val){if (testResults.Count > 0 && val != testResults[testResults.Count - 1]){testResults.Add(val);}else if (testResults.Count == 0){testResults.Add(val);}}// Start is called before the first frame updatevoid Start(){//例如:44100/2=22050//eg:44100/2=22050MaxHerzOfSpectrumData = AudioSettings.outputSampleRate / 2;thisAudioSource = gameObject.GetComponent<AudioSource>();InitAllPianoKeysHerz();BindKeysAndSpectrumData();thisAudioSource.Play();Invoke("DebugTestResults", thisAudioSource.clip.length);}// Update is called once per framevoid Update(){thisAudioSource.GetSpectrumData(spectrumData, 0, FFTWindow.BlackmanHarris);AnalyzeMusic();}//debug functionvoid DebugAllPianoKeysHerz(){for (int i = 0; i < Herz_PianoKeys.Length; i++){Debug.Log(i);Debug.Log("Herz:" + Herz_PianoKeys[i]);}}void DebugKeysDataMap(){foreach (var key in KeysDataMap.Keys){Debug.Log(key);Debug.Log(KeysDataMap[key]);}}void DebugTestResults(){string result = "";for(int i = 0; i < testResults.Count; i++){result += testResults[i].ToString() + " ";}Debug.Log(result);}
}

未解决的问题

观察图1的钢琴按键的频率分布,低音区的数值间隔很小,例如按键2和3的频率只相差不到2,而Unity GetSpectrumData获取的数据的最大频率间隔(使用最长的数组8192)是2.6916Hz,这样在映射低音区时会有些精确度问题,虽然那部分超低音键在实际演奏中比较少见。

另外,音乐的发声体的震动所产生的声波并不是单一频率波,例如拉小提琴的弦或敲击钢琴的音板都会导致有规律的共振,一个钢琴键按下会产生一个基音和多个泛音,在一个简单的声音环境中(例如上面测试中分析的简单钢琴爬音音频)我们可以通过一些阈值变量(例如代码中的threadshold变量)滤掉泛音,但在一个复杂的各种音色的基音和泛音叠加的快速变化的环境中,简单的通过上面的寻找最大值配合过滤的算法是很难分析出正确的基音的,需要调整FFT window参数并配合一些更好的更适合于被分析数据的插值和过滤算法。


传送门:
上一篇系列文章----用Unity3D内部频谱分析方法做音乐视觉特效的原理说明
英文版:编辑中…


GitHub项目链接:
https://github.com/liu-if-else/UnitySpectrumDataDetectsPianoKeys


参考:
各种维基百科 --wikipedia
音乐理论基础 --李重光

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/66746.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国内智能音箱市场逐渐崛起,那么问题来了……

来源&#xff1a;AI锐见 “嗨&#xff0c;Alexa&#xff0c;放首歌。” 2014年&#xff0c;搭载了亚马逊虚拟语音个人助理Alexa的Echo面世&#xff0c;让人类同机器的交互方式出现了另一种可能。 苹果联合创始人Steve Wozniak就曾在2016年表示&#xff0c;智能语音交互将成为计…

前途未卜的智能音箱,语音助手还差一个杀手级应用

作者 | Rani Molla 译者 | 刘旭坤 整理 | 非主流、Jane 出品 | AI科技大本营 近日&#xff0c;亚马逊推出了一款可以用语音控制的智能微波炉。至此&#xff0c;亚马逊推出的由 Alexa 语音控制的智能家居产品的数量已累计超过十种。由此&#xff0c;我们可以看到亚马逊推广自家语…

当你问智能音箱问题的时候,它会回答你什么?

笔者测试了25个不同类型的问题&#xff0c;比较了小爱mini和小度在家的回答&#xff0c;借此设计声学测试的问题&#xff0c;问题、问题类型、期望回复以及音箱的真实回复如下。测试时间为2018年10月份左右。 对话服务 序号对话期望回复对话类型小爱mini小度在家1珠海明天天气…

热闹了一年的智能音箱,为什么说只是过渡?

IT派 - {技术青年圈} 持续关注互联网、大数据、人工智能领域 关注 往期 精彩回顾 重大改变&#xff01;Excel即将接入Python&#xff01;办公软件也要革命 2017年大数据领域薪资有多高&#xff1f; 支付宝的套路 行业热&#xff0c;行业洗牌&#xff0c;行业发展&#xff0c;下…

智能音箱对比:Google,Amazon,Apple,讯飞

智能音箱 好久没写Blog&#xff0c;最近主要也在关心异军突起的智能音箱行列&#xff0c;这里也来稍微的分析下。 前言 2014年&#xff0c;Amazon不声不响的推出了智能音箱Echo&#xff0c;并慢慢建立了Alexa语音服务平台&#xff0c;直到2016年销量忽然剧增达到了1000万台。消…

钉钉添加代收邮箱地址

如163邮箱添加到钉钉客户端代收&#xff0c;需要在163邮箱登录后在设置中添加协议&#xff0c;并获取验证密码&#xff0c;验证密码在代收客户端使用&#xff0c;代收客户端输入账号是原始账号&#xff0c;密码就是获取的验证密码

出口托收与进口代收

出口托收与进口代收 出口托收&#xff08;Ontward Documentary Collection&#xff09;与进口代收&#xff08;Import Documentary Collection&#xff09;同属于托收业务范畴&#xff0c;出口托收和进口代收均为商业银行的业务之一&#xff0c;如托收流程图来说&#xff0c;对…

用EXCEL表格发送物业催费短信

催费成功者都是英雄 &#xff08;一&#xff09;催费的困境 做为一名物业工作人员&#xff0c;你是否也遇到过这样的困境呢&#xff1f; 1、心理被一种潜在的恐惧所困扰着——业主肯定会拒交费用&#xff0c;“那个业主真难办&#xff0c;我根本没办法说服他&#xff0c;真不…

批量查询快递单号筛选出代收单号

查询多个快递还在一个一个查吗&#xff1f;费时又费力&#xff0c;今天小编教你使用一款查询工具“快递批量查询高手”轻松解决难题&#xff0c;有兴趣的接着看下去吧。 工具/原料 快递批量查询高手 多个快递单号 方法/步骤 打开软件点击【添加单号】将需要查询的单号复制到文…

开快递代收点挣钱吗?怎么盈利?

现在是网络时代&#xff0c;很多事情都是在网络上面进行的。由于人们已经习惯了这种生活&#xff0c;网购显得更加受欢迎。正是因为如此&#xff0c;快递行业越来越红火。很多人开始开快递代收点&#xff0c;这样是否挣钱呢? 一、挣钱吗? 首先农村快递代收点会和各个快递公…

(附源码)计算机毕业设计SSM快递代收系统

&#xff08;附源码&#xff09;计算机毕业设计SSM快递代收系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xf…

多多买菜代收被叫停?拼多多这样回应

最近&#xff0c;有媒体报道称“多多买菜快递代收业务未取得经营许可被叫停”。在舆论不断发酵的情况下&#xff0c;拼多多方面对此做出了回应&#xff1a;拼多多并未在多多买菜所有站点开展快递代收业务&#xff0c;而是为有快递业务资质的站点提供“多多买菜”代收服务系统。…

香农 | 流行潮(bandwagon)

【编者按&#xff1a;面对当前的Chatgpt热潮&#xff0c;该如何看待呢&#xff1f;英语当中有种说法叫 jump on the bandwagon&#xff0c;意为跟风随大流。60多年前&#xff0c;当信息论的发展进入越来越多的领域&#xff0c;一向低调的香农在《流行潮》&#xff08;The Bandw…

AI学术交流——“人工智能”和“神经网络学习”

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 前言 一.人工智能 1.“人工智能之父” 2.达特茅斯会议&#xff08;人工智能起源&a…

微信小程序跳转微信内置浏览器

最近遇到一个需求&#xff0c;需要微信小程序跳转到微信内置浏览器&#xff0c;但是官网并没有给出相关文档。查阅了大量资料&#xff0c;发现有两种实现方式。 1、通过微信公众号文章实现 小程序可以使用web-view打开关联的公众号文章&#xff0c;公众号文章可以在阅读全文放…

微信PC端 微信浏览器打开控制台

1. 版本回退 下载旧版本微信客服端&#xff0c;3.2.1版本之前的都可以&#xff0c;这里是https://link.csdn.net/?targethttps%3A%2F%2Fwebcdn.m.qq.com%2Fspcmgr%2Fdownload%2FWeChat_for_XP_SP3_To_Vista.exe 2. 下载 devtools_resources.pak devtools_resources.pak是必…

将您的 Linux 应用程序迁移到 Amazon 云,第 4 部分: 征服管理挑战

避免您发展过程中的难题 Sean A. Walberg, 高级网络工程师 Sean Walberg 自 1994 年开始就先后在学院&#xff0c;企业&#xff0c;以及 Internet Service Provider 环境中研究 Linux 和 UNIX 系统。在过去几年中&#xff0c;他撰写了大量有关系统管理的书籍。 简介&#xff1a…

阿里 VP 贾扬清确认离职!尚未创立公司,方向或是 AI 架构?

程序员的成长之路 互联网/程序员/技术/资料共享 关注 阅读本文大概需要 2.8 分钟。 来自&#xff1a;https://mp.weixin.qq.com/s/3cWT8M8kU7UT3UwaVO9Bmg出品 | CSDN 整理 | 郑丽媛 昨天&#xff0c;我们还在感慨美团元老王慧文、前京东 AI 掌门人周伯文、创新工场 CEO 李开复…

推荐两个AI神器:只需1个标题,2分钟全自动生成PPT!

今天给大家分享两个工具&#xff0c;帮助你全自动生成PPT&#xff0c;接下来以自动化测试为主题&#xff0c;教大家如何2分钟生成好PPT。 1、第一个工具&#xff1a;ChatGPT 1、打开ChatGPT页面&#xff0c;输入prompt&#xff0c;告诉它&#xff0c;让它帮你生成一份自动化测试…

小达人点读笔报电量低无法充电故障的处理

小达人的点读笔&#xff0c;突然发现电池不行了&#xff0c;充电之后用半个小时&#xff0c;放一会&#xff0c;就提示“电量不足&#xff0c;请充电”。怀疑电池老化了&#xff0c;之前出过类似的问题&#xff0c;更换过一次电池。 X宝购买电池&#xff0c;更换上&#xff0c;…