单片机通过蜂鸣器播放任意音乐代码实现(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 版权协议,转载请附上原文出处链接和本声明。