单片机通过蜂鸣器播放任意音乐代码实现(2):音乐单片机代码自动生成

  • Post author:
  • Post category:其他




单片机通过蜂鸣器播放任意音乐代码实现(2):音乐的单片机代码自动生成

上一节我们已经构建了基于51单片机的蜂鸣器音乐播放环境,接下来只需要手动或者自动添加音乐代码便能实现不同音乐的播放。当然,第二节将告诉你如何自动生成这些音乐代码。



所用软件

  • MuseScore 3;用于转换mid文件为musicxml文件
  • C/C++ IDE(本文为Visual Studio 2019)



1.生成音乐对应的MID文件

可以直接下载mid格式音乐文件,也可以将某段音乐转换为mid文件。最好是直接使用mid格式音乐文件,可以大大节省时间。在本例中使用的便是现成的mid文件。如果你需要将任意音乐转为mid文件,可以参考网络上的教程,本文不再赘诉。下图为本文所用的mid格式文件:

在这里插入图片描述



2.将MID文件转为MUSICXML文件

直接将mid文件拖拽进或是添加进MuseScore 3,打开后如下图所示:

在这里插入图片描述

点击“文件”→“导出”,按照下图设置输出文件:

在这里插入图片描述



3.提取MUSICXML文件关键字,自动生成音乐代码

该部分代码由C语言实现,主要功能是寻找musicxml格式文件中有关音高、音长的关键字,该部分代码如下:

