MIDIデータの見える化

このワークシートはMath by Codeの一部です。 アプレット、背景、実装の順に見ていきましょう。 前回は鍵盤、ギターの調律につながる、音律を学んだ。 音楽では、アナログ楽器も大切だけれど、デジタルな電子楽器や電子データも大切なことがある。 電子楽器どうしの情報のやり取りの手段として、MIDIが始まった。 geogebraではmidiファイルの再生がclassic5でだけできるらしい。 今はclassicは6になっているので、再生するにはわざわざバージョンダウンのデスクトップ版の環境を 作る必要があるので現実味がない。 かといって、midiの重要性がなくなるわけではない。 音楽に興味のある人にとって、「教養としてのMIDI」というものがあるでしょう。 今回はMIDIファイルを読み込んで、見える化してみよう。
Image

1.背景

<MIDIデータの形式> MIDIのMIはMusical Instrument(楽器)、DIはDigital Interface(デジタルなメッセージのやりとりの規格)という意味です。 MIDIデータは8ビット(1バイト)を区切りのためのスタートビットとストップビットの1ビットずつでサンドウィッチにした10ビットの羅列です。 (例) |01234567| 中身の1バイトは2種類あります。ステータスとデータです。 どの1バイトも4ビット2個に切れるので、16進数2けたに表示できます。どのバイトも00~FFですね。 ステータスとデータを区別は、8ビットの最上位のビットがフラグとして1が立つとステータスです。 ステータスの上位4ビットは1000、つまり8以上、今後これをsとかく。 データの上位4ビットは1000未満、つまり0から7、今後これをdとかく。 (例) sn di djは、ステータスsnについてのデータがdi,djということです。 <MIDIチャンネルと接続> では、どんなデータがやりとりできるのでしょうか。 もともと、MIDI機器、楽器にはMIDI端子がありました。IN, OUT, THRUです。 DINケーブルというMIDI専用のケーブルを使うことが主流でしたが、小型のMIDI端子とケーブルも出てきています。MIDI機器どうしは、送信チャンネルと受信チャンネルが一致するときに、メッセージのやりとりが成立します。 最近は、MIDI端子がなく、USBケーブル1本でお手軽にキーボードをPCにつなげる入門用のキーボードがよくみられます。BluetoothでUSBケーブルすら不要だったりします。 しかし、ハード的に送信・受信ともにミディチャンネルがch=1やch=4に固定などの場合がありますので要注意です。 (例) 楽器A,B,Cの3台があるとき、Aをマスターキーボードにして、B,Cは音源として使いたいときにどうしたら よいでしょうか。 ・直列配列(A→B→C)AのoutとBのIn,BのthruとCのin。 Aのoutのチャンネルがch=1のとき、他のチャンネルBch=1,Cch=1 ならB,CともにAの信号を受信してB,Cが発音します。 Aがch=1でoutしても、Bch=2, Cch=3なら、Aの信号はAにしかわかりません。ただし、B,Cをそのままにして、Aoutのチャンネルをch=2,ch=3と変えると、それに対応する楽器がなるでしょう。 課題は、信号の遅れがありうることです。 ・並列配列(A→(B,C))AのoutとMIDIスルーボックスのIn, スルーボックスのtrueからB,CのInへ。 A,B,Cの関係性はそのままですが、信号の遅れの可能性が減ることです。
<MIDIメッセージ> やりとりするメッセージにはチャンネルに対するものとシステムに対するものがあります。 また、チャンネルに対してはボイスとモードに対するものがあります。 一番よく使うのはチャンネル・ボイス・メッセージでしょう。 それをくわしく見ていきましょう。 ・ノートオン、ノートオフ(鍵盤を押すときの音と押す強さ、鍵盤を離すと音を止める)  ノートオフは鍵盤を離したときに発せられますが、  ノートオンのボリュームゼロとすることもできます。  直前のステータスと同じメッセージのステータスは省略できるというルール(ランニング・ステータス)があるので、ノートのオンオフの繰り返しのたびに、ステータスを切り替えるよりも、ノートオンのままにした方が合理的ですね。 ・チャンネルアフタータッチ(チャンネルごとに、鍵盤を押し込む効果量の調節) ・ポリフォニックアフタータッチ(鍵盤ごとに、鍵盤を押し込む効果量の調節) ・コントロールチェンジCC(音量、左右バランスなどの演奏情報パラメータのコントロールをする) ・プログラムチェンジ(プログラムとは音色番号のことで、音色の切り替えをする) ・ピッチベンド(鍵盤を押したままピッチベンドホイールなどで音程を連続変化させる) <.MIDファイル> 拡張子midのついたフィアルはスタンダードMIDIファイル(SMF)という規格があります。 データのかたまりの先頭に、カタマリの宣言(ヘッダチャンクかトラックチャンク)があります。 ・ヘッダチャンクは8バイトで"MThd"0006、 次の6バイトでフォーマット、トラック数、時間単位を2バイトずつで表示します。 (例) 4d 54 68 64  Chunk ("MThd") 00 00 00 06 続くバイト数は6 00 01    フォーマット=1 00 05    トラック数=5 00 c0     TimeBase =12×16=192(4分音符の分解能=192ticks) この情報自体が4+4+6=14バイトだから、 このあとのバイトはトラックチャンクの宣言がくるはず。 ・トラックチャンクは8バイトで"MTrk"xxxx、xxxxが続くデータバイト数  トラックデータ先頭の数値はイベント時間で直前メッセージイベントからの差分(デルタ)時間。 デルタタイムの各バイトの先頭ビット(MSB)=1のときはデータが続くフラグで、MSB=0なら そのバイトで終了というフラグになります。どちらにしても残り7ビット(LSB)がデータです。 (例) 88 03=>0x1000 1000 0000 0011。2つの7ビットを連結して0001000+0000011 =100 00000011=>0x403=4*16*16+3=1027(10)です。 (例) 8b 66=>0x1000 1011 0110 0110。2つの7ビットを連結して0001011+1100110 =101 1110 0110=>0x5e6=5*16*16+14*16+6=1280+224+6=1510(10)です。 ・トラックチャンクのデルタタイムのあとにMIDIメッセージが来ます。  最初にくるのはプログラムチェンジcnpp(n+1チャンネルのpp番(00~7F)の音色で演奏。  チャンネル番号は0x0~0xFで、1番~16番まで。  最もよく使うのは9nxxyyでノートオン(n+1チャンネルをxx音をyyの強さで)  対になるのが、8nxxyyでノートオフ(n+1チャンネルをxx音をyyの強さで)  9n,8nのような80以上のバイトがないときはランニングステータスで、ステータスの前回同様として  省略されますね。 (例) 00 92 26 4f=>3番チャンネルをノートオン(2は0,1,2の2だからチャンネルとしては3番でch3) 0x26=>38番の音C0=12に+12+12+2して、D2の鍵盤を強さ0x4f=>4*16+15=79で押した。 (例) 88 03 82 26 40=>3番チャンネルをノートオフ 0x26=>38番の音D2の鍵盤を強さ0x40=>4*16=64で離した。 ノートオンから1027ticks時間、4分音符の1027/192=5.35個のゲートタイム。 (例) 8b 66 29 4f=>ランニングステータスでノートオンかノートオフか? デルタタイム8b66=1510経過したら、80未満の29だから、データがきた。 ということは、ステータスが省略されているので、直前のメッセージと同じステータス。 音は0b29=2*16+9=41=12+12+12+5からA2で、強さは0x4f=79で弾く。 (例) トラックチャンネル開始から途中まで 4d 54 72 6b   Chunk ("MTrk") 00 00 00 cb    続くデータバイト数はcb=12*16+11=203 00 c2 40 0 : プログラムチェンジ (ch:3 pg:64) 00 92 26 4f 0 : ノートオン (ch:3 note:38 velo:79) 88 03 82 26 40 1027 : オフ (ch:3 note:38 velo:64) 0b 92 26 4f +=11 =>1038 : ノートオン (ch:3 note:38 velo:79) 88 05 82 26 40 +=1029 =>2067 : オフ (ch:3 note:38 velo:64) 0b 92 26 4f +=11 =>2078 : ノートオン (ch:3 note:38 velo:79) 8b 66 29 4f +1510 =>3588 : (ノートオン) (ch:3 note:41 velo:79)(ステータス継続) ........... ・メタテキスト メタテキストは 00 ff のあとに続いて、楽曲の情報のデータの記録をするのがメタテキストです。 最初のバイトはタイプで、次のバイトがデータバイト数です。 01 テキスト(自由にかけます) 02 copyright 03 シーケンス名/トラック名 04 楽器名 05 歌詞 06 マーカー 07 キューポイント 20 01 midiチャンネルプリフィックス 2f 00 エンドオブトラック 51 03 tttttt SetTempo 54 05 hr mn se fr ff SEMPT Offset 58 04 nn dd cc bb 拍子(Time Signature) nn =numerate で分子、dd=分母denomiで底2の指数 59 02 sf mi 調(Key Signature) (例) 00 ff 59 02 00 00 は、00 00調=>Cメジャー調 00 ff 51 03 09 27 c0は、テンポは0x0927c0=600000=> BPM=100 00 ff 58 04 04 02 18 08、  拍子は4/2^2=4/4。1拍のmidiclick数=1*16+8=24。4分音符に入る32音符数=8
<コントロールチェンジなど> コントロールチェンジは、すでにノートON しているものに操作します。 CC#1:モジュレーション(ビブラートなどの変調)、bn01dd(ch n+1に00~7fを設定) CC#2:ブレスコントロール(息の強さで音の変化)、bn02dd(ch n+1に00~7fを設定) CC#5:ポルタメントタイム(ピッチ変化の反応時間)、bn05dd(ch n+1に00~7fを設定) CC#7:ボリューム(チャンネルの音量)、bn07dd(ch n+1に00~7fを設定) CC#10:パンポット(左右の音の定位の変化)、bn0add(ch n+1に00~7fを設定。0x40=64が中央) CC#11:エクスプレッション(フェードインアウトなど)、bn0bdd(ch n+1に00~7fを設定) CC#64:ホールド1(サステインペダルのONOFF)、bn40dd(ch n+1に00~7fを設定,64以上でON) CC#65:ポルタメント(ピッチ変化のONOFF)、bn41dd(ch n+1に00~7fを設定,64以上でON) CC#66:ソステヌートペダル(ペダルのONOFF)、bn42dd(ch n+1に00~7fを設定,64以上でON) CC#67:ソフトペダル(ペダルのONOFF)、bn42dd(ch n+1に00~7fを設定,64以上でON) CC#71:サウンドコントロール(レゾナンス効果の増大)、bn47dd(ch n+1に00~7fを設定) CC#72:サウンドコントロール(リリースタイム)、bn48dd(ch n+1に00~7fを設定) CC#73:サウンドコントロール(アタックタイム)、bn49dd(ch n+1に00~7fを設定) CC#74:サウンドコントロール(カットオフ周波数の高さ)、bn4add(ch n+1に00~7fを設定) CC#84:ポルタメントコントロール(ピッチ変化開始の音の高さ)、bn54dd(ch n+1に00~7fを設定) CC#91~95:内臓エフェクト(かかり具合)、bn5Bdd~bn5fdd(ch n+1に00~7fを設定) チャンネルアフタータッチ:dndd ポリフォニックアフタータッチ:an kk dd (音kkに対して)

2.実装

質問:midiファイルをbinary(つまり0か1)で表示するにはどうしたらよいでしょうか。 midiファイルはbinaryです。ファイルオープンはwith open がおすすめです。 読み取ったデータをdataに入れたら、表示するバイト数LineLengthで区切りましょう。 区切ったかたまりをsetsとします。sets=dataの文字列の0番目からLineLength個です。 そのかたまりsetsをLineLength個の8桁の2進数のリストとして表示します。 <Python> #[IN]=================== def showBinary(midi_filepath): LineLength=5 #読み取りと1行表示のバイト数 with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く data = f.read() for i in range(0, len(data), LineLength): sets = data[i:i + LineLength] print( [f"{byte:08b}" for byte in sets]) filepath = "C:/Users/example.mid" # 例: "example.midをフルパスで与えたとき" showBinary(filepath) #[OUT]================================== ['01001101', '01010100', '01101000', '01100100', '00000000'] ['00000000', '00000000', '00000110', '00000000', '00000001'] ['00000000', '00000101', '00000000', '11000000', '01001101'] ['01010100', '01110010', '01101011', '00000000', '00000000'] ['00000000', '11001011', '00000000', '11111111', '01011000'] ……………
質問:midiファイルをhex(つまり16進)で表示するにはどうしたらよいでしょうか。 midiファイルはbinaryです。binaryをhexにするには、 表示の問題なので、書式指定文字列の指定を変えるだけで、ロジックと流れは2進数と同じです。 f"{byte:08b}"の8桁2進数を、 f"{byte:02x}"の2桁16進数にするだけですね。 表示するバイト数LineLengthは多めにしてもよいでしょう。 <Python> #[IN]=================== def showHex(midi_filepath): LineLength=8 with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く data = f.read() for i in range(0, len(data), LineLength): sets = data[i:i + LineLength] print([f"{byte:02x}" for byte in sets]) filepath = "C:/Users/example.mid" # 例: "example.midをフルパスで与えたとき" showHex(filepath) #[OUT]================================== ['4d', '54', '68', '64', '00', '00', '00', '06'] ['00', '01', '00', '05', '00', 'c0', '4d', '54'] ['72', '6b', '00', '00', '00', 'cb', '00', 'ff'] ['58', '04', '04', '02', '18', '08', '00', 'ff'] ['51', '03', '09', '27', 'c0', '00', 'ff', '59'] ['02', '00', '00', '00', 'ff', '01', '32', '54'] ……………
質問:midiファイルから、1トラック分のデータだけをぬきだすにはどうしたらよいでしょう。 トラックの先頭は、4d 54 72 6bつまり、"MTrk"です。 トラックの最後は、00 ff 2f 0つまり、 エンドオブトラックです。 この16進文字列ではさまれた部分が1トラック目のデータになります。 だから、読み込んだmidiファイルデータdataを連続した16進文字列strdataにしておき、 それをスライスstrdata[fd1:fd2]すればよいですね。 #[IN]========================== def takeTrack1(midi_filepath): with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く data = f.read() strdata ="" for byte in data: strdata +=f"{byte:02x}" fd1 = strdata.find('4d54726b') fd2 = strdata.find('00ff2f00') taken = strdata[fd1:fd2] length = int(strdata[fd1+8:fd1+16],16) if length*2==len(taken)-8: print("正しく切り取られました。") else: print("切り取りは失敗です。") print(taken[8*2:]) filepath = "C:/Users/example.mid" takeTrack1(filepath) #[OUT]======================== 00ff58040402180800ff51030927c000ff5902000000ff013254686520426561746c6573272 02861637475616c6c79204d63436172746e657920616c6f6e6529205965737465726461792e00ff013d536571 75656e63656420666f722074686520526f6c616e64204d542d333220616e6420636f6d70617469626c6573206 279204d696b6520446f796c652e00ff010000ff013344656469636174656420746f206d79206661746865722c2 0526f6265727420452e20446f796c652c20313931392d313939312e
質問:midiファイルから1トラック分のmidiメッセージを取得するにどうしますか。 トラックによっては、ノートオンのステータスが入っていない場合があります。 そのときは、そのあとのデータから1トラックを抜き出しましょう。 デルタタイムは可変長なのですが、2バイトくらいは対応できるようにします。 また、ランニングステータスとして、ステータスバイトが前のmidiメッセージと同じ場合の対応も 必要です。 また、デルタタイムintdeltaだけでは、ノートオンオフのデータとして使えないので、 それを累積する変数accumTimeを用意しましょう。 str_takenが16進データの各バイトを文字列としてくっつけたものです。 list_takenが16進データの各バイトをリストとして入れたもので、情報としては同じです。 1文字ずつの個数と、2文字で1バイトでの個数では、個数比が2:1なので、ここが注意点ですね。 文字列だと、文字列.find(検索文字列)が使えるので便利です。 バイト単位にwhile文をまわして、情報をひろっていくにはリストが便利です。 [IN]=============================================== def takeTrk1Msgs(midi_filepath): with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く data = f.read() strdata ="" for byte in data: strdata +=f"{byte:02x}" fd1 = strdata.find('4d54726b') + 8*2 fd2 = strdata.find('00ff2f00') listdata =[f"{byte:02x}" for byte in data] str_taken = strdata[fd1:fd2] #1トラック目に演奏情報が含まれてなければ、次のトラックを取り出す。 noteOnCount = str_taken.count('0092') if noteOnCount == 0: strdata = strdata[fd2 + 8:] listdata =listdata[(fd2+8)//2:] fd1 = strdata.find('4d54726b') + 8*2 fd2 = strdata.find('00ff2f00') str_taken = strdata[fd1:fd2] list_taken = listdata[fd1//2:fd2//2]   #エンドオブトラック以外のメタデータはトラックの先頭にかたまると仮定する。 #演奏データはノートonとノートoffに限ると仮定する。 pos = str_taken.find('0092')//2 #最初のノートONのデータ位置 msgs=[] # msg=[accumulated_deltaTime, status, notenum,velo]を格納する #notenum 0=>C0, 12=>C1,...... status = 0 size = len(list_taken) accumTime = 0 while pos < size: intdelta = int(list_taken[pos],16) if intdelta >= 2**7: #デルタタイムの2バイト対応 pos +=1 intdelta =(intdelta - 2**7)*(2**7) + int(list_taken[pos],16) accumTime += intdelta if list_taken[pos+1]!='92' and list_taken[pos+1]!='82':#ランニングステータス対応 status = msgs[-1][1] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)] pos += 3 else: status = list_taken[pos+1] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)] pos += 4 print(msg) msgs.append(msg) filepath = "C:/Users/example.mid" #フルパスがおすすめ。 takeTrk1Msgs(filepath) #[OUT]======================================================= [0, '92', 38, 79] [1027, '82', 38, 64] [1038, '92', 38, 79] [2067, '82', 38, 64] [2078, '92', 38, 79] [3588, '92', 79, 136] [4616, '82', 41, 64] [4627, '92', 40, 79] [5136, '82', 40, 64] [5147, '92', 45, 79] [5656, '82', 45, 64] [5668, '82', 64, 0] [5668, '92', 38, 79] [6448, '92', 79, 129] [6696, '82', 48, 64] [6708, '92', 46, 79] [7216, '82', 46, 64] [7227, '92', 48, 79] …… ……
質問:midiデータからピアノロールを表示するにはどうしたらよいでしょうか。 同じ音ごとに、音の辞書を作る方法があります。 音べつにノートのオンオフのデータをならべれば、音のスタートとストップのデータができます。 データ数が半分になります。 [(スタート、音の高さ)、(ストップ、音の高さ)]のセグメント情報を作ることで、 midiデータをピアノロール風に表示できるようになります。 [IN]================================================== import numpy as np import matplotlib.pyplot as plt import matplotlib.collections as mc import matplotlib.cm as cm def takeTrk1gate(midi_filepath): with open(midi_filepath, 'rb') as f: # バイナリモードでファイルを開く data = f.read() strdata ="" for byte in data: strdata +=f"{byte:02x}" fd1 = strdata.find('4d54726b') + 8*2 fd2 = strdata.find('00ff2f00') listdata =[f"{byte:02x}" for byte in data] str_taken = strdata[fd1:fd2] #1トラック目に演奏情報が含まれてなければ、次のトラックを取り出す。 noteOnCount = str_taken.count('0092') if noteOnCount == 0: strdata = strdata[fd2 + 8:] listdata =listdata[(fd2+8)//2:] fd1 = strdata.find('4d54726b') + 8*2 fd2 = strdata.find('00ff2f00') str_taken = strdata[fd1:fd2] list_taken = listdata[fd1//2:fd2//2] #演奏データはノートonとノートoffに限ると仮定する。 pos = str_taken.find('0092')//2 #最初のノートONのデータ位置 msgs=[] # msg=[accumulated_deltaTime, status, notenum,velo]を格納する #notenum 0=>C0, 12=>C1,...... status = 0 size = len(list_taken) accumTime = 0 while pos < size: intdelta = int(list_taken[pos],16) if intdelta >= 2**7: #デルタタイムの2バイト対応 pos +=1 intdelta =(intdelta - 2**7)*(2**7) + int(list_taken[pos],16) accumTime += intdelta if list_taken[pos+1]!='92' and list_taken[pos+1]!='82':#ランニングステータス対応 status = msgs[-1][1] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)] pos += 3 else: status = list_taken[pos+1] msg=[accumTime, status, int(list_taken[pos+2],16), int(list_taken[pos+3],16)] pos += 4 #print(msg) msgs.append(msg) lastTime = msgs[-1][0]/320 #横の長さの調整のために時間軸のサイズダウンをする。 noteDict={} #同じノート別に辞書を作る。 for msg in msgs: note = msg[2] if note in noteDict: noteDict[note].append(msg) else: noteDict[note]=[msg] #print(noteDict) notes = noteDict.keys() miny=min(notes) maxy=max(notes) #ノート別noteにmsgのオンオフのペアからノートオンaccumTimeをstartTime、 #ノートオフaccumTimeをstopTImeにして、velocityはノートオンのみ採用。 #[(startTime,note), (stopTime,note)]の形式に変換する。 noteSegments=[] noteVelos=[] startTime =0 stopTime = 0 velocity = 0 note = 0 for k,v in noteDict.items(): note = k for msg in v: if msg[1]=='92': startTime = msg[0]/320 noteVelos.append(msg[3]) velocity = msg[3] elif msg[1]=='82': stopTime = msg[0]/320 noteSegments.append([(startTime, note),(stopTime,note)] ) print(noteSegments) #print(noteVelos) return noteSegments,noteVelos,lastTime, miny,maxy #=================== 以下でデータ渡しと視覚化をする filepath = "C:/Users/example.mid" noteSegments,noteVelos,lastTime,miny,maxy= takeTrk1gate(filepath) xmin = 0 xmax = lastTime ymin = miny ymax = maxy lines = noteSegments lc = mc.LineCollection(lines, linewidths=2) fig = plt.figure(figsize=(20,20)) ax = fig.add_subplot(aspect='1') ax.add_collection(lc) ax.autoscale() plt.xlim(xmin, xmax); plt.ylim(ymin, ymax) plt.xlabel('x'); plt.ylabel('y') plt.show() #[OUT]========================================== [[(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)]]
Image
ここまでのデータ化と視覚化は、midiメッセージをnoteon,noteofに限定しています。しかも、1トラックだけです。本格的な音楽作成にはまったく役に立たないですが、MIDIファイル、SMFの構造を知るという経験はできたと思います。 汎用のmidiデータのpianoロールが必要ならば、DAWでファイルを開く方が簡単ですね。 <Geogebra> geogebraで同じことをするには、関数型の記法には限界があるので、pythonコードをjavascriptに移し替えて、実行して最後のsegmentデータまで作ったら、それをgeogebraのオブジェクトとしてのsegmentを表示するということでできるでしょう。今回はアプレットはつくってません。