從網上找的,已經將用BC寫的改成了VC的,由于對音樂的理解比樂盲還差,對于程序中轉換是否有問題我也不得而知,反正用VC生成的MIDI文件聽起來慘不忍睹。
對于制作MIDI音樂來說,比播放MIDI文件本身更復雜得多。我們得了解一些樂理常識和MIDI文件結構。
一、MIDI文件結構分析 MIDI文件包含首部塊(Header Chunk)和音軌塊(Track Chunk)兩部分。其格式一般如下:
MThd <數據長度> <Header數據> //首部塊
.......
Mtrk <數據長度> <Track數據> //音軌塊
Header Chunk 結構為:
char MidiId[4];
long length;
int foarmt;
int TrackNum;
int division;
其中:
MidiId稱為MIDI文件頭標志,一般將其設置為MThd;
length為文件首部數據長度(除它本身和文件頭標志占用的字節以外),通常它設置為6,即format,TrackNum和division共占用的字節數據長度;
format表示MIDI文件存放的格式,當前只有3種格式:
0 表示MIDI文件只有一個Track Chunk;
1 表示MIDI文件只有一個或多個Track Chunk;
2 表示MIDI文件只有一個或多個各處獨立的Track Chunk。
division指定計數的方法,一種隨時間計數(最高位設置為0時),另一種使用制式的時間碼(最高位設置為1時)。這里,主要介紹隨時間計數的一種格式。其各位意義如下:
┌─┬─────────┐
│0 │ 每一拍的計數值 │
└─┴─────────┘
b15 b14  ̄ b0
其最高位一定要設置為0,其它的15位表示每一拍的計數值。如該數據為96(以八分音符為一拍),則表示一個四分音符延時數應該為192。
另外,在MIDI文件中,long和int型數據均將高字節值存放入低地址上,如一個long型數據為0x45678,則在文件中,存放的結果為:0x00,0x04,0x56,0x78。而在內存中,int,long的變量值通常將崐高字節值存放高地址上。因此,存放數據時,應該作一下調整。
Track Chunk為用來播放歌曲的數據信息。每一個Track Chunk是一組簡單的MIDI碼(包括一些非MIDI碼)的集合。它又由頭部信息和崐若干個Mtrk event組合而成。
頭部結構和意義為:
char TrackChunkId[4]; //Track Chunk標志MTrk
long TrackChunkMsgLength; //該Track Chunk信息長度
而Mtrk event是由時間計數值(dela-time)和event(MIDI碼信崐息)組合成的。即:
<Mtrk event>=<dela-time> <event>
<dela-time>使用可變長度的形式存儲數據,它代表處理event之前要計數時間值。 它在音樂中,即表示拍數。通常音樂開始演奏時,總是將計數時間值設置為0。為了能連續處理兩個event,我們可以將deta-time設置為0。如:3和5同時演奏2拍(每一拍計數值為24),可以設置如下:
deta-time event
0 開始演奏3
0 開始演奏5
48 停止3演奏
0 停止5演奏
event表示MIDI碼信息集,如0x9n表示開始發音,0x8n表示關閉發音等等(下有說明)。
上述的dela-time使用可變長度的形式表示數據值。可變長度形崐式是MIDI文件中對于大于8位的數據打用的一種存儲方式,它把每一個數據定義為7位,剩下的最高位作為數據長度的識別。當這一位為0時,表示數據是最后一個,若為1,則表示還有下一個。
如:數值0x3fff,可變長度形式便為0xff,0x7f;0x4000則應該為0x81,0x80,0x00。此數據的轉換可以參閱WriteLenghtToBuf()函數。
二、常見MIDI碼說明
MIDI碼是制定音樂交換的信息碼,它使用串行非同步傳送,因此數據碼是用多碼形式。第一個MIDI碼是狀態碼,剩余的都是數據碼,其長度視狀態而定。
以下是一些常見的MIDI碼。
1、開始發音(0x9n)
格式為:0x9n note speed
它一共占用3個字節,n表示通道號,取值0-15。MIDI可以同時演奏16個通道,用此指定在哪一個通道上發音(以下n相同)。
note表示音高數值,即音階碼值。如C4(中音1)為60,它的取值在0xc和0x6c之間(具體碼值,可參考「參考書籍1」)。
speed表示按鍵時的速度,用此表示音的力度。若沒有力度感,可以將其設置為64,若為0,表示關閉發音。
如:在第2通道上開始演奏3,則MIDI碼便為0x91,63,40。
MIDI規范還規定,若連續向同一通道上發送多個音,則可以不指出狀態碼。如上述同時演奏3,5,MIDI碼便為:0x91,63,40,65,40。
2、關閉發音(0x8n)。
格式:0x8n note speed
說明同上。通常它用0x9n,note,0來代替。
3、切換音色(0xcn)。
格式:0xcn,PRogram
program表示音色代碼,0 ̄255之間,如Acou Piano 1(電鋼1值為0),Synth Bass 1(電貝司1值為64)等(詳見「參考書籍1」)。
4、設置音量大小
格式:0xbn ,07,size
0xbn,39,size
7,表示設置主音量的高字節值;39表示設置主音量的低字節值。
5、設置時間記號
格式:0xff 0x58 04 nn dd cc bb
nn和dd直接對應到譜號的數字,dd使用2的指數。如3/8,則nn=3,dd=3。cc是代表第次節拍器打后的時間是幾個MIDI clock。bb通常設置為8表示多少個MIDI clock等于1/4 拍。
6、設置演奏速度
格式:0xff 0x51 03 tt tt tt
tt tt tt 表示第一拍定義多少個Miscro Seconds。它即是用來崐變演奏的速度。
7、寫歌詞
格式:0xff 0x05 len text
len表示歌詞的長度,text表示歌詞文本碼。
8、磁道結束
格式:0xff,0x2f,00
它表示結束點。每外track chunk后都應該有此MIDI碼。
三、MIDI信息文本文件制作
為了能制成符合規范的MIDI文件,我們在此規定MIDI信息文本制作格式如下:
[MIDI]
<調號>,<節拍>,<每分鐘節拍數>,<音軌個數>
[1]
.....
[n]
....
說明:
1、調號,占用一個字符,必須為A、B、C、D、E、F、G,否則視為C調;
2、節拍,取值如下:2/4,3/4,4/4,3/8,6/8....等。
3、每分鐘節拍數:表示每分鐘演奏的節拍總數,取值在40-200崐之間,否則視為120。
4、音軌個數表示此歌曲聲部數。如三聲部,可將其設置為3。
5、[n]后表示此音軌的音樂信息。有如下說明字符組合而成。
音高:
高音 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 *TextFileName,
char *MidiFileName);
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 */
/* 文件. */
/* 入口參數:TextFileName 文本文件名 */
/* MidiFileName MIDI文件名 */
/* 出口參數:見 ERRORCODE 說明 */
/*************************************************/
int MIDI::ChangeTextToMidi(char *TextFileName,
char *MidiFileName)
{ int tracks,ntrack,delaytime;
int speed,IsFirst,nn,dd;
unsigned char buf[80],*msgbuf,c;
FILE *TextFp,*MidiFp;
long FileSize;
char SpeedVal;
TextFp=fopen(TextFileName,"r");
if (TextFp==NULL)
{sprintf(ErrorMsg,
"文本文件[%s]不能打開。/n",TextFileName);
return(TextFileNotOpen);
}
fseek(TextFp,0,SEEK_END); /*測試文件大小*/
FileSize=ftell(TextFp);
TextFileBuf=(char *)malloc(FileSize);/*為文件分配內存*/
if (TextFileBuf==NULL)
{ sprintf(ErrorMsg,
"文本文件[%s]太大,沒有足夠的內存處理。/n",
TextFileName);
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(MidiFileName,"wb");
if (MidiFp==NULL)
{ sprintf(ErrorMsg,
"Midi文件[%s]不能建立。/n",MidiFileName);
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",MidiFileName);
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.",
TextFileName,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)。",
TextFileName,c);
free(MidiFileOldBuf);
free(TextFileOldBuf);
fcloseall();
return(InvalideChar);
}
}
}
WriteTrackEndMsg(); //設置磁道結束信息
WriteTrackMsgToFile(MidiFp); //將磁道音樂信息定入文件中
ntrack++;
}
free(MidiFileOldBuf);
free(TextFileOldBuf);
fclose(MidiFp);
sprintf(ErrorMsg,"MIDI文件[%s]轉換成功。",MidiFileName);
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)。",
TextFileName,c);
free(MidiFileOldBuf);
free(TextFileOldBuf);
fcloseall();
return(InvalideChar);
}
}
}
WriteTrackEndMsg(); //設置磁道結束信息
WriteTrackMsgToFile(MidiFp); //將磁道音樂信息定入文件中
ntrack++;
}
free(MidiFileOldBuf);
free(TextFileOldBuf);
fclose(MidiFp);
sprintf(ErrorMsg,"MIDI文件[%s]轉換成功。",MidiFileName);
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);
}
新聞熱點
疑難解答