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

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

[第349回]


●MIDI演奏プログラムのプログラムリスト

やっとプログラムの説明にまでたどりつきました。
MIDI演奏プログラムはE−80(仮称)ミニコンで動作するプログラムです。
当然のことながらMIDI出力回路が必須になりますし、またMIDI音源も必須です。
またZ8S180内蔵の16ビットタイマーを使った割り込みも利用しています。
そういうことからしますと、かなり特殊なプログラムということになりますので、そんなプログラムの動作をここで説明しましても、読者の皆様方にとりましては、なんの役にも立たないではないか、ということかもしれません。
あ。
しかし。
せっかくここまでいろいろやってきましたことでもありますから、近い内にND80ZV(ND80Z3.5)でも同じことができるようにしてみたいと思っております。
もっともその場合でもMIDI音源は必要ですし、最低MIDI出力回路を増設する必要はありますけれど。

今回お見せしますのは、そういうやや特殊なプログラムなのですけれど、タイマーによる割込みを使っている点などは、応用のための参考にしていただけると思いますし、複数トラックに記述されたMIDIイベントを同時進行的に処理するあたりなども何かの参考にしていただけるのではないかと思います。
E−80(仮称)ミニコンに移植したZB3BASICシステムの上で動作していますが、システムルーチンは使っていません。
動作に必要なプログラムは全て下のプログラムリストに含まれています。
MIDIファイル特有の可変長データから普通の16進数データに変換するところや、演奏のための最低時間単位(ティック)を算出するために、テンポデータを4分音符の分解能で除算するところなども、プログラムテクニックとして参考にしていただけるのではと思います。

MIDI演奏プログラムのアセンブルリストです。

2013/3/15  10:9  midisq8.txt
END=82DE
              ;;; E-80 232C(MIDI) midi sequence
              ; 13/1/19 2/24 2/25 2/26 3/4 3/5 3/6 3/10
              ;3/11 3/12 3/15
              ;
              ;cpu clock=10MHz
              ;
                ORG $8000
              ;
                ZB3ENTRY=$1033
              ;
                DATATOP=$8400
                TRKNO=$840B
                BPQN=$840C
              ;
8000 C30680     JP START
8003 00         NOP
              ;
8004 7F82       DW INT;timer0 int address
              ;
8006 3E80     START:LD A,80
8008 ED47       LD I,A
800A AF         XOR A
800B 013300     LD BC,$0033;ILregister
800E ED79       OUT (C),A
8010 D3FF       OUT (FF),A;start message
              ;
              ;cpu wait settei
8012 0D         DEC C
8013 3E00       LD A,00
8015 ED79       OUT (C),A;memory wait=0,i/o wait=1
              ;
              ;16F886 settei
8017 3E88       LD A,88
8019 D3FB       OUT (FB),A;82C55 A=out,B=out,C0-3=out,C4-7=in
801B 3EFB       LD A,FB;16F886out enable
801D D3FA       OUT (FA),A
              ;
                
801F DD21DE82   LD IX,TPRMTOP
8023 210B84     LD HL,TRKNO
8026 7E         LD A,(HL)
8027 32DC82     LD (CNTR),A
802A 23         INC HL
802B 111182     LD DE,MTRK
802E CDEB81     CALL SEARCH
8031 7D         LD A,L
8032 FE12       CP 12
8034 C26281     JP NZ,ERROR
8037 7C         LD A,H
8038 FE84       CP 84
803A C26281     JP NZ,ERROR
              ;set track parameter
