2013.3.25
前へ
次へ
ホームページトップへ戻る

復活!CP/M ワンボードマイコンでCP/Mを!
CP/MがTK−80互換のワンボードマイコンの上で復活します
ND80ZVとMYCPU80の上でCP/Mが走ります

[第356回]


●メタイベント(FF)の処理

前回からの続きです。
前回はMIDIイベントデータの第1コードを解読して、それぞれの処理に分岐するところまでを説明しました。
今回はそれぞれに分岐したあとの処理について説明をします。

MIDIイベントデータの第1バイトがFFはメタイベントです。
それに続く第2バイトのコードによって、テンポであるとか、曲名とか楽器名とかというようにMIDI音源には送らないでMIDIシーケンスプログラム内で処理される情報を示します。

8107 23       CODEFF:INC HL
8108 7E         LD A,(HL)
8109 FE2F       CP 2F
810B CA3381     JP Z,TRKEND
810E FE51       CP 51
8110 CA7081     JP Z,TMPSET
              ;pass text data or something
8113 23         INC HL
8114 CD1682     CALL VLREAD
8117 19         ADD HL,DE;pass to next data top(variable length top)

メタイベントコードの第2バイトは00〜7Fまでが許されるのですが、全てのコードは使われていないようです。
そのうちMIDI演奏プログラムでとりあえず必要なものはコード2F(トラックエンド)と51(テンポ)だけです。
詳しく見ていけばそのほかにも必要なものがあるかも知れませんが、とりあえず上記の2つのコードを処理するだけで、今までのところ特にMIDIファイルの演奏には支障はないようです。

第2バイトが2Fはトラックの終わりを示します。
TRKENDにジャンプします。
第2バイトが51はテンポを示します。
TMPSETにジャンプします。
コード2F、5Fのときの各ジャンプ先での処理についてはのちほど説明をします。

第2バイトが01〜09は曲名や楽器名などのテキスト文字を表記するのに使われます。
このコードに限らずメタイベントの第3バイトには可変長表記でそれに続くデータのバイト数が置かれます。
そこで上記の2F、51以外はそのバイト数を読んで、データ部分をスルーします。
VLREADは可変長データを16進数表記に変換してDEレジスタに格納するサブルーチンです([第351回]参照)。
HLレジスタには可変長データの次のアドレスが入っていますから、それにDEレジスタの値を加算することで、データ部分をスルーします。

スルーしたあとは、その次に書かれているMIDIイベントデータの読み込み処理に入ります。

●次のMIDIイベントデータのセット(NEXTDT)

次のMIDIイベントデータの読み込み処理ルーチンNEXTDTです。

8118 CD1682   NEXTDT:CALL VLREAD
811B DD7302     LD (IX+02),E
811E DD7203     LD (IX+03),D
8121 DD7004     LD (IX+04),B
8124 78         LD A,B
8125 B2         OR D
8126 B3         OR E
8127 DD7701     LD (IX+01),A
812A DD7505     LD (IX+05),L
812D DD7406     LD (IX+06),H
8130 C33781     JP NEXT

イベントデータの前には必ず可変長形式の時間データが置かれています。
CALL VLREADを実行すると可変長データをが16進数データ3バイトに変換され、B、D、Eの各レジスタに格納されます。
その3バイトの時間データを(IX+02)〜(IX+04)に書き込みます。
そしてその3バイトのORを(IX+01)に入れます。
(IX+01)が00になるのは、時間データが0のときだけです。

ここではそのように書き込むデータの順序が重要です。
割り込みプログラムが常に動いていることを忘れてはいけません。
自分で言うのもおかしいのですが、このMIDI演奏プログラムは周到に計算して作成されているのです。

割り込みプログラムは非常に危険なプログラムです。
扱いを誤るとたちどころに暴走してしまいます。
このプログラムで危険なのは、時間データが複数バイトから成っていることです。

割り込みプログラムはメインプログラムが処理をしているどんなタイミングでも実行されてしまう可能性があります(もちろん、もし割り込みが許可されていれば、ですが)。
ここではメインプログラムが3バイトの時間データを(IX+02)〜(IX+04)に順に格納しています。
もしも、(IX+02)または(IX+03)にデータを書き込んで、まだ(IX+04)にはデータが書き込まれていないときに割り込みが発生したとしたら何がおきるでしょうか。

割り込みプログラムは(IX+02)〜(IX+04)が正しい時間データであるという前提で、その値を−1します。
当然その結果は誤ったものになります。
さらに割り込みプログラムがそのようにして計算した結果を(IX+02)〜(IX+04)に書き込んでリターンしたあとで、保留されていたメインプログラムの実行が再開され、そこで残されていた(IX+04)に値が書き込まれます。
めちゃくちゃになってしまうことがお分かりいただけると思います。

このプログラムが周到に計算されている、と書きましたのはそこなのです。
(IX+01)は時間データが0のときのみ00になります。
これによって3バイトの時間データを参照しなくても(IX+01)だけを見れば、時間データが0であることが判断できますから、処理が簡単になります。
しかし実は(IX+01)にはもっと重要な役割があるのです。
(IX+01)が00の時には、割込み処理がスルーされるようになっています。
時間データが0ならそれでカウントダウンは終わりですから、割り込みが発生しても、このトラックに対しては割り込み処理は行なわれません。

