如下说明字符组合而成。
音高:
高音 c d e f g a b
中音 1 2 3 4 5 6 7
低音 c d e f g a b
若某音升半音,则在其后加#号;降半音,在其后加b字符。
音长: -(延长四分音符的一拍)、_(8分音符,后可带符点)、=(16分音符,后可带符点)、.(附点音符,后不可带符点)、:(32分音符,后可带符点)、;(64分音符,后不可带符点)。
说明:在书写时,请先写完整的音高,再写音长,如简谱中的"3-",则应该为"3#-"。
pn:表示设置音色,取值1-256之间。
{}:歌词或注释。
|: 表示小节分隔符。
\: 后继音均降八度
/: 后继音均升八度
sn:音量大小,n数值越大,音量越大。
其它的字符,视为非法字符。
以下为歌曲《解放军的天》片断midi文本文件。
[midi]
f,2/4,150,2
[1]
p53
/3=3=3=2= 3_.2= | 1_1=e= g | 3=3=3=2= 3_3=2= | 1_1=e= g |
\6_. 5= 6_.5= | 6_c_ 3_5_| 6=6=6=6= 6_6_ | 5=6=c_ c_3_|
2_.3= 5_c_ | 6=5=3_ 5 | 6_. / 1= 2_.1=| 2_0_ 3_.2= |
1_0_ 2_2_ | \5_.6= /1_3_ | 3=1=a_ 1 \ |
[2]
p53 \
1_c_ 5_g_ | 1_c_ 5_g_ | 1_c_ 5_g_ | 1_c_ 5_g_ |
a_6_ 4_6_ | 1_5_ 3_ 5_ | a_6_ 4_6_ | 1_5_ 3_ 5_ |
1_3_ 5_1_ | 1_6_ 4_6_ | 2_5_ 1_5_ | g_5_ 1_5_ |
1_5_ 3_5_ | 1_6_ 4_6_| 3_2_ 1 |
四、程序实现
以下为midi文件生成的全部源程序,经borland c++3.1编译、连接通过。
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <string.h>
#define c1 60 //c调1的键名值
#define fourpainum 64 //1/4音符计数
#define midiclock 24 //每1/64音符的midiclock数
#define jumpnullchar(x) \ //跳过空字符
{ \
while(*x==' ' \
||*x=='\t' \
||*x=='\n' \
||*x=='|') \
x++; \
};
enum errorcode{ //处理错误信息
changeok, //转换成功
textfilenotopen, //文本文件不能打开
midifilecannotcreate, //指定的midi文件不能建立
textfiletobig, //文本文件太大
mallocerror, //内存分配错误
invalidechar, //在文本文件中出现了非法字符
notfoundtrack, //没有找到指定的磁道信息
notmiditextfile, //文本文件不是midi文本文件
};
void swap(char *x,char *y) //两数据交换
{ char i;
i=*x;
*x=*y;
*y=i;
}
union lenght
{ long length;
char b[4];
} ;
struct mh { //midi文件头
char midiid[4]; //midi文件标志mthd
long length; //头部信息长度
int format; //存放的格式
int ntracks; //磁道数目
int perpainum; //每节计算器值
};
struct th //音轨头
{ char trackid[4]; //磁道标志mtrk
long length; //信息长度
} ;
class midi
{
public:
char errormsg[100]; //错误信息
private:
unsigned char *textfilebuf,
*textfileoldbuf;
unsigned char *midifilebuf,
*midifileoldbuf;
char oneval; //某调时,1的健值
char painum; //第一小节节拍总数
char onepaitonenum; //用几分音符作为一基本拍
public:
//将符全midi书定格式的文本文件生成midi文件
int changetexttomidi(char *text952.htm,
char *midi952.htm);
char *geterrormsg() //获取错误信息
{ return(errormsg);}
private:
char getcurpaispeed(int n); //取当前拍的按下强度
void writesoundsize(char ntrack,unsigned int );
void setonepaitonenum(int n)
{ onepaitonenum=n; };
void setoneval(char *m) ; //取m大调或小调时,1的实际键值
char gettonenum(char c, //取记名对应的键值
char flag) ;
void writemhtofile(long length, //建立midi文件头
int format,
int ntracks,
int perpainum,
file *fp);
void writethtofile(long lenght,
file *fp); //建立midi磁道头
void writetrackmsgtofile(file *fp);
//将磁道音乐信息定入文件中
void writespeed(int speed);
void setpainum(int n)
{ painum=n;}
long newlong(long n); //新的long值
int newint(int n) //新的int值
{ return(n<<8|n>>8);}
//将n改为可变长度,内入buf处
void writelenghttobuf(unsigned long n,
char *buf);
void changeprommgram(char channel, //设置音色
char promgram);
void noteon (char n, //演奏乐音
char speed,
unsigned long delaytime);
void writenoteon(char,char,char ,unsigned long) ;
void writetextmsg(char *msg); //定一串文本信息
void writetimesignature(char n, //设置时间信息
char d);
void writetrackendmsg(); //设置磁道结束信息
};
/**************************************************
/* 作用:将符合midi文本文件的text文件转换成midi */
/* 文件. */
/* 入口参数:text952.htm 文本文件名 */
/* midi952.htm midi文件名 */
/* 出口参数:见 errorcode 说明 */
/*************************************************/
int midi::changetexttomidi(char *text952.htm,
char *midi952.htm)
{ int tracks,ntrack,delaytime;
int speed,isfirst,nn,dd;
unsigned char buf[80],*msgbuf,c;
file *textfp,*midifp;
long filesize;
char speedval;
textfp=fopen(text952.htm,"r");
if (textfp==null)
{sprintf(errormsg,
"文本文件[%s]不能打开。\n",text952.htm);
return(textfilenotopen);
}
fseek(textfp,0,seek_end); /*测试文件大小*/
filesize=ftell(textfp);
textfilebuf=(char *)malloc(filesize);/*为文件分配内存*/
if (textfilebuf==null)
{ sprintf(errormsg,
"文本文件[%s]太大,没有足够的内存处理。\n",
text952.htm);
fclose(textfp);
return(textfiletobig);
}
memset(textfilebuf,0,filesize);
midifilebuf=(char *) malloc(filesize*4);
if ( midifilebuf==null)
{ sprintf(errormsg,"不能为midi文件分配内存。\n");
fclose(textfp);
free(textfilebuf);
return(mallocerror);
}
midifp=fopen(midi952.htm,"wb");
if (midifp==null)
{ sprintf(errormsg,
"midi文件[%s]不能建立。\n",midi952.htm);
fclose(textfp);
free(midifilebuf);
free(textfilebuf);
return(midifilecannotcreate);
}
midifileoldbuf=midifilebuf;
textfileoldbuf=textfilebuf;
fseek(textfp,0,seek_set);
fread(textfilebuf,filesize,1,textfp);
fclose(textfp);
jumpnullchar(textfilebuf);
c=strnicmp(textfilebuf,"[midi]",6);
if (c)
{sprintf(errormsg,
"文本文件[%s]不是midi文本文件。\n",midi952.htm);
fcloseall();
free(textfileoldbuf);
free(midifileoldbuf);
return(notmiditextfile);
}
textfilebuf+=6;
jumpnullchar(textfilebuf);
sscanf(textfilebuf,"%c,%d/%d,%d,%d", //取调号等信息
&c,&nn,&dd,&speed,&tracks);
buf[0]=c;buf[1]=0; setoneval(buf); //设置该调1的键值
if (nn<1 || nn> 7) nn=4;
if (dd<2 || dd>16) dd=4;
while(*textfilebuf!='\n') textfilebuf++;
jumpnullchar(textfilebuf);
if (speed<60 || speed >200) speed=120;
jumpnullchar(textfilebuf);
if (tracks<1 || tracks>16) tracks=1;
jumpnullchar(textfilebuf);
ntrack=1;
writemhtofile(6,1,tracks,speed,midifp);
writetimesignature(nn,dd); //设置时间记录格式
setpainum(nn);
writespeed(speed); //设置演奏速度
while(ntrack<=tracks && *textfilebuf!=0)
{sprintf(buf,"[%d]",ntrack);
textfilebuf=strstr(textfilebuf,buf);//查找该磁道起始位置
if (textfilebuf==null) //没有找到
{ sprintf(errormsg,
"在文件[%s]中,第%d磁道音乐信息没找到。\n.",
text952.htm,ntrack);
free(midifileoldbuf);
free(textfileoldbuf);
fcloseall();
return(notfoundtrack);
}
if (ntrack!=1) midifilebuf=midifileoldbuf;
speedval=0;
textfilebuf+=strlen(buf);
isfirst=1;
while(*textfilebuf!=0 && *textfilebuf!='[')
{ jumpnullchar(textfilebuf);
c=*(textfilebuf++);
if ( (c>='0' && c<='7')
|| (c>='a' && c<='g')
|| (c>='a' && c<='g')
)
{jumpnullchar(textfilebuf);
if (*textfilebuf=='b' || *textfilebuf=='#')
{ c=gettonenum(c,*textfilebuf);/*取出实际的音符*/
textfilebuf++;
jumpnullchar(textfilebuf);
}
else c=gettonenum(c,' ');
switch(*(textfilebuf++))
{ case '-': //延长一拍
delaytime=2*fourpainum;
jumpnullchar(textfilebuf);
while(*textfilebuf=='-')
{ textfilebuf++;
delaytime+=fourpainum;
jumpnullchar(textfilebuf);
}
break;
case '_': //8分音符
delaytime=fourpainum/2;
jumpnullchar(textfilebuf);
if(*textfilebuf=='.')
{textfilebuf++;
delaytime=delaytime*3/2;
}
break;
case '=': //16分音符
delaytime=fourpainum/4;
jumpnullchar(textfilebuf);
if(*textfilebuf=='.')
{delaytime=delaytime*3/2;
textfilebuf++;}
break;
case '.': //附点音符
delaytime=fourpainum*3/2;
break;
case ':': //32分音符
delaytime=fourpainum/16;
jumpnullchar(textfilebuf);
if(*textfilebuf=='.')
{delaytime=delaytime*3/2;
textfilebuf++;}
break;
case ';': //64分音符
delaytime=fourpainum/32;
if(*textfilebuf=='.')
{ delaytime=delaytime*3/2;
textfilebuf++;}
break;
default:
delaytime=fourpainum;
textfilebuf--;
break;
}
if (isfirst)
{writenoteon(ntrack,c,
getcurpaispeed(speedval/(fourpainum*4/dd)+1),
delaytime);
isfirst=0;}
else
noteon(c,
getcurpaispeed(speedval/(fourpainum*4/dd)+1),
delaytime);
speedval=(speedval+delaytime) //下一音符所处的节拍
%(painum*fourpainum*4/dd);
}
else
{switch(c)
{ case 's':
case 's':
case 'p':
case 'p': /*设置音色*/
sscanf(textfilebuf,"%d",&isfirst);
while(*textfilebuf>='0' && *textfilebuf<='9')
textfilebuf++;
if (c=='p'||c=='p') //若为p,表示改变音色
changeprommgram(ntrack,(char)isfirst);
else //否则,表示设置音量大小
writesoundsize(ntrack,(unsigned int)isfirst);
isfirst=1;
break;
case '{': /*写歌词*/
msgbuf=buf;
while(*textfilebuf!='}'
&& *textfilebuf!='\n'
&& *textfilebuf!=0
&& *textfilebuf!='[')
*(msgbuf++)=*(textfilebuf++);
*msgbuf=0;
isfirst=1;
writetextmsg(buf);
if (*textfilebuf=='}') textfilebuf++;
break;
case '\\': //降八度
oneval-=12;
break;
case '/': //升八度
oneval+=12;
break;
case '[':
case 0:
textfilebuf--;
break;
default:
sprintf(errormsg,"文本文件[%s]出现非法字符(%c)。",
text952.htm,c);
free(midifileoldbuf);
free(textfileoldbuf);
fcloseall();
return(invalidechar);
}
}
}
writetrackendmsg(); //设置磁道结束信息
writetrackmsgtofile(midifp); //将磁道音乐信息定入文件中
ntrack++;
}
free(midifileoldbuf);
free(textfileoldbuf);
fclose(midifp);
sprintf(errormsg,"midi文件[%s]转换成功。",midi952.htm);
return(changeok);
}
/*****************************************************/
/*作用:将长整型数据变成可变长度,存入buf处 */
/*入口参数:n 数据 buf 结果保存入 */
/****************************************************/
void midi::writelenghttobuf(unsigned long n,char *buf)
{ unsigned char b[4]={0};
int i;
b[3]=(unsigned char)(n&0x7f);
i=2;
while(n>>7)
{ n>>=7;
b[i--]=(char)( (n&0x7f)|0x80);
}
for (i=0;i<4;i++)
if (b[i]) *(buf++)=b[i];
*buf=0;
}
long midi::newlong(long n) //将长整型数据改成高位在前
{ union lenght l={0};
char i;
l.length=n;
swap(&l.b[3],&l.b[0]);
swap(&l.b[2],&l.b[1]);
return(l.length);
}
//开始演奏音乐
void midi::writenoteon(char channel, //通道号
char note, //音符值
char speed, //按键速度
unsigned long delaytime) //延时数
{ unsigned char buf[5];
int i;
channel--;
*(midifilebuf++)=0;
*(midifilebuf++)=0x90|channel&0x7f;//write channel
*(midifilebuf++)=note;
*(midifilebuf++)=speed;
writelenghttobuf(delaytime*midiclock,buf);
i=0;
while(buf[i]>=0x80) //write delay time
*(midifilebuf++)=buf[i++];
*(midifilebuf++)=buf[i];
*(midifilebuf++)=note;
*(midifilebuf++)=0;
}
void midi::noteon(char note,
char speed,
unsigned long delaytime) //发音
{ unsigned char buf[5];
int i;
*(midifilebuf++)=0;
*(midifilebuf++)=note;
*(midifilebuf++)=speed;
writelenghttobuf(delaytime*midiclock,buf);
i=0;
while(buf[i]>0x80)
*(midifilebuf++)=buf[i++];
*(midifilebuf++)=buf[i];
*(midifilebuf++)=note;
*(midifilebuf++)=0;
}
void midi::changeprommgram(char channel,char n) //改变音色
{ *(midifilebuf++)=0;
*(midifilebuf++)=0xc0|(channel-1)&0x7f;
*(midifilebuf++)=n;
}
void midi::writetextmsg(char *msg) //向内存写入一文本信息
{ char bufmsg[100]={0xff,5,0,0,0};
int len;
*(midifilebuf++)=0;
bufmsg[2]=(char)strlen(msg);
strcpy(&bufmsg[3],msg);
strcpy(midifilebuf,bufmsg);
midifilebuf+=strlen(bufmsg)+3;
}
void midi::writetrackendmsg() //磁道结束信息
{ *(midifilebuf++)=0;
*(midifilebuf++)=0xff;
*(midifilebuf++)=0x2f;
*(midifilebuf++)=0;
}
char midi::gettonenum(char n,char flag)
/*入口参数: n 音高
flag 升降记号
返回值: 该乐音的实际标号值*/
{ static char val[7]={9 ,11,0,2,4,5,7};
static char one[7]={0,2,4,5,7,9,11};
int i;
i=oneval;
if (n<='7'&& n>='1') i=i+one[n-'1'];
else
if (n>='a' && n<='g')
i=i+val[n-'a']-12; //低音,降12个半音
else
if (n>='a' &n<='g') //高音,升12个半音
i=i+val[n-'a']+12;
else //否则,识为休止符
i=0;
if (flag=='b') i--;
else if (flag=='#') i++;
return(i);
}
if (isfirst)
{writenoteon(ntrack,c,
getcurpaispeed(speedval/(fourpainum*4/dd)+1),
delaytime);
isfirst=0;}
else
noteon(c,
getcurpaispeed(speedval/(fourpainum*4/dd)+1),
delaytime);
speedval=(speedval+delaytime) //下一音符所处的节拍
%(painum*fourpainum*4/dd);
}
else
{switch(c)
{ case 's':
case 's':
case 'p':
case 'p': /*设置音色*/
sscanf(textfilebuf,"%d",&isfirst);
while(*textfilebuf>='0' && *textfilebuf<='9')
textfilebuf++;
if (c=='p'||c=='p') //若为p,表示改变音色
changeprommgram(ntrack,(char)isfirst);
else //否则,表示设置音量大小
writesoundsize(ntrack,(unsigned int)isfirst);
isfirst=1;
break;
case '{': /*写歌词*/
msgbuf=buf;
while(*textfilebuf!='}'
&& *textfilebuf!='\n'
&& *textfilebuf!=0
&& *textfilebuf!='[')
*(msgbuf++)=*(textfilebuf++);
*msgbuf=0;
isfirst=1;
writetextmsg(buf);
if (*textfilebuf=='}') textfilebuf++;
break;
case '\\': //降八度
oneval-=12;
break;
case '/': //升八度
oneval+=12;
break;
case '[':
case 0:
textfilebuf--;
break;
default:
sprintf(errormsg,"文本文件[%s]出现非法字符(%c)。",
text952.htm,c);
free(midifileoldbuf);
free(textfileoldbuf);
fcloseall();
return(invalidechar);
}
}
}
writetrackendmsg(); //设置磁道结束信息
writetrackmsgtofile(midifp); //将磁道音乐信息定入文件中
ntrack++;
}
free(midifileoldbuf);
free(textfileoldbuf);
fclose(midifp);
sprintf(errormsg,"midi文件[%s]转换成功。",midi952.htm);
return(changeok);
}
/*****************************************************/
/*作用:将长整型数据变成可变长度,存入buf处 */
/*入口参数:n 数据 buf 结果保存入 */
/****************************************************/
void midi::writelenghttobuf(unsigned long n,char *buf)
{ unsigned char b[4]={0};
int i;
b[3]=(unsigned char)(n&0x7f);
i=2;
while(n>>7)
{ n>>=7;
b[i--]=(char)( (n&0x7f)|0x80);
}
for (i=0;i<4;i++)
if (b[i]) *(buf++)=b[i];
*buf=0;
}
long midi::newlong(long n) //将长整型数据改成高位在前
{ union lenght l={0};
char i;
l.length=n;
swap(&l.b[3],&l.b[0]);
swap(&l.b[2],&l.b[1]);
return(l.length);
}
//开始演奏音乐
void midi::writenoteon(char channel, //通道号
char note, //音符值
char speed, //按键速度
unsigned long delaytime) //延时数
{ unsigned char buf[5];
int i;
channel--;
*(midifilebuf++)=0;
*(midifilebuf++)=0x90|channel&0x7f;//write channel
*(midifilebuf++)=note;
*(midifilebuf++)=speed;
writelenghttobuf(delaytime*midiclock,buf);
i=0;
while(buf[i]>=0x80) //write delay time
*(midifilebuf++)=buf[i++];
*(midifilebuf++)=buf[i];
*(midifilebuf++)=note;
*(midifilebuf++)=0;
}
void midi::noteon(char note,
char speed,
unsigned long delaytime) //发音
{ unsigned char buf[5];
int i;
*(midifilebuf++)=0;
*(midifilebuf++)=note;
*(midifilebuf++)=speed;
writelenghttobuf(delaytime*midiclock,buf);
i=0;
while(buf[i]>0x80)
*(midifilebuf++)=buf[i++];
*(midifilebuf++)=buf[i];
*(midifilebuf++)=note;
*(midifilebuf++)=0;
}
void midi::changeprommgram(char channel,char n) //改变音色
{ *(midifilebuf++)=0;
*(midifilebuf++)=0xc0|(channel-1)&0x7f;
*(midifilebuf++)=n;
}
void midi::writetextmsg(char *msg) //向内存写入一文本信息
{ char bufmsg[100]={0xff,5,0,0,0};
int len;
*(midifilebuf++)=0;
bufmsg[2]=(char)strlen(msg);
strcpy(&bufmsg[3],msg);
strcpy(midifilebuf,bufmsg);
midifilebuf+=strlen(bufmsg)+3;
}
void midi::writetrackendmsg() //磁道结束信息
{ *(midifilebuf++)=0;
*(midifilebuf++)=0xff;
*(midifilebuf++)=0x2f;
*(midifilebuf++)=0;
}
char midi::gettonenum(char n,char flag)
/*入口参数: n 音高
flag 升降记号
返回值: 该乐音的实际标号值*/
{ static char val[7]={9 ,11,0,2,4,5,7};
static char one[7]={0,2,4,5,7,9,11};
int i;
i=oneval;
if (n<='7'&& n>='1') i=i+one[n-'1'];
else
if (n>='a' && n<='g')
i=i+val[n-'a']-12; //低音,降12个半音
else
if (n>='a' &n<='g') //高音,升12个半音
i=i+val[n-'a']+12;
else //否则,识为休止符
i=0;
if (flag=='b') i--;
else if (flag=='#') i++;
return(i);
}