803D 23       ST0LP:INC HL
803E 23         INC HL;pass upper 2bytes
803F 56         LD D,(HL)
8040 23         INC HL
8041 5E         LD E,(HL);bytes of TRK
8042 23         INC HL
8043 ED53D882   LD (DEWK),DE
8047 22DA82     LD (HLWK),HL
804A CD1682     CALL VLREAD
804D DD3600FF   LD (IX+00),FF
8051 78         LD A,B
8052 B2         OR D
8053 B3         OR E
8054 DD7701     LD (IX+01),A
8057 DD7302     LD (IX+02),E
805A DD7203     LD (IX+03),D
805D DD7004     LD (IX+04),B
8060 DD7505     LD (IX+05),L
8063 DD7406     LD (IX+06),H
8066 3ADC82     LD A,(CNTR)
8069 3D         DEC A
806A CA8A80     JP Z,START10
806D 32DC82     LD (CNTR),A
8070 110800     LD DE,$0008
8073 DD19       ADD IX,DE
8075 ED5BD882   LD DE,(DEWK)
8079 2ADA82     LD HL,(HLWK)
807C 19         ADD HL,DE
807D 7E         LD A,(HL)
807E FE4D       CP 4D;'M'
8080 C26281     JP NZ,ERROR
8083 23         INC HL
8084 23         INC HL
8085 23         INC HL
8086 23         INC HL
8087 C33D80     JP ST0LP
              ;
808A 01A107   START10:LD BC,$07A1;(tempodata=0.5s)
808D CD8281     CALL TMPSB
8090 CDD381     CALL TMSB
8093 DD21DE82 START1:LD IX,TPRMTOP
8097 3A0B84     LD A,(TRKNO)
809A 32DC82     LD (CNTR),A
809D AF         XOR A
809E 32DD82     LD (ENDCK),A
              ;loop
80A1 3A0B84   ST1LP:LD A,(TRKNO)
80A4 47         LD B,A
80A5 3D         DEC A
80A6 CAB680     JP Z,ST1LP1;mode0?
80A9 3ADC82     LD A,(CNTR)
80AC B8         CP B
80AD C2B680     JP NZ,ST1LP1
80B0 DD7E00     LD A,(IX+00);if mode1,pass trk1 timedata
80B3 C3C380     JP ST1LP2
80B6 3ADD82   ST1LP1:LD A,(ENDCK)
80B9 47         LD B,A
80BA DD7E00     LD A,(IX+00)
80BD F5         PUSH AF
80BE B0         OR B
80BF 32DD82     LD (ENDCK),A
80C2 F1         POP AF
80C3 B7       ST1LP2:OR A
80C4 CA3781     JP Z,NEXT
80C7 DD7E01     LD A,(IX+01)
80CA B7         OR A
80CB C23781     JP NZ,NEXT
80CE DD6E05     LD L,(IX+05)
80D1 DD6606     LD H,(IX+06)
80D4 7E         LD A,(HL)
80D5 FEFF       CP FF
80D7 CA0781     JP Z,CODEFF
80DA FEF0       CP F0
80DC CA5381     JP Z,CODEF0
80DF E6F0       AND F0
80E1 F2FE80     JP P,RUNSTS;running status
80E4 FEC0       CP C0
80E6 CAF580     JP Z,CXOUT
80E9 FED0       CP D0
80EB CAF580     JP Z,CXOUT
80EE 7E         LD A,(HL)
80EF DD7707     LD (IX+07),A;code save
80F2 CD4B82     CALL MIDIOUT
80F5 CD4B82   CXOUT:CALL MIDIOUT
80F8 CD4B82     CALL MIDIOUT
80FB C31881     JP NEXTDT
              ;running status
80FE DD7E07   RUNSTS:LD A,(IX+07)
8101 CD6582     CALL MIDIAOUT
8104 C3F580     JP CXOUT
              ;
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)
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
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
              ;
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 end
8162 3E0E     ERROR:LD A,0E
8164 D3FF       OUT (FF),A
8166 F3       MIDIEND:DI
8167 011000     LD BC,$0010
816A AF         XOR A
816B ED79       OUT (C),A;INT disenable,countdown stop
816D C33310     JP ZB3ENTRY
              ;
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
              ;
              ;subroutine
              ;
              ;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
              ;
              ;timer set
81D3 010C00   TMSB:LD BC,$000C
81D6 2AD682     LD HL,(TIMEDT)
81D9 ED69       OUT (C),L;timer0 data registerL
81DB 0C         INC C
81DC ED61       OUT (C),H;timer0 data registerH
81DE 0C         INC C
81DF ED69       OUT (C),L;timer0 reload registerL
81E1 0C         INC C
81E2 ED61       OUT (C),H;timer0 reload registerH
81E4 0C         INC C
81E5 3E11       LD A,11;INT enable,countdown start
81E7 ED79       OUT (C),A
81E9 FB         EI
81EA C9         RET
              ;
              ;string search
