由于内存和处理能力有限,很难在 Arduino 上从音频信号中检测音符。检测精度因仪器而异。有些乐器(如钢琴)在有限的范围内是准确的,而有些乐器(如口琴)则准确度较低。
此代码使用了以前开发的称为 EasyFFT 的 FFT 代码。链接:https ://create.arduino.cc/projecthub/abhilashpatel121/easyfft-fast-fourier-transform-fft-for-arduino-9d2677
如上一步所述,由于音频样本中存在多个频率,因此检测很困难。
该程序按以下流程工作:
1、数据采集:
- 本部分从音频数据中提取 128 个样本,两个样本之间的间隔(采样频率)取决于感兴趣的频率。在这种情况下,我们使用两个样本之间的间距来应用 Hann 窗函数以及振幅/RMS 计算。此代码还通过从 analogread 值中减去 500 来进行粗调归零。如果需要,可以更改此值。对于典型情况,这些值运行良好。此外,需要添加一些延迟以获得大约 1200Hz 的采样频率。在1200Hz采样频率的情况下最大可以检测到600HZ的频率。
for(int i=0;i<128;i++)
{
a=analogRead(Mic_pin)-500; //rough zero shift
sum1=sum1+a; //to average value
sum2=sum2+a*a; // to RMS value
a=a*(sin(i*3.14/128)*sin(i*3.14/128)); // Hann window
in[i]=4*a; // scaling for float to int conversion
delayMicroseconds(195); // based on operation frequency range
}
2.快速傅里叶变换:
数据准备就绪后,将使用EasyFFT执行 FFT 。此 EasyFFT 函数已修改为固定 128 个样本的 FFT。还修改了代码以减少内存消耗。原来的 EasyFFT 函数设计为最多 1028 个样本(使用兼容板),而我们只需要 128 个样本。与原始 EasyFFT 函数相比,此代码减少了大约 20% 的内存消耗。
FFT 完成后,代码会返回前 5 个最主要的频率峰值以供进一步分析。该频率按振幅降序排列。
3.音符检测:对于每个峰,代码检测可能与之关联的音符。此代码最多只能扫描 1200 Hz。不必注意与最大幅度相同的频率。
所有频率都映射在 0 到 255 之间,
这里检测到第一个八度,例如,65.4 Hz 到 130.8 代表一个八度,130.8 Hz 到 261.6 Hz 代表另一个。对于每个八度音程,频率从 0 映射到 255。这里映射从 C 开始到 C'。
if(f_peaks[i]>1040){f_peaks[i]=0;}
if(f_peaks[i]>=65.4 && f_peaks[i]<=130.8) {f_peaks[i]=255*((f_peaks[i]/65.4)-1);}
if(f_peaks[i]>=130.8 && f_peaks[i]<=261.6) {f_peaks[i]=255*((f_peaks[i]/130.8)-1);}
if(f_peaks[i]>=261.6 && f_peaks[i]<=523.25){f_peaks[i]=255*((f_peaks[i]/261.6)-1);}
if(f_peaks[i]>=523.25 && f_peaks[i]<=1046) {f_peaks[i]=255*((f_peaks[i]/523.25)-1);}
if(f_peaks[i]>=1046 && f_peaks[i]<=2093) {f_peaks[i]=255*((f_peaks[i]/1046)-1);}
NoteV 数组值用于将音符分配给检测到的频率。
byte NoteV[13]={8,23,40,57,76,96,116,138,162,187,213,241,255};
4. 在为每个频率计算音符后,可能存在多个暗示相同音符的频率。要有准确的输出代码还要考虑重复。该代码根据振幅顺序和重复次数将所有频率值相加,并以最大振幅使音符达到峰值。
for (int i=0;i<12;i++)
{
in[20+i]=in[i]*in[i+4]*in[i+7];
in[32+i]=in[i]*in[i+3]*in[i+7]; //all chord check
}
此部分通过根据主要和次要代码组合将音符值相互相乘来检查所有和弦。本节还使用相同的输入数组进行数据存储。
此外,选择显示具有最大可能性(最大乘法)的和弦。
使用代码很简单,但是,在使用时也需要牢记多个限制。代码可以复制,因为它用于纸币检测。使用它时需要考虑以下几点。
1. 引脚分配:
根据所附的引脚分配需要修改。对于我的实验,我将其保留在模拟引脚 7 上,
void setup()
{Serial.begin(250000);
Mic_pin = A7;
}
2.麦克风灵敏度:
需要修改麦克风灵敏度,这样可以生成具有良好振幅的波形。大多数情况下,麦克风模块带有灵敏度设置。选择适当的灵敏度,使信号既不会太小,也不会因振幅较高而被削掉。
3.振幅阈值:
仅当信号幅度足够高时,此代码才会激活。此设置需要由用户手动设置。该值取决于麦克风灵敏度和应用。
if(sum2-sum1>5){
.
.
在上面的代码中,sum2 给出 RMS 值,而 sum 1 给出平均值。所以这两个值之间的差异给出了声音信号的幅度。在我的例子中,它在 5 左右的振幅值下正常工作。
4. 默认情况下,此代码将打印检测到的注释。但是,如果您打算将票据用于其他目的,则应使用直接指定的号码。例如 C=0;C#=1,D=2,D#=3 及以后。
5. 如果仪器有更高的频率,代码可能会给出错误的输出。最大频率受采样频率限制。所以你可以在延迟值以下进行调整以获得最佳输出。在下面的代码延迟 195 微秒。可以对其进行调整以获得最佳输出。这将影响整体执行时间。
{ a=analogRead(Mic_pin)-500; //rough zero shift
sum1=sum1+a; //to average value
sum2=sum2+a*a; // to RMS value
a=a*(sin(i*3.14/128)*sin(i*3.14/128)); // Hann window
in[i]=4*a; // scaling for float to int conversion
delayMicroseconds(195); // based on operation frequency range
}
6. 此代码只能工作到 2000Hz 频率。通过消除采样之间的延迟,可以获得大约 3-4 kHz 的采样频率。
防范措施:
音符检测是一项计算密集型工作,获得实时输出非常困难,尤其是在 Arduino 上。此代码可以提供大约 6.6 个样本/秒(添加 195 微秒延迟)。此代码适用于钢琴和其他一些乐器。
我希望这段代码和教程对您与音乐相关的项目有所帮助。如有任何疑问或建议,请随时沟通。
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
全部0条评论
快来发表一下你的评论吧 !