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

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

[第352回]


●MIDIデータの実際の処理作業にエントリする

前回からの続きです。
前回までのところで前準備が完了しましたので、いよいよMIDIデータを実際に読み込んで解読し処理を行なう部分に入ります。
今回は2進数除算を扱います。
理解するのはかなり難しいと思います。
なんとか分かりやすいように努力したいとは思いますが…。

●デフォルトの演奏速度の設定

いろいろなMIDIファイルを演奏していく中で、演奏速度を設定するよりもさきに実際の音符データが出てきてしまうというMIDIファイルに遭遇しました([第346回])。
そこで実際のMIDIデータの処理を始める前に、デフォルトの演奏速度を設定しておくことにしました。
下がその部分です。

808A 01A107   START10:LD BC,$07A1;(tempodata=0.5s)
808D CD8281     CALL TMPSB
8090 CDD381     CALL TMSB


標準的な演奏速度として1分間当たりの4分音符=120としました。
4分音符1個の演奏時間は0.5sになります([第349回]のリストでは、0.5msと誤記していましたので訂正しました)。
ここで設定している$07A1には説明が必要です。
0.5秒は500000μsです。
500000を16進数に直すと7A120になります。
ここでは以下の計算でティックを求めるときの値数として、それを256で除したものを使います。
あまり細かい数を求めても実際には意味が無いからです。

ここでBCレジスタに入れている値は、データトラック中でテンポデータとして示される4分音符1個の演奏時間(単位μs)を256で除した数と同じ扱いです。
テンポデータを4分音符の分解能で割ることで、この曲の演奏時間の最小単位(ティック)を求めます。
そのようにして求めた最小単位時間ごとにタイマー割り込みを発生させることで、演奏を進めることができます。
4分音符の分解能は普通480(1E0H)か192(C0H)が使われます。
実はもう少し後でまた説明しますが、Z8S180をCPUクロック10MHzで実行する場合には、内蔵タイマーの割込みの最小時間は2μs以下にはできません。
ということは4分音符の時間(μs)/分解能によって求める最小時間単位(ティック)をさらに2で割らなければ実際の時間データとの整合を取ることはできません。
すると分解能192の場合でも結局は384で割ることになりますから、割られる数を256で除したときの整数部以下の数値はどのみち利用されません。
値数として256で除した数を使うのはそういう理由からです。

上の説明がよく分からないという方は、簡単な10進数で考えてみてください。
たとえば123456(μs)/234という計算をする場合を考えてみます。
123456/234=527.5897…
ですが結果の数の小数点以下は使わないという前提でしたら求める答えは527になります(小数点以下は切り捨て)。
それならば
123400/234=527(小数点以下切捨て)
のようにすれば計算を簡単にすることができます。

10進数ではそれほど簡単になったようには見えませんが2進数では3バイト/2バイトと2バイト/2バイトとではプログラムが大きく変わってきます。
2バイトの数なら、HL、DE、BCレジスタペアのみで演算できますから効率が全然違ってきます。
3バイトを2バイトにする効果は大きいものがあります。


●2進数整数除算サブルーチン

整数除算の場合にプログラムが最も簡単なのは、被除数から除数を引ききれなくなるまで減算するという方法です。
プログラムは簡単ですが、被除数が大きい場合には演算時間が増大するため余り実用的ではありません。
ここはやはり正攻法がよいでしょう。
2進数の除算は10進数の筆算での除算と同じですが、10進数よりもむしろ簡単です。


                            ;
              ;TEMPO keisan
              ;
8182 210C84   TMPSB:LD HL,BPQN
8185 56         LD D,(HL)
8186 23         INC HL
8187 5E         LD E,(HL);DE=BPQN(bit per quarter note)
              ;TEMPO/BPQN
8188 60         LD H,B
8189 69         LD L,C
818A 3E09       LD A,09
              ;if bit7 of D is '0',shift to left DE
818C CB7A     SFTDE:BIT 7,D
818E C29A81     JP NZ,SFTDE2
8191 B7         OR A
8192 CB13       RL E
8194 CB12       RL D
8196 3C         INC A
8197 C38C81     JP SFTDE
819A B7       SFTDE2:OR A
819B CB1A       RR D
819D CB1B       RR E
              ;if bit7 of H is '0',shift to left HL