81EB 7C       SEARCH:LD A,H
81EC FEA0       CP A0
81EE CA0D82     JP Z,SEARCHE
81F1 D5         PUSH DE
81F2 1A         LD A,(DE)
81F3 BE       SEARCH1:CP (HL)
81F4 CAFB81     JP Z,SEARCH2
81F7 23         INC HL
81F8 C3F381     JP SEARCH1
81FB E5       SEARCH2:PUSH HL
81FC 23       SEARCH3:INC HL
81FD 13         INC DE
81FE 1A         LD A,(DE)
81FF B7         OR A
8200 CA0E82     JP Z,SEARCH4
8203 BE         CP (HL)
8204 CAFC81     JP Z,SEARCH3
8207 E1         POP HL
8208 23         INC HL
8209 D1         POP DE
820A C3EB81     JP SEARCH
820D B7       SEARCHE:OR A;reset Zflag
820E D1       SEARCH4:POP DE;dummy
820F D1         POP DE
8210 C9         RET
              ; 
8211 4D       MTRK:DB 4D;M
8212 54         DB 54;T
8213 72         DB 72;r
8214 6B         DB 6B;k
8215 00         DB 00
              ;
              ;variable length data read
8216 0600     VLREAD:LD B,00
8218 7E         LD A,(HL)
8219 B7         OR A
821A F24682     JP P,VLREAD2
821D E67F       AND 7F
821F 57         LD D,A
8220 23         INC HL
8221 7E         LD A,(HL)
8222 5F         LD E,A
8223 B7         OR A
8224 F23E82     JP P,VLREAD1
8227 E67F       AND 7F
8229 5F         LD E,A
822A CB13       RL E
822C CB1A       RR D
822E CB1B       RR E
8230 42         LD B,D
8231 53         LD D,E
8232 23         INC HL
8233 5E         LD E,(HL)
8234 CB13       RL E
8236 CB18       RR B
8238 CB1A       RR D
823A CB1B       RR E
823C 23         INC HL
823D C9         RET
823E CB13     VLREAD1:RL E
8240 CB1A       RR D
8242 CB1B       RR E
8244 23         INC HL
8245 C9         RET
8246 5F       VLREAD2:LD E,A
8247 1600       LD D,00
8249 23         INC HL
824A C9         RET
              ;
824B DBFE     MIDIOUT:IN A,(FE)
824D E620       AND 20
824F CA4B82     JP Z,MIDIOUT
8252 7E         LD A,(HL)
8253 D3FD       OUT (FD),A
8255 3E02       LD A,02;bit1=L,bit0=H;bit0-4 are INVERT
8257 D3FE       OUT (FE),A
8259 DBFE     MIDIOUT2:IN A,(FE)
825B E620       AND 20
825D C25982     JP NZ,MIDIOUT2
8260 AF         XOR A
8261 D3FE       OUT (FE),A
8263 23         INC HL
8264 C9         RET
              ;
8265 F5       MIDIAOUT:PUSH AF
8266 DBFE     MIDIAOUT1:IN A,(FE)
8268 E620       AND 20
826A CA6682     JP Z,MIDIAOUT1
826D F1         POP AF
826E D3FD       OUT (FD),A
8270 3E02       LD A,02;bit1=L,bit0=H;bit0-4 are INVERT
8272 D3FE       OUT (FE),A
8274 DBFE     MIDIAOUT2:IN A,(FE)
8276 E620       AND 20
8278 C27482     JP NZ,MIDIAOUT2
827B AF         XOR A
827C D3FE       OUT (FE),A
827E C9         RET
              ;
              ;INT routine
              ;
