MIDIファイルの内部を見る [理系的音楽道]

ピアノの演奏をScore Editorで作る作業を行っていますが、最後は結局MIDIファイルにして、電子ピアノで演奏させています。しかし、MIDI規格やMIDIファイル自体は私は理解していない状況です。また、これまでも「理系的音楽道」のカテゴリーでいろいろな人工的な音を発生させてきましたが、単にAの音を出すだけで曲の演奏には至っていません。曲を演奏させるにはMIDIファイルを読み込まなければなりません。

そこで今回の記事は、MIDIファイルを自作プログラムに読み込んで中に何が書かれているかを読み解くという企画です。このプログラムを作成することによりMIDI規格を理解することができました。これが最大の成果です。今回公開のプログラムは実はMIDIファイルが読み込み何をしているか画面に書くだけで、演奏はしてくれません。つまり役立たずですが、勉強の一過程とお考えください。それからこのプログラムは、私が作っているMIDIファイルを理解できるソフトであり、MIDI規格の全て文法に対応したものではありません。あくまで途中経過であり、使えないと思いますので、使ってみることはお薦めしません。でもMIDI規格を勉強する上ではこのソースコードは有用かも知れません。

感想ですが、MIDIファイルは古い規格なので今から見ると容量圧縮に無駄なエネルギーを使ってます。例えば可変長変数などです。つまりコンピュータが使う数値形式と違う方法で数値が格納されています。プログラミングをするとなるとそれなりのエネルギーが必要でした。