#include <iostream> 
#include<stdlib.h>
int zifuchange(char x);
char word[11]={0};//音符、节拍数组
int yinfushu=0;//音符个数统计
int error=0;//错误计数
int flag=0;
int jiepai=0;
int print=0;
int divisions=1;
int jiepaishichang=1;
int main()
{
	int i=0;
	int x;
	int j;
	FILE *fp; //文件指针
	fp = fopen("D:\\(34拍)丁香花(旋律).musicxml", "r"); //以只读方式打开文件
	if(fp==NULL)
		printf("打开文件失败!\n");
	else
	{
			printf("请输入每分钟节拍数:");
			scanf("%d",&jiepai); 
			for(i=0;;i++)
			{
				fseek(fp,i,SEEK_SET);
				word[0]=fgetc(fp);
				if(word[0]==EOF)	
					break;
				//printf("%c",word[0]);
				fseek(fp,i+1,SEEK_SET);
				word[1]=fgetc(fp);
				if(word[1]==EOF)	
					break;
				//printf("%c",word[1]);
				fseek(fp,i+2,SEEK_SET);
				word[2]=fgetc(fp);
				if(word[2]==EOF)	
					break;
				//printf("%c",word[2]);
				fseek(fp,i+3,SEEK_SET);
				word[3]=fgetc(fp);
				if(word[3]==EOF)	
					break;
				//printf("%c",word[3]);
				fseek(fp,i+4,SEEK_SET);
				word[4]=fgetc(fp);
				if(word[4]==EOF)	
					break;
				//printf("%c",word[4]);
				fseek(fp,i+5,SEEK_SET);
				word[5]=fgetc(fp);
				if(word[5]==EOF)	
					break;
				//printf("%c",word[5]);
				fseek(fp,i+6,SEEK_SET);
				word[6]=fgetc(fp);
				if(word[6]==EOF)	
					break;
				//printf("%c",word[5]);
				fseek(fp,i+7,SEEK_SET);
				word[7]=fgetc(fp);
				if(word[7]==EOF)	
					break;
				//printf("%c",word[5]);
				fseek(fp,i+8,SEEK_SET);
				word[8]=fgetc(fp);
				if(word[8]==EOF)
					break;
				//printf("%c",word[5]);
				fseek(fp,i+9,SEEK_SET);
				word[9]=fgetc(fp);
				if(word[9]==EOF)	
					break;
				//printf("%c",word[5]);
				fseek(fp,i+10,SEEK_SET);
				word[10]=fgetc(fp);
				if(word[10]==EOF)	
					break;
				//printf("%c",word[5]);
				if(word[0]=='<'&&word[1]=='d'&&word[2]=='i'&&word[3]=='v'&&word[4]=='i'&&word[5]=='s'
					&&word[6]=='i'&&word[7]=='o'&&word[8]=='n'&&word[9]=='s'&&word[10]=='>') //判断此处连续11个字符是否为<divisions>
				{
					for(j=0;;j++)
					{
						fseek(fp,i+11+j,SEEK_SET);
							if(fgetc(fp)=='<')
								break;
							else
							{
								if(j==0)
								{
									fseek(fp,i+11+j,SEEK_SET);
									divisions=zifuchange(fgetc(fp));
								}
								else
								{
									fseek(fp,i+11+j,SEEK_SET);
									divisions=10*divisions+zifuchange(fgetc(fp));
								}

							}

					}
					printf("%d,",divisions);
					break;
				}

			}
			while(1)//解析音符
			{
				fseek(fp,i,SEEK_SET);
				word[0]=fgetc(fp);
				if(word[0]==EOF)	
					break;
				//printf("%c",word[0]);
				fseek(fp,i+1,SEEK_SET);
				word[1]=fgetc(fp);
				if(word[1]==EOF)	
					break;
				//printf("%c",word[1]);
				fseek(fp,i+2,SEEK_SET);
				word[2]=fgetc(fp);
				if(word[2]==EOF)	
					break;
				//printf("%c",word[2]);
				fseek(fp,i+3,SEEK_SET);
				word[3]=fgetc(fp);
				if(word[3]==EOF)	
					break;
				//printf("%c",word[3]);
				fseek(fp,i+4,SEEK_SET);
				word[4]=fgetc(fp);
				if(word[4]==EOF)	
					break;
				//printf("%c",word[4]);
				fseek(fp,i+5,SEEK_SET);
				word[5]=fgetc(fp);
				if(word[5]==EOF)	
					break;
				//printf("%c",word[5]);
				if(word[0]=='<'&&word[1]=='s'&&word[2]=='t'&&word[3]=='e'&&word[4]=='p'&&word[5]=='>') //判断此处连续六个字符是否为<step>
				{
					fseek(fp,i+6,SEEK_SET);
					word[6]=fgetc(fp);
					switch(word[6])
					{
						case 'C':word[6]='1';break;
						case 'D':word[6]='2';break;
						case 'E':word[6]='3';break;
						case 'F':word[6]='4';break;
						case 'G':word[6]='5';break;
						case 'A':word[6]='6';break;
						case 'B':word[6]='7';break;
						default:break;
					}
					fseek(fp,i+32,SEEK_SET);
					word[7]=fgetc(fp);
					if(word[7]=='>')
					{
						fseek(fp,i+33,SEEK_SET);
						word[7]=fgetc(fp);

						for(j=0;;j++)
					{
						fseek(fp,i+81+j,SEEK_SET);
							if(fgetc(fp)=='<')
								break;
							else
							{
								if(j==0)
								{
									fseek(fp,i+81+j,SEEK_SET);
									jiepaishichang=zifuchange(fgetc(fp));
								}
								else
								{
									fseek(fp,i+81+j,SEEK_SET);
									jiepaishichang=10*jiepaishichang+zifuchange(fgetc(fp));
								}

							}

					}
					}
					else
					{
						fseek(fp,i+61,SEEK_SET);
						word[7]=fgetc(fp);
						if(word[7]=='<')
						{
							fseek(fp,i+60,SEEK_SET);
						word[7]=fgetc(fp);
						}


						for(j=0;;j++)
					{
						fseek(fp,i+109+j,SEEK_SET);
							if(fgetc(fp)=='<')
								break;
							else
							{
								if(j==0)
								{
									fseek(fp,i+109+j,SEEK_SET);
									jiepaishichang=zifuchange(fgetc(fp));
								}
								else
								{
									fseek(fp,i+109+j,SEEK_SET);
									jiepaishichang=10*jiepaishichang+zifuchange(fgetc(fp));
								}

							}

					}

					}
				/*	if(word[7]=='4')
						word[7]='2';
					else if(word[7]>'4')
						word[7]='3';
					else if(word[7]<'4') 
						word[7]='1';
						*/


					/*
					if(word[8]>'9'|word[8]<'0')
					{
						error++;
					}
					switch(word[8])
					{
						case '1':word[8]='1';break;
						case '2':word[8]='2';break;
						case '3':word[8]='3';break;
						case '4':word[8]='4';break;
						case '6':word[8]='6';break;
						case '8':word[8]='8';break;
						case '12':word[8]='12';break;
						default:break;
					}
					*/

					if(flag==0)
					{
						printf("%d,",jiepai);
						flag++;
						printf("\n");
					}
					if(word[6]<'0'||word[6]>'9')
						error++;
					if(word[7]<'0'||word[7]>'9')
						error++;



						printf("%c,%c,%d,",word[6],word[7],jiepaishichang);
					yinfushu++;//音符计数+1
					print++;
					if(print==3)
					{
						printf("\n");
						print=0;
					}

				}
				i++;
			}
			i=0;
	}
	fclose(fp);//关闭文件
	printf("0");
	printf("\n");
	printf("解析完成,共有%d个音符,共有%d个错误\n",yinfushu,error);
	system("pause");
	return 0;
}

int zifuchange(char x)
{
	switch(x)
	{
		case '0':return 0;break;
		case '1':return 1;break;
		case '2':return 2;break;
		case '3':return 3;break;
		case '4':return 4;break;
		case '5':return 5;break;
		case '6':return 6;break;
		case '7':return 7;break;
		case '8':return 8;break;
		case '9':return 9;break;
		default:return -1;break;
	}
}

使用该部分代码只需要更改以下文件路径为自己的文件路径即可,该部分代码如下:

