108键全调性钢琴
- 一 基本介绍
- 1 项目简介
- 2 实现方式
- 3 项目构成
- 二 实现过程
- 0 前置基本外设驱动
- 1 声音控制
- 2 乐谱录入&基础乐理
- 3 点阵屏谱点动态刷新
- 4 项目交互控制
- 5 录入新曲子过程
- 三 展示,与链接视频地址
- 1 主要功能函数一览
- 2 下载链接
- 3 视频效果
一 基本介绍
本章为项目整体介绍
1 项目简介
本项目通过摇杆模块
控制模式切换,TFTST7735S屏幕
LED与点阵屏Max7219
显示交互效果,喇叭输出声音。摇杆可控制8种模式,摇杆Z轴
控制确定返回:
- 播放音乐1-2-3
- 控制声音:增加,减小,串口直接设定具体值
- 摇杆音乐模式
- 点阵谱点开关
下为具体特点介绍:
(1)广泛音调支持
支持从1=CC#DD#EFF#GG#AA#B
的所有音调,还可以设置大小调,或者用升降半调
作为起始音,还能使用不同的八度区作为起始音等级,完美适配数字简谱
,几乎可以演奏你熟悉的任何曲子。内部代码会根据设置的初始参数,算出该调性下其余一百零七个键的频率,不需要提前打表一堆数组,节省空间。
(2)精准还原声音
因为支持修改每个音符的升降半调
和升降度
,并能精准控制音长节拍
,所以播放音乐分辨率很高,音准极好,类似一台一百零八键全频率的电子钢琴。录入了3首音乐:含克罗地亚狂想曲
与梦回还
。
(3)使用简单
录一首谱子只需:一个纯数字
的一维
乐谱数组
,和一个一键设置调性的函数
,用来配置新曲子。前者只要按照顺序和一定的规律对着数字简谱抄数字
,后者更简单,根据简谱信息填两三个字母数字。不用懂很多,只要会看数字简谱。可播放任何
单手数字简谱。
(4)点阵灯谱扫描
可以通过点阵屏
显示不同音符
和节拍长度
。一条扫描线
从顶部扫描至底部,每个音符都有长度与不同位置,触碰到扫描线
即消失
,类似音游
。有两个注意点,一是如何让扫描线和声音同步变化,对时序必须严格控制。二是如何及时更新不同音符在点阵屏上的谱点,因为点阵屏长度有限,所以一定会遇到某个音符触碰底部,部分被截断的情况,需要及时在屏幕顶部恢复被截断的信息。
详细效果,见底部视频
项目涉及到许多常用外设,如用像是
AD控制
摇杆模块,
脉宽调制PWM控制声音,
串口收发数据,
SPI,IIC`等基础知识。
2 实现方式
- 音符 1-7:PWM控制喇叭发出不同声音。
- 不同调性:了解乐理基础,根据数字简谱,提取参数,编写算法。
- 乐谱录入:一个音符含:
度数/本身频率/时长/升降半调
4个综合信息,通过4位十进制表示。(第二章最后详细解释) - 动态乐谱显示:点阵屏驱动控制,精准到控制每一个点,从左到右x列对应以音符1-7,y轴表示音符长度,一行则表示最小节拍时长。
- 交互:摇杆控制功能切换,TFT与LED显示内容变化。
3 项目构成
(1) 软件环境
上传程序:keil5
图片转c数组:img-lcd
字体编辑:PctoLcd
串口测试:串口助手
(2) 所需硬件
(3)接线
串口 | stm32 |
---|---|
RX | A9 |
TX | A10 |
OLED | stm32 |
---|---|
SCL | B8 |
SDA | B9 |
ST7735 | stm32 |
---|---|
GND | 电源地 |
VCC | 3.3v电源 |
SCL | 接PA5 |
SDA | 接PA7 |
RES | 接PB0 |
DC | 接PB1 |
CS | 接PA4 |
BL | 空/3.3v |
MAX7219点阵屏 | stm32 |
---|---|
Din | A6 |
Cs | B13 |
Clk | B14 |
LED | stm32 |
---|---|
R | B10 |
G | B11 |
B | B13 |
摇杆模块 | stm32 |
---|---|
X | A0 |
Y | A1 |
Z | C15 |
喇叭 | stm32 |
---|---|
红 | A2 |
黑 | GND |
二 实现过程
本章详解每一步如何实现
0 前置基本外设驱动
基础驱动实现,本文不在赘述。详细解释请跳转以下文章:
MAX7219 & IIC过程
TFTST7735 & SPI过程
串口收发数据 & UART协议
PWM脉宽调制
AD模数转换
1 声音控制
本节解释如何利用pwm发出不同频率,响度的声音
(1)声音特性
音调:指声音的高低,由物体振动的频率决定,频率越高,音调越高;频率越低,音调越低
响度:表示声音的大小或强弱,由振幅和人耳与声源的距离决定。振幅越大,距离越近,响度越大;振幅越小,距离越远,响度越小
声音的物理量
频率:单位时间内物体振动的次数,单位为赫兹(Hz),人类能听到的音频范围是20Hz~20kHz。
周期:物体完成一次振动所需的时间,单位为秒(s),与频率成反比。
(2)PWM控制声音
控制发出不同声音,就是是控制声音不同的音调
与响度
,也就是频率
与声音大小
。回想使用PWM控制舵机时,舵机频率是固定的50Hz,通过改变占空比控制舵机角度。而控制声音完全不同:
- 第一:需要生成
多种PWM频率
- 第二:修改占空比,仅能改变
声音大小
,不改变音调频率。
故而在使用PWM播放音乐是,一定会面临需要经常变化PWM频率的问题,而我们知道:
PWM频率=(定时器频率/PSC)/ARR = 定时器频率/(PSC * ARR)
所以改变PWM频率,本质是不断修改PSC或ARR的值,为了简便二者改一个就可以。如:把pwm频率
修改为:fre
,则只要令
ARR=(定时器频率/PSC)/fre
PSC不变,故分子为常数。以72Mh
时钟频率,PSC=72
为例,修改PWM频率代码为:
/*
功能:运行中,修改PWM的频率
参数:修改后的频率 fre
公式:PWM频率=(定时器频率/PSC)/ARR = 定时器频率/(PSC * ARR)
*/
void PWM_SetFrequency(uint16_t fre) {int arr=72000000/72/fre;TIM2->ARR = arr;
}
正常人耳能听到声音的频率范围:大致在20Hz~20000Hz之间
108键钢琴的频率范围:是从最低音A0的27.5Hz到最高音C8的4186.01Hz
(3)代码核心部分
PWM.h
#ifndef __PWM_H
#define __PWM_H
/*
Tim2 4通道可控制4路PWM信号,使用通道3
PWM输出引脚:A2
*/
void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare); //改声音大小 1-999
void PWM_SetFrequency(uint16_t fre); //改声音发声#endif
PWM.c 初始设定
#include "stm32f10x.h" // Device header
#include "PWM.h"
/*
功能:PWM初始化
*/
void PWM_Init(void)
{//仅举核心部分TIM_TimeBaseInitStructure.TIM_Period = 1000 - 1; //初始给个1000TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //分频后为1MH
}
/*
功能:TIM2通道3-A2,占空比 (改变每个周期内高电平或低电平的持续时间)
参数:Compare要写入的CCR的值,范围:0~20000 ,即为ARR范围
*/
void PWM_SetCompare3(uint16_t Compare)
{TIM_SetCompare3(TIM2, Compare); //设置CCR1的值,精确地设置高电平持续时间
}
/*
功能:运行中,修改PWM的频率
参数:计数周期,需要的频率
公式:PWM频率=(定时器频率/PSC)/ARR = 定时器频率/PSC * ARR
*/
void PWM_SetFrequency(uint16_t fre) {int arr=1000000.0/fre;TIM2->ARR = arr;
}
2 乐谱录入&基础乐理
数字简谱阅读基础必备:
基础乐理
(1)音符规律
由于音符间的音符十二平均律
,相邻键(含黑键)频率乘以1.0594630943
(二的十二次方根),频率大一度为2倍
关系,低一度除以2。故只要确定1=?
初始调性,即可计算出剩余108键的频率值,存入一个二维数组即可,大小为[9][12]
,代表0-8 九个度数区间
的1-7音符频率、以及中间的黑键频率
,对应钢琴9个区,每个区为12个键。
(2)大小调
大小调简单理解为就是一种1-7不同位置
的按法
,大小调频率表相同,不需要重新计算。区分
大小调只需在代码种只需要加入2个映射数
组即可(20种弹法也没有问题)
计算频率参考全全局变量值:
//------------------------计算频率参数-------------------
/*
音符数组-标准钢琴108键
1 8个度区,升降度直接改一维下标
2 每个度区,含cdefgab以及降半调共12个全黑白键,设计一映射数组1-7到第二维度,降半调+10计算
3 乐谱4位十进制:半调(0-2) 音调:1-7 音度:0-8 (大1则高8度) 时长:1-2-4 拍数
*///计算频率参考数组
uint16_t note[9][12]={//C C# D D# E F F# G G# A A# B{16,17,18,19,20,21,23,24,25,27,29,30}, //0 八度区 {32,34,36,38,41,43,46,48,51,55,58,61}, //1{65,69,73,77,82,87,92,97,103,110,116,123},//2{130,138,146,155,164,174,785,196,207,220,233,246}, //3{261,277,293,311,329,349,369,392,415,440,466,493}, //4{523,554,587,622,659,698,739,783,830,880,932,987}, //5{1046,1108,1174,1244,1318,1396,1480,1568,1661,1760,1864,1975},//6{2093,2217,2349,2489,2637,2793,2960,3136,3322,3520,3729,3951},//7{4186},//8 C8
};
//在note中找值的映射数组
uint8_t book_note[8]={0,0,2,4,5,7,9,11};//计算107个频率生成数组,存入此处,一曲一更,覆盖。
uint16_t note2[9][12]={0};//大调映射 0-0 1-0 2-2 3-4 4-5 5-7 6-9 7-11(下一个1略) 全全半全全全(半),+2 +2 +1 +2 +2 +2 +(1)
uint8_t major_scale[8]={0,0,2,4,5,7,9,11};//
//小调映射 0-0 1-0 2-2 3-3 4-5 5-7 6-8 7-10(下一个1略) 全半全全半全(全)+2 +1 +2 +2 +1 +2 +(2)
uint8_t minor_key[8]={0,0,2,3,5,7,8,10};
生成对应调性如下:另外107键的频率过程:
/*
功能:制造专属歌曲的频率数组
参数Scale:音调 C——B,c-b 大小调
参数dp: 0-1-2 降半调,无,升半调
参数degree:度数0-8
解释:适配 'do'=(C-B,c-b,#/b,0-8)全音调,108键全区每次播放歌曲前,调用此函数,计算出合适的频率并保存例:make_scale(C,1,4) do=C4make_scale(D,2,5) do=D#5
原理:根据一个参考数组,算出当前调性下的初始'do'的频率,往后排11个键,就是该度下所有键上下跨度,乘2或除2就可以得出。
*/
void make_scale(int Scale,int dp,int degree){//大小调do频率固定int t;double ans=1.0594630943;double tem=0;if(Scale=='a'||Scale=='b') //定初始调位置1-7t=6+Scale-'a';else t=Scale-'c'+1;if(Scale=='A'||Scale=='B')t=6+Scale-'A';else t=Scale-'C'+1;t=book_note[t]+dp-1; //定do的位置,dp-1 -1 0 1,计算升降调tem=note[degree][t];//不管大小调,初始do频率相同,变化都的是2-7位置,也就是2个映射数组//degree度,12键音note2[degree][0]=tem;for(int i=1;i<12;i++){tem*=ans; //保留进度note2[degree][i]=round(tem); //四舍五入}//degreee-1~0for(int i=degree-1;i>=0;i--)for(int j=0;j<12;j++)note2[i][j]=note2[i+1][j]/2;//degreee+1~8for(int i=degree+1;i<=8;i++)for(int j=0;j<12;j++)note2[i][j]=note2[i-1][j]*2;
}
(3)曲谱录入
由于本项目对于音准的高要求,所以简谱中每个音符包含4个信息:是否升降半调
、本身音调
、是否升降度
、节拍
。不需要引入字母,使用4位数字记录,个十百千
位的数字记录信息,做法类似于寄存器2进制写入数据。
- 千:0 1 2
降/平/升
半调 (升降半调,即#/b,输出数组后一位或前一位元素) - 百:频率 1-7
- 十:度:0-8度 (升度频率乘2)
- 个:记录节拍 1 2 4 (根据曲速可计算
一拍时长t
,t除以4/(1、2、4)便是该音符发声时长,为了方便记忆,否则设为421也可以,本质是得出有几个最小一拍
) - 曲速:如曲速为112,则一拍时长为:60/112 s
如实际谱子:
/*
曲1:Dream of cherry tree
1=Db 大调 beat=68 4/4 68太慢了,提高到80
*/
double beat1=60.0/80*1000; //DC曲速 原曲:60.0/68
uint16_t song_DC[]={1351,1251,1352,1552,1351,1351,1051,1041,1251,1351,1251,1352,1552,1161,1161,1061,1162,1752,1652,1552,1351,1251,1352,1552,1351,1351,1051,1542,1642,1152,1252,1351,1251,1352,1552,1361,1361,1061,1262,1162,1752,1552,1651,1051,1551,1051,1451,1551,1652,1552,1351,1251,1051,1152,1742,1642,1152,1741,1041,1041,1041,1552,1652,1752,1162,1651,1652,1352,1451,1551,1652,1452,1351,1251,1042,1352,1742,1252,1151,1152,1644,1744,1152,1742,1152,1252,1352,1252,1251,1351,1251,1352,1552,1361,1042,1262,1162,1752,1652,1552,1551,1351,1251,1352,1552,1361,1042,1462,1362,1262,1162,1752,1751,1042,1752,1162,1262,1161,1162,1552,1651,1652,1452,1552,1452,1352,1252,1151,1051,1051,1051
};
(4)播放声音
遍历乐谱数组,依次播放每个音符即可。下为播放一个音符的函数:
/*
功能:按照时长演奏一个音符(不含点阵屏变化)
参数ans:复合音符
参数btspeed:一拍的曲速单位:ms
参数scale:大调1 小调0
解释:根据音符信息,演奏音符,oled显示参数无灯模式效果精准,音长更及时
*/
void unlight_Sound(int ans,int btspeed,int scale){int half=ans/1000; //0 1 2 降半音b 正常 升半音#int msc=ans/100%10; //1-7 ,不含0 8int degree=ans/10%10; //0-8 度int tm=ans%10; //节拍 1 2 4 int fre;//<1> fre为获取二维数组下标,1-7与大小调映射if(scale) fre=major_scale[msc]; //大小调-判断else fre=minor_key[msc];if(half-1==1) fre++; //<2> 判断是否升降半调else if(half-1==-1) fre--; // -1 0 1 升/降半调,前一个或后一个元素if(msc==0) PWM_SetCompare3(0); //<3>修改声音大小,空音设占空比0else PWM_SetCompare3(voice);PWM_SetFrequency(note[degree][fre]); //<4>设置声音频率发声tm=btspeed/tm; //<5>控制发声时长Delay_ms(tm); //OLDED参数显示OLED_ShowString(3,7,"degree:"); OLED_ShowNum(3,14,degree,1);OLED_ShowString(4,1,"fre:");OLED_ShowNum(4,5,note[degree][fre],4);OLED_ShowString(4,10,"msc:");OLED_ShowNum(4,14,msc,1);//PWM_SetCompare3(0);//每个音符后额外停顿,曲谱精准时,无需设置//Delay_ms(5);
}
此函数仅用于播放声音,不控制点阵屏,注意与下面函数区分
3 点阵屏谱点动态刷新
点阵屏8*8XY轴代表信息:
X:1-7列对应音符1-7
Y:每一行代表音符发声时长
,一行为最小节拍,通过60/曲速计算得出,Delay进行控制
扫描线会从上至下扫描,碰到音符
,使其消失
,和乐谱中每个音符的时长对应同步。此算法设计包含以下部分:
- 扫描线
变化
与乐谱的声音长度同步
(以Delay延时最小节拍为基础) - 当扫描线达到
顶端
-立刻向后更新一屏的音符信息(非同步,而是某时刻
更新) 预处理
曲谱中每一个音符的显示信息(代码计算处理,人无需计算)- 恢复被截断的底部音符保证正确性(准确性)
(1)预处理乐谱每个音符的点阵信息
预处理
每个音符在点阵屏上出现的起始行
与结束行
位置,存入标记数组msc_sort中。用于后续输出某音符在点阵屏上的具体位置
,注意截断:
/*
功能: 并预处理曲铺每个音符灯点位置信息
参数: 1-3,播放歌曲前,先调用此函数
解释:msc_sort数组覆盖,每次播放前都需调用获取每个音符在点阵屏上 出现的起始行与结束行位置,存入msc_sort*起始不可超过8,结尾可大于等于8,会在动态更新点阵屏时利用结尾特性,判断触底截断音符
*/
void load_msc(int num){int tem,mucis_num; if(num==1){mucis_num=sizeof(song_DC)/sizeof(song_DC[0]); //音符数msc_sort[0][0]=1;msc_sort[0][1]=4/(song_DC[0]%10);for(int i=1;i<mucis_num;i++) //每一个的值,依据前一个结尾计算{tem=msc_sort[i-1][1];if(tem>=8) tem=tem%8+1;else tem+=1;int r=4/(song_DC[i]%10); //当前音符所占1/4节拍数,就是需要几个点点亮msc_sort[i][0]=tem; //起始必须小于等于8msc_sort[i][1]=tem+r-1; //结尾可以大于等于8}}if(num==2){//同上}if(num==3){//同上}
}
(2)显示单个音符谱点函数
欲显示整个乐谱,需先从显示一个音符开始:
/*
功能:显示一个音符的位置
参数k:曲库中下标,音符
参数ans:复合音符值
介 绍:在预处理数组中记录了开始点与结束点,此处显示时超过底部不予显示在动态更新乐谱时,在对确实的另外半截手动设置填补,否则会覆盖第一行
*/
void note_show(int k,int ans){int t=4/(ans%10); //音符长度int x=ans/100%10; //音符0-7int y=msc_sort[k][0]; //音符起始行,不会超过8//向下显示t个音符,超过不显示,所以不用msc_sort[1]for(int i=0;i<t;i++){Max7219_ShowGraph(y,x,x);y++;if(y>8) break; //先显示一次,在结束}
}
需注意,由于音符触底超长部分,需要截断,留在下一次显示,所以超过第8行不显示。
补充截断部分
已在对应处单独编写代码。
(3)扫描线、声音、音符点、同步方式(核心)
此处与上述unlight_Sound数组区别是,添加的while循环部分
。单纯播放声音,只需要PWM写入频率
然后延时
即可。但添加乐谱后,由于需要控制扫描线
与音符声长
同步,以2个最小节拍长的音符为例,则实际过程为:
PWM
发声
->扫描线1出现->Delay最小节拍1->扫描线1消失->扫描线2出现->Delay最小节拍2->扫描线2消失。
这样可以做出声音与点阵屏同步更新
的效果,以最小延时
为基础,人眼是察觉不到先后顺序的,形成声画同步效果。其次是,每个音符的谱点并非是:只有播放到该音符,才更新一个
,而是当扫描线出现在顶部
时,一次提前更新一屏幕
的内容,这样才有提前读谱的效果(类似全民k歌),而判段什么时候到达底部,以及如何恢复被截断的音符部分:通过预处理的谱点数组msc_sort
实现。
/*
功能:按照时长演奏一个音符,扫描线对应同步变化
参数1:音符编号
参数3:复合数组 (需要预处理点阵,所以要传递数组)
参数3:beat节拍速度
参数4:大调1,小调0
解释:扫描线于声音同步,乐谱点是在每次扫描到第一行时更新满屏。扫描线:与声音同步,每个最小节拍间隔,点阵屏都要亮灭一次乐谱点阵显示:每次到第一行时,刷新一整个点阵屏的音符点,向后遍历遇到底部停止难点在于补全上一次末尾超长截断的灯点
*/
void light_Sound(int k,uint16_t *ans,int btspeed,int scale){int half=ans[k]/1000; //0 1 2 降半音b 正常 升半音#int msc=ans[k]/100%10; //0-7int degree=ans[k]/10%10; //0-8 度int tm=4/(ans[k]%10); //几个1/4节拍 1 2 4 int fre; //<1> fre为获取二维数组下标,1-7与大小调映射if(scale) fre=major_scale[msc]; //大小调-判断else fre=minor_key[msc];if(half-1==1) fre++; //<2> 是否升/降半调,-1 + 1前一个或后一个元素else if(half-1==-1) fre--; if(msc==0) PWM_SetCompare3(0); //<3> 音量设置,注意0不发声else PWM_SetCompare3(voice);PWM_SetFrequency(note[degree][fre]); //<4>发声int y=msc_sort[k][0]; //扫描线位置 1-8行while(tm--) //tm个最小节拍{if(y==1){ //<5> 扫描到第一行,立刻向后更新一屏的乐谱点阵for(int i=0;i<8;i++){if(i==0){ //当前字符是否为被截断字符,并恢复截断长度if(msc_sort[k][1]>=8){ //避免连续出现一个长度,无法到达下一个,从而断屏for(int n=1;n<=msc_sort[i][1]-8;n++)Max7219_ShowGraph(n,msc,msc);}elsenote_show(k,ans[k]);}else{note_show(k+i,ans[k+i]); //显示单个字符的点阵if(msc_sort[k+i][1]>=8) //到底,结束,超长部分不显示break;}}}Max7219_ShowGraph(y,1,8); //<6>扫描线随 最小节拍更新Delay_ms(btspeed/4.0); //发音,同时亮灯 Max7219_Clearline(y);y++; //扫描线位置更新,最多到9if(y>8) y=y%8; //超过8,则%8为1-7,不能在加1,会跳过空行}OLED_ShowString(3,7,"degree:");OLED_ShowNum(3,14,degree,1);OLED_ShowString(4,1,"fre:");OLED_ShowNum(4,5,note[degree][fre],4);OLED_ShowString(4,10,"msc:");OLED_ShowNum(4,14,msc,1);//PWM_SetCompare3(0);//每个音符间额外停顿//Delay_ms(5);
}
此处为 模拟过程,想要直接理解有难度,最好自己去编写尝试
(4)完整播放乐曲函数
最终封装遍历音乐的函数,可设置是否打开点阵
屏模式与大小调
选择,由于基于最小延时节拍
,同步点阵与声音,所以会占据部分cpu性能,若只需播放音乐的话,可设置关闭。
/*
功能:播放音乐
参数1:曲目1-3
参数2:1 灯光 0 非灯光
参数3:1 大调 0 小调
解释:可选择灯光模式
*/
void Play_msc(int num,int mode,int scale){//可调控曲速if(num==1){int r=sizeof(song_DC)/sizeof(song_DC[0]);if(mode){for(int i=0;i<r;i++) //有动态灯谱light_Sound(i,song_DC,beat1,scale); //<2>修改2,大小调,1为大PWM_SetCompare3(0);//播放完静音}else{for(int i=0;i<r;i++)unlight_Sound(song_DC[i],beat1,scale);PWM_SetCompare3(0);}}if(num==2){//同上}if(num==3){//同上}
}
变速: 如一首曲子需要
多个曲速
,可以在对应for循环内部中判断,当到达某个音符时 ,改变beat值接口,加几个if语句即可解决。
4 项目交互控制
本质为利用摇杆8个模拟值区间(上下左右与斜对角),控制8个不同模式,利用不同flag状态
控制界面
rocker_music
函数效果为,根据AD采集摇杆信息,判断摇杆位置
0-8值,来播放不同的音调1-7
TFT_update
效果为:根据不同数字,显示不同的文字内容
AD摇杆模块的实现,过于基础不再描述。包括设置全局变量声音、串口通信获取信息、TF图片文字显示。
/*接线
1 串口连接:RX-A9 TX-A10
2 OLED连接:SCL-B8 SDA-B9
3 TFTST7735:GND 电源地VCC 3.3v电源SCL 接PA5SDA 接PA7RES 接PB0DC 接PB1CS 接PA4 BL 空/3.3v
4 MAX7219点阵屏: Din-A6 Cs-B13 Clk-B14
5 LEd_RGB: B10 B11 A3
6 摇杆:XYZ: x-A0 y-A1 z-C15
7 喇叭:A2 (另一根线接GND)
*/
#include "stm32f10x.h" // USE_STDPERIPH_DRIVER
#include "delay.h" //--no-multibyte-chars
#include "OLED.h"
#include "Serial.h"#include "LED.h"
#include "KEY.h"
#include "AD.h"
#include "PWM.h"#include "St7735tft.h" //TFT
#include "Max7219.h" //点阵屏
#include "playmusic.h" //主要功能集成函数int main(void)
{AD_Init();PWM_Init();LED_Init();KEY_Init();TFT_Init();Max7219_InitDisplay();OLED_Init();Serial_Init();TFT_UI(); //显示自定义界面OLED_ShowString(1,1,"SKY_Music");int z,m1,mode=0; //z轴状态,m1与mode用来记录摇杆位置int falg_rocker=0;//摇杆标记/*C-B调全升降调打表,以do=4度为例,最大支持0-8度区间for(int i='A';i<='G';i++){for(int j=0;j<=2;j++)make_scale(i,j,4);Serial_Printf("\r\n");}*/while (1){//可随时串口更新音量if(Serial_RXString())set_voice();z=KEY_Getnum(); //z轴状态OLED_ShowString(3,1,"Z:");OLED_ShowNum(3,3,z,1);//自由编曲独占模式if(falg_rocker){if(z==1) falg_rocker=0;rocker_music();continue;}//记录摇杆值m1=Get_rocker(); //摇杆一松手会归0,所以需要2个值记录,无法及时TFT_update(m1);if(m1!=0) mode=m1; //记录变化的操作/*@@@@@串口测试,获取值Serial_Printf("z=%d m1=%d mode=%d\r\n",z,m1,mode); *///调用功能if(z&&mode==1){ //上:DC Dream of cherry treeload_msc(1);//载入歌曲make_scale('D',DOWN,4); //调性D,b降半调,度数4 1=Db4Play_msc(1,light_Turn,MAJOR); //曲目编号,灯光模式,MAJOR大调}else if(z&&mode==2){ //左上:add声音lower_voice();TFT_update(2);}else if(z&&mode==3){ //左:克罗迪亚load_msc(2);make_scale('E',DOWN,4); //1=Eb4,小调Play_msc(2,light_Turn,MAJOR); }else if(z&&mode==4){ //左下:low声音upper_voice();TFT_update(4);}else if(z&&mode==5){ //下:梦回还load_msc(3);Delay_ms(400);//曲目长,更新下数组make_scale('A',NORMAL,4); //A4 不降调Play_msc(3,light_Turn,MAJOR);}else if(z&&mode==6){ //下右:空}else if(z&&mode==7){ //右: 自由摇滚-独占,rocker音乐falg_rocker=1; }else if(z&&mode==8){ //右上:灯光模式light_Turn=!light_Turn;TFT_update(8);LED_On(1);Delay_ms(100);LED_On(2);Delay_ms(150);LED_On(3);Delay_ms(200);LED_Off(1);LED_Off(2);LED_Off(3);}}
}//摇杆的按钮挂个中断,及时反应
void EXTI15_10_IRQHandler(void){ //(5)创建中断函数//startup_stm32f0x_md.s找——->Table Mapped,DCD栏以EXTI_IRQHandler结尾//判断是不是15通道,exti.h————>EXTI_GetITStatus与EXTI_ClearITPendingBit函数if(EXTI_GetITStatus(EXTI15_10_IRQn)==RESET){EXTI_ClearITPendingBit(EXTI_Line15);//清除标志位Serial_Printf("Z按钮进入中断\r\n");}
}
5 录入新曲子过程
(1)录入乐谱
进入Song.h文件:
- 乐谱数组
覆盖
3首中随机一首的信息即可。 - 修改对应beat曲速,数字简谱会有曲速说明
以一小节为例,4/4拍:
录谱过程:
实际录谱时,使用
语音识别输入
,与excel
批量处理数字数据,很快捷,因为规律十分的简单
(2)修改带调性函数
进入main.c文件,修改对应调性
,大小调
参数即可:
//根据编号信息修改
else if(z&&mode==3){load_msc(2);make_scale('E',DOWN,4); //1=Eb4, 改A-G,DOWN/NORMAL/RISE 降平升半调,初始度数0-8(一般为4)Play_msc(2,light_Turn,MAJOR); //改大小调,MAJOR或MIRROR }
#为升:使用RISE
b为降:用DOWN
否则:NORMAL
A-G大小写通用
大调:MAJOR
小调:MIRROR
三 展示,与链接视频地址
本章内容为:主要功能函数展示与实现效果,附上下载链接
1 主要功能函数一览
#ifndef __PLAYMUSIC_H
#define __PLAYMUSIC_H/*song中引用文件:
note[9][12] 频率文件
book_note[8] 映射
song_DC[] 曲库1
song_Croatia[] 曲库2
song_DreamBack[] 曲库3
beat1 曲1节拍
beat2
beat3
voice 声音
*/#define RISE 2 //升半调
#define NORMAL 1
#define DOWN 0 //降半调
#define MAJOR 1 //大调
#define MINOR 0 //小调//playmusic定义
extern int light_Turn; //开关灯光模式
extern uint16_t msc_sort[800][2]; //预处理:每个音符在点阵屏初始位置信息//----------------功能函数-----------------------
//音乐播放
void set_voice(void); //串口准确更新声音1-999
void lower_voice(void); //按一下,+200
void upper_voice(void);void load_msc(int num); //载入曲谱灯铺信息
void make_scale(int Scale,int dp,int degree);//制作不同曲目调108键调性表
void rocker_music(void);//摇杆音乐
void Play_msc(int num,int mode,int scale); //曲目编号,灯光模式0-1//TFT
void TFT_UI(void);
void TFT_update(int k);//-------------------内部函数-----------------------
//灯
void unlight_Sound(int ans,int btspeed,int scale); //音符无灯
void light_Sound(int k,uint16_t *ans,int btspeed,int scale); //音符带灯,动态更新乐谱
void note_show(int k,int ans); //显示单个音符的点阵(超过底部部分不显示)#endif
2 下载链接
点击下载源文件:
与君共勉
3 视频效果
stm32自制可视化乐谱108键钢琴,小白轻松奏神曲