home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip: Special Sound & MIDI
/
Chip-Special_Sound-und-Midi-auf-dem-PC.bin
/
midiprog
/
mplay.asm
< prev
next >
Wrap
Assembly Source File
|
1993-11-21
|
46KB
|
1,961 lines
title MPLAY - Standard MIDI to MPU && PLAY Utility
page 64,132
.186
IS_PCNO equ 1
IS_TEST equ 2
IS_VERBOSE equ 4
IS_NOTEON equ 8
IS_DISPLAY equ 16
IS_TEMPOCHANGE equ 32
IS_PLAY equ 128
DATA_END equ 0fch
CHANGE_TEMPO equ 0f2h
MAX_TRACKS equ 64
SCREEN_SAVE equ 2000h
STACK_ADDR equ 4000h
ADDR_TRKNAME equ STACK_ADDR
ADDR_INSTRNAME equ ADDR_TRKNAME + 800h
ADDR_FIRSTTRK equ ADDR_INSTRNAME + 800h
code segment 'code'
assume cs:code,ds:code,es:code
org 100h
start : jmp anfang
; -----
; Texte
msg_hello db 'MPLAY Roland MPU 401 SMF Typ 0/1 Play Utility Version 1.04a',13,10
db 'Copyright (C) 1993 by Andreas Nieden'
db 13,10,10,0
msg_help db 'Aufruf: MPLAY [-tv] Datei(en)'
msg_cr db 13,10,0
msg_nompu db 'Kein ROLAND MPU401 Interface gefunden',13,10,0
msg_mpuio db 'ROLAND MPU401 gefunden auf IO-Adresse 0x',0
msg_intr db 13,10,'Interface installiert auf IRQ 0x',0
msg_nointr db 13,10,'Interruptvektor-Test fehlgeschlagen',13,10,0
msg_fnf db 'Datei(en) nicht gefunden',13,10,0
msg_nosmf db 'Kein Standard MIDI File Typ 0/1',13,10,0
msg_notbase db 'Ungültige Timebase',13,10,0
msg_toomanytrk db 'Zuviele Tracks im SMF',13,10,0
msg_trktoolong db 'Track leider zu lang',13,10,0
msg_unexpeof db 'Unerwartetes Dateiende',13,10,0
msg_invdelta db 'Ungültiges Time-Delta',13,10,0
msg_runerror db 'SMF-Laufzeitfehler',13,10,0
msg_loop db 'FATAL: LOOP in put_cmd()',13,10,0
msg_load db 'Lade Datei, ',0
msg_convert db 'konvertiere, ',0
msg_ready db 'fertig.',13,10,0
msg_already db 'MPLAY ist bereits installiert!',13,10,0
msg_shell db 'SHELL-ESCAPE - mit "EXIT" gelangen Sie zurück zu MPLAY'
db 13,10,10,0
; ---------
; Variablen
ALIGN 4
oldvec dd 0 ; Alter Interruptvektor
mpuaddr dd 0 ; Zeiger auf MPU-Spur
mmseconds dd 0 ; Microsekunden insgesamt
smftime dd 0 ; Microsekunden / Tick
semaphor dw 0500h,0 ; Semaphor für MPLAY
trackcnt dd MAX_TRACKS dup (0) ; Trackcounter
screen dw 0b800h
expected dw 0 ; Zeit des Stücks in Sekunden
passed dw 0 ; Wird inkrementiert
tunits dw 0 ; Exp. Sekunden * 32
mpuseg dw 0 ; Segment der MPU-Spur
mpucnt dw 0 ; MPU Track Time Counter
trackseg dw MAX_TRACKS dup (0) ; Segmente der Tracks
trackofs dw MAX_TRACKS dup (0) ; Trackoffsets
fileaddr dw 0 ; Adresse Dateiname
shandle dw 0 ; Filehandle Datei
tracks dw 0 ; Anzahl der Tracks im SMF
timebase dw 0 ; Timebase
trklen dw 0 ; Tracklänge
akt_track dw 0 ; Enthält gerade aktuellen Track
trackstogo dw 0 ; Zähler für große Schleife
mpuio dw 0 ; Startadresse ROLAND MPU401
starttime dw 0 ; Start Zeit
lasttime dw 0 ; Letzte Messung
oldsec dw 0 ; Letzte Sekundenzahl
tbvalues dw 48,72,96,120,144,168,192
; ----
; Hier kommen die Werte der anderen Attribute
attributes dw 7*160 + 2*1
db 22,1fh
dw 7*160 + 2*24
db 32,2
dw 7*160 + 2*57
db 22,1fh
dw 9*160 + 2*1
db 22,1fh
dw 9*160 + 2*24
db 32,4
dw 9*160 + 2*57
db 22,1fh
dw 11*160 + 2*1
db 78,7
dw 12*160 + 2*1
db 78,7
dw 13*160 + 2*1
db 78,7
dw 14*160 + 2*1
db 78,7
dw 15*160 + 2*1
db 78,7
dw 16*160 + 2*1
db 78,7
dw 17*160 + 2*1
db 78,7
dw 0
; -------------
; Bytevariablen
ALIGN 16
msg_keyboard label byte
db '╒══════════════════════╤════════════════════════════════╤══════════════════════╕'
db '│ TIME EXPECTED 00:00 │■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■│ TIME PASSED 00:00 │'
db '╞══════════════════════╪════════════════════════════════╪══════════════════════╡'
db '│ Current Tempo = 000 │■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■│ SMF Timebase = 000 │'
db '╞══════════════════════╧════════════════════════════════╧══════════════════════╡'
db '│ ┌─▄─▄─┬─▄─▄─▄─┬─▄─▄─┬─▄─▄─▄─┬─▄─▄─┬─▄─▄─▄─┬─▄─▄─┬─▄─▄─▄─┬─▄─▄─┬─▄─▄─▄─┬─┐ │'
db '│ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ │ │'
db '│ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ │ │'
db '│ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ █ █ │ █ █ █ │ │ │'
db '│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │'
db '│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │'
db '│ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ │'
db '╘══════════════════════════════════════════════════════════════════════════════╛'
notentext db '▄█████▀' ; Noten an
db '─ ─' ; Note off
trackflags db MAX_TRACKS dup (0) ; Trackflags
keymaps db 0,7 ; C
db 2,4 ; C#
db 4,7 ; D
db 6,4 ; D#
db 8,7 ; E
db 12,7 ; F
db 14,4 ; F#
db 16,7 ; G
db 18,4 ; G#
db 20,7 ; A
db 22,4 ; A#
db 24,7 ; B
command db 'C:\COMMAND.COM',0
param db 0,13
ALIGN 4
pcmd dw 0
dw offset param
dw 0
dw 4 dup (0)
mflag db 0 ; Flag
tbmultiply db 0 ; Timebase Multiplyer
tbtodrop db 0 ; Relativ TEMPO
tbcmd db 0 ; Timebase Kommando
tempo db 0 ; Tempo setzen
wtempo db 0
mpuacks db 0
intcnt db 0
oldmask db 0
mpudata db 0
irq db 0
irqvec db 0
irqtab db 2, 3, 4, 5, 7,-1
vectab db 10, 11,12,13,15
options db 'PTQ',0
smf_ext db '.MID',0
attr1 db 14
attr2 db 4fh
attr3 db 21h
attr4 db 3ah
aktnote db 0
ampm db 0 ; AM oder PM
smftempo db 3 dup (0)
lastevent db 3 dup (0) ; letzter Event für dispnote
controllers db 01h,00h,07h,7fh,40h,00h,0
; -------
; MPU 401 Interruptservice Routine
ALIGN 16
mpuintr: pusha
mov dx,cs:mpuio ; Base I/O Adresse MPU401
inc dx ; +1 = STATPORT
in al,dx ; kömmt der Interrupt von der MPU?
test al,80h
jz mpui100 ; Ja, dann ok.
mpuiex proc far
popa
jmp cs:oldvec ; Wenn nicht, dann auf Originalvektor!
mpuiex endp
mpui100: push ds
push es
mov ax,cs
mov ds,ax
mov es,ax
cld ; Ganz wichtig für String Befehle !
dec dx
inc intcnt
in al,dx ; Byte aus dem Datenregister lesen
cmp al,0feh ; Acknowledge ?
jnz mpui120
inc mpuacks
jmp mpui900
mpui120: cmp al,0fch ; ALL END !
jnz mpui140
and mflag,not IS_PLAY
jmp mpui900 ; Flag zurücksetzen
mpui140: cmp al,0f9h ; Conductor Request ?
jnz mpui200 ; nein, weiterschauen
mov al,tempo
or al,al
jz mpui160 ; Hat sich das Tempo überhaupt geändert ?
or mflag,IS_TEMPOCHANGE
mov wtempo,al ; Tempowert für Anzeige
mov tempo,0 ; altes Tempo setzen
push ax ; Tempowert sichern
mov al,0 ; Timedelta = 0
call put_midi ; Ausgeben
mov al,0e0h ; CMD Setze tempo
call put_midi ; ausgeben
pop ax ; Tempowert holen
call put_midi ; ausgeben
jmp mpui900 ; und zurück
mpui160: mov al,4 ; Alle 4 MIDI Clocks
call put_midi
mov al,0f8h ; No Operation
call put_midi
jmp mpui900
mpui200: cmp al,0f0h ; Alles andere als Track DATA Request 1
jz mpui400
jmp mpui900 ; ist Mumpitz ...
; ----
; Hier ist der MAIN Server für den aktuellen TRACK
mpui400: ; mov word ptr cs:lastevent,0
lds si,mpuaddr ; Adresse MPU-Spur
call getmpu ; Time Delta nach AL holen
call put_midi ; Ausgeben
call getmpu ; MIDI-Event holen
cmp al,0fch ; ALL End ?
jnz mpui410
and cs:mflag,not IS_PLAY
jmp mpui600
mpui410: call testlast ; Letzter Event
cmp al,0f8h ; No Operation ?
jz mpui600 ; Ausgeben und - ENDE -
cmp al,CHANGE_TEMPO ; Change Tempo ?
jnz mpui420
call getmpu ; Wert holen
mov cs:tempo,al ; und ablegen
mov al,0f8h ; No Operation
jmp short mpui600
mpui420: mov cs:lastevent,al ; erstes Byte sichern
test al,80h ; Runtime Event ?
jnz mpui430 ; nein, dann weiterschauen !
call put_midi ; Notennummer ausgeben
call getmpu ; Velocity nach AL holen
mov cs:lastevent+1,al ; zweites Byte sichern
jmp short mpui600 ; und ausgeben - ENDE -
mpui430: call put_midi ; EVENT ausgeben!
mov ah,al
shr ah,4
cmp ah,0bh ; Alles drunter ist 3 Byte groß!
ja mpui460
mpui440: call getmpu ; Zweites Byte holen
mov cs:lastevent+1,al ; zweites Byte sichern
mpui450: call put_midi ; Und ausgeben
jmp short mpui480 ; Und letztes Byte bearbeiten ...
mpui460: cmp ah,0eh ; PitchBender
jz mpui440 ; ist auch drei Bytes groß
mpui480: call getmpu ; ... letztes Byte holen und ausgeben
mov cs:lastevent+2,al ; drittes Byte sichern
mpui600: call put_midi
mov word ptr cs:mpuaddr,si
mpui900: pop es
pop ds
mov al,20h
out 20h,al
popa
iret
; -----
; shell - Führt eine SHELL aus
shell : call endscr
lea si,msg_shell
call wstring
mov ax,cs
mov pcmd + 4,ax
lea dx,command
lea bx,pcmd
mov ax,4b00h
int 21h
shell100:mov ax,cs
mov ds,ax
mov es,ax
call initscr
ret
; --------
; testlast - Testet letzten Event !
testlast:push ax
and cs:mflag,not IS_NOTEON
mov al,cs:lastevent
test al,80h
jnz testl100
cmp cs:lastevent+1,0
jz testl140
or cs:mflag,IS_NOTEON
jmp short testl140
testl100:shr al,4
cmp al,9
ja testl_ex
jnz testl120
cmp cs:lastevent+2,0
jz testl120
or cs:mflag,IS_NOTEON
testl120:mov al,cs:lastevent+1
testl140:call dispnote
testl_ex:pop ax
ret
; ------
; getmpu - Holt Aktuelles Byte aus der MPU-Spur
getmpu : lodsb
or si,si
jnz getmpuex
add word ptr cs:mpuaddr+2,1000h
getmpuex:ret
; --------
; dispnote - Note in AL
dispnote:cmp al,36
jb dispn10
cmp al,96
ja dispn10
mov cs:aktnote,al
or cs:mflag,IS_DISPLAY
dispn10: ret
disploop:test mflag,IS_DISPLAY
jz dispnex
push es
mov al,aktnote
mov es,screen
mov di,11*160 + 8 ; Startoffset
sub al,36
cbw
mov bl,12
div bl ; Es gilt die Oktave zu ermitteln
push ax ; Rest sichern
mov ah,28
mul ah
add di,ax ; Auf den Offset addieren
pop ax
shr ax,8
mov si,ax
shl si,1
add si,offset keymaps
lodsb
mov ah,0
add di,ax ; Endgültiger Offset
lodsb
mov cl,al
mov ch,0
lea si,notentext
mov ah,15
test mflag,IS_NOTEON
jnz dispn20
mov ah,7
cmp cl,4
jz dispn20
add si,7
dispn20: lodsb
stosw
add di,158
loop dispn20
dispn90: and mflag,not IS_DISPLAY
pop es
dispnex: ret
; ------
; prtime - Gibt Zeit in AX = Sekunden aus
; Adresse SCREEN = DI
prtime: pusha
push es
mov es,screen
xor dx,dx
mov bx,60
div bx
push dx ; Rest sichern
call prtime10
or byte ptr es:[di+1],80h
add di,2
pop ax
call prtime10
pop es
popa
ret
prtime10:aam
or ax,3030h
xchg al,ah
stosb
inc di
mov al,ah
stosb
inc di
ret
; ------
; bcd - Rechnet BCD AL in Binärzahl um
bcd : push bx
mov bl,al
shr al,4
mov ah,10
mul ah
and bl,15
add al,bl
mov ah,0
pop bx
ret
; -------
; calcsec - Rechnet REALTIME Clock Daten in Sekunden um!
calcsec: mov ampm,0
mov al,ch
cmp al,12
jbe calcs10
sub al,12
inc ampm
calcs10: call bcd ; Stunden
mov bx,ax
imul bx,3600
mov al,cl
call bcd
mov ah,60
mul ah
add bx,ax
mov al,dh
call bcd
add bx,ax
mov ax,bx
ret
; -------
; initscr
initscr: pusha
push ds
xor ax,ax
mov ds,ax
mov si,463h
lodsw
pop ds
cmp ax,03d4h
jz inits10
mov screen,0b000h
mov ax,7070h
mov word ptr attr1,ax
mov word ptr attr3,ax
inits10: push ds
mov ds,screen
mov si,6*160
mov di,SCREEN_SAVE
mov cx,13*80
rep movsw
pop ds
push es
mov es,screen
mov di,6*160
mov ah,attr1
lea si,msg_keyboard
mov cx,13*80
inits20: lodsb
stosw
loop inits20
; ----
; Hier setzen wir Attribute !
lea si,attributes
inits30: lodsw
or ax,ax
jz inits_ex
mov di,ax
lodsb
mov ah,0
mov cx,ax
lodsb
inits40: inc di
stosb
loop inits40
jmp short inits30
inits_ex:pop es
popa
ret
; --------
; inittime - Initialisiert die Zeitberechnung
inittime:mov ax,0200h
int 1ah ; Get REAL TIME
call calcsec ; Tageszeit in Sekunden umrechnen
mov starttime,ax
mov lasttime,ax
mov passed,0
add ax,expected
mov expected,ax
mov ax,expected
sub ax,lasttime
mov di,7*160 + 2*17
call prtime
mov ax,lasttime
sub ax,starttime
mov di,7*160 + 2*71
call prtime
ret
; -----
; wtime - Gibt die aktuelle Zeit aus
wtime : test mflag,IS_TEMPOCHANGE
jz wtime00
mov di,9*160 + 2*19
mov al,wtempo
mov ah,0
cwd
call wdz
mov di,9*160 + 2*73
mov ax,timebase
cwd
call wdz
and mflag,not IS_TEMPOCHANGE
wtime00: mov ax,0200h
int 1ah
call calcsec
cmp lasttime,ax
jz wtime_ex
mov lasttime,ax
mov ax,expected
sub ax,lasttime
jb wtime_ex
mov di,7*160 + 2*17
call prtime
mov ax,lasttime
sub ax,starttime
mov passed,ax
mov di,7*160 + 2*71
call prtime
mov ax,32
mul word ptr passed
div word ptr tunits
mov cx,ax
jcxz wtime_ex
push es
mov es,screen
mov di,7*160 + 2*24
push cx
mov al,4 ; ROT
wtime10: inc di
stosb
loop wtime10
pop cx
mov di,9*160 + 2*24
mov al,2 ; GRÜN
wtime20: inc di
stosb
loop wtime20
pop es
wtime_ex:ret
; ------
; endscr - Bildschirm wiederherstellen
endscr : pusha
push es
mov es,screen
mov di,6*160
mov si,SCREEN_SAVE
mov cx,13*80
rep movsw
pop es
popa
ret
; ---------
; Utilities
; -------
; wstring
wstring: push ax
push si
wstr100: lodsb
or al,al
jz wstr200
call wchar
jmp short wstr100
wstr200: pop si
pop ax
ret
wchar : push ax
push dx
mov dl,al
mov ah,2
int 21h
pop dx
pop ax
ret
; ------
; werror
werror : push bx
push cx
push si
xor cx,cx
mov dx,si
werr10 : lodsb
or al,al
jz werr20
inc cx
jmp short werr10
werr20 : mov ax,4000h
mov bx,2
int 21h
pop si
pop cx
pop bx
ret
hexw : xchg al,ah
call hexb
xchg al,ah
hexb : push ax
push cx
mov ah,al
mov cl,4
shr al,cl
call hexb500
mov al,ah
and al,15
call hexb500
pop cx
pop ax
ret
hexb500: add al,'0'
cmp al,'9'
jbe hexb600
add al,7
hexb600: call wchar
ret
; ------------
; DZ - Ausgabe einer Dezimalzahl in DX:AX
dz : pusha
xor cx,cx
de100 : call dv
push bx
inc cx
or ax,ax
jnz de100
or dx,dx
jnz de100
mov bx,8
de200 : cmp bx,cx
jz de300
mov al,' '
call wchar
dec bx
jmp short de200
de300 : pop ax
or al,'0'
call wchar
loop de300
popa
ret
; ------------
; WDZ - Ausgabe einer Dezimalzahl in DX:AX WINDOW Routine
wdz : pusha
push es
mov es,screen
xor cx,cx
wdz100 : call dv
push bx
inc cx
or ax,ax
jnz wdz100
or dx,dx
jnz wdz100
mov bx,3
wdz200 : cmp bx,cx
jz wdz300
mov al,' '
stosb
inc di
dec bx
jmp short wdz200
wdz300 : pop ax
or al,'0'
stosb
inc di
loop wdz300
pop es
popa
ret
dv : push cx
xor bx,bx
mov cx,20h
dv10 : shl ax,1
rcl dx,1
rcl bx,1
sub bx,10
jnb dv20
add bx,10
add ax,1
adc dx,0
dv20 : loop dv10
not ax
not dx
pop cx
ret
; -----
; lumul Long Unsigned Multiplikation
; eingabe operand1 = ax:dx operand2 = cx:bx
; ausgabe ergebnis = ax:dx
lumul : push ax ;save low word
push dx ;save high word
mul bx
mov bx,ax ;keep partial product
pop ax ;get high word
mul cx
add bx,ax ;add to partial product
pop ax ;get low word
mul cx
add dx,bx ;add in partial products
ret
; -----
; ludiv - Long Unsigned Division
; Eingabe: Dividend in AX:DX
; Divisor in CX:BX
; Ausgabe: Quotient in AX:DX
; Rest in CX:BX
ludiv : test bx,bx
jnz lud200
cmp cx,dx
ja lud100
push ax
mov ax,dx
sub dx,dx
div cx
mov bx,ax
pop ax
div cx
mov cx,dx
mov dx,bx
sub bx,bx
ret
lud100 : div cx
mov cx,dx
mov dx,bx
ret
lud200 : push bp
push di
push si
mov si,cx
mov di,bx
sub bx,bx
sub bp,bp
mov cx,32
lud220 : shl ax,1
rcl dx,1
rcl bp,1
rcl bx,1
sub bp,si
sbb bx,di
js lud280
lud240 : inc ax
loop lud220
jmp short lud300
lud260 : shl ax,1
rcl dx,1
rcl bp,1
rcl bx,1
add bp,si
adc bx,di
jns lud240
lud280 : loop lud260
add bp,si
adc bx,di
lud300 : mov cx,bp
pop si
pop di
pop bp
ret
toupper: cmp al,'a'
jb tou_ex
cmp al,'z'
ja tou_ex
and al,not 32
tou_ex : ret
; -----
; taste - löscht den Tastaturpuffer und holt eine Tastenkombination nach AX
taste : mov ah,1
int 16h
jz tast100
mov ah,0
int 16h
jmp short taste
tast100: mov ah,0
int 16h
ret
; -----
; wtick - Wartet für einen Timertick ...
wtick : push ax
push bx
push cx
push dx
sti
xor ax,ax
int 1ah
mov bx,dx
wtick10: xor ax,ax
int 1ah
cmp dx,bx
jz wtick10
pop dx
pop cx
pop bx
pop ax
ret
; ------
; alloff - ALL OFF über ACK !
alloff : mov al,3fh
call put_cmd
call wtick
mov al,0feh
call put_midi
mov cx,9
alloff10:call wtick
loop alloff10
call resetmpu
ret
; -------
; waitrcv - Wait For MPU Receive Ready
waitrcv: push ax
push dx
mov dx,cs:mpuio
inc dx
waitr10: in al,dx
test al,40h
jnz waitr10
pop dx
pop ax
ret
; --------
; put_midi - Ausgabe eines MidiBytes
put_midi:push ax
push dx
call waitrcv
mov dx,cs:mpuio
pushf
cli
out dx,al
popf
pop dx
pop ax
ret
; -------
; put_cmd - Ausgabe eines MPU-Kommandos
put_cmd: push ax
push cx
push dx
call waitrcv
pushf
cli
mov dx,cs:mpuio
inc dx
out dx,al
mov cx,4000h
put_c20: in al,dx
test al,80h
jz put_c30
loop put_c20
mov al,20h
out 20h,al
sti
mov ax,cs
mov ds,ax
call idisable
lea si,msg_loop
call werror
jmp ende
put_c30: dec dx
in al,dx
inc dx
cmp al,0feh
jnz put_c20
put_c90: popf
pop dx
pop cx
pop ax
ret
; -------
; resetmpu - Reset MPU
resetmpu:push ax
push dx
call waitrcv
pushf
cli
mov dx,cs:mpuio
inc dx
mov al,0ffh
out dx,al
call waitrcv
dec dx
in al,dx
popf
pop dx
pop ax
ret
; -------
; parscmd - Die Kommandozeile untersuchen
parscmd: mov si,80h
lodsw
or al,al
jnz pars100
helpexit:lea si,msg_help
err_exit:call werror
mov al,1
jmp ende
pars100: call pars500
jb helpexit
cmp byte ptr [si],'-'
jz pars120
cmp byte ptr [si],'/'
jnz pars200
pars120: lodsw
mov al,ah
pars130: call toupper
lea di,options
mov bl,1
pars140: cmp byte ptr [di],0
jz helpexit
scasb
jz pars160
shl bl,1
jmp short pars140
pars160: or mflag,bl
cmp byte ptr [si],' '
jz pars100
cmp byte ptr [si],9
jz pars100
cmp byte ptr [si],13
jz helpexit
lodsb
jmp short pars130
pars200: lea di,filename
mov fileaddr,di
mov bl,0 ; BL kennzeichnet "."
pars220: lodsb
cmp al,13
jz pars300
cmp al,'.'
jnz pars240
inc bl
pars240: call toupper
stosb
cmp al,'\'
jz pars260
cmp al,':'
jnz pars220
pars260: mov fileaddr,di
jmp short pars220
pars300: or bl,bl
jnz pars320
lea si,smf_ext
mov cx,5
rep movsb
ret
pars320: mov al,0
stosb
ret
pars500: cmp byte ptr [si],' '
jz pars540
cmp byte ptr [si],9
jz pars540
cmp byte ptr [si],13
jnz pars520
stc
ret
pars520: clc
ret
pars540: inc si
jmp short pars500
; --------
; fileloop - Durchsucht alle Dateien
fileloop:mov ax,1a00h
mov dx,80h
int 21h
mov ax,4e00h
mov cx,27h
lea dx,filename
int 21h
jnb filel100
fnf_exit:lea si,msg_fnf
jmp err_exit
filel100:mov si,9eh
mov di,fileaddr
mov cx,6
rep movsw
mov al,0
stosb
lea dx,filename
mov ax,3d00h
int 21h
jb fnf_exit
mov shandle,ax
call loadfile
mov ax,3e00h
mov bx,shandle
int 21h
call convert
lea si,msg_ready
call wstring
test mflag,IS_TEST
jnz filel120
cld
mov ax,word ptr mmseconds
mov dx,word ptr mmseconds+2
mov cx,1000
xor bx,bx
call ludiv
mov cx,1000
div cx
mov expected,ax
mov passed,0
mov tunits,ax
call initscr
call inittime
call mplay
call endscr
filel120:mov ax,4f00h
int 21h
jnb filel100
ret
; --------
; loadfile - Lädt die Datei
loadfile:lea si,filename
call wstring
lea si,msg_cr
call wstring
lea si,msg_load
call wstring
mov ax,3f00h
mov bx,shandle
mov cx,14
lea dx,msg_hello
int 21h
mov si,dx
lodsw
cmp ax,'TM' ; Magic Header SMF Teil 1
jz loadf100
nosmfex: lea si,msg_nosmf
jmp err_exit
loadf100:lodsw
cmp ax,'dh' ; Magic Header SMF Teil 2
jnz nosmfex
lodsw
or ax,ax
jnz nosmfex
lodsw
xchg al,ah
cmp ax,6 ; die obligatorische Anzahl der Bytes
jnz nosmfex ; im Header des SMF
lodsw
xchg al,ah
cmp ax,1 ; MIDI File Typ 0 oder 1
ja nosmfex ; alles andere ist Mumpitz
lodsw
xchg al,ah
mov tracks,ax ; Anzahl der Tracks
cmp ax,MAX_TRACKS
jb loadf120
lea si,msg_toomanytrk
jmp err_exit
loadf120:lodsw
xchg al,ah
mov timebase,ax ; Und zuletzt die Timebase laden
call settb
; Jetzt muß das Startsegment des ersten Tracks bestimmt werden
mov ax,cs ; Codesegment nach AX
mov bx,ADDR_FIRSTTRK
shr bx,4 ; Zum Segment konvertieren
add ax,bx ; Zum Codesegment hinzuaddieren
mov trackseg,ax ; Erstes Tracksegment setzen
mov akt_track,0 ; Aktueller Track = 0
; --------
; loadf200 - Einsprung für jeden neuen Track
loadf200:mov ax,3f00h
mov bx,shandle
mov cx,8
lea dx,msg_hello
int 21h
mov si,dx
lodsw
cmp ax,'TM' ; Magic Header Track Teil 1
jnz nosmfex
lodsw
cmp ax,'kr' ; Magic Header Track Teil 2
jnz nosmfex
lodsw
or ax,ax
jz loadf220
lea si,msg_trktoolong
jmp err_exit
loadf220:lodsw
xchg al,ah
mov trklen,ax ; Tracklänge nur obligatorisch speichern
mov si,akt_track
shl si,1
mov word ptr trackofs[si],0
; Trackoffsets auf 0 setzen !
mov bx,trackseg[si] ; Aktuelles Tracksegment laden
add ax,15
shr ax,4 ; Tracklänge zum Segment konvertieren
add bx,ax ; Endgültiges nächstes Segment
mov trackseg[si+2],bx
mov mpuseg,bx ; Segment für MPU Spur setzen
mov ax,3f00h ; Funktion LESEN
mov bx,shandle
mov cx,trklen
xor dx,dx
push ds
mov ds,trackseg[si]
int 21h
pop ds
cmp ax,cx
jz loadf240
lea si,msg_unexpeof
jmp err_exit
loadf240:inc akt_track
mov ax,akt_track
cmp ax,tracks
jz loadfex
jmp loadf200
loadfex: ret
; ------
; putmpu - Legt ein Byte in AL auf der MPU - Spur ab
putmpu : push es
push di
les di,dword ptr mpuaddr
stosb
mov word ptr mpuaddr,di
or di,di
jnz putmpuex
add word ptr mpuaddr+2,1000h
putmpuex:pop di
pop es
ret
; --------
; putdelta - Legt Timing Bytes in der MPU-Spur ab
putdelta:push ax
push dx
mov ax,mpucnt
mov cl,tbmultiply
shr ax,cl
putdel02:cmp ax,0f0h ; Maximalwert Timing Byte
jb putdel20
push cx
xor dx,dx
mov cx,080h
div cx
mov cx,ax
putdel10:mov al,080h
call putmpu
mov al,0f8h
call putmpu
loop putdel10
pop cx
mov al,dl
putdel20:call putmpu
mov cx,mpucnt ; Anzahl der Ticks
xor bx,bx
mov ax,word ptr smftime
mov dx,word ptr smftime+2
call lumul
add word ptr mmseconds,ax
adc word ptr mmseconds+2,dx
mov mpucnt,0
pop dx
pop ax
ret
; ------
; getsmf - Holt ein Byte aus dem aktuellen Track nach AL
getsmf : push bx
push si
push ds
mov bx,akt_track
shl bx,1
mov si,trackofs[bx]
mov ds,trackseg[bx]
lodsb
pop ds
mov trackofs[bx],si
pop si
pop bx
ret
; --------
; getdelta - Liest Timedelta nach AX:DX
getdelta:push bx
push cx
push bp ; Register sichern
mov bp,4 ; 4 Bytes = maximale Länge Timing Delta
xor ax,ax
xor dx,dx ; AX:DX beherbergen Time Delta
getdel10:mov cx,7
getdel20:shl ax,1
rcl dx,1
loop getdel20 ; * 128
push ax
call getsmf ; Byte aus dem Track holen!
mov cl,al
pop ax
mov bl,cl
and cx,07fh
add ax,cx
adc dx,0
test bl,80h
jz getdel30
dec bp
jnz getdel10
lea si,msg_invdelta
jmp err_exit
getdel30:pop bp
pop cx
pop bx
ret
; -------
; convert - Konvertiert SMF -> MPU
convert: lea si,msg_convert
call wstring
mov word ptr mmseconds,0
mov word ptr mmseconds+2,0
xor ax,ax
mov di,ADDR_TRKNAME
mov cx,800h
rep stosw ; Track + Instrumentenname löschen
lea di,trackflags
mov cx,MAX_TRACKS
rep stosb ; Trackflags auf 0 setzen
mov word ptr mpuaddr,0 ; Offset MPU Pointer auf 0 setzen!
mov ax,mpuseg
mov word ptr mpuaddr+2,ax ; Und Segment setzen
mov mpucnt,0 ; MPU Time Counter = 0
mov cx,tracks ; CX = Anzahl der Tracks
mov trackstogo,cx ; soviele Tracks sind abzuhandeln
mov akt_track,0 ; Aktueller Track = 0
; ----
; Hier setzen wir das erste Timingdelta
conv100: call getdelta
mov bx,akt_track
shl bx,2
mov word ptr trackcnt[bx],ax
mov word ptr trackcnt[bx+2],dx
inc akt_track
loop conv100
; -------
; conv200 - äußerer Schleifenkopf
conv200: mov akt_track,0 ; Aktueller Track = 0
conv220: mov bx,akt_track
cmp byte ptr trackflags[bx],0
jnz conv500 ; Wenn Track nicht aktiv weiter
shl bx,2
cmp word ptr trackcnt[bx],0
jnz conv240
cmp word ptr trackcnt[bx+2],0
jz conv300
conv240: sub word ptr trackcnt[bx],1
sbb word ptr trackcnt[bx+2],0
jmp short conv500
; ----
; Hier ist der Trackzähler abgelaufen, der neue Wert muß geladen werden
conv300: call putevent ; Event holen und ggfls. ablegen
mov bx,akt_track
cmp byte ptr trackflags[bx],0
jnz conv500 ; Wenn Track nicht aktiv weiter
call getdelta ; nächstes Timedelta holen
shl bx,2
mov word ptr trackcnt[bx],ax
mov word ptr trackcnt[bx+2],dx
or ax,ax
jnz conv240
or dx,dx
jz conv300
jmp short conv240
conv500: inc akt_track
mov ax,akt_track
cmp ax,tracks
jb conv220
inc mpucnt
conv520: cmp trackstogo,0
jnz conv200
mov al,0
call putmpu
mov al,DATA_END
call putmpu
mov bx,word ptr mpuaddr
add bx,16
shr bx,4
add bx,word ptr mpuaddr+2
mov ax,cs
sub bx,ax
mov ax,4a00h
int 21h
ret
; --------
; putevent - bearbeitet einen EVENT
putevent:call getsmf
cmp al,-1 ; META Event ?
jz putev020
jmp putev200 ; Nein, dann weiter
; ----------
; Abhandlung META Event
putev020:call getsmf ; Event lesen
cmp al,2fh ; Event Trackende!
jnz putev100
dec trackstogo
mov bx,akt_track
inc byte ptr trackflags[bx]
ret
; --------
; putev100 - Tempo Change ?
putev100:cmp al,51h ; Change Tempo wäre noch interessant (!)
jnz putev120
call getsmf ; Länge ist immer = 3
call getsmf
mov smftempo,al
push ax
call getsmf
mov smftempo+1,al
mov ch,al
call getsmf
mov smftempo+2,al
pop bx
mov cl,al
mov bh,0
mov ax,8700h
mov dx,393h
call ludiv
call putdelta
push ax
mov al,CHANGE_TEMPO ; MPU Kommando - Change Tempo
call putmpu
pop ax
call putmpu
mov cx,timebase
xor bx,bx
mov dh,0
mov dl,smftempo
mov ah,smftempo+1
mov al,smftempo+2
call ludiv
; MICROSEC. / TIMEBASE = MICROSEC pro TICK
mov word ptr smftime,ax
mov word ptr smftime+2,dx
ret
; -----------
; Ein für uns uninteressanter EVENT - überlesen und zurück!
putev120:call getsmf ; Länge nach AL
mov ah,0
mov cx,ax
jcxz putev160
putev140:call getsmf
loop putev140 ; Und Event überlesen
putev160:ret
; ----
; Kein META-Event - also weitermachen
putev200:cmp al,0f0h ; SYSEX's
jz putev120 ; die überlesen wir einfach
cmp al,0f7h
jz putev120
test al,80h
jnz putev220
; Runtime EVENT
call putdelta
call putmpu
call getsmf
test al,80h
jnz runexit
call putmpu
ret
putev220:mov ah,al
shr ah,4
cmp ah,0bh
jbe putev240
cmp ah,0eh
jz putev240
cmp ah,0ch
jnz putev222
test mflag,IS_PCNO
jz putev222
jmp getsmf
putev222:call putdelta
call putmpu
call getsmf
test al,80h
jz putev230
runexit: lea si,msg_runerror
jmp err_exit
putev230:call putmpu
ret
putev240:call putdelta
call putmpu
call getsmf
test al,80h
jnz runexit
call putmpu
call getsmf
test al,80h
jnz runexit
call putmpu
ret
; -----
; settb - Set Internal Timebase
settb : mov ax,timebase
mov tbmultiply,0
settb10: lea di,tbvalues
mov cx,7
xor bx,bx
settb20: scasw
jz settb30
inc bx
loop settb20
inc tbmultiply
shr ax,1
or ax,ax
jnz settb10
lea si,msg_notbase
jmp err_exit
settb30: add bl,0c2h
mov tbcmd,bl
ret
; -----
; mplay - Spielt die Datei
mplay : call ienable ; Enable Interrupt Processing
lea si,controllers
mplay100:mov cx,16
mov ah,0
mplay110:mov al,0d0h
or al,ah
call put_cmd
mov al,0b0h
or al,ah
call put_midi
mov al,[si]
call put_midi
mov al,[si+1]
call put_midi
inc ah
loop mplay110
add si,2
cmp byte ptr [si],0
jnz mplay100
call wtick
call wtick
call wtick
call wtick
mov ax,mpuseg
mov word ptr mpuaddr,0
mov word ptr mpuaddr+2,ax
; Adresse der MPU Spur setzen
mov al,0ech ; Activate Tracks
call put_cmd
mov al,1
call put_midi ; Und zwar nur 1 Track!
mov al,tbcmd
call put_cmd
mov al,8fh ; Conductor ON
call put_cmd
mplay140:mov al,0b8h ; Clear Play Counter
call put_cmd
mov al,0ah ; Start Play
call put_cmd
or mflag,IS_PLAY
mplay200:mov ah,1
int 16h
jz mplay220
mov ah,0
int 16h
cmp al,27
jz mplay300
call toupper
cmp al,'S'
jnz mplay220
call shell
jmp short mplay200
mplay220:call wtime
call disploop
test mflag,IS_PLAY
jnz mplay200
mov al,5
call put_cmd ; Stop PLAY
call wtick
call idisable ; Disable Interrupt Processing
ret
mplay300:and mflag,not IS_PLAY
call wtick
call wtick
mov al,5
call put_cmd ; Stop PLAY
call wtick
call wtick
call idisable ; Disable Interrupt Processing
call alloff ; Alle Stimmen über ACK ausschalten
ret
; -------
; ienable - Enable Interrupt Processing
ienable: push es
mov ah,35h
mov al,irqvec
int 21h
mov word ptr oldvec,bx
mov word ptr oldvec+2,es
pop es
mov ah,25h
mov al,irqvec
lea dx,mpuintr
int 21h
in al,21h
mov oldmask,al
mov ah,1
mov cl,irq
shl ah,cl
not ah
and al,ah
out 21h,al
ret
; --------
; idisable - Disable Interrupt Processing
idisable:push ds
mov ah,25h
mov al,irqvec
lds dx,oldvec
int 21h
pop ds
mov al,oldmask
out 21h,al
ret
; ------
; chkmpu - Schauen, ob MPU401 vorhanden ist.
chkmpu : mov dx,0330h
mov mpuio,dx
in al,dx
cmp al,-1
jnz chkmpu10
mov dx,0300h
mov mpuio,dx
in al,dx
cmp al,-1
jnz chkmpu10
lea si,msg_nompu
jmp err_exit
chkmpu10:lea si,msg_mpuio
call wstring
mov ax,mpuio
call hexw
call resetmpu
; -----
; Jetzt kommt der Interruptvektoren Test
; ---
; wir testen alle möglichen IRQ's
lea si,irqtab
lea di,vectab
chkmpu20:lodsb
cmp al,-1
jnz chkmpu30
lea si,msg_nointr
jmp err_exit
chkmpu30:mov irq,al
mov ah,[di]
inc di
mov irqvec,ah
call ienable
mov intcnt,0
mov al,89h ; MIDI thru ON
call put_cmd
mov al,0b8h ; Clear Play Counter
call put_cmd
mov al,0ach ; Requests Version
call put_cmd
xor cx,cx
chkmpu50:xchg al,ah
xchg al,ah
loop chkmpu50
call idisable
chkmpu60:cmp intcnt,0
jnz chkmpuex
jmp chkmpu20
chkmpuex:lea si,msg_intr
call wstring
mov al,irq
call hexb
lea si,msg_cr
call wstring
ret
; -----
; testi - Testen, ob bereits installiert
testi : push es
les di,dword ptr semaphor
mov ax,-1
scasw
jnz testi10
lea si,msg_already
call werror
jmp ende100
testi10: sub di,2
stosw
testi_ex:pop es
ret
; -------
semi_ex: pusha
push es
les di,dword ptr semaphor
xor ax,ax
stosw
pop es
popa
ret
; ------
; anfang
anfang : cld
mov sp,STACK_ADDR - 2
mov ax,0f00h
int 10h
mov ah,0
int 10h
lea si,msg_hello
call werror
call testi
call parscmd ; Kommandozeile durchforsten
call chkmpu ; Schauen, ob MPU 401 da ist.
call fileloop ; Und alle Dateien abgrasen
ende : call semi_ex
ende100: mov ah,4ch
int 21h
ALIGN 16
filename label byte
code ends
end start