论文部分内容阅读
摘 要:钢琴是一种传统演奏乐器,距今已有几百年的发展和使用历史。我国自从改革开放以来,越来越多的家庭购买了钢琴。但是钢琴调音仍然处于人工调试阶段,需要调音师的经验和手艺来判断钢琴音色是否标准,调音效果千差萬别。随着智能手机以及计算机技术发展,设计应用软件通过语音识别技术对琴键频率进行采集,采用傅里叶变换对接收到的音频进行分析,得到真实的音键频率,可以弥补人为因素对乐音调试的误差;将分析得到的琴键频率与标准乐音库进行对比,将对比后的乐音信息通过可视化界面展示出来,便于人们更加快速准确地调音。
关键词:Visual Studio;钢琴调音;语音识别;傅里叶变换
中图分类号:TN912.3 文献标识码:A
Abstract:Piano is a traditional musical instrument,which has a history of development and use for hundreds of years. Since Chinese reform and opening up,more and more families have bought piano. However,the piano tuning is still in the stage of manual debugging,which requires the experience and skills of the tuner to judge whether the piano tone is standard,and the tuning effect is very different. With the development of smart phones and computer technology,the design application software collects the piano key frequency through speech recognition technology,analyzes the received music by using Fourier transform,and gets the sound key frequency approaching to the standard,which can completely make up for the error of human factors in music debugging;compares the piano key frequency obtained by analysis with the standard music library,and then compares it Through the visual interface,the music information is displayed,which is convenient for people to tune more quickly and accurately.
Key words:Visual Studio;Piano tuning;Speech Recognition;Fourier transform
一、项目背景
21世纪,人们更加注重于对精神的追求,钢琴的发展和普及恰好可以为现代人们带来精神上的快乐和满足[1]。钢琴的音质长期受到各种外在环境因素的影响,使得钢琴发音不准,严重干扰钢琴演奏的稳定性和演奏效果,因此必须在钢琴使用前进行准确的调音。近些年由于演奏钢琴人数的增加,传统的钢琴调音过于复杂化和机械化,现有的调音模式始终满足不了大众的调音需求[2]。因此,需要一款方便、快捷、准确、实用的调音装置或者软件工具来帮助解决调音的音准问题。
设计一种钢琴调音软件,可以实现智能化精密测量,高效地协助调音师完成钢琴调音工作,避免了人为调音的不足,满足了实际工作的要求。调音软件也适用于其他乐器的调音,使调音工作不再枯燥烦琐。
二、技术方案
(一)音频识别技术构建模型
将模拟的音频信号进行采样得到波形数据之后,要输入到特征提取模块,提取出合适的声学特征参数供后续声学模型训练使用。好的声学特征应当考虑以下三个方面的因素:第一,应当具有比较优秀的区分特性,以使声学模型不同的建模单元可以方便准确地建模;其次,特征提取也可以认为是音频信息的压缩编码过程,既需要将信道、环境噪音的因素消除,保留与内容相关的信息,又需要在不损失过多有用信息的情况下使用尽量低的参数维度,便于高效准确地进行模型的训练;最后,需要考虑鲁棒性,即对环境噪声的抗干扰能力[3-4]。
如今主流语音识别系统都采用隐马尔科夫模型(HMM)作为声学模型,这是因为HMM具有很多优良特性。HMM的状态跳转模型很适合人类语音的短时平稳特性,方便对不断产生的观测值(语音信号)进行统计建模;与HMM相伴生的动态规划算法可以有效地实现对可变长度的时间序列进行分段和分类的功能;HMM的应用范围广泛,只要选择不同的生成概率密度,离散分布或者连续分布,都可以使用HMM进行建模。其原理框架如下图1中所示。
钢琴键频率是指钢琴上琴键所产生的声音频率,以赫兹(Hz)为单位。现代的钢琴上共有88至108键。88键钢琴的第49键,即第5个A(亦称A4)一般被用作调音标准。现行的标准是440Hz,亦称A440。钢琴的琴键号和频率对应关系如下表1。本项目对音频信号建模,标准频率模型即为该表。
(二) FFT(Fast Fourier Transform)算法原理
FFT是一种基于离散傅里叶变换(DFT)的高效傅里叶算法,被人们广泛称为快速傅里叶变换[5-6]。该方法主要基于Fourier的傅里叶函数变换,在时域上的任何傅里叶函数都必须是完全可以整数表示的,它可以构成由一个正交余弦阵列函数所组合构成的无穷数量级数。Fourier函数定义为:时域上的一个信号x(t)会对应于频域f上的另外一个信号 X(f)。即X(f)=dt。 通过Fourier变换后,可以得出信号的离散频谱,从而精确地求出信号的离散频率。实际应用时,计算得到离散信号周期x(t)的离散采样值,即x(nt),t代表离散采样信号的周期。通过计算离散采样的值x(nt),可以精確地计算出离散信号周期x(t)的离散频谱。
麦克风记录声音时,只能获取不同时刻的电压变化值,这是多种声音频率的总和。利用傅里叶变换将多个不同频率的音频分别提取出来,将语音信号转化为电脑可识别的数字信号,其中傅里叶变换还可将混合的复杂音波分别提取出单一的音频信号。本项目通过快速傅里叶变换的算法获取采样音频的能量值。
三、程序的实现
本项目运行环境为微软公司的Visual Studio 2017(VS),采用C语言和Windows编程来实现钢琴调音的基本功能。首先建立一个控制台项目文件,新建项目完成后,将后缀名称.cpp改为.c。为了更加方便地调试程序,手动添加一个FFT算法.c文件。完成总体框搭建以后,只需向项目文件中添加代码即可,采用多文件联合编程的思想,可以更加快速有效地实现程序的功能。
(一)整体思路
软件的工作流程如下图2所示。
频率识别需要对外部声音进行采集,添加导入winmm.lib库,可以进行Windows多媒体编程。因为还需调用接口函数和添加mmsystem.h头文件来实现声音的输入输出。mmsystem.h中包含了多媒体的大多数接口,可以简化程序。主程序包含了1个main函数和5个可调用函数,将钢琴音键和对应频率值设为全局变量,定义为结构体数组。钢琴各按键频率,为测试方便,本项目定义了70个(可以在堆栈空间中开辟88个。)
struct MusicNote
{
char noteName[10];
double frequence;
}
note[VOLUMESIZE]={{“1”,27.5},{“2”, 29.1},{“3”, 30.8},{“4”, 32.7},{“5”, 34.6},{“6”, 36.7 7”, 38.8},{“8”, 41.2},{“9”, 43.6},{“10”,46.2},{“11”,48.9},{“12”, 51.9},{“13”, 55.0},{“14”, 58.2},{“15”, 61.7},{“16”, 65.4}, {“17”, 69.2}, {“18”, 73.4}, { “19”, 77.7}, {“20”, 82.4}, {“21”, 87.3},{“22”, 92.4}, {“23”, 97.9}, {“24”, 103.8},{“25”, 110.0},{“26”,116.5},{“27”,123.4},{“28”,130.8},{“29”,138.5},{“30”,146.8},{“31”,155.5},{“32”,164.8},{“33”,174.6},{“34”,184.9},{“35”,195.9},{“36”,207.6},{“37”,220.0},{“38”,233.0},{“39”,246.9},{“40”,261.6},{“41”,277.1},{“42”,293.6},{“43”,311.1},{“44”,329.6},{“45”, 349.2},{“46”, 369.9},{“47”, 391.9}, {“48”, 415.3}, {“49”, 440.0},{“50”,466.1},{“51”,493.8},{“52”,523.2},{“53”,554.3},{“54”,587.3},{“55”,622.2}, {“56”,659.2},{“57”,698.4},{“58”,739.9},{“59”,783.9},{“60”,830.6},{“61”,880.0},{“62”,932.3},{“63”,987.7},{“64”,1046.5},{“65”,1108.7},{“66”,1174.6},{“67”,1244.5},{“68”,1318.5},{“69”,1396.9}, {“70”, 1479.9}};
(二)设计过程
1.调用Record()函数,实现PCM(脉冲编码调制)声音采集,将生成的录音文件保存于D盘的根目录下。保存的文件采用二进制进行存储,读取函数时也要进行相应的二进制读取。定义音频流格式的数据结构WAVEFORMATEX waveform和输入设备HWAVEIN hWaveIn,输入设备的初始值为0。采集音频时,需要包含数据缓存结构体LPWAVEHDR pWaveHdr以及采集时的数据缓存PBYTE pBuffer,数据缓存的类型为PBYTE,数据定义格式均为Windows核心编程内容。
WAVEFORMATEX waveform; //采集音频格式,结构体
HWAVEIN hWaveIn = 0; //输入设备
LPWAVEHDR pWaveHdr = NULL; //采集音频时,包含数据缓存结构体
PBYTE pBuffer = NULL; //采集音频时的数据缓存
MMRESULT error = 0; //枚举型数据变量
double i = 4000000000; //循环变量,用于录音需要的一段时间
FILE *fp = NULL; char directory[50] = “d:\\shengyin.pcm”;
waveform.wFormatTag = WAVE_FORMAT_PCM; //音频格式为PCM
waveform.nChannels = 1; //采样声道数(2声道)
waveform.nSamplesPerSec = 8000; //采样率(采样率,16000Hz)
waveform.wBitsPerSample = 16; //采样精度(采样比特,16bits)
waveform.nBlockAlign = 2; //数据块大小
waveform.nAvgBytesPerSec = 16000; //每秒采集的数据字节数
waveform.cbSize = 0; //额外描述信息块大小
定义好基本类型数据,开辟录音所需空间和数据缓存结构体空间:
pBuffer = (PBYTE)malloc(10 * waveform.nAvgBytesPerSec);
pWaveHdr = (PWAVEHDR)malloc(sizeof(WAVEHDR));
设置录音时间为10秒,如果开辟堆栈空间错误,将其置0,程序退出。
if (pBuffer == NULL)
{printf(“[record]pBuffer malloc error!\n”); exit(0);}
打开录音设备:
error = waveInOpen(&hWaveIn, WAVE_MAPPER, &waveform, 0, 0, CALLBACK_NULL);
为录音设备准备一个缓冲区:
error = waveInPrepareHeader(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
向录音设备输入一个缓冲区:
error = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
啟动录音:
error = waveInStart(hWaveIn);
用一个while循环来记录时间:
while (i > 0)
{i--;}
用于录音的缓存判断和函数结束:
waveInStop(hWaveIn);
waveInReset(hWaveIn);
fp = fopen(directory, “wb”);
if (fp == NULL)
{printf(“[record]打开失败!\n”);
free(pWaveHdr);
pWaveHdr = NULL;
free(pBuffer);
pBuffer = NULL;
exit(0);}
将文件以二进制形式写入:
fwrite(pBuffer, pWaveHdr->dwBytesRecorded, 1, fp);
函数结尾需要关闭文件和释放堆栈空间:
fclose(fp);
free(pWaveHdr);
pWaveHdr = NULL;
free(pBuffer);
pBuffer = NULL;
2.调用getValue()函数,从录音文件中提取数据点。
二进制形式打开录音文件:
FILE *fp = NULL;
char directory[50] = “d:\\shengyin.pcm”;
fp = fopen(directory, “rb”);
查找文件并计算大小:
fseek(fp, 0, SEEK_END);
length = ftell(fp);
fseek(fp, 0, SEEK_SET);
读取数据点操作:
fread(&datatmp, 1, sizeof(datatmp), fp);
*(data + i) = datatmp;
因录音不能在开始时马上进行,需要对数据进行筛选操作,要先跳过前面10000个点左右,时间为大于1秒的准备时间:
for (i = 0; i < 8000; i++)
{fread(&datatmp, 1, sizeof(datatmp), fp);}
3.调用getFFTEnergy()函数,从提取的数据进行傅里叶变换,得到各音频的能量值。
定义采集数据值为双精度double类型:
double pr[DATASIZE], pi[DATASIZE], fr[DATASIZE], fi[DATASIZE];
以原始数据作为实部,虚数部分为0.0,fr表示为傅里叶变换后的实部,fi为傅里叶变换后的虚部。
调用傅里叶变换函数:
kfft(pr, pi, DATASIZE, 12, fr, fi, 0, 1); 得到能量的返回值:
*(data + i) = pr[i];
4.调用getFrequence()函数,计算出音频的频率值。
设置能量的频率、坐标和能量最大值:
double locate = 0;
int i;
double frequence;
double max;
max = *data;
求出能量最大值对应的横坐标:
for (i = 0; i < (DATASIZE / 2); i++)
{if (*(data + i) > max)
{max = *(data + i);
locate = i;}}
计算出其频率值:
frequence = locate * (8000.0 / DATASIZE);
5.调用showVolume()函数。根据频率,在音高频率对照表中,查看对应的音符并显示结果。
函数开始时打印出标准钢琴70个键的参照表:
printf(“%s:%fHz\n”, note[i].noteName, note[i].frequence);
输出当前音键的频率值:
printf(“当前频率值:%f\n”, fre);
输入的音律太高或音律太低,提示出现错误,并重新进行下一轮的声音读取操作:
if (fre <= note[0].frequence)
{Low = note[0].frequence - fre;
printf(“音律太低,请重新录入\n”);}
else if(fre >= note[69].frequence)
{Heigh = fre - note[69].frequence;
printf(“音律太高,请重新录入\n”);}
如果频率在相应合理的范围,显示出对应高出或低于标准频率的频率值,误差在5Hz以内时,提示调音较为合理:
if (note[choose].frequence < fre)
{printf(“当前频率高于標准频率,高了%fHz\n”, fre - note[choose].frequence);
while ( fre - note[choose].frequence<Wucha )
{printf(“误差在0至5Hz之间,调音基本完成\n”);
break;}}
else
{printf(“当前频率低于标准频率,低了%fHz\n”, note[choose].frequence - fre);
while(note[choose].frequence-fre < Wucha)
{printf(“误差在0至5Hz之间,调音基本完成\n”);
break;}}
(三)测试案例
1.当在一个相对安静的环境下时,接收到的声音频率为0,低于钢琴所发出的最低频率,则会出现相应提示如下,测试结果达到预期目标。
2.当在一个相对频率较高的环境下时,接收到的频率远高于对照表中的数据,则会出现相应提示如下,测试结果达到预期目标。
3.测试任意钢琴的频率按键。
(1)选择52号琴键为低音键(一组7个音键中Do键),对应的标准频率为523.2Hz,测试结果如下:
与标准频率相差仅为0.23Hz,达到了预期调音目标。
(2)选择63号高音键Xi,对应标准频率为987.7Hz,测试结果如下:
与标准频率仅相差0.58Hz,达到了预期效果。
(四)误差分析
选取部分钢琴琴键,测出调音后的频率,与标准音频值做比对,检验软件准确度。测量结果如表2所示。52、54、56、57、59、61、63键号分别对应钢琴键号中的C5、D5、E5、F5、G5、A5、B5。
从表2数据可以看出,测得误差与标准值基本控制在1Hz左右,误差百分比在0%~0.5%之间,测量精度相对较高,完全可以担任调音工作。
调音中的误差主要来源于以下几个方面:
1.系统声卡将声音文件转化为二进制文件进行保存时,声音信号转化为数字信号,会出现失真和数据丢失的情况;将二进制文件转化为音频文件时又会出现音质损失。
2.不存在绝对安静的环境,环境因素会对频率检测出现影响,有一些杂音会通过麦克风进入声卡,导致有时测量相对误差较大。
四、总结
本项目通过Visual Studio平台进行了整体项目搭建,对每一个重要功能函数进行了详细分析和开发应用,根据程序测试案例进行分析数据,最后对比分析出结果。通过快速离散傅里叶变换计算得出的频率值与钢琴调音时弹奏出的频率值基本吻合,该软件可以初步满足对钢琴调音的需求。
参考文献:
[1]林琳娜.浅谈钢琴中的“律”及“调试”[J].科技信息,2011(20):259.
[2]崔笑.钢琴调音仪的研究及实现[D].成都:电子科技大学,2014.
[3]柯鑫.基于Web客户端和语音识别的智能家居交互系统设计[D].武汉:华中科技大学,2019.
[4]林圣晔.语音识别技术[J].数码设计(下),2019(04):182-183.
[5]徐健,李晓慧.一种基于FFT的高分辨率音频频率测量方法[J].电子质量,2020(02):11-14.
[6]张嘉.基于ARM的钢琴调校装置的研发[D].哈尔滨:哈尔滨理工大学,2013.
关键词:Visual Studio;钢琴调音;语音识别;傅里叶变换
中图分类号:TN912.3 文献标识码:A
Abstract:Piano is a traditional musical instrument,which has a history of development and use for hundreds of years. Since Chinese reform and opening up,more and more families have bought piano. However,the piano tuning is still in the stage of manual debugging,which requires the experience and skills of the tuner to judge whether the piano tone is standard,and the tuning effect is very different. With the development of smart phones and computer technology,the design application software collects the piano key frequency through speech recognition technology,analyzes the received music by using Fourier transform,and gets the sound key frequency approaching to the standard,which can completely make up for the error of human factors in music debugging;compares the piano key frequency obtained by analysis with the standard music library,and then compares it Through the visual interface,the music information is displayed,which is convenient for people to tune more quickly and accurately.
Key words:Visual Studio;Piano tuning;Speech Recognition;Fourier transform
一、项目背景
21世纪,人们更加注重于对精神的追求,钢琴的发展和普及恰好可以为现代人们带来精神上的快乐和满足[1]。钢琴的音质长期受到各种外在环境因素的影响,使得钢琴发音不准,严重干扰钢琴演奏的稳定性和演奏效果,因此必须在钢琴使用前进行准确的调音。近些年由于演奏钢琴人数的增加,传统的钢琴调音过于复杂化和机械化,现有的调音模式始终满足不了大众的调音需求[2]。因此,需要一款方便、快捷、准确、实用的调音装置或者软件工具来帮助解决调音的音准问题。
设计一种钢琴调音软件,可以实现智能化精密测量,高效地协助调音师完成钢琴调音工作,避免了人为调音的不足,满足了实际工作的要求。调音软件也适用于其他乐器的调音,使调音工作不再枯燥烦琐。
二、技术方案
(一)音频识别技术构建模型
将模拟的音频信号进行采样得到波形数据之后,要输入到特征提取模块,提取出合适的声学特征参数供后续声学模型训练使用。好的声学特征应当考虑以下三个方面的因素:第一,应当具有比较优秀的区分特性,以使声学模型不同的建模单元可以方便准确地建模;其次,特征提取也可以认为是音频信息的压缩编码过程,既需要将信道、环境噪音的因素消除,保留与内容相关的信息,又需要在不损失过多有用信息的情况下使用尽量低的参数维度,便于高效准确地进行模型的训练;最后,需要考虑鲁棒性,即对环境噪声的抗干扰能力[3-4]。
如今主流语音识别系统都采用隐马尔科夫模型(HMM)作为声学模型,这是因为HMM具有很多优良特性。HMM的状态跳转模型很适合人类语音的短时平稳特性,方便对不断产生的观测值(语音信号)进行统计建模;与HMM相伴生的动态规划算法可以有效地实现对可变长度的时间序列进行分段和分类的功能;HMM的应用范围广泛,只要选择不同的生成概率密度,离散分布或者连续分布,都可以使用HMM进行建模。其原理框架如下图1中所示。
钢琴键频率是指钢琴上琴键所产生的声音频率,以赫兹(Hz)为单位。现代的钢琴上共有88至108键。88键钢琴的第49键,即第5个A(亦称A4)一般被用作调音标准。现行的标准是440Hz,亦称A440。钢琴的琴键号和频率对应关系如下表1。本项目对音频信号建模,标准频率模型即为该表。
(二) FFT(Fast Fourier Transform)算法原理
FFT是一种基于离散傅里叶变换(DFT)的高效傅里叶算法,被人们广泛称为快速傅里叶变换[5-6]。该方法主要基于Fourier的傅里叶函数变换,在时域上的任何傅里叶函数都必须是完全可以整数表示的,它可以构成由一个正交余弦阵列函数所组合构成的无穷数量级数。Fourier函数定义为:时域上的一个信号x(t)会对应于频域f上的另外一个信号 X(f)。即X(f)=dt。 通过Fourier变换后,可以得出信号的离散频谱,从而精确地求出信号的离散频率。实际应用时,计算得到离散信号周期x(t)的离散采样值,即x(nt),t代表离散采样信号的周期。通过计算离散采样的值x(nt),可以精確地计算出离散信号周期x(t)的离散频谱。
麦克风记录声音时,只能获取不同时刻的电压变化值,这是多种声音频率的总和。利用傅里叶变换将多个不同频率的音频分别提取出来,将语音信号转化为电脑可识别的数字信号,其中傅里叶变换还可将混合的复杂音波分别提取出单一的音频信号。本项目通过快速傅里叶变换的算法获取采样音频的能量值。
三、程序的实现
本项目运行环境为微软公司的Visual Studio 2017(VS),采用C语言和Windows编程来实现钢琴调音的基本功能。首先建立一个控制台项目文件,新建项目完成后,将后缀名称.cpp改为.c。为了更加方便地调试程序,手动添加一个FFT算法.c文件。完成总体框搭建以后,只需向项目文件中添加代码即可,采用多文件联合编程的思想,可以更加快速有效地实现程序的功能。
(一)整体思路
软件的工作流程如下图2所示。
频率识别需要对外部声音进行采集,添加导入winmm.lib库,可以进行Windows多媒体编程。因为还需调用接口函数和添加mmsystem.h头文件来实现声音的输入输出。mmsystem.h中包含了多媒体的大多数接口,可以简化程序。主程序包含了1个main函数和5个可调用函数,将钢琴音键和对应频率值设为全局变量,定义为结构体数组。钢琴各按键频率,为测试方便,本项目定义了70个(可以在堆栈空间中开辟88个。)
struct MusicNote
{
char noteName[10];
double frequence;
}
note[VOLUMESIZE]={{“1”,27.5},{“2”, 29.1},{“3”, 30.8},{“4”, 32.7},{“5”, 34.6},{“6”, 36.7 7”, 38.8},{“8”, 41.2},{“9”, 43.6},{“10”,46.2},{“11”,48.9},{“12”, 51.9},{“13”, 55.0},{“14”, 58.2},{“15”, 61.7},{“16”, 65.4}, {“17”, 69.2}, {“18”, 73.4}, { “19”, 77.7}, {“20”, 82.4}, {“21”, 87.3},{“22”, 92.4}, {“23”, 97.9}, {“24”, 103.8},{“25”, 110.0},{“26”,116.5},{“27”,123.4},{“28”,130.8},{“29”,138.5},{“30”,146.8},{“31”,155.5},{“32”,164.8},{“33”,174.6},{“34”,184.9},{“35”,195.9},{“36”,207.6},{“37”,220.0},{“38”,233.0},{“39”,246.9},{“40”,261.6},{“41”,277.1},{“42”,293.6},{“43”,311.1},{“44”,329.6},{“45”, 349.2},{“46”, 369.9},{“47”, 391.9}, {“48”, 415.3}, {“49”, 440.0},{“50”,466.1},{“51”,493.8},{“52”,523.2},{“53”,554.3},{“54”,587.3},{“55”,622.2}, {“56”,659.2},{“57”,698.4},{“58”,739.9},{“59”,783.9},{“60”,830.6},{“61”,880.0},{“62”,932.3},{“63”,987.7},{“64”,1046.5},{“65”,1108.7},{“66”,1174.6},{“67”,1244.5},{“68”,1318.5},{“69”,1396.9}, {“70”, 1479.9}};
(二)设计过程
1.调用Record()函数,实现PCM(脉冲编码调制)声音采集,将生成的录音文件保存于D盘的根目录下。保存的文件采用二进制进行存储,读取函数时也要进行相应的二进制读取。定义音频流格式的数据结构WAVEFORMATEX waveform和输入设备HWAVEIN hWaveIn,输入设备的初始值为0。采集音频时,需要包含数据缓存结构体LPWAVEHDR pWaveHdr以及采集时的数据缓存PBYTE pBuffer,数据缓存的类型为PBYTE,数据定义格式均为Windows核心编程内容。
WAVEFORMATEX waveform; //采集音频格式,结构体
HWAVEIN hWaveIn = 0; //输入设备
LPWAVEHDR pWaveHdr = NULL; //采集音频时,包含数据缓存结构体
PBYTE pBuffer = NULL; //采集音频时的数据缓存
MMRESULT error = 0; //枚举型数据变量
double i = 4000000000; //循环变量,用于录音需要的一段时间
FILE *fp = NULL; char directory[50] = “d:\\shengyin.pcm”;
waveform.wFormatTag = WAVE_FORMAT_PCM; //音频格式为PCM
waveform.nChannels = 1; //采样声道数(2声道)
waveform.nSamplesPerSec = 8000; //采样率(采样率,16000Hz)
waveform.wBitsPerSample = 16; //采样精度(采样比特,16bits)
waveform.nBlockAlign = 2; //数据块大小
waveform.nAvgBytesPerSec = 16000; //每秒采集的数据字节数
waveform.cbSize = 0; //额外描述信息块大小
定义好基本类型数据,开辟录音所需空间和数据缓存结构体空间:
pBuffer = (PBYTE)malloc(10 * waveform.nAvgBytesPerSec);
pWaveHdr = (PWAVEHDR)malloc(sizeof(WAVEHDR));
设置录音时间为10秒,如果开辟堆栈空间错误,将其置0,程序退出。
if (pBuffer == NULL)
{printf(“[record]pBuffer malloc error!\n”); exit(0);}
打开录音设备:
error = waveInOpen(&hWaveIn, WAVE_MAPPER, &waveform, 0, 0, CALLBACK_NULL);
为录音设备准备一个缓冲区:
error = waveInPrepareHeader(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
向录音设备输入一个缓冲区:
error = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
啟动录音:
error = waveInStart(hWaveIn);
用一个while循环来记录时间:
while (i > 0)
{i--;}
用于录音的缓存判断和函数结束:
waveInStop(hWaveIn);
waveInReset(hWaveIn);
fp = fopen(directory, “wb”);
if (fp == NULL)
{printf(“[record]打开失败!\n”);
free(pWaveHdr);
pWaveHdr = NULL;
free(pBuffer);
pBuffer = NULL;
exit(0);}
将文件以二进制形式写入:
fwrite(pBuffer, pWaveHdr->dwBytesRecorded, 1, fp);
函数结尾需要关闭文件和释放堆栈空间:
fclose(fp);
free(pWaveHdr);
pWaveHdr = NULL;
free(pBuffer);
pBuffer = NULL;
2.调用getValue()函数,从录音文件中提取数据点。
二进制形式打开录音文件:
FILE *fp = NULL;
char directory[50] = “d:\\shengyin.pcm”;
fp = fopen(directory, “rb”);
查找文件并计算大小:
fseek(fp, 0, SEEK_END);
length = ftell(fp);
fseek(fp, 0, SEEK_SET);
读取数据点操作:
fread(&datatmp, 1, sizeof(datatmp), fp);
*(data + i) = datatmp;
因录音不能在开始时马上进行,需要对数据进行筛选操作,要先跳过前面10000个点左右,时间为大于1秒的准备时间:
for (i = 0; i < 8000; i++)
{fread(&datatmp, 1, sizeof(datatmp), fp);}
3.调用getFFTEnergy()函数,从提取的数据进行傅里叶变换,得到各音频的能量值。
定义采集数据值为双精度double类型:
double pr[DATASIZE], pi[DATASIZE], fr[DATASIZE], fi[DATASIZE];
以原始数据作为实部,虚数部分为0.0,fr表示为傅里叶变换后的实部,fi为傅里叶变换后的虚部。
调用傅里叶变换函数:
kfft(pr, pi, DATASIZE, 12, fr, fi, 0, 1); 得到能量的返回值:
*(data + i) = pr[i];
4.调用getFrequence()函数,计算出音频的频率值。
设置能量的频率、坐标和能量最大值:
double locate = 0;
int i;
double frequence;
double max;
max = *data;
求出能量最大值对应的横坐标:
for (i = 0; i < (DATASIZE / 2); i++)
{if (*(data + i) > max)
{max = *(data + i);
locate = i;}}
计算出其频率值:
frequence = locate * (8000.0 / DATASIZE);
5.调用showVolume()函数。根据频率,在音高频率对照表中,查看对应的音符并显示结果。
函数开始时打印出标准钢琴70个键的参照表:
printf(“%s:%fHz\n”, note[i].noteName, note[i].frequence);
输出当前音键的频率值:
printf(“当前频率值:%f\n”, fre);
输入的音律太高或音律太低,提示出现错误,并重新进行下一轮的声音读取操作:
if (fre <= note[0].frequence)
{Low = note[0].frequence - fre;
printf(“音律太低,请重新录入\n”);}
else if(fre >= note[69].frequence)
{Heigh = fre - note[69].frequence;
printf(“音律太高,请重新录入\n”);}
如果频率在相应合理的范围,显示出对应高出或低于标准频率的频率值,误差在5Hz以内时,提示调音较为合理:
if (note[choose].frequence < fre)
{printf(“当前频率高于標准频率,高了%fHz\n”, fre - note[choose].frequence);
while ( fre - note[choose].frequence<Wucha )
{printf(“误差在0至5Hz之间,调音基本完成\n”);
break;}}
else
{printf(“当前频率低于标准频率,低了%fHz\n”, note[choose].frequence - fre);
while(note[choose].frequence-fre < Wucha)
{printf(“误差在0至5Hz之间,调音基本完成\n”);
break;}}
(三)测试案例
1.当在一个相对安静的环境下时,接收到的声音频率为0,低于钢琴所发出的最低频率,则会出现相应提示如下,测试结果达到预期目标。
2.当在一个相对频率较高的环境下时,接收到的频率远高于对照表中的数据,则会出现相应提示如下,测试结果达到预期目标。
3.测试任意钢琴的频率按键。
(1)选择52号琴键为低音键(一组7个音键中Do键),对应的标准频率为523.2Hz,测试结果如下:
与标准频率相差仅为0.23Hz,达到了预期调音目标。
(2)选择63号高音键Xi,对应标准频率为987.7Hz,测试结果如下:
与标准频率仅相差0.58Hz,达到了预期效果。
(四)误差分析
选取部分钢琴琴键,测出调音后的频率,与标准音频值做比对,检验软件准确度。测量结果如表2所示。52、54、56、57、59、61、63键号分别对应钢琴键号中的C5、D5、E5、F5、G5、A5、B5。
从表2数据可以看出,测得误差与标准值基本控制在1Hz左右,误差百分比在0%~0.5%之间,测量精度相对较高,完全可以担任调音工作。
调音中的误差主要来源于以下几个方面:
1.系统声卡将声音文件转化为二进制文件进行保存时,声音信号转化为数字信号,会出现失真和数据丢失的情况;将二进制文件转化为音频文件时又会出现音质损失。
2.不存在绝对安静的环境,环境因素会对频率检测出现影响,有一些杂音会通过麦克风进入声卡,导致有时测量相对误差较大。
四、总结
本项目通过Visual Studio平台进行了整体项目搭建,对每一个重要功能函数进行了详细分析和开发应用,根据程序测试案例进行分析数据,最后对比分析出结果。通过快速离散傅里叶变换计算得出的频率值与钢琴调音时弹奏出的频率值基本吻合,该软件可以初步满足对钢琴调音的需求。
参考文献:
[1]林琳娜.浅谈钢琴中的“律”及“调试”[J].科技信息,2011(20):259.
[2]崔笑.钢琴调音仪的研究及实现[D].成都:电子科技大学,2014.
[3]柯鑫.基于Web客户端和语音识别的智能家居交互系统设计[D].武汉:华中科技大学,2019.
[4]林圣晔.语音识别技术[J].数码设计(下),2019(04):182-183.
[5]徐健,李晓慧.一种基于FFT的高分辨率音频频率测量方法[J].电子质量,2020(02):11-14.
[6]张嘉.基于ARM的钢琴调校装置的研发[D].哈尔滨:哈尔滨理工大学,2013.