fp = fopen("D:\\(34拍)丁香花(旋律).musicxml", "r"); 

将上面获取到的爱拼才会赢的musicxml格式文件路径添加进去后,手动输入每分钟节拍数(可自定义,也可填简谱节拍数)控制台会自动生成音乐代码,复制并粘贴进上一节所述的music[]数组即可。本次生成的代码如下:

4,89,
5,4,2,5,4,1,6,4,1,
1,5,6,6,4,2,5,4,2,
6,4,1,1,5,1,6,4,2,
5,4,2,3,4,12,3,4,2,
3,4,1,5,4,1,6,4,6,
1,5,2,5,4,2,3,5,2,
3,5,1,2,5,1,1,5,2,
2,5,12,1,5,2,2,5,2,
3,5,2,3,5,2,2,5,2,
3,5,1,2,5,1,1,5,6,
6,4,1,1,5,1,2,5,2,
2,5,2,2,5,1,1,5,1,
6,4,2,5,4,8,6,4,2,
5,4,1,6,4,1,5,4,2,
1,5,2,6,4,2,5,4,2,
3,4,2,1,4,4,2,4,12,
1,4,2,2,4,2,3,4,6,
5,4,2,5,4,2,3,4,2,
1,5,2,7,4,2,6,4,6,
6,4,2,6,4,4,1,5,2,
2,5,1,3,5,1,5,5,4,
3,5,4,2,5,2,1,5,2,
5,4,2,1,5,2,2,5,1,
3,5,1,2,5,12,3,5,1,
2,5,1,1,5,4,2,5,1,
1,5,1,6,4,4,1,5,2,
3,4,1,1,5,1,1,5,2,
6,4,2,1,5,4,6,4,1,
5,4,1,3,4,2,5,4,16,
5,4,2,6,4,2,1,5,2,
6,4,2,6,4,8,3,5,2,
3,5,2,3,5,2,3,5,1,
2,5,1,2,5,6,2,5,1,
1,5,1,6,4,6,3,5,2,
2,5,4,1,5,1,2,5,1,
1,5,1,6,4,1,1,5,16,
5,4,2,6,4,2,1,5,6,
6,4,2,5,4,2,6,4,1,
1,5,1,6,4,2,5,4,2,
3,4,12,3,4,2,3,4,1,
5,4,1,6,4,6,1,5,2,
5,4,2,3,5,2,3,5,1,
2,5,1,1,5,2,2,5,12,
1,5,2,2,5,2,3,5,2,
3,5,2,2,5,2,3,5,1,
2,5,1,1,5,6,6,4,1,
1,5,1,2,5,2,2,5,2,
2,5,1,1,5,1,6,4,2,
5,4,8,6,4,2,5,4,1,
6,4,1,5,4,2,1,5,2,
6,4,2,5,4,2,3,4,2,
1,4,4,2,4,12,1,4,2,
2,4,2,3,4,6,5,4,2,
5,4,2,3,4,2,1,5,2,
7,4,2,6,4,6,6,4,2,
6,4,4,1,5,2,2,5,1,
3,5,1,5,5,2,3,5,4,
2,5,2,1,5,2,5,4,2,
1,5,2,2,5,1,3,5,1,
2,5,12,3,5,1,2,5,1,
1,5,4,2,5,1,1,5,1,
6,4,4,1,5,2,3,4,1,
1,5,1,1,5,2,6,4,2,
1,5,4,6,4,1,5,4,1,
3,4,2,5,4,16,5,4,2,
6,4,2,1,5,2,6,4,2,
6,4,8,3,5,2,3,5,2,
3,5,2,3,5,1,2,5,1,
2,5,6,2,5,1,1,5,1,
6,4,6,3,5,2,2,5,4,
1,5,1,2,5,1,1,5,1,
6,4,1,1,5,16,1,4,2,
2,4,2,3,4,6,5,4,2,
5,4,2,3,4,2,1,5,2,
7,4,2,6,4,6,6,4,2,
6,4,4,1,5,2,2,5,1,
3,5,1,5,5,4,3,5,4,
2,5,2,1,5,2,5,4,2,
1,5,2,2,5,1,3,5,1,
2,5,12,3,5,1,2,5,1,
1,5,4,2,5,1,1,5,1,
6,4,4,1,5,2,3,4,1,
1,5,1,1,5,2,6,4,2,
1,5,4,6,4,1,5,4,1,
3,4,2,5,4,16,5,4,2,
6,4,2,1,5,2,6,4,2,
6,4,8,3,5,2,3,5,2,
3,5,2,3,5,1,2,5,1,
2,5,6,2,5,1,1,5,1,
6,4,6,3,5,2,2,5,4,
1,5,1,2,5,1,1,5,1,
6,4,1,1,5,16,0
解析完成,共有275个音符,共有0个错误

至此,你便可以利用借助单片机通过蜂鸣器播放任意音乐!试试吧!



版权声明:本文为kevin1499原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。