笔记:C51单片机——音乐播放,模拟钢琴键。

首先知道单片机的蜂鸣器是有源蜂鸣器,可以利用修改频率来对其蜂鸣器改变发出不同的声音频率。

但是修改频率这个怎么修改呢。可以通过延迟修改,但是如果有一个钢琴乐谱在这里,就不知道呢个延迟对应着哪个按键。而且一个个延迟可能会导致代码太多,复用性也不好。

所以就可以通过定时器来代替延迟函数,而且这样子也省去了,占用运行内存。但是利用定时器也是需要模拟钢琴按键的。模拟钢琴按键怎么模拟呢,下面讲到就是首先知道钢琴按键有

低音,低半,中音,中半音,高音,高半音。半音其实就是黑色的那个就是按键就是对应着它旁边的半音键。

可以看一下这个钢琴谱,这里可以明显的看到低音的是数字底下有黑点,中音没有,高音的是数字头上有黑点,其实钢琴键有很多个键,但是音都是比原来的音高或者低。所以模拟按键只需要用到普遍的D介就可以了。可以看到就是没有黑点开始左边和右边其实只是比中介这个高或低。

还有在低音那里的黑色的键就是对应旁边的键的半音键,意思就是只发出半个声音。其它的都是依次这样的。

认识到了这个琴谱接下来就是怎么识别这个琴谱的频率了。这个频率其实也是有公式的,只需要知道一个琴谱的频率,其它的都可以知道。公式就是知道前一个音的频率。这个频率的后一个音等于前一个音乘2的12分之一次方(前一个频率*2^(1/12))同理知道后一个频率前一个频率等于后一个除以2的12分之一次方(后一个频率/2^(1/12))。但是知道频率之后还不能直接用,还有利用单片机的频率周期然后计算重装载值,所以得到频率之后,计算周期,周期等于=((1/频率*1000000)/2),重装载值就是定时器的65536-周期。所以最后的出一张琴谱模拟频率重装载值就可以直接用到定时器。

 

 上面有个数字旁边的#号就是半音的意思。这个琴谱频率其实就是对应着第一张图片左往有。

认识到这里就大致了解这个琴谱频率怎么转换了。但是转换完了之后,对于乐谱又要看这个音调了。接下来就是怎么看乐谱对应着那个按键了。

先随便符一张琴谱的图片,这个其实怎么看呢。很简单的。只需要看着下面的数字就可以知道是那个按键了。就比如第一个6 7 底下有一行的就是音乐播放时间减半。而且这个6旁边没有点就是中介音的6音时长减原来的一半,然后7也是同理。在过去就是1这个1头顶有小点的就是高介的1而且右边有点就是把播放时长加原来的一半。在在过去的7就是中介时长减半。然后 1 3头顶有点就高介,正常的时长。这里没有减半和加半。再过去就可以看到7有边有这个符号— ,这个就是加时长的意思,而且是加一个正常时长 比如说正常是4 那么就是4+4+4因为自身正常是4然后有两个个—所以就是4 +4+4。再下去第二行可以看到有一个#号这个意思就是说的升半音的意思,#4 —在底下意思就是4中音升半介然后时间减半。还有第二行后面头顶有个半括号的意思就是延音键,就是把按键的时长加长,而且是在一次按下的意思,而不是分开两次按。两个7 7头顶一个括号)。就是中音一个时长减半所以剩下2+4,后面的7没有减半。

看着上面有点乱,所以下面总结了一下。

(1)#是升半音 而且在同一个小节的音调同样的音调都要升,同一个小节的意思就是用  |   分开为一个小节

(2)b是减半音

(3)一个—在有右边是加一个自身时长

(4).在右边加原来的2分之一时长

(5)底下加—就是减半时长