/****************************************************************** ・本ソースファイル名はmidiread100505.cです。 ・本プログラムの著作権はyablinskyに属します。 ・本プログラムで作ったファイルの著作権はwavファイルの作成者にあります。 ・本プログラムのコピー・改変は自由です。 ただし、改変されたプログラムは個人で楽しむ以外に使う場合、 例えば、wavデータやそれを変換したデータをブログやホームページに アップロードする場合はソースコードも公開しなければならない。 version 10.05.02 MIDI dataの読み込み <a href="http://desktop-piano.blog.so-net.ne.jp" target="_blank">http://desktop-piano.blog.so-net.ne.jp</a> Copyright 2010 yablinsky All rights reserved. Presented by yablinsky *******************************************************************/ #include <stdio.h> #include <math.h> #include <stdlib.h> #include <malloc.h> void midiread(char **argv); void write_error(char *c); int getdata(FILE *fpi,int *num,int counter); void main(int argc,char *argv[]) { int i,N,sampling; struct LR *sound; if (argc!=2) { printf("midiread000000 input_data_file_name(*.mid)\n"); exit(1); } midiread(argv); } // wavデータをキャラクタデータへ変換 void midiread(char **argv) { int headsize=0,format=0,tracknumber=0,deltatime=0,counter=0, datasize=0,midichannel=0,num,i,tempo,port,flag=0,sequence=0,st; unsigned int DT=0; char n,dd,cc,bb,sf,mi; unsigned char c0,c1,nn; FILE *fpi; fpi=fopen(argv[1],"rb"); // header fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'M') write_error("R"); printf("%c",c1); fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'T') write_error("I"); printf("%c",c1); fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'h') write_error("F"); printf("%c",c1); fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'd') write_error("F"); printf("%c\n",c1); counter=getbytedata(4,fpi,&headsize,counter); printf("headsize=%d\n",headsize); counter=0; counter=getbytedata(2,fpi,&format,counter); printf("format=%d [%d]\n",format,counter); counter=getbytedata(2,fpi,&tracknumber,counter); printf("tracknumber=%d [%d]\n",tracknumber,counter); counter=getbytedata(2,fpi,&deltatime,counter); printf("deltatime=%d [%d]\n",deltatime,counter); if (counter!=headsize) {printf("error\n"); exit(1);} // data fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'M') write_error("M"); printf("%c",c1); fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'T') write_error("T"); printf("%c",c1); fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'r') write_error("r"); printf("%c",c1); fread(&c1,sizeof(char),(size_t)1,fpi); if (c1 != 'k') write_error("k"); printf("%c\n",c1); counter=getbytedata(4,fpi,&datasize,counter); printf("datasize=%d\n",datasize); counter=0; do { DT=0; counter=getdata(fpi,&DT,counter); printf("[DT=%d]",DT); counter=getonebyte(fpi,&c0,counter); if (c0!=0xFF && (c0<0x80 || c0>0x8F) && (c0<0x90 || c0>0x9F) && (c0<0xB0 || c0>0xBF) && (c0<0xC0 || c0>0xCF) && c0!=0xF0) { flag=1; c1=c0; c0=st; } if (flag==0) printf("[%x]",c0); if (c0==0xFF) { if (flag==0) counter=getonebyte(fpi,&c1,counter); printf("(%x)",c1); switch (c1) { case 0x00: counter=getonebyte(fpi,&n,counter); if (n!=2) printf("error",n); sequence=0; for (i=1;i<=n;++i) { counter=getonebyte(fpi,&c1,counter); printf("%d ",c1); sequence=(sequence << 8) + c1; } printf("sequence number=%d\n",sequence); break; case 0x02: counter=getonebyte(fpi,&n,counter); printf("(copyright length=%d)",n); for (i=1;i<=n;++i) { counter=getonebyte(fpi,&c1,counter); printf("%c",c1); } printf("\n"); break; case 0x03: counter=getonebyte(fpi,&n,counter); printf("(sequence name length=%d)",n); for (i=1;i<=n;++i) { counter=getonebyte(fpi,&c1,counter); printf("%c",c1); } printf("\n"); break; case 0x04: counter=getonebyte(fpi,&n,counter); printf("(instrument length=%d)",n); for (i=1;i<=n;++i) { counter=getonebyte(fpi,&c1,counter); printf("%c",c1); } printf("\n"); break; case 0x08: counter=getonebyte(fpi,&n,counter); printf("(program name length=%d)",n); for (i=1;i<=n;++i) { counter=getonebyte(fpi,&c1,counter); printf("%c",c1); } printf("\n"); break; case 0x20: counter=getonebyte(fpi,&c1,counter); printf("(%x)",c1); if (c1!=1) {printf("error\n"); exit(1);} counter=getdata(fpi,&midichannel,counter); printf("midichannel=%d\n",midichannel); break; case 0x21: counter=getonebyte(fpi,&c1,counter); printf("(%x)",c1); if (c1!=1) {printf("error\n"); exit(1);} counter=getdata(fpi,&port,counter); printf("port=%d\n",port); break; case 0x2F: counter=getonebyte(fpi,&c1,counter); printf("(end of track %x)\n",c1); if (c1!=0) {printf("error\n"); exit(1);} break; case 0x51: counter=getonebyte(fpi,&n,counter); printf("(tempo length=%d) ",n); tempo=0; for (i=1;i<=n;++i) { counter=getonebyte(fpi,&c1,counter); printf("%d ",c1); tempo=(tempo << 8) + c1; } printf("tempo=%d\n",tempo); break; case 0x54: counter=getonebyte(fpi,&n,counter); printf("(SMPTE length=%d) ",n); for (i=1;i<=n;++i) { counter=getonebyte(fpi,&c1,counter); printf("%d ",c1); } printf("\n"); break; case 0x58: counter=getonebyte(fpi,&n,counter); printf("(takt length=%d) ",n); counter=getonebyte(fpi,&nn,counter); printf("%d/",nn); counter=getonebyte(fpi,&dd,counter); printf("%d ",(int)pow(2,dd)); counter=getonebyte(fpi,&cc,counter); printf("midiclock=%d ",cc); counter=getonebyte(fpi,&bb,counter); printf("4 / 32=%d\n",bb); break; case 0x59: counter=getonebyte(fpi,&n,counter); printf("(key signature length=%d) ",n); counter=getonebyte(fpi,&sf,counter); if (sf>=0) printf("(# x %d) ",sf); if (sf<0) printf("(b x %d) ",-sf); counter=getonebyte(fpi,&mi,counter); if (mi==0) printf("major\n"); if (mi==1) printf("minor\n"); break; default: printf("error\n"); exit(1); } }else if (c0>=0x80 && c0<=0x8F) { if (flag==0) counter=getonebyte(fpi,&c1,counter); printf("[note OFF](note number=%d) ",c1); counter=getonebyte(fpi,&c1,counter); printf("(velocity=%d)\n",c1); }else if (c0>=0x90 && c0<=0x9F) { if (flag==0) counter=getonebyte(fpi,&c1,counter); printf("[note ON](note number=%d) ",c1); counter=getonebyte(fpi,&c1,counter); printf("(velocity=%d)\n",c1); }else if (c0>=0xB0 && c0<=0xBF) { if (flag==0) counter=getonebyte(fpi,&c1,counter); printf("(control number=%d) ",c1); if (c1>=121 && c1<=127) { printf("(reserved)"); }else if (c1==0 || c1==32) { printf("(bank select)"); }else if (c1==6 || c1==38) { printf("(data entry)"); }else if (c1==7) { printf("(channel volume)"); }else if (c1==10) { printf("(panpot)"); }else if (c1==11) { printf("(expression)"); }else if (c1==64) { printf("(Pedal)"); }else if (c1==91) { printf("(reverb)"); }else if (c1==93) { printf("(chorus)"); }else if (c1==94) { printf("(celeste)"); }else if (c1==98 || c1==99) { printf("(NRPN)"); }else if (c1==100 || c1==101) { printf("(RPN)"); }else{ printf("(unknown control)"); } counter=getonebyte(fpi,&c1,counter); printf("(%d)\n",c1); }else if (c0>=0xC0 && c0<=0xCF) { if (flag==0) counter=getonebyte(fpi,&c1,counter); printf("(program change=%d)\n",c1); }else if (c0==0xf0) { if (flag==0) { num=0; counter=getdata(fpi,&num,counter); } printf("(SysEx length=%d)",num); for (i=1;i<=num;++i) { counter=getonebyte(fpi,&nn,counter); printf("%x ",nn); } printf("\n"); }else{ printf("error B\n"); exit(1); } st=c0; flag=0; }while(counter<=datasize); fclose(fpi); } void write_error(char *c) { printf("%s\n",c); exit(1); } int getonebyte(FILE *fpi,char *a,int counter) { char c1; fread(&c1,sizeof(char),(size_t)1,fpi); ++counter; *a=c1; return counter; } int getbytedata(int N,FILE *fpi,int *num,int counter) { int a=0,i; char c1; for (i=1;i<=N;++i) { fread(&c1,sizeof(char),(size_t)1,fpi); a=(a<<8) + (int)c1; ++counter; } *num=a; return counter; } int getdata(FILE *fpi,int *num,int counter) { unsigned char c1,flag; fread(&c1,sizeof(char),(size_t)1,fpi); ++counter; flag=c1 & 0b10000000; if (flag==0) { *num = *num + c1; }else{ *num = (*num << 7) + (c1 - 0b10000000); counter=getdata(fpi,num,counter); } return counter; }


