MIDIデータの見える化

[b]このワークシートは[url=https://www.geogebra.org/m/twxxx3yq]Math by Code[/url]の一部です。[br][br]アプレット、背景、実装の順に見ていきましょう。[br][/b][br]前回は鍵盤、ギターの調律につながる、音律を学んだ。[br]音楽では、アナログ楽器も大切だけれど、デジタルな電子楽器や電子データも大切なことがある。[br]電子楽器どうしの情報のやり取りの手段として、MIDIが始まった。[br][br]geogebraではmidiファイルの再生がclassic5でだけできるらしい。[br]今はclassicは6になっているので、再生するにはわざわざバージョンダウンのデスクトップ版の環境を[br]作る必要があるので現実味がない。[br]かといって、midiの重要性がなくなるわけではない。[br]音楽に興味のある人にとって、「教養としてのMIDI」というものがあるでしょう。[br][br]今回はMIDIファイルを読み込んで、見える化してみよう。
1.背景
[b][size=150]<MIDIデータの形式>[br][/size][/b]MIDIのMIはMusical Instrument(楽器)、DIはDigital Interface(デジタルなメッセージのやりとりの規格)という意味です。[br]MIDIデータは8ビット(1バイト)を区切りのためのスタートビットとストップビットの1ビットずつでサンドウィッチにした10ビットの羅列です。[br](例)[br]|01234567|[br]中身の1バイトは2種類あります。ステータスとデータです。[br]どの1バイトも4ビット2個に切れるので、16進数2けたに表示できます。どのバイトも00~FFですね。[br]ステータスとデータを区別は、[b]8ビットの最上位のビットがフラグとして1が立つとステータスです[/b]。[br]ステータスの上位4ビットは1000、つまり8以上、今後これをsとかく。[br]データの上位4ビットは1000未満、つまり0から7、今後これをdとかく。[br](例)[br]sn di djは、ステータスsnについてのデータがdi,djということです。[br][br][b][size=150]<MIDIチャンネルと接続>[br][/size][/b]では、どんなデータがやりとりできるのでしょうか。[br]もともと、MIDI機器、楽器には[b]MIDI端子[/b]がありました。[b]IN, OUT, THRU[/b]です。[br]DINケーブルというMIDI専用のケーブルを使うことが主流でしたが、小型の[b]MIDI端子とケーブル[/b]も出てきています。MIDI機器どうしは、送信チャンネルと受信チャンネルが一致するときに、メッセージのやりとりが成立します。[br]最近は、MIDI端子がなく、USBケーブル1本でお手軽にキーボードをPCにつなげる入門用のキーボードがよくみられます。BluetoothでUSBケーブルすら不要だったりします。[br]しかし、ハード的に送信・受信ともにミディチャンネルがch=1やch=4に固定などの場合がありますので要注意です。[br](例)[br]楽器A,B,Cの3台があるとき、Aをマスターキーボードにして、B,Cは音源として使いたいときにどうしたら[br]よいでしょうか。[br]・[b]直列[/b]配列(A→B→C)AのoutとBのIn,BのthruとCのin。[br]Aのoutのチャンネルがch=1のとき、他のチャンネルBch=1,Cch=1[br]ならB,CともにAの信号を受信してB,Cが発音します。[br]Aがch=1でoutしても、Bch=2, Cch=3なら、Aの信号はAにしかわかりません。ただし、B,Cをそのままにして、Aoutのチャンネルをch=2,ch=3と変えると、それに対応する楽器がなるでしょう。[br]課題は、信号の遅れがありうることです。[br]・[b]並列[/b]配列(A→(B,C))Aのoutと[b]MIDIスルーボックス[/b]のIn, スルーボックスのtrueからB,CのInへ。[br]A,B,Cの関係性はそのままですが、信号の遅れの可能性が減ることです。
[b]<MIDIメッセージ>[/b][br]やりとりするメッセージにはチャンネルに対するものとシステムに対するものがあります。[br]また、チャンネルに対してはボイスとモードに対するものがあります。[br][br]一番よく使うのはチャンネル・ボイス・メッセージでしょう。[br]それをくわしく見ていきましょう。[br][b]・ノートオン、ノートオフ(鍵盤を押すときの音と押す強さ、鍵盤を離すと音を止める)[br][/b] ノートオフは鍵盤を離したときに発せられますが、[br] ノートオンのボリュームゼロとすることもできます。[br] 直前のステータスと同じメッセージのステータスは省略できるというルール(ランニング・ステータス)があるので、ノートのオンオフの繰り返しのたびに、ステータスを切り替えるよりも、ノートオンのままにした方が合理的ですね。[br]・チャンネルアフタータッチ(チャンネルごとに、鍵盤を押し込む効果量の調節)[br]・ポリフォニックアフタータッチ(鍵盤ごとに、鍵盤を押し込む効果量の調節)[br][b][color=#0000ff]・コントロールチェンジCC(音量、左右バランスなどの演奏情報パラメータのコントロールをする)[br][/color][/b]・プログラムチェンジ(プログラムとは音色番号のことで、音色の切り替えをする)[br]・ピッチベンド(鍵盤を押したままピッチベンドホイールなどで音程を連続変化させる)[br][br][b][size=150]<.MIDファイル>[br][/size][/b]拡張子midのついたフィアルはスタンダードMIDIファイル(SMF)という規格があります。[br]データのかたまりの先頭に、カタマリの宣言(ヘッダチャンクかトラックチャンク)があります。[br][br][b]・ヘッダチャンクは8バイトで"MThd"0006、[br][/b]次の6バイトでフォーマット、トラック数、時間単位を2バイトずつで表示します。[br][color=#0000ff](例)[/color][br][b]4d 54 68 64[/b]  Chunk ("MThd")[br]00 00 00 06 続くバイト数は6[br]00 01    フォーマット=1[br]00 05    トラック数=5[br][b][color=#0000ff]00 c0     TimeBase =12×16=192(4分音符の分解能=192ticks)[br][/color][/b]この情報自体が4+4+6=14バイトだから、[br]このあとのバイトはトラックチャンクの宣言がくるはず。[br][br][b]・トラックチャンクは8バイトで"MTrk"xxxx、xxxxが続くデータバイト数[br][/b] トラックデータ先頭の数値はイベント時間で直前メッセージイベントからの[b]差分(デルタ)時間[/b]。[br]デルタタイムの各バイトの[b]先頭ビット(MSB)=1[/b]のときはデータが続くフラグで、MSB=0なら[br]そのバイトで終了というフラグになります。どちらにしても[b]残り7ビット(LSB)がデータ[/b]です。[br][color=#0000ff](例)[/color][br]88 03=>0x1[u]000 1000[/u] 0[u]000 0011[/u]。2つの7ビットを連結して0001000+0000011[br]=100 00000011=>0x403=4*16*16+3=1027(10)です。[br][color=#0000ff](例)[/color][br]8b 66=>0x1[u]000 1011[/u] 0[u]110 0110[/u]。2つの7ビットを連結して0001011+1100110[br]=101 1110 0110=>0x5e6=5*16*16+14*16+6=1280+224+6=1510(10)です。[br][br]・トラックチャンクのデルタタイムのあとに[b]MIDIメッセージ[/b]が来ます。[br] 最初にくるのは[b]プログラムチェンジ[/b]で[b]cnpp[/b](n+1チャンネルのpp番(00~7F)の[b]音色で演奏[/b]。[br] チャンネル番号は0x0~0xFで、1番~16番まで。[br] 最もよく使うのは[b]9nxxyyでノートオン[/b](n+1チャンネルをxx音をyyの強さで)[br] 対になるのが、[b]8nxxyyでノートオフ[/b](n+1チャンネルをxx音をyyの強さで)[br] 9n,8nのような80以上のバイトがないときはランニングステータスで、ステータスの前回同様として[br] 省略されますね。[br][color=#0000ff](例)[/color][br][color=#0000ff]00[/color] [b]92[/b] 26 4f=>3番チャンネルをノートオン(2は0,1,2の2だからチャンネルとしては3番でch3)[br]0x26=>38番の音[b][color=#0000ff]C0=12に+12+12+2[/color][/b]して、D2の鍵盤を強さ0x4f=>4*16+15=79で押した。[br][color=#0000ff](例)[/color][br][color=#0000ff]88 03[/color] [b]82[/b] 26 40=>3番チャンネルをノートオフ[br]0x26=>38番の音D2の鍵盤を強さ0x40=>4*16=64で離した。[br]ノートオンから1027ticks時間、4分音符の1027/192=5.35個のゲートタイム。[br][color=#0000ff](例)[/color][br][color=#0000ff]8b 66[/color] 29 4f=>ランニングステータスでノートオンかノートオフか?[br]デルタタイム8b66=1510経過したら、80未満の29だから、データがきた。[br]ということは、ステータスが省略されているので、直前のメッセージと同じステータス。[br]音は0b29=2*16+9=41=12+12+12+5からA2で、強さは0x4f=79で弾く。[br][color=#0000ff](例)[/color][b][br]トラックチャンネル開始から途中まで[br]4d 54 72 6b[/b]   Chunk ("MTrk")[br][color=#0000ff]00[/color] 00 00 cb    続くデータバイト数はcb=12*16+11=203[br][color=#0000ff]00[/color] [b]c2[/b] 40 0 : プログラムチェンジ (ch:3 pg:64)[br][color=#0000ff]00[/color] [b]92[/b] 26 4f 0 : ノートオン (ch:3 note:38 velo:79)[br][color=#0000ff]88 03[/color] [b]82[/b] 26 40 1027 : オフ (ch:3 note:38 velo:64)[br][color=#0000ff]0b[/color] [b]92[/b] 26 4f +=11 =>1038 : ノートオン (ch:3 note:38 velo:79)[br][color=#0000ff]88 05[/color] [b]82[/b] 26 40 +=1029 =>2067 : オフ (ch:3 note:38 velo:64)[br][color=#0000ff]0b[/color] [b]92[/b] 26 4f +=11 =>2078 : ノートオン (ch:3 note:38 velo:79)[br][color=#0000ff]8b 66[/color] 29 4f +1510 =>3588 : (ノートオン) (ch:3 note:41 velo:79)(ステータス継続)[br]...........[br][br]・メタテキスト[br]メタテキストは[br][br]00 ff のあとに続いて、楽曲の情報のデータの記録をするのがメタテキストです。[br]最初のバイトはタイプで、次のバイトがデータバイト数です。[br]01 テキスト(自由にかけます)[br]02 copyright[br][b]03 シーケンス名/トラック名[br][/b]04 楽器名[br]05 歌詞[br]06 マーカー[br]07 キューポイント[br]20 01 midiチャンネルプリフィックス[br]2f 00 エンドオブトラック[br][b]51 03[/b] tttttt [b]SetTempo[/b][br]54 05 hr mn se fr ff SEMPT Offset[br][b]58 04 nn dd cc bb 拍子(Time Signature) nn =numerate で分子、dd=分母denomiで底2の指数[/b][br][b]59 02 sf mi 調(Key Signature)[br][/b][color=#0000ff](例)[/color][br]00 ff 59 02 00 00 は、00 00調=>Cメジャー調[br]00 ff 51 03 09 27 c0は、テンポは0x0927c0=600000=> BPM=100 [br]00 ff 58 04 04 02 18 08、[br] 拍子は4/2^2=4/4。1拍のmidiclick数=1*16+8=24。4分音符に入る32音符数=8
[b][size=150]<コントロールチェンジなど>[/size][/b][br][size=100]コントロールチェンジは、すでにノートON しているものに操作します。[br][/size]CC#1:モジュレーション(ビブラートなどの変調)、bn01dd(ch n+1に00~7fを設定)[br]CC#2:ブレスコントロール(息の強さで音の変化)、bn02dd(ch n+1に00~7fを設定)[br]CC#5:ポルタメントタイム(ピッチ変化の反応時間)、bn05dd(ch n+1に00~7fを設定)[br]CC#7:ボリューム(チャンネルの音量)、bn07dd(ch n+1に00~7fを設定)[br]CC#10:パンポット(左右の音の定位の変化)、bn0add(ch n+1に00~7fを設定。0x40=64が中央)[br]CC#11:エクスプレッション(フェードインアウトなど)、bn0bdd(ch n+1に00~7fを設定)[br]CC#64:ホールド1(サステインペダルのONOFF)、bn40dd(ch n+1に00~7fを設定,64以上でON)[br]CC#65:ポルタメント(ピッチ変化のONOFF)、bn41dd(ch n+1に00~7fを設定,64以上でON)[br]CC#66:ソステヌートペダル(ペダルのONOFF)、bn42dd(ch n+1に00~7fを設定,64以上でON)[br]CC#67:ソフトペダル(ペダルのONOFF)、bn42dd(ch n+1に00~7fを設定,64以上でON)[br]CC#71:サウンドコントロール(レゾナンス効果の増大)、bn47dd(ch n+1に00~7fを設定)[br]CC#72:サウンドコントロール(リリースタイム)、bn48dd(ch n+1に00~7fを設定)[br]CC#73:サウンドコントロール(アタックタイム)、bn49dd(ch n+1に00~7fを設定)[br]CC#74:サウンドコントロール(カットオフ周波数の高さ)、bn4add(ch n+1に00~7fを設定)[br]CC#84:ポルタメントコントロール(ピッチ変化開始の音の高さ)、bn54dd(ch n+1に00~7fを設定)[br]CC#91~95:内臓エフェクト(かかり具合)、bn5Bdd~bn5fdd(ch n+1に00~7fを設定)[br]チャンネルアフタータッチ:dndd[br]ポリフォニックアフタータッチ:an kk dd (音kkに対して)
2.実装
[color=#9900ff][b][u][size=150]質問:midiファイルをbinary(つまり0か1)で表示するにはどうしたらよいでしょうか。[br][/size][/u][/b][/color][br]midiファイルはbinaryです。ファイルオープンはwith open がおすすめです。[br]読み取ったデータをdataに入れたら、表示するバイト数LineLengthで区切りましょう。[br]区切ったかたまりをsetsとします。sets=dataの文字列の0番目からLineLength個です。[br]そのかたまりsetsをLineLength個の[b]8桁の2進数[/b]のリストとして表示します。[br]<Python>[br]#[IN]===================[br]def [b]showBinary[/b](midi_filepath):[br] LineLength=5 #読み取りと1行表示のバイト数[br] with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く[br] data = f.read()[br] for i in range(0, len(data), LineLength):[br] sets = data[i:i + LineLength][br][b] print( [f"{byte:08b}" for byte in sets])[br][/b]filepath = "C:/Users/example.mid" # 例: "example.midをフルパスで与えたとき"[br][b]showBinary[/b](filepath)[br]#[OUT]==================================[br][color=#0000ff]['01001101', '01010100', '01101000', '01100100', '00000000'][br]['00000000', '00000000', '00000110', '00000000', '00000001'][br]['00000000', '00000101', '00000000', '11000000', '01001101'][br]['01010100', '01110010', '01101011', '00000000', '00000000'][br]['00000000', '11001011', '00000000', '11111111', '01011000'][br][/color]……………
[color=#9900ff][b][u][size=150]質問:midiファイルをhex(つまり16進)で表示するにはどうしたらよいでしょうか。[br][/size][/u][/b][/color][br]midiファイルはbinaryです。binaryをhexにするには、[br]表示の問題なので、書式指定文字列の指定を変えるだけで、ロジックと流れは2進数と同じです。[br]f"{byte:08b}"の8桁2進数を、[br]f"{byte:02x}"の2桁16進数にするだけですね。[br]表示するバイト数LineLengthは多めにしてもよいでしょう。[br]<Python>[br]#[IN]===================[br]def [b]showHex[/b](midi_filepath):[br] LineLength=8[br] with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く[br] data = f.read()[br] for i in range(0, len(data), LineLength):[br] sets = data[i:i + LineLength][br] print([f"{byte:02x}" for byte in sets])[br]filepath = "C:/Users/example.mid" # 例: "example.midをフルパスで与えたとき"[br][b]showHex[/b](filepath)[br]#[OUT]==================================[br][color=#0000ff]['4d', '54', '68', '64', '00', '00', '00', '06'][br]['00', '01', '00', '05', '00', 'c0', '4d', '54'][br]['72', '6b', '00', '00', '00', 'cb', '00', 'ff'][br]['58', '04', '04', '02', '18', '08', '00', 'ff'][br]['51', '03', '09', '27', 'c0', '00', 'ff', '59'][br]['02', '00', '00', '00', 'ff', '01', '32', '54'][br][/color]……………[br]
[color=#9900ff][b][u][size=150]質問:midiファイルから、1トラック分のデータだけをぬきだすにはどうしたらよいでしょう。[br][/size][/u][/b][/color][br]トラックの先頭は、[b]4d 54 72 6bつまり、[/b]"MTrk"です。[br]トラックの最後は、[b]00 ff 2f 0つまり、 エンドオブトラックです。[br][/b]この16進文字列ではさまれた部分が1トラック目のデータになります。[br]だから、読み込んだmidiファイルデータ[b]data[/b]を連続した16進文字列[b]strdata[/b]にしておき、[br]それをスライス[b]strdata[fd1:fd2][/b]すればよいですね。[br]#[IN]==========================[br]def takeTrack1(midi_filepath):[br] with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く[br] data = f.read()[br] strdata =""[br] for byte in data:[br] strdata +=f"{byte:02x}"[br] fd1 = strdata.find('4d54726b')[br] fd2 = strdata.find('00ff2f00')[br] taken = strdata[fd1:fd2][br] length = int(strdata[fd1+8:fd1+16],16)[br] if length*2==len(taken)-8:[br] print("正しく切り取られました。")[br] else:[br] print("切り取りは失敗です。")[br] print(taken[8*2:])[br]filepath = "C:/Users/example.mid"[br]takeTrack1(filepath)[br]#[OUT]========================[br][color=#0000ff]00ff58040402180800ff51030927c000ff5902000000ff013254686520426561746c6573272[br]02861637475616c6c79204d63436172746e657920616c6f6e6529205965737465726461792e00ff013d536571[br]75656e63656420666f722074686520526f6c616e64204d542d333220616e6420636f6d70617469626c6573206[br]279204d696b6520446f796c652e00ff010000ff013344656469636174656420746f206d79206661746865722c2[br]0526f6265727420452e20446f796c652c20313931392d313939312e[/color]
[b][u][size=150][color=#9900ff]質問:midiファイルから1トラック分のmidiメッセージを取得するにどうしますか。[br][/color][/size][/u][/b][br]トラックによっては、ノートオンのステータスが入っていない場合があります。[br]そのときは、そのあとのデータから1トラックを抜き出しましょう。[br]デルタタイムは可変長なのですが、2バイトくらいは対応できるようにします。[br]また、ランニングステータスとして、ステータスバイトが前のmidiメッセージと同じ場合の対応も[br]必要です。[br]また、デルタタイム[b]intdelta[/b]だけでは、ノートオンオフのデータとして使えないので、[br]それを累積する変数accumTimeを用意しましょう。[br]str_takenが16進データの各バイトを文字列としてくっつけたものです。[br]list_takenが16進データの各バイトをリストとして入れたもので、情報としては同じです。[br]1文字ずつの個数と、2文字で1バイトでの個数では、個数比が2:1なので、ここが注意点ですね。[br][b]文字列[/b]だと、文字列.find(検索文字列)が使えるので便利です。[br]バイト単位にwhile文をまわして、情報をひろっていくには[b]リスト[/b]が便利です。[br][br][IN]===============================================[br]def [b]takeTrk1Msgs[/b](midi_filepath):[br] with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く[br] data = f.read()[br] strdata =""[br] for byte in data:[br] strdata +=f"{byte:02x}"[br] fd1 = strdata.find('4d54726b') + 8*2[br] fd2 = strdata.find('00ff2f00')[br] listdata =[f"{byte:02x}" for byte in data][br] str_taken = strdata[fd1:fd2][br][br] [color=#0000ff] #1トラック目に演奏情報が含まれてなければ、次のトラックを取り出す。[br][/color] noteOnCount = str_taken.count('0092')[br] if noteOnCount == 0:[br] strdata = strdata[fd2 + 8:][br] listdata =listdata[(fd2+8)//2:][br] fd1 = strdata.find('4d54726b') + 8*2[br] fd2 = strdata.find('00ff2f00')[br][b][color=#0000ff] str_taken = strdata[fd1:fd2][br] list_taken = listdata[fd1//2:fd2//2][br][/color][/b][br]  [color=#0000ff]#エンドオブトラック以外のメタデータはトラックの先頭にかたまると仮定する。[br] #演奏データはノートonとノートoffに限ると仮定する。[br][/color] pos = str_taken.find('0092')//2 [color=#0000ff]#最初のノートONのデータ位置 [/color][br] msgs=[] [color=#0000ff]# msg=[accumulated_deltaTime, status, notenum,velo]を格納する [br] #notenum 0=>C0, 12=>C1,......[br][/color] status = 0[br] size = len(list_taken)[br] accumTime = 0[br] while pos < size:[br] intdelta = int(list_taken[pos],16) [br] if intdelta >= 2**7: [color=#0000ff]#デルタタイムの2バイト対応[/color][br] pos +=1[br] intdelta =(intdelta - 2**7)*(2**7) + int(list_taken[pos],16)[br] accumTime += intdelta[br] if list_taken[pos+1]!='92' and list_taken[pos+1]!='82':[color=#0000ff]#ランニングステータス対応[br][/color] status = msgs[-1][1][br] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)][br] pos += 3[br] else:[br] status = list_taken[pos+1][br] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)][br] pos += 4[br] print(msg)[br] msgs.append(msg)[br] [br][b]filepath = "C:/Users/example.mid" #フルパスがおすすめ。[br]takeTrk1Msgs[/b](filepath)[br]#[OUT]=======================================================[br][0, '92', 38, 79][br][1027, '82', 38, 64][br][1038, '92', 38, 79][br][2067, '82', 38, 64][br][2078, '92', 38, 79][br][3588, '92', 79, 136][br][4616, '82', 41, 64][br][4627, '92', 40, 79][br][5136, '82', 40, 64][br][5147, '92', 45, 79][br][5656, '82', 45, 64][br][5668, '82', 64, 0][br][5668, '92', 38, 79][br][6448, '92', 79, 129][br][6696, '82', 48, 64][br][6708, '92', 46, 79][br][7216, '82', 46, 64][br][7227, '92', 48, 79][br]……[br]……
[color=#9900ff][u][b][size=150]質問:midiデータからピアノロールを表示するにはどうしたらよいでしょうか。[br][/size][/b][/u][/color][br]同じ音ごとに、音の辞書を作る方法があります。[br]音べつにノートのオンオフのデータをならべれば、音のスタートとストップのデータができます。[br]データ数が半分になります。[br][(スタート、音の高さ)、(ストップ、音の高さ)]のセグメント情報を作ることで、[br]midiデータをピアノロール風に表示できるようになります。[br][IN]==================================================[br][b]import numpy as np[br]import matplotlib.pyplot as plt[br]import matplotlib.collections as mc[br]import matplotlib.cm as cm[br]def takeTrk1gate(midi_filepath):[br][/b] [br] with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く[br] data = f.read()[br] strdata =""[br] for byte in data:[br] strdata +=f"{byte:02x}"[br] fd1 = strdata.find('4d54726b') + 8*2[br] fd2 = strdata.find('00ff2f00')[br] listdata =[f"{byte:02x}" for byte in data][br] str_taken = strdata[fd1:fd2][br][br] #1トラック目に演奏情報が含まれてなければ、次のトラックを取り出す。[br] noteOnCount = str_taken.count('0092')[br] if noteOnCount == 0:[br] strdata = strdata[fd2 + 8:][br] listdata =listdata[(fd2+8)//2:][br] fd1 = strdata.find('4d54726b') + 8*2[br] fd2 = strdata.find('00ff2f00')[br] str_taken = strdata[fd1:fd2][br] list_taken = listdata[fd1//2:fd2//2][br] #演奏データはノートonとノートoffに限ると仮定する。[br] pos = str_taken.find('0092')//2 #最初のノートONのデータ位置 [br] msgs=[] # msg=[accumulated_deltaTime, status, notenum,velo]を格納する [br] #notenum 0=>C0, 12=>C1,......[br] status = 0[br] size = len(list_taken)[br] accumTime = 0[br] while pos < size:[br] intdelta = int(list_taken[pos],16) [br] if intdelta >= 2**7: #デルタタイムの2バイト対応[br] pos +=1[br] intdelta =(intdelta - 2**7)*(2**7) + int(list_taken[pos],16)[br] accumTime += intdelta[br] if list_taken[pos+1]!='92' and list_taken[pos+1]!='82':#ランニングステータス対応[br] status = msgs[-1][1][br] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)][br] pos += 3[br] else:[br] status = list_taken[pos+1][br] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)][br] pos += 4[br] #print(msg)[br] msgs.append(msg)[br] lastTime = msgs[-1][0]/320 #横の長さの調整のために時間軸のサイズダウンをする。[br] noteDict={}[br] [b][color=#0000ff] #同じノート別に辞書を作る。[br][/color][/b] [color=#0000ff]for msg in msgs:[br] note = msg[2][br] if note in noteDict:[br] noteDict[note].append(msg)[br] else:[br] noteDict[note]=[msg][br] #print(noteDict)[br] notes = noteDict.keys()[br] miny=min(notes)[br] maxy=max(notes)[br] [b] #ノート別noteにmsgのオンオフのペアからノートオンaccumTimeをstartTime、[br] #ノートオフaccumTimeをstopTImeにして、velocityはノートオンのみ採用。[br] #[(startTime,note), (stopTime,note)]の形式に変換する。[br][/b] noteSegments=[][br] noteVelos=[][br] startTime =0[br] stopTime = 0[br] velocity = 0[br] note = 0[br] for k,v in noteDict.items():[br] note = k [br] for msg in v:[br] if msg[1]=='92':[br] startTime = msg[0]/320[br] noteVelos.append(msg[3])[br] velocity = msg[3][br] elif msg[1]=='82':[br] stopTime = msg[0]/320[br] noteSegments.append([(startTime, note),(stopTime,note)] )[br] print(noteSegments) [br] #print(noteVelos) [br] [b] return noteSegments,noteVelos,lastTime, miny,maxy[br][/b]#=================== 以下でデータ渡しと視覚化をする[br][/color]filepath = "C:/Users/example.mid"[br]noteSegments,noteVelos,lastTime,miny,maxy= takeTrk1gate(filepath)[br][b]xmin = 0[br]xmax = lastTime[br]ymin = miny[br]ymax = maxy[br]lines = noteSegments[br]lc = mc.LineCollection(lines, linewidths=2)[br]fig = plt.figure(figsize=(20,20))[br]ax = fig.add_subplot(aspect='1')[br]ax.add_collection(lc)[br]ax.autoscale()[br]plt.xlim(xmin, xmax); plt.ylim(ymin, ymax)[br]plt.xlabel('x'); plt.ylabel('y')[br]plt.show()[br][/b]#[OUT]==========================================[br][[(0.0, 38), (3.209375, 38)], [(3.24375, 38), (6.459375, 38)], [(50.36875, 38), (51.959375, 38)], [(60.2625, 38), (61.04375, 38)], [(89.628125, 38), (92.025, 38)], [(99.378125, 38), (100.9625, 38)], [(138.634375, 38), (141.034375, 38)], [(148.3875, 38), (149.975, 38)], [(112.934375, 41), (14.425, 41)], [(24.2125, 41), (26.6125, 41)], [(31.96875, 41), (34.075, 41)], [(34.1125, 41), (37.328125, 41)], [(47.11875, 41), (49.51875, 41)], [(79.775, 41), (83.084375, 41)], [(83.125, 41), (86.3375, 41)], [(96.128125, 41), (98.528125, 41)], [(103.884375, 41), (105.984375, 41)], [(115.778125, 41), (118.99375, 41)], [(128.784375, 41), (132.09375, 41)], [(132.1375, 41), (135.35, 41)], [(145.1375, 41), (147.5375, 41)], [(152.89375, 41), (155.234375, 41)], [(155.278125, 41), (157.078125, 41)], [(159.95, 41), (160.503125, 41)], [(160.553125, 41), (166.003125, 41)], [(14.459375, 40), (16.05, 40)], [(26.65, 40), (27.428125, 40)], [(37.3625, 40), (38.953125, 40)], [(49.553125, 40), (50.328125, 40)], [(86.375, 40), (87.9625, 40)], [(98.5625, 40), (99.3375, 40)], [(135.384375, 40), (136.975, 40)], [(147.575, 40), (148.35, 40)], [(16.084375, 45), (17.675, 45)], [(33.25, 45), (33.64375, 45)], [(38.9875, 45), (40.578125, 45)], [(57.01875, 45), (59.05, 45)], [(70.025, 45), (73.234375, 45)], [(88.0, 45), (89.5875, 45)], [(119.034375, 45), (122.24375, 45)], [(137.009375, 45), (138.6, 45)], [(137.009375, 64), (17.7125, 64)], [(137.009375, 64), (27.4625, 64)], [(137.009375, 64), (34.075, 64)], [(137.009375, 64), (43.0125, 64)], [(137.009375, 64), (54.8375, 64)], [(137.009375, 64), (56.978125, 64)], [(137.009375, 64), (60.228125, 64)], [(137.009375, 64), (61.04375, 64)], [(137.009375, 64), (61.853125, 64)], [(137.009375, 64), (63.484375, 64)], [(137.009375, 64), (65.534375, 64)], [(137.009375, 64), (109.2375, 64)], [(137.009375, 64), (110.053125, 64)], [(137.009375, 64), (110.8625, 64)], [(137.009375, 64), (112.4875, 64)], [(137.009375, 64), (115.74375, 64)], [(137.009375, 48), (20.925, 48)], [(22.584375, 48), (24.178125, 48)], [(43.053125, 48), (43.828125, 48)], [(45.4875, 48), (47.078125, 48)], [(61.078125, 48), (61.853125, 48)], [(74.084375, 48), (74.859375, 48)], [(78.15, 48), (79.7375, 48)], [(92.059375, 48), (92.8375, 48)], [(94.503125, 48), (96.0875, 48)], [(110.0875, 48), (110.8625, 48)], [(123.0875, 48), (123.86875, 48)], [(127.153125, 48), (128.74375, 48)], [(141.075, 48), (141.85, 48)], [(143.509375, 48), (145.103125, 48)], [(20.9625, 46), (22.55, 46)], [(30.71875, 46), (31.934375, 46)], [(43.8625, 46), (45.453125, 46)], [(53.625, 46), (54.8375, 46)], [(74.89375, 46), (76.484375, 46)], [(92.875, 46), (94.4625, 46)], [(102.634375, 46), (103.84375, 46)], [(123.903125, 46), (125.49375, 46)], [(141.884375, 46), (143.475, 46)], [(151.64375, 46), (152.859375, 46)], [(158.959375, 46), (159.903125, 46)], [(158.959375, 43), (30.678125, 43)], [(158.959375, 43), (33.2125, 43)], [(51.99375, 43), (53.584375, 43)], [(63.51875, 43), (65.109375, 43)], [(65.55, 43), (65.91875, 43)], [(76.525, 43), (78.1125, 43)], [(101.0, 43), (102.59375, 43)], [(112.528125, 43), (114.11875, 43)], [(125.534375, 43), (127.11875, 43)], [(150.009375, 43), (151.603125, 43)], [(157.11875, 43), (158.925, 43)], [(157.11875, 57), (56.978125, 57)], [(157.11875, 57), (108.428125, 57)], [(157.11875, 47), (59.825, 47)], [(157.11875, 58), (63.484375, 58)], [(64.328125, 58), (65.5125, 58)], [(110.903125, 58), (112.4875, 58)], [(113.3375, 58), (114.525, 58)], [(113.3375, 50), (64.29375, 50)], [(73.26875, 50), (74.05, 50)], [(109.275, 50), (110.053125, 50)], [(109.275, 50), (113.303125, 50)], [(122.278125, 50), (123.059375, 50)], [(66.6, 53), (67.9375, 53)], [(108.4625, 59), (108.834375, 59)], [(114.559375, 55), (114.928125, 55)]][br][br]
ここまでのデータ化と視覚化は、midiメッセージをnoteon,noteofに限定しています。しかも、1トラックだけです。本格的な音楽作成にはまったく役に立たないですが、MIDIファイル、SMFの構造を知るという経験はできたと思います。[br]汎用のmidiデータのpianoロールが必要ならば、DAWでファイルを開く方が簡単ですね。[br][br][b][size=150]<Geogebra>[/size][/b][br]geogebraで同じことをするには、関数型の記法には限界があるので、pythonコードをjavascriptに移し替えて、実行して最後のsegmentデータまで作ったら、それをgeogebraのオブジェクトとしてのsegmentを表示するということでできるでしょう。今回はアプレットはつくってません。

Informazioni: MIDIデータの見える化