(6)头顶上( 是延音键 的意思就是两个音的时长相加在一个音上比如 高音2+4

(7).在头顶是高音

(8).在地下是低音

(9)无点是中音

(10)0就是休止的意思,就是停止按键的时长

 

接下来就是代码实现了

首先是定时器

void Time_Init(void)
{
    TMOD&=0xf0;   //把TMOD低四位清0,高四位保持不变
    TMOD|=0x01;  //把TMOd低四位置1,高四位保持不变
    TMOD=0X01;       //设置定时器/计数器模式  
	
    TL0=0x66;	   //1ms	
	TH0=0xfc;   

	TF0=0; //定时器中断要清零,意思是当我定时器设置的数到了,即将溢出我就要清零,开始执行设置的任务
	TR0=1;		//定时器启动开始工作

	ET0=1;		//中断允许控制位 128 64 32 16   8 4 2 1 
	EA=1;	   //开启中断电路
	PT0=0;	   //接通外部中断源

}

定时器中断代码,这里只有一个函数,其实就没到1ms就进入一次这个函数意思,执行这个函数里面的代码

void time1_Init()  interrupt 1	   //这个是调用定时器模板每进入一次都记1ms
{
	  Beep_Loop();
	 
} 

这个就是每次进入重装载值得时间了。

void Beep_Loop(void)
{
   if(Beep_frep[Freplect])		  //用来休止符的,如果位置的符号是选择这个数组的 Beep_frep[] 第0个则不进入定时器计时
	  {
		TL0=Beep_frep[Freplect]%256;	  //低位
		TH0=Beep_frep[Freplect]/256;     //设置高位每次进入的初始值   因为这个没有设置停止位  只能每次溢出的时候才会重新进入,所以要找到定时器的时间只能设置初始值,就是在中间截取到溢出停止
    	Beep1=!Beep1;        //按照上面得频率蜂鸣器响
	   }

}

然后钢琴谱的代码也不是很难,只需要把上面的重装载值做成一个数组。就像这样,说一下为什么要用到重装载值呢,就是因为定时器要计算这个数的频率,但是这个定时器每次都是溢出它才会重新计算,但是不能再溢出阻止它的时长度,那么就可以在它进入的时间里控制,也是一样达到控制时长的。就好比本是1ms时间溢出,但是控制不可了到0.5ms溢出就停止,那么我可以在0.5ms的时候再让它进入,一样可以达到让它计算0.5ms 这样也是一样达到0.5ms的效果。这就是重装载值得意思。

unsigned int code Beep_frep[]=
{	  0,63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64466,64528,
      64580,646353,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
	  65058,65085,65110,65134,65158,65178,65198,65217,65235,65252,65268,65283

};
//这里是按照低音到高音排序的。
//有一个0等下选键的时候要用到的。

定义完重装载值的数组,然后就是选择数组,选择的意思就是选择按那个音键。可以这样定义一个宏定义,这样可以达到复用的效果

#define Stop  0         //这个0就是选择重装载值的第0个位置
#define L1  1           //下面的一次是低音,然后中音,高音,也是按照琴谱的位置来放的
#define L1_ 2
#define L2  3
#define L2_ 4
#define L3  5
#define L4  6
#define L4_ 7
#define L5  8
#define L5_ 9
#define L6  10
#define L6_ 11
#define L7  12
//中音
#define M1  13
#define M1_ 14
#define M2  15
#define M2_ 16
#define M3  17
#define M4  18
#define M4_ 19
#define M5  20
#define M5_ 21
#define M6  22
#define M6_ 23
#define M7	24
//高音
#define H1  25
#define H1_ 26
#define H2  27
#define H2_ 28
#define H3  29
#define H4  30
#define H4_ 31
#define H5  32
#define H5_ 33
#define H6  34
#define H6_ 35
#define H7	36

下面就是宏定义数组得代码了。这个数组就是用来存放需要按下得那个按键了。只要按照琴谱修改这个数组得定义,就可以得到一首新的曲子了。

 unsigned int  code Beep_freplectMusic[]={ 
	 //音符是钢琴谱C调的4/4	  把1/16作为基准1   1/8则是2   1/4是4   1/2是8   1是16
	 //0是休止符用于选中Beep_frep[]的第0位

	//1行
//音频位选择   音频时长             里面都是写的宏定义得值,也就是重装载数组得第几个位置。后面得数字就是要播放得时长了。
	Stop,4,
	Stop,4,
	Stop,4,
	M6,2,
	M7,2,

	H1,4+2,
	M7,2,
	H1,4,
	H3,4,

	M7,4+4+4,
	M3,2,
	M3,2,

//2行

	M6,4+2,
	M5,2,
	M6,4,
	H1,4,

	M5,4+4+4,
	M3,4,

	M4,4+2,
	M3,2,
	M4,4,
	H1,4,

//3行
    
	M3,4+4,
	Stop,2,
	H1,2,
	H1,2,
	H1,2,

	M7,4+2,
	M4_,2,
	M4_,4,
	M7,2,

	M7,4+4,
	Stop,4,
	M6,2,
	M7,2,

//4行
	H1,4+2,
	M7,2,
	H1,4,
	H3,4,

	M7,4+4+4,
	M3,2,
	M3,2,

	M6,4+2,
	M5,2,
	M6,4,
	H1,4,

//5行

	M5,4+4+4,
	M2,2,
	M3,2,

	M4,4,
	H1,2,
	M7,2+2,
	H1,2+4,

	H2,2,
	H2,2,
	H3,2,
	H1,2+4+4,

//6行
	
	H1,2,
	M7,2,
	M6,2,
	M6,2,
	M7,4,
	M5_,4,

	M6,4+4+4,
	H1,2,
	H2,2,

	H3,4+2,
	H2,4,
	H3,4,
	H5,2,

//7行

	H2,4+4+4,
	M5,2,
	M5,2,

	H1,4+2,
	M7,2,
	H1,4,
	H3,4,

	H3,4+4+4+4,

//8行

	M6,2,
	M7,2,
	H1,4,
	M7,4,
	H2,2,
	H2,2,

	H1,4+2,
	M5,2+4+4,

	H4,4,
	H3,4,
	H2,4,
	H1,4,

//9行
	H3,4+4+4,
    H3,4,

	H6,4+4,
	H5,4,
	H5,4,

	H3,2,
	H2,2,
	H1,4+4,
	Stop,2,
	H1,2,

//10行

	H2,4,
	H1,2,
	H2,2,
	H2,4,
	H5,4,

	H3,4+4+4,
	H3,4,

	H6,4+4,
	H5,4+4,

//11行

	H3,2,
	H2,2,
	H1,4+4,
	Stop,2,
	H1,2,

	H2,4,
	H1,2,
	H2,2+4,
	M7,4,

	M6,4+4+4,
	M6,2,
	M7,2,
	0xff	    //音乐停止符

};

然后接下来就是主要得选择位置得代码了,不明白可以看注释的

void Beep_music_Init(unsigned int  SPEED)
{
	 if(Beep_freplectMusic[Musiclect]!=0xff)	   //播放音乐就停止   这个在宏定义数组的最后一个位置,只有把这个曲子播放一遍就会轮到后面的这个0xff
		   {	
		        Freplect=Beep_freplectMusic[Musiclect];	   //这里开始的是数组0值每次轮回都是双数 用来控制Beep_freplectMusic[双数位置]播放那个音乐的调  ,可以看到双数的位置都是调
				Musiclect++;
				delay(SPEED/4*Beep_freplectMusic[Musiclect]);  //到这里是数组的1值,每次轮回都是单数是控制Beep_freplectMusic[单数位置]音乐每个频率的时长  ,可以看到单数位置都是播放的时长
		    	Musiclect++;
				TR0=0;	     //设置模拟钢琴抬手关闭在打开  ,如果没有这个延迟的定时器开关,听到声音就会很连贯,没有断点。
				delay(5);
				TR0=1;	
			}
			else
			{			 
			  	TR0=0;		//关闭定时器音乐就停止
			}				  
		}

上面的是封装好的代码。下面的是直接在一个main.c文件的代码

#include <REGx52.H>

#define Stop  0
#define L1  1
#define L1_ 2
#define L2  3
#define L2_ 4
#define L3  5
#define L4  6
#define L4_ 7
#define L5  8
#define L5_ 9
#define L6  10
#define L6_ 11
#define L7  12
//中音
#define M1  13
#define M1_ 14
#define M2  15
#define M2_ 16
#define M3  17
#define M4  18
#define M4_ 19
#define M5  20
#define M5_ 21
#define M6  22
#define M6_ 23
#define M7	24
//高音
#define H1  25
#define H1_ 26
#define H2  27
#define H2_ 28
#define H3  29
#define H4  30
#define H4_ 31
#define H5  32
#define H5_ 33
#define H6  34
#define H6_ 35
#define H7	36


sbit Beep1=P1^5;         
unsigned int  Freplect=0,Musiclect=0;


 void Time_Init(void);
 void Beep_Loop(void);
 void delay(unsigned int xms);


unsigned int code Beep_frep[]=
{	  0,63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64466,64528,
      64580,646353,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
	  65058,65085,65110,65134,65158,65178,65198,65217,65235,65252,65268,65283

};
//这个数组是用来选择Beep_frep[]数组的位置,以更好的确认音乐频率
 unsigned int  code Beep_freplectMusic[]={ 
	 //音符是钢琴谱C调的4/4	  把1/16作为基准1   1/8则是2   1/4是4   1/2是8   1是16
	 //0是休止符用于选中Beep_frep[]的第0位

//音频位选择   音频时长
	Stop,4,
	Stop,4,
	Stop,4,
	M6,2,
	M7,2,

	H1,4+2,
	M7,2,
	H1,4,
	H3,4,

	M7,4+4+4,
	M3,2,
	M3,2,

//2行

	M6,4+2,
	M5,2,
	M6,4,
	H1,4,

	M5,4+4+4,
	M3,4,

	M4,4+2,
	M3,2,
	M4,4,
	H1,4,

//3行
    
	M3,4+4,
	Stop,2,
	H1,2,
	H1,2,
	H1,2,

	M7,4+2,
	M4_,2,
	M4_,4,
	M7,2,

	M7,4+4,
	Stop,4,
	M6,2,
	M7,2,

//4行
	H1,4+2,
	M7,2,
	H1,4,
	H3,4,

	M7,4+4+4,
	M3,2,
	M3,2,

	M6,4+2,
	M5,2,
	M6,4,
	H1,4,

//5行

	M5,4+4+4,
	M2,2,
	M3,2,

	M4,4,
	H1,2,
	M7,2+2,
	H1,2+4,

	H2,2,
	H2,2,
	H3,2,
	H1,2+4+4,

//6行
	
	H1,2,
	M7,2,
	M6,2,
	M6,2,
	M7,4,
	M5_,4,

	M6,4+4+4,
	H1,2,
	H2,2,

	H3,4+2,
	H2,4,
	H3,4,
	H5,2,

//7行

	H2,4+4+4,
	M5,2,
	M5,2,

	H1,4+2,
	M7,2,
	H1,4,
	H3,4,

	H3,4+4+4+4,

//8行

	M6,2,
	M7,2,
	H1,4,
	M7,4,
	H2,2,
	H2,2,

	H1,4+2,
	M5,2+4+4,

	H4,4,
	H3,4,
	H2,4,
	H1,4,

//9行
	H3,4+4+4,
    H3,4,

	H6,4+4,
	H5,4,
	H5,4,

	H3,2,
	H2,2,
	H1,4+4,
	Stop,2,
	H1,2,

//10行

	H2,4,
	H1,2,
	H2,2,
	H2,4,
	H5,4,

	H3,4+4+4,
	H3,4,

	H6,4+4,
	H5,4+4,

//11行

	H3,2,
	H2,2,
	H1,4+4,
	Stop,2,
	H1,2,

	H2,4,
	H1,2,
	H2,2+4,
	M7,4,

	M6,4+4+4,
	M6,2,
	M7,2,
	0xff	    //音乐停止符

};

void Time_Init(void)
{
    TMOD&=0xf0;   //把TMOD低四位清0,高四位保持不变
    TMOD|=0x01;  //把TMOd低四位置1,高四位保持不变
   TMOD=0X01;       //设置定时器/计数器模式  
    

	TL0=0x66;	   //1ms	
	TH0=0xfc;   

	TF0=0;      //定时器中断要清零,意思是当我定时器设置的数到了,即将溢出我就要清零,开始执行设置的任务
	TR0=1;		//定时器启动开始工作

	ET0=1;		//中断允许控制位 128 64 32 16   8 4 2 1 
	EA=1;	   //开启中断电路
	PT0=0;	   //接通外部中断源

}

void Beep_Loop(void)
{
   if(Beep_frep[Freplect])		  //用来休止符的,如果位置的符号是选择这个数组的 Beep_frep[] 第0个则不进入定时器计时
	  {
		TL0=Beep_frep[Freplect]%256;	  //低位
		TH0=Beep_frep[Freplect]/256;     //设置高位每次进入的初始值   因为这个没有设置停止位  只能每次溢出的时候才会重新进入,所以要找到定时器的时间只能设置初始值,就是在中间截取到溢出停止
    	Beep1=!Beep1;
	   }

}

void delay(unsigned int xms)	  //延迟函数  1表示1ms
{
	unsigned char i, j;
	while(xms--){
	i = 2;
	j = 239;
	do
	{
	  while (--j);
	} while (--i); }


}

void main(void)
{

    if(Beep_freplectMusic[Musiclect]!=0xff)	   //播放音乐就停止
		   {	
		        Freplect=Beep_freplectMusic[Musiclect];	   //这里开始的是数组0值每次轮回都是双数 用来控制播放那个音乐的调
				Musiclect++;
				delay(100/4*Beep_freplectMusic[Musiclect]);  //到这里是数组的1值,每次轮回都是单数是控制音乐每个频率的时长
		    	Musiclect++;
				TR0=0;	     //设置模拟钢琴抬手关闭在打开
				delay(5);
				TR0=1;	
			}
			else
			{			  
			  	TR0=0;		//关闭定时器音乐就停止
			}	


}


void time1_Init()  interrupt 1	   //这个是调用定时器模板每进入一次都记1ms
{
	  Beep_Loop();
	 
} 

总之就是一句话,上面的代码忘记了没事,只需要把unsigned int  code Beep_freplectMusic[]数组里面的钢琴键位置按照上面教的对应的钢琴谱来放置位置就可以得到一首新的曲子了。

也就是下面这个,看着谱子按照这个方式来改就没有多大问题。

(1)#是升半音 而且在同一个小节的音调同样的音调都要升,同一个小节的意思就是用  |   分开为一个小节

(2)b是减半音

(3)一个—在有右边是加一个自身时长

(4).在右边加原来的2分之一时长

(5)底下加—就是减半时长

(6)头顶上( 是延音键 的意思就是两个音的时长相加在一个音上比如 高音2+4

(7).在头顶是高音

(8).在地下是低音

(9)无点是中音

(10)0就是休止的意思,就是停止按键的时长

  • 9
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c-tion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值