2010-05-20 00:00  nice!(11)  コメント(18)  トラックバック(0) 
共通テーマ:音楽

nice! 11

コメント 18

Caelum

これまたコアな事をなさいますなー('w`)
MIDI(SMF)は format0 と format1 がありますね。
format1 の方が、format0 よりもゴチャゴチャしていないので
データ的には扱いやすいはずっス。
by Caelum (2010-05-20 01:37) 

yablinsky

Caelumさん、コメントありがとうございます。今回は音が出ませんからね。ちょっとコアすぎです。まあ、私の勉強の記録程度にお考えください。今回のプログラムはformat0専用です。format1も多いのでそちらも作るべきでしょうがエネルギーが切れました。
by yablinsky (2010-05-20 07:16) 

Enrique

MIDI規格が出来た頃は,プログラマの手間よりも,メモリの方が貴重な時代だったのですね。
オリジナルな試みをするには手間がかかります。お体に留意のうえ精励ください。
by Enrique (2010-05-20 07:39) 

Cecilia

うっ・・・私にはちんぷんかんぷんです。
でもきっととてもお手間がかかっているのだと思います。

by Cecilia (2010-05-20 17:38) 

yablinsky

Enriqueさん、コメントありがとうございます。本当に手間がかかりますね。どこまでできるかわかりませんが、暇がなかなか見つからず苦労しています。
by yablinsky (2010-05-20 23:16) 

yablinsky

Ceciliaさん、コメントありがとうございます。なんだか変なものお見せして失礼しました。今回は音もなりませんので失礼しました。
by yablinsky (2010-05-20 23:18) 

yablinsky

広島ピアノさん、matchaさん、niceありがとうございます。

by yablinsky (2010-05-21 00:26) 

nyankome

MDIファイルの中身を見るのも大変ですね。
昔、テキストエディタで開いたことがあります。
by nyankome (2010-05-21 01:00) 

yablinsky

nyankomeさん、コメントありがとうございます。規格が古いので見るのも大変です。いまさらなのですが、テキスト版のMIDI規格も作ってほしいところです。同時にヴェロシティが0-127しかない問題点も解消してほしいものです。しかし、ここまで定着するとなかなか新しい規格を作る機運は生まれないのでしょうね。
by yablinsky (2010-05-21 07:07) 

yablinsky

江州石亭さん、コメントありがとうございます。
by yablinsky (2010-05-21 22:18) 

ながぐつ

興味深く読みました。
最近授業の関連で必要に迫られ、ローランドのUA25EXというのを買って、MIDI規格ができたての頃に買ったヤマハのキーボードをつないでPCに信号送って音符を入力しています(以前ご紹介していただいUA-4FXをアマゾンで注文したのですが、なかなか来ないのでキャンセルして上位機種にしました)
MIDI規格のことはほとんど無知だったのですが、yablinskyさんの記事とWikipediaをみて、だいたいの原理はわかりました。

作曲・編曲ソフトを使って作った曲を、MIDIファイルに変換して再演奏させるとなんか変な感じだった理由がわかりました。音域も強弱も音色も128種類しかないのだから、変換する際に適当に割り当てていたんですね~。
by ながぐつ (2010-05-22 16:43) 

ワカタカタカコ

コアですね
by ワカタカタカコ (2010-05-22 18:41) 

yablinsky

ながぐつさん、コメントありがとうございます。MIDI規格自体は私はよく理解しているわけではありません。それでこのような記事を書いてみました。強弱は普通の演奏では十分な解像度のようです。人間はどうも周波数のずれには敏感でも、強弱には鈍感なようです。しかしながらピアノの場合、消え入るような小さな音や鍵盤をたたきつける音は128段階ではでないようです。私の意見では、通常MIDIが変に聞こえるのは、強弱がついておらず、テンポ一定で、間もないときのようです。

by yablinsky (2010-05-22 21:09) 

yablinsky

ワカタカタカコさん、コアです。
by yablinsky (2010-05-22 21:11) 

yablinsky

tamanossimoさん、niceありがとうございます。

by yablinsky (2010-05-22 21:12) 

optimist

MIDIファイルを自作プログラムに読み込んで読み解く・・・面白そうですね。
こういうのって、習うより慣れろじゃありませんが、自分でプログラミングする時にもかなり役立つんじゃないでしょうか?
by optimist (2010-05-23 10:39) 

yablinsky

optimistさん、コメントありがとうございます。そうですね。規格書をみて勉強したり、本を買ってきて勉強するのを考えれば、プログラミングしてしまう方が私は理解が早いですね。ここまでできていれば、後は実際の音の波に変換すれば音が出ます。
by yablinsky (2010-05-23 11:49) 

yablinsky

伊藤さん、niceありがとうございます。
by yablinsky (2010-05-23 14:54) 

コメントを書く

お名前:
URL:
コメント:
画像認証: 下の画像に表示されている文字を入力してください。

 

このブログの更新情報が届きます

すでにブログをお持ちの方は[こちら]


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。