827F DDE5     INT:PUSH IX
8281 E5         PUSH HL
8282 D5         PUSH DE
8283 C5         PUSH BC
8284 F5         PUSH AF
8285 011000     LD BC,$0010
8288 ED78       IN A,(C);
828A 0E0C       LD C,0C
828C ED78       IN A,(C);clear TIF0 flag
828E DD21DE82   LD IX,TPRMTOP
8292 3A0B84     LD A,(TRKNO)
8295 4F         LD C,A
8296 DD7E01   INT1:LD A,(IX+01)
8299 B7         OR A
829A CAC182     JP Z,INT2
829D DD6E02     LD L,(IX+02)
82A0 DD6603     LD H,(IX+03)
82A3 DD4604     LD B,(IX+04)
82A6 78         LD A,B
82A7 B7         OR A
82A8 CAB482     JP Z,INT12
82AB 7C         LD A,H
82AC B5         OR L
82AD C2B482     JP NZ,INT12
82B0 05         DEC B
82B1 DD7004     LD (IX+04),B    
82B4 2B       INT12:DEC HL
82B5 DD7502     LD (IX+02),L
82B8 DD7403     LD (IX+03),H
82BB 7C         LD A,H
82BC B5         OR L
82BD B0         OR B
82BE DD7701     LD (IX+01),A
82C1 0D       INT2:DEC C
82C2 CACD82     JP Z,INT3
82C5 110800     LD DE,$0008
82C8 DD19       ADD IX,DE
82CA C39682     JP INT1
82CD F1       INT3:POP AF
82CE C1         POP BC
82CF D1         POP DE
82D0 E1         POP HL
82D1 DDE1       POP IX
82D3 FB         EI
82D4 ED4D       RETI
              ;
              ; work space
              ;
82D6 0000     TIMEDT:DW $0000
82D8 0000     DEWK:DW $0000
82DA 0000     HLWK:DW $0000
82DC 00       CNTR:DB 00
82DD 00       ENDCK:DB 00
              ;
82DE 00       TPRMTOP:NOP
                ;TRK1 endmk 1byte
                ;TRK1 time0mk 1byte
                ;TRK1 timedata 3bytes
                ;TRK1 dataaddress 2bytes
                ;TRK1 previous datacode
                ; .......
              ;
              ;end      
              ;
BPQN         =840C  CNTR         =82DC  CODEF0       =8153  
CODEF0_2     =8158  CODEFF       =8107  CXOUT        =80F5  
DATATOP      =8400  DEWK         =82D8  DIVHLDE      =81B5  
DIVHLDE2     =81BC  ENDCK        =82DD  ERROR        =8162  
HLWK         =82DA  INT          =827F  INT1         =8296  
INT12        =82B4  INT2         =82C1  INT3         =82CD  
IX           =0866  MIDIAOUT     =8265  MIDIAOUT1    =8266  
MIDIAOUT2    =8274  MIDIEND      =8166  MIDIOUT      =824B  
MIDIOUT2     =8259  MTRK         =8211  NEXT         =8137  
NEXT2        =8149  NEXTDT       =8118  RUNSTS       =80FE  
SEARCH       =81EB  SEARCH1      =81F3  SEARCH2      =81FB  
SEARCH3      =81FC  SEARCH4      =820E  SEARCHE      =820D  
SFTDE        =818C  SFTDE2       =819A  SFTHL        =819F  
SFTHL2       =81AD  ST0LP        =803D  ST1LP        =80A1  
ST1LP1       =80B6  ST1LP2       =80C3  START        =8006  
START1       =8093  START10      =808A  TIMEDT       =82D6  
TMPSB        =8182  TMPSET       =8170  TMSB         =81D3  
TPRMTOP      =82DE  TRKEND       =8133  TRKNO        =840B  
VLREAD       =8216  VLREAD1      =823E  VLREAD2      =8246  
ZB3ENTRY     =1033  

プログラム本体は8000H〜82D5Hの726バイトです。
その後ろにデータトラックの数だけワークエリアが設定されます。
1トラックにつき8バイトを使います。
MIDIファイルはプログラムを実行するまえにZB3BASICの/LDコマンドで8400Hからのエリアにロードしておきます。
BASICを使わずにマシン語プログラムだけを実行する場合には、F000Hよりも前のエリアはユーザーが使うことができますから、MIDIファイルは8400H〜EFFFHまでの範囲にロードできるサイズが最大のものになります。
計算をしますと27KBになります。
一方で、上で説明しましたデータトラックのためのワークエリアは、82DEH〜83FFHまでの範囲を使用することになります。
この範囲のメモリサイズを計算しますと、290バイトになります。
1トラック8バイトですから、最大36トラックまで対応できることになります。
ここでのトラック数はヘッダートラックに記載されているデータトラック数に一致します。