819F CB7C     SFTHL:BIT 7,H
81A1 C2AD81     JP NZ,SFTHL2
81A4 B7         OR A
81A5 CB15       RL L
81A7 CB14       RL H
81A9 3D         DEC A
81AA C39F81     JP SFTHL
81AD B7       SFTHL2:OR A
81AE CB1C       RR H
81B0 CB1D       RR L
81B2 010000     LD BC,$0000
              ;divide HL by DE
81B5 B7       DIVHLDE:OR A
81B6 ED52       SBC HL,DE
81B8 D2BC81     JP NC,DIVHLDE2
81BB 19         ADD HL,DE
81BC 3F       DIVHLDE2:CCF
81BD CB11       RL C
81BF CB10       RL B
81C1 CB15       RL L
81C3 CB14       RL H
81C5 3D         DEC A
81C6 C2B581     JP NZ,DIVHLDE
              ;BC/2
81C9 B7         OR A
81CA CB18       RR B
81CC CB19       RR C
81CE ED43D682   LD (TIMEDT),BC
81D2 C9         RET

ここでは被除数にBCレジスタに入れた4分音符の演奏時間(3バイトの数値の上位2バイト)、除数には4分音符の分解能を置いた除算を行います。
最初に被除数をHLレジスタに除数をDEレジスタに入れます。
2進数の除算は1ビットずつ被除数を左にシフトしながら被除数から除数を減算することを繰り返します。
繰り返し回数は被除数と除数が同じ桁数の場合には1回ですが、当プログラムでは本来は被除数が3バイトで除数が2バイトのところを簡略化するため被除数の下位8ビットを切り捨てて行ないますから、その下位8ビットを繰り返し回数に加えて9回とします。
繰り返し回数はAレジスタに入れます。

●正規化

実際の計算の前に正規化の処理を行ないます。
2進数の正規化とは、数値を2進数で表現したときに上位ビットの0をなくすように全体を左にシフトする操作です。
本来は最上位ビットに1がくるところまで左シフトするのですが、ここでは計算の効率化のため、最上位ビットが0でそのすぐ下位のビットが1になるようにシフトします。
たとえば00001011を正規化すると01011000になります。
ただそのようにしただけでは数の大きさが変わってしまいますから、そのようにシフトすると同時に2のn乗の値も操作します。
上の例では00001011×2の0乗→01011000×2の−3乗になります。
こうすれば2つの値は同じになります。
今回は除算の被除数と除数をともに正規化しますから、被除数を左にn回シフトした場合には、繰り返し回数(Aレジスタの値)を−nし、除数を左にm回シフトした場合にはAレジスタの値を+mします。
ここがよくわからないかも知れません。
そういうときは簡単な数で考えてみると理解しやすくなります。

123/18=6(端数切捨て)
という計算を2進数でやってみます。
ともに1バイト8ビットの数での除算ですから繰り返し回数は1回です。
123は16進数の7Bですから2進数では01111011になります。
これはすでに正規化されています。
18は16進数の12ですから2進数では00010010になります。
これを正規化すると2回左にシフトして01001000になります。
除数を左に2回シフトしましたから繰り返し回数は1+2=3回になります。

これで準備ができましたから、実際に計算をしてみます。
被除数をHレジスタに、除数をDレジスタに、繰り返し回数をAレジスタに、結果をBレジスタに入れます。
計算開始前の各レジスタの値です。
H=01111011
D=01001000
A=00000011
B=00000000

1回目の計算です。
H−Dを計算します。
引けるときは計算結果をHにいれてBのビット0を1にします。
引けないときは何もしません。
Aを−1します。
H=00110011
D=01001000
A=00000010
B=00000001
になります。
次の計算の前にHとBを左シフトします。
H=01100110
D=01001000
A=00000010
B=00000010
になります。

2回目も同じ計算をします。
H=00011110
D=01001000
A=00000001
B=00000011
になります。
次の計算の前にHとBを左シフトします。
H=00111100
D=01001000
A=00000001
B=00000110
になります。

3回目の計算です。
H−Dができませんから、HもBも変化しません。
Aは00000000になりますからこれで計算終了です。
Bレジスタの値は00000110です。

上のプログラムではそれと同じことを被除数をHLに、除数をDEに、繰り返し回数をAに、結果をBCに入れて行なっています。
計算結果の単位はμsですが、今回の始めのところに少し書きましたように、Z8S180をCPUクロック10MHzで実行するときの内蔵タイマーのクロックはCPUクロック(=0.1μs)×20=2μsです。
そこでそれに合わせるために、最後にBCの値を1/2にしています。

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

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