ところでここは次に処理をするイベントデータの前に置かれている時間データをメモリに書き込むところなのですが、書き込む直前の(IX+01)の値はどうなっているでしょうか?
実はこのとき(IX+01)の値は必ず00なのです。
今までの説明をもういちど読んでみてください。
すると、(IX+01)の値が00だから、MIDIイベントデータの処理をして、それが終ったから、ここに来たということが分かりますでしょう。

ここでは(IX+01)が割り込み禁止の役目を果たしているのです。
ですからたとえ(IX+02)〜(IX+04)の途中を書き込んでいるときに割込みが発生したとしても、割込みプログラムがその書き込み途中の値をカウントダウンしてしまうことは決してありません。

そして3バイトの値を書き込んだあとで(IX+01)の値を更新します。
そういう順序でなければならない、ということがお分かりいただけたと思います。

最後にイベントデータの先頭アドレスが入っているHLレジスタの値を(IX+05)と(IX+06)に書き込んで、次のトラックの処理(NEXT)に行きます。

●トラックの終わり(TRKEND)と次のトラックヘの処理(NEXT)

メタイベント FF 2F 00 は、トラックの終わりを示します。
TRKENDはトラックの終わりの処理です。

8133 DD360000 TRKEND:LD (IX+00),00
8137 3ADC82   NEXT:LD A,(CNTR)
813A 3D         DEC A
813B CA4981     JP Z,NEXT2
813E 32DC82     LD (CNTR),A
8141 110800     LD DE,$0008
8144 DD19       ADD IX,DE
8146 C3A180     JP ST1LP
8149 3ADD82   NEXT2:LD A,(ENDCK)
814C B7         OR A
814D CA6681     JP Z,MIDIEND
8150 C39380     JP START1
トラックの終わりの処理は簡単です。
(IX+00)に00を書き込むだけです。
こうすることで、これ以後はこのトラックはスルーされることになります。

NEXTは次のトラックへの処理です。
最初にトラック数を入れた(CNTR)の値を−1します。
結果が0なら全てのトラックを一巡したことになりますから、NEXT2にジャンプします。
0ではない場合にはまだ処理すべきトラックがありますから、IXレジスタの値を1トラック分(8バイト)加算して、次のトラックデータテーブルアドレスをセットして巡回処理を続けます(ST1LP)。
ST1LPは[第354回]で説明をしています。

NEXT2では全てのトラックの処理が終ったかどうかをチェックします。
(ENDCK)の値が00のままならばすべてのトラックの処理が終っていますから、MIDIENDにジャンプしてプログラムの終わりの処理をします([第351回])。
そうではない場合には、巡回処理の先頭に戻ります(START1)。
START1は[第354回]で説明をしています。

●テンポの設定(TMPSET)

メタイベント FF 51 03 はテンポデータの指定です。
TMPSETはテンポデータを読んで、割り込みの基準時間(ティック)を計算します。

8170 23       TMPSET:INC HL
8171 23         INC HL;pass 'length'
8172 46         LD B,(HL)
8173 23         INC HL
8174 4E         LD C,(HL);BC=TEMPO*256 cut off LSB(byte)
8175 23         INC HL
8176 23         INC HL
8177 E5         PUSH HL
8178 CD8281     CALL TMPSB
              ;
              ;timer set and int on
817B CDD381     CALL TMSB
817E E1         POP HL
817F C31881     JP NEXTDT

テンポデータは3バイトです。
可変長ではありません。
下位1バイトは無視して、上位2バイトの値をBCレジスタに入れて、TMPSBをコールし、続いてTMSBをコールします。
TMPSBは[第352回]で説明をしました。
またTMSBは[第353回]で説明をしました。
ティックを算出し、その値をZ8S180内蔵カウンタにセットして、割り込みを許可します。
そのあと次のデータ処理(NEXTDT)にジャンプします。

●システムエクスクルーシブメッセージ(F0)の処理

システムエクスクルーシブメッセージは下のようになっています。
F0 nn cc dd ee F7
nnはMIDI機器に送るバイト数で、cc〜F7のバイト数になります(この例では04)。
最後はF7で終ります。
MIDI機器にはnnを除いた F0 cc dd ee F7 が送られます。

システムエクスクルーシブメッセージの処理ルーチンCODEF0です。

8153 CD4B82   CODEF0:CALL MIDIOUT
8156 4E         LD C,(HL)
8157 23         INC HL
8158 CD4B82   CODEF0_2:CALL MIDIOUT
815B 0D         DEC C
815C C25881     JP NZ,CODEF0_2
815F C31881     JP NEXTDT

これでやっとMIDI演奏プログラムの説明が完了しました。

ワンボードマイコンでCP/Mを![第356回]
2013.3.25upload

前へ
次へ
ホームページトップへ戻る