プログラムはメインプログラムと内蔵タイマーによる割込みプログラムの2つの部分からなります。
メインプログラムは、最初にMIDIファイルデータがロードされているデータ領域を先頭からサーチしてファイルの構造を分析し、トラックごとに基本的なパラーメータをワークエリアに設定します。
そのあとはワークエリアの時間データマークをトラックごとに順にチェックするのを繰り返します。
この間に、算出したティック時間ごとに内蔵タイマーからの割り込みが発生し、割り込みプログラムによって各トラックの時間データが減じられていきます。
メインルーチンはそれを監視していて時間データが0になったら、そのイベントコードをMIDI送信し、その後次の時間データとイベント情報をワークエリアにセットします。

MIDIのルールの基本はチャンネルです。
チャンネルごとにデータの振り分けがされるということになっていますから、MIDI演奏プログラムでもチャンネル毎になんらかの整理が必要ではないか、とお考えかも知れませんが、実はこのMIDI演奏プログラムではチャンネル番号は全く意識していません。
チャンネル番号はイベントコードの最初のバイトの下位4ビットです。
プログラムリストを見ていただければ分かりますが、コードの下位4ビットをチェックして、それをキーにしているようなところはありません。
それでよいのか、と思われるかも知れませんが、沢山のMIDIファイルを演奏させてみた結果から、それで構わないと考えております。

むしろ肝心なのは、プログラムリストにありますように、トラック毎に逐次処理を行なうことです。
トラックは楽譜のパートに対応していることが多いようです(そうではない場合もありますが)。
言えるのはMIDIファイルではトラック1からトラックNまでが直列に並んでいますが、それをMIDI音源に送るときにはトラック1からトラックNまでを並列に並べて送らなければならないということです。
ちょうど楽譜の各パートが同時に進行していくのと同じです。

その効果を、タイマーによる割込みプログラムとメインプログラムが役割を分担しながら実現しています。
具体的な処理の内容についての説明は次回以降に少しずつしていくことにいたします。
それまでにもしご興味がおありでしたら、プログラムの動作を推理していただくための手がかりとしまして、トラック毎のワークエリアについて説明をしておくことにいたします。

ワークエリアはトラック毎に8バイトです。

第1バイトはトラックエンドマークです。
プログラムの開始時にここにはFFがセットされます。
時間が進行して、トラックエンドまで処理されたとき、ここに00がセットされます。
それ以後はこのトラックの8バイトは読み飛ばされます。
無駄な時間を省くための1バイトです。

第2バイトは時間0マークです。
ここには第3〜5バイトの時間値のORが入れられます。
最初から時間データが0のときには第2バイトも第3〜5バイトも00になります。
そうではないときには、第2バイトには非00の数がセツトされます。
これは割り込みによる誤処理を避けるための対策です。
どういうことかについては次回以降で説明いたします。

第3〜5バイトには可変長の時間データを16進数に直したものが入れられます。
ファイルサイズが27KBまでという制限がありますから、時間データとしては最大3バイトあればよいのでは、と判断しました。
このサイズのMIDIファイルではせいぜい数分間の演奏にしかなりません(おそらく最長でも10分以下)。
たとえば10分をmsに直すと10×60×1000=600000msです。
ティックを1msとした場合には、600000がそのまま時間データですから、これを16進数に直しますと927C0になります。
ですから3バイトを越える値は時間データとして想定しなくてもよいのです。

第6、7バイトには、次に実行予定のイベントデータの先頭アドレスが格納されます。

第8バイトには、直前に実行されたイベントコードが格納されます。
ランニングステータス対策です。
ランニングステータスについては[第344回]を参照願います。

次回に続きます。

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

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