1.前言
本文为本学期一门课程的一次课程设计大作业,题目是《音乐合成器设计》,想着作业做也做了,不如写下这篇博客巩固一下学习所得,同时也给有需要的人分享经验。所做工作主要有一下几个:1.基于乐曲简谱和“十二平均律”,使用MATLAB分析不同单音节的频谱;2.基于STM32利用乐音频率实现完整音乐的合成和播放功能。
2.前置知识
十二平均律
十二平均律,亦称“十二等程律”,世界上通用的一组音(八度)分成十二个半音音程的律制,各相邻两律之间的振动数之比完全相等。十二平均律在交响乐队和键盘乐器中得到广泛使用,钢琴即是根据十二平均律来定音的。-——来源百度百科
划重点,十二平均律是组成一首音乐的基础,各相邻两律之间的振动数之比完全相等。换言之就是常见的Do Re Mi Fa So La Si Do和其他四个半音。
3.单音频谱分析
篇幅有限,笔者只录下7个常见单音。话不多说,立马翻出半途而废,尘封多年的吉他,用手机把7个单音逐个录了下来。
Do 2弦第1品、 Re 2弦第3品、Mi 1弦空弦、Fa 1弦第1品、So 1弦第3品、La 1弦第5品、Si 1弦第7品。——来自百度,笔者吉他水平实在太差
使用MATLAB对单音进行傅里叶变换(FFT),得到该音节的频谱图
MATLAB代码:
close all;
clear all;
clc;
figure;
[filename,filepath]=uigetfile(‘.wav’,‘Open wav file’);
[w,fs,bits]=wavread([filepath,filename]); %从电脑文件夹选择wav音频文件
sound(w,fs,bits);
y=w(:,1);
display(‘声音文件的大小为:’);size(w)
subplot(211);plot(y);title(‘时域波形’);
N=pow2(nextpow2(length(y)));
Y=fft(y,N);
subplot(212);plot(fs*[1:N]/N,abs(Y));title(‘幅度值’);grid;
注意有些手机录音机输出的是.m4a的MPEG-4音频文件,要使用转码软件转成.wav格式。
频谱如下:
1.Do:频率大约在380Hz
2.Re:436Hz
3.Mi:492Hz
4.Fa:522Hz
5.So:584Hz
6.La:654Hz
7.Si:734Hz
综上,Do:380Hz Re:436Hz Mi:492Hz Fa:522Hz So:584Hz La:654Hz Si:734Hz。
他们之间的频率差56Hz 56Hz 30Hz 62Hz 70Hz 80Hz。
除了Re 和Mi之间没有半度音,其他都有半度音,考虑到手机录音存在偏差,基本各音节的频率差为30,基本满足了“各相邻两律之间的振动数之比完全相等”。
十二平均律的频谱分析到此为止。
4.基于STM32电子琴设计
本实验使用的是STM32F103和一个无源蜂鸣器。由于无源蜂鸣器的工作频率是在2KHz~5KHz,而上面吉他所测得的频率都在1KHz以下,显然不能满足无源蜂鸣器的工作频率。于是笔者查到下表:
小字四组则是2KHz以上的十二平均律频率。
4.1 利用STM32内部定时器产生PWM输出信号
PWM就是脉冲宽度调制,由高电平和低电平来回跳变组成一组信号来驱动元件,其中有两个关键指标:频率和占空比。
1秒内,0.5秒开,0.5秒灭,占空比是50%对吧?那么,1毫秒内,0.5毫秒开,0.5毫秒灭,占空比也是50%,对吧?如果是1秒呢,频率就是1HZ,如果是1毫秒,频率就是1KHZ,显然,同样是50%占空比,如果频率是1HZ,那电机肯定是跳着走的,灯光肯定闪得可以跳舞,不具有调速和调光的意义。——@一个在奋斗路上前行的小菜鸟
笔者使用Tim3通道2输出一路PWM信号,定时器的参数由结构体TimeBaselnitTypeDef定义,主要包括预分频系数、时钟分割、计数器模式、计数溢出大小等。例如,要由TIM3(定时器3)产生一个时长为1 s的定时,首先,应进行系统时钟的设置,得到TIM3CLK=72MHz,然后进行定时器设置。其中,预分频系数为35 999,此时,TIM3时钟为72 MHz/36 000=2 kHz,无时钟分割。设置计数溢出大小为1 999,即每计2 000个数就产生一个更新事件,输出频率为2 kHz/2 000=1 Hz。设置如下:
TIM_TimeBaseStructure.TIM_Period=2000-1; //自动装载周期值
TIM_TimeBaseStructure.TIM_Prescaler=36000-1; //预分频值
TIM_TimeBaseStructure.TIM_ClockDivision=0; //时钟分割
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
4.2占空比
有如下含义:在一串理想的脉冲周期序列(如方波)中,正脉冲的持续时间与脉冲总周期的比值。
当TIM_Period为1 999时,若想得到占空比50%,则TIM_Pulse应设置为(1999+1)/2=1000。具体设置如下:
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2; //PWM模式2
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性高
4.3频率
音阶的产生依赖于PWM输出信号的频率。为了简化设计,我们令定时器的TIM_Period为1 999,且占空比始终为50%,根据式(1)则TIM_ Pulse为1000。此时,PWM输出信号频率仅与定时器预分频系数TIM_Prescaler有关,只需要调整该系数,即可得到所需信号频率。
TIM_Prescaler由下式得到:
fsound为音阶对应的频率,如Do频率为2093 Hz。要产生该音频,TIM_Prescaler应为17。
4.4威廉希尔官方网站
设计
4.5主要实现代码
beer.h
#ifndef _beer_H
#define _beer_H
#include “stm32f10x.h”
#include “delay.h”
void TIM3_PWM_Init(u16 arr,u16 psc);//arr
void music_note(u16 psc,u16 ms);
#endif
1
2
3
4
5
6
7
8
9
10
beer.c
#include “beer.h”
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能 PB,AFIO 端口时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //部分重映射,到PB5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0--》PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIOB.5
TIM_TimeBaseStructure.TIM_Period=arr; //自动装载周期值
TIM_TimeBaseStructure.TIM_Prescaler=psc; //预分频值
TIM_TimeBaseStructure.TIM_ClockDivision=0; //时钟分割
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2; //PWM模式2
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//使能预装载寄存器
TIM_Cmd(TIM3, ENABLE);//使能TIM3
}
void music_note(u16 psc,u16 ms) //单音节输出,包含预分频值和延迟时间
{
TIM3_PWM_Init(2000-1,psc);
TIM_SetCompare2(TIM3,1000);
delay_ms(ms);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
main.c
#include “stm32f10x.h”
#include “delay.h”
#include “beer.h”
int main()
{
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
while(1)
{
music_note(17,500);
music_note(15,500);
music_note(14,500);
music_note(13,500);
music_note(11,500);
music_note(10,500);
music_note(9,500);
}
}
实现效果:Do Re Mi Fa So La Si循环播放。
1.前言
本文为本学期一门课程的一次课程设计大作业,题目是《音乐合成器设计》,想着作业做也做了,不如写下这篇博客巩固一下学习所得,同时也给有需要的人分享经验。所做工作主要有一下几个:1.基于乐曲简谱和“十二平均律”,使用MATLAB分析不同单音节的频谱;2.基于STM32利用乐音频率实现完整音乐的合成和播放功能。
2.前置知识
十二平均律
十二平均律,亦称“十二等程律”,世界上通用的一组音(八度)分成十二个半音音程的律制,各相邻两律之间的振动数之比完全相等。十二平均律在交响乐队和键盘乐器中得到广泛使用,钢琴即是根据十二平均律来定音的。-——来源百度百科
划重点,十二平均律是组成一首音乐的基础,各相邻两律之间的振动数之比完全相等。换言之就是常见的Do Re Mi Fa So La Si Do和其他四个半音。
3.单音频谱分析
篇幅有限,笔者只录下7个常见单音。话不多说,立马翻出半途而废,尘封多年的吉他,用手机把7个单音逐个录了下来。
Do 2弦第1品、 Re 2弦第3品、Mi 1弦空弦、Fa 1弦第1品、So 1弦第3品、La 1弦第5品、Si 1弦第7品。——来自百度,笔者吉他水平实在太差
使用MATLAB对单音进行傅里叶变换(FFT),得到该音节的频谱图
MATLAB代码:
close all;
clear all;
clc;
figure;
[filename,filepath]=uigetfile(‘.wav’,‘Open wav file’);
[w,fs,bits]=wavread([filepath,filename]); %从电脑文件夹选择wav音频文件
sound(w,fs,bits);
y=w(:,1);
display(‘声音文件的大小为:’);size(w)
subplot(211);plot(y);title(‘时域波形’);
N=pow2(nextpow2(length(y)));
Y=fft(y,N);
subplot(212);plot(fs*[1:N]/N,abs(Y));title(‘幅度值’);grid;
注意有些手机录音机输出的是.m4a的MPEG-4音频文件,要使用转码软件转成.wav格式。
频谱如下:
1.Do:频率大约在380Hz
2.Re:436Hz
3.Mi:492Hz
4.Fa:522Hz
5.So:584Hz
6.La:654Hz
7.Si:734Hz
综上,Do:380Hz Re:436Hz Mi:492Hz Fa:522Hz So:584Hz La:654Hz Si:734Hz。
他们之间的频率差56Hz 56Hz 30Hz 62Hz 70Hz 80Hz。
除了Re 和Mi之间没有半度音,其他都有半度音,考虑到手机录音存在偏差,基本各音节的频率差为30,基本满足了“各相邻两律之间的振动数之比完全相等”。
十二平均律的频谱分析到此为止。
4.基于STM32电子琴设计
本实验使用的是STM32F103和一个无源蜂鸣器。由于无源蜂鸣器的工作频率是在2KHz~5KHz,而上面吉他所测得的频率都在1KHz以下,显然不能满足无源蜂鸣器的工作频率。于是笔者查到下表:
小字四组则是2KHz以上的十二平均律频率。
4.1 利用STM32内部定时器产生PWM输出信号
PWM就是脉冲宽度调制,由高电平和低电平来回跳变组成一组信号来驱动元件,其中有两个关键指标:频率和占空比。
1秒内,0.5秒开,0.5秒灭,占空比是50%对吧?那么,1毫秒内,0.5毫秒开,0.5毫秒灭,占空比也是50%,对吧?如果是1秒呢,频率就是1HZ,如果是1毫秒,频率就是1KHZ,显然,同样是50%占空比,如果频率是1HZ,那电机肯定是跳着走的,灯光肯定闪得可以跳舞,不具有调速和调光的意义。——@一个在奋斗路上前行的小菜鸟
笔者使用Tim3通道2输出一路PWM信号,定时器的参数由结构体TimeBaselnitTypeDef定义,主要包括预分频系数、时钟分割、计数器模式、计数溢出大小等。例如,要由TIM3(定时器3)产生一个时长为1 s的定时,首先,应进行系统时钟的设置,得到TIM3CLK=72MHz,然后进行定时器设置。其中,预分频系数为35 999,此时,TIM3时钟为72 MHz/36 000=2 kHz,无时钟分割。设置计数溢出大小为1 999,即每计2 000个数就产生一个更新事件,输出频率为2 kHz/2 000=1 Hz。设置如下:
TIM_TimeBaseStructure.TIM_Period=2000-1; //自动装载周期值
TIM_TimeBaseStructure.TIM_Prescaler=36000-1; //预分频值
TIM_TimeBaseStructure.TIM_ClockDivision=0; //时钟分割
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
4.2占空比
有如下含义:在一串理想的脉冲周期序列(如方波)中,正脉冲的持续时间与脉冲总周期的比值。
当TIM_Period为1 999时,若想得到占空比50%,则TIM_Pulse应设置为(1999+1)/2=1000。具体设置如下:
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2; //PWM模式2
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性高
4.3频率
音阶的产生依赖于PWM输出信号的频率。为了简化设计,我们令定时器的TIM_Period为1 999,且占空比始终为50%,根据式(1)则TIM_ Pulse为1000。此时,PWM输出信号频率仅与定时器预分频系数TIM_Prescaler有关,只需要调整该系数,即可得到所需信号频率。
TIM_Prescaler由下式得到:
fsound为音阶对应的频率,如Do频率为2093 Hz。要产生该音频,TIM_Prescaler应为17。
4.4威廉希尔官方网站
设计
4.5主要实现代码
beer.h
#ifndef _beer_H
#define _beer_H
#include “stm32f10x.h”
#include “delay.h”
void TIM3_PWM_Init(u16 arr,u16 psc);//arr
void music_note(u16 psc,u16 ms);
#endif
1
2
3
4
5
6
7
8
9
10
beer.c
#include “beer.h”
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能 PB,AFIO 端口时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //部分重映射,到PB5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0--》PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIOB.5
TIM_TimeBaseStructure.TIM_Period=arr; //自动装载周期值
TIM_TimeBaseStructure.TIM_Prescaler=psc; //预分频值
TIM_TimeBaseStructure.TIM_ClockDivision=0; //时钟分割
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2; //PWM模式2
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//使能预装载寄存器
TIM_Cmd(TIM3, ENABLE);//使能TIM3
}
void music_note(u16 psc,u16 ms) //单音节输出,包含预分频值和延迟时间
{
TIM3_PWM_Init(2000-1,psc);
TIM_SetCompare2(TIM3,1000);
delay_ms(ms);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
main.c
#include “stm32f10x.h”
#include “delay.h”
#include “beer.h”
int main()
{
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
while(1)
{
music_note(17,500);
music_note(15,500);
music_note(14,500);
music_note(13,500);
music_note(11,500);
music_note(10,500);
music_note(9,500);
}
}
实现效果:Do Re Mi Fa So La Si循环播放。
举报