{$A-,B+,D+,E+,F+,G+,I+,L+,N-,O+,P+,Q+,R+,S+,T-,V+,X+}
{$M 16384,0,655360}
program NewMID;
(*
* 2. Versuch fuer einen MID-Player
*
* laeuft wegen Timing-Problemen nicht komplett im Interrupt (wenn sehr
viele
* Events hintereinander kommen, gibts Haenger)
* Aber: ich arbeite daran :-)
*
* Hat jemand eine Idee oder ein System (Unit?), die 2 unterschiedlich
* schnell getaktete Interrupts bietet (z.B. einer 768 mal pro sek., der
* andere z.B. 50 mal pro sek., der erste muss aber einer hoehere
Prioritaet
* wie der zweite haben. Noch besser waere: der 1. muss den 2. ausloesen
koennen)
* [ Ich hoffe, das hat jetzt jeder verstanden :-) ]
* Wer also sowas hat oder kennt, meldet sich mal bitte bei mir ;)
*
* Aber trotz allem: Verbesserungsvorschlaege, Lobesgesaenge,
Dankesschreiben
* Kritik etc. an: Rene Werner@2:249/4060.3 oder hier im Echo (PASCAL.GER)
*
* (C) 1994,1995 by Rene Werner, Freeware
* Dieser Source ist Freeware! Er darf in Sachen verwendet werden, fuer
* die kein Geld verlangt wird (also nicht in Shareware oder gar
* kommerziellen Programmen). Wenn jemand den Player in seinen eigenen
* Programmen verwendet, wuerde ich mich ueber eine Kopie des Programms
sehr
* freuen.
* Fall jemand den Source aendert, dann moechte ich eine Kopie der
veraenderten
* Version haben!
*
* 01.05.95 : 1.0 (RW) : fertig :-)
*)
uses
crt,dos;
type
BPTR = ^byte;
const
{ Fehlermeldungen fuer loadMidiFile() }
lmf_ok : string[2] = 'OK';
lmf_no_file : string[35] = 'kann Datei nicht oeffnen';
lmf_out_of_mem : string[35] = 'nicht genug Speicher frei';
lmf_no_mid_file : string[35] = 'keine MIDI-Datei (''MTrk''fehlt)';
lmf_mid_format_err : string[35] = 'unerwartete Daten in MIDI-Datei';
lmf_not_typ01 : string[35] = 'MID-Datei ist nicht Typ 0 oder 1';
lmf_missing_mtrk : string[35] = 'kein ''MTrk'' am Track-Anfang';
lmf_track_too_large: string[35] = 'Track ist zu gross ( > 65528 bytes)';
lmf_too_many_tracks: string[35] = 'zu viele Tracks (max. 64)';
{ auf 'false' setzen um Ausgabe zu unterdruecken }
debug : boolean = true;
{ standard values for MPU-401: }
Dataport = $330; {Standard MPU-401 port NoteVals}
Statusport = dataport+1;
Commandport = dataport+1;
DRR = $40; {Data Ready to Receive}
DRS = $80; {Data Ready to Send}
ACKmsg = $FE;
ResetCmd = $FF;
UartModeCmd = $3F;
hexChars: array [0..$F] of Char ='0123456789ABCDEF';
{ die Laengen der Midi-Events }
event_lens : array[$80..$FF] of longint = (
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, { $8x }
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, { $9x }
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, { $Ax }
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, { $Bx }
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, { $Cx }
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, { $Dx }
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, { $Ex }
1,1,3,2,1,1,1,1,1,1,1,1,1,1,1,1 { $Fx }
);
var
s : string;
tracks : word;
track_ptr : array[1..64] of pointer;
track_size : array[1..64] of longint;
play_ptr : array[1..64] of BPTR;
bp : BPTR;
play_delta : array[1..64] of longint;
old_cmds : array[1..64] of byte;
trk_finished : array[1..64] of boolean;
deltaticks : word;
bpm : word;
ints : word;
i : word;
il,jl : longint;
c : char;
b,meta : byte;
finished : boolean;
finished_t : word;
tick : boolean;
absDelta : longint;
call08 : boolean;
oldint08 : procedure;
{ modint: Frequenz des Interrupts aendern }
procedure modint(frames:longint);
var
Factor : longint;
begin
asm cli end;
port[$43]:=$36; { Steuerbefehl }
Factor:=1193180 DIV frames; { frequenz in Hz }
port[$40]:=Lo(Factor);
port[$40]:=Hi(Factor);
asm sti end;
end;
{ sendet ein Midi-Byte an den MPU-Port }
procedure sendMidiByte(b : byte);
var
junk : byte;
begin
{Warten, bis MPU bereit zum empfangen ist...}
while port[StatusPort] and DRR <> 0 do begin
{ Solange uns die MPU was senden will, holen wir es uns ;-) }
while port[StatusPort] and DRS = 0 do
junk := port[Dataport];
end;
{ Jetzt ist sie soweit ;) }
port[Dataport] := b;
END;
{ Hex4: Umwandlung word in Hex-String }
function Hex4(w: Word):string;
var
s : string;
begin
s:=hexChars[Hi(w) shr 4]+hexChars[Hi(w) and $F]+hexChars[Lo(w) shr 4]+
hexChars[Lo(w) and $F];
Hex4:=s;
end;
{$F+}
{ der Timer-Interrupt }
procedure int08Handler(Flags, rCS, rIP, rAX, rBX, rCX, rDX,
rSI, rDI, rDS, rES, rBP: Word);
interrupt;
var
i : word;
begin
tick:=true;
inc(absDelta);
if call08 then
{ call original system int 08 vector }
begin
asm
pushf
end;
oldint08;
end
else
port[$20]:=$20;
end;
{$F-}
{ verbiegt Timer-Int 08 und stellt Freuqenz ein }
procedure setint(frames:longint);
var
Factor : longint;
begin
port[$43]:=$36; { Steuerbefehl }
Factor:=1193180 DIV frames; { frequenz in Hz }
port[$40]:=Lo(Factor);
port[$40]:=Hi(Factor);
call08 := false;
GetIntVec($08, @oldint08);
SetIntVec($08, Addr(int08Handler));
end;
{ Setzt Int 08 zurueck }
procedure endint;
var
Factor : longint;
begin
asm
cli
end;
Factor := $FFFF;
Port[$40] := Lo(Factor); { Low-Byte fuer die Timer-Frequenz }
Port[$40] := Hi(Factor); { High-Byte fuer den Timer-Frequenz }
SetIntVec($08, @oldint08);
asm
sti
end;
end;
{ versucht, die MPU zurueckzusetzen, gibt TRUE zurueck, wenn eine MPU
vorhanden ist und auf MIDI-Kommandos reagiert }
function MidiReset : boolean;
var
junk : byte;
retries : integer;
timeout : word;
begin
MidiReset := true;
{ Try to clear pending data }
timeout := 0;
while (port[StatusPort] and DRS = 0) and (timeout < 10) do begin
junk := port[Dataport];
inc(timeout);
end;
for retries := 1 to 2 do begin
timeout := 0;
{Give it 3000 chances to say it
is ready to receive}
while (port[StatusPort] and DRR <> 0) and(timeout < 3000) do
inc(timeout);
if (timeout < 3000) then begin
{Send the reset command}
port[StatusPort] := ResetCmd;
timeout := 0;
{Give it 3000 tries to RESPOND to the
reset with an ACKnowledge}
while (TimeOut < 3000) do begin
if (port[StatusPort]and DRS = 0)
and (port[Dataport] = ACKmsg) then
exit;
inc(timeout);
end;
end;
end;
{If you get here, it failed to initialize}
MidiReset := false;
end;
{ LoadMidiFile
E: Dateiname der zu ladenden Datei
A: Errorcode ('OK', wenn alles i.O. ist) }
function LoadMidiFile(midfn:string):string;
var
f : file;
buf : array[1..4] of byte;
ts : longint;
i,j : word;
begin
{ track_ptr und track_size auf 0 }
for i:=1 to 64 do begin
track_ptr[i]:=nil;
track_size[i]:=0;
end;
if debug then writeln('DEBUG: versuche, ',midfn,' zu laden');
FileMode:=0;
assign(f,midfn);
{$I-}
reset(f,1);
{$I+}
if IOResult<>0 then begin
LoadMidiFile:=lmf_no_file;
exit; { exit mit 'lmf_no_file' }
end;
FileMode:=2;
{ Header lesen und auswerten (MThd) }
blockread(f,buf,4);
if not((buf[1]=ord('M')) and (buf[2]=ord('T')) and (buf[3]=ord('h'))
and (buf[4]=ord('d'))) then begin
close(f);
LoadMidiFile:=lmf_no_mid_file;
exit; { exit mit 'lmf_no_mid_file' }
end;
blockread(f,buf,4);
ts:=longint(buf[1]) shl 24+longint(buf[2]) shl 16+longint(buf[3]) shl
8+
longint(buf[4]);
if ts<>6 then begin
close(f);
LoadMidiFile:=lmf_mid_format_err;
exit; { MThd nicht 6 Bytes gross ! }
end;
blockread(f,buf,2);
if buf[1]<>0 then begin
close(f);
LoadMidiFile:=lmf_mid_format_err;
exit; { format error, muss 0 sein ! }
end;
if not((buf[2]=0) or (buf[2]=1)) then begin
close(f);
LoadMidiFile:=lmf_not_typ01;
exit; { nicht typ 0 oder 1, 2 wird nicht unterstuetzt }
end;
if debug then writeln('DEBUG: MIDI-Datei Typ ',buf[2]);
blockread(f,buf,2);
tracks:=word(buf[1]) shl 8+word(buf[2]);
if debug then writeln('DEBUG: Anzahl Tracks: ',tracks);
if tracks>64 then begin
close(f);
LoadMidiFile:=lmf_too_many_tracks;
exit;
end;
blockread(f,buf,2);
deltaticks:=word(buf[1]) shl 8+word(buf[2]);
if debug then writeln('DEBUG: DeltaTicks pro Viertelnote:',deltaticks);
if debug then writeln('DEBUG: lade Tracks... (',tracks,')');
for i:=1 to tracks do begin
if debug then writeln('DEBUG: lade Spur #',i);
blockread(f,buf,4); { MTrk }
if not((buf[1]=ord('M')) and (buf[2]=ord('T')) and
(buf[3]=ord('r'))
and (buf[4]=ord('k'))) then begin
close(f);
LoadMidiFile:=lmf_missing_mtrk;
exit;
end;
blockread(f,buf,4);
ts:=longint(buf[1]) shl 24+longint(buf[2]) shl 16+
longint(buf[3]) shl 8+longint(buf[4]);
if MemAvail65528 then begin
close(f);
LoadMidiFile:=lmf_track_too_large;
exit;
end;
track_size[i]:=ts;
GetMem(track_ptr[i],ts);
blockread(f,track_ptr[i]^,ts);
end;
loadMidiFile:=lmf_ok;
close(f);
end;
begin
clrscr;
if paramcount<1 then begin
writeln('Aufruf: ',paramstr(0),' midfilename');
halt;
end;
TextMode(CO80+Font8x8);
if not MidiReset then begin
s:=hexChars[Hi(dataport) and $F]+hexChars[Lo(dataport) shr 4]+
hexChars[Lo(dataport) and $F];
writeln('Kann MPU an Port '+s+'h nicht initialisieren!');
{ halt;}
{ Dieser Block wird auf meinem Terratec MiniWaveSystem immer ausgel”st.
Danach funktioniert die MIDI-Ausgabe dennoch einwandfrei.
- Arno (F_Fleck@compuserve.com) }
end
else
if debug then writeln('DEBUG: MPU ok.');
repeat { MPU in UART-mode bringen }
until (port[StatusPort] and DRR) = 0;
port[CommandPort] := UartModeCmd;
while port[StatusPort] and DRS <> 0 DO;
s:=loadMidiFile(paramstr(1));
if s='OK' then
writeln('MID-Datei erfolgreich geladen')
else begin
writeln('loadMidiFile() brachte folgenden Fehler:');
writeln(' -> ',s);
for i:=1 to 64 do
if track_ptr[i]<>nil then
FreeMem(track_ptr[i],track_size[i]);
halt;
end;
{ player initialisieren }
for i:=1 to tracks do begin
bp:=track_ptr[i];
il:=ord(bp^) AND $7F;
while ord(bp^)>127 do begin
il:=il SHL 7;
inc(bp);
il:=il+(ord(bp^) AND $7F);
end;
inc(bp);
play_delta[i]:=il+1;
play_ptr[i]:=bp; { korrigierter Ptr, zeigt auf 1. Event }
trk_finished[i]:=false;
end;
bpm:=120; { default }
ints:=word((longint(bpm)*longint(deltaticks)) div 60);
if debug then begin
write('Druecke eine Taste...');
if readkey=#0 then readkey;
clrscr;
asm { Cursor aus }
xor cl,cl
mov ch,36
mov ah,1
int 10h
end;
gotoxy(1,3);
write('Track Current');
for i:=1 to tracks do begin
gotoxy(1,4+i);
write(' ',i:2,' : $',hex4(seg(play_ptr[i]^)),':',
hex4(ofs(play_ptr[i]^)));
end;
gotoxy(10,1);
write('Speed: ',bpm:3,' BPM ; ',deltaticks:4,'Ticks/Quarternote');
end;
finished:=false;
finished_t:=0;
c:=#0;
tick:=false;
setint(longint(ints)); { start player }
while not tick do;
absDelta:=0;
repeat
if not finished then begin
for i:=1 to tracks do begin
while ((not(trk_finished[i])) and
(play_delta[i]<=absDelta)) do begin
bp:=play_ptr[i];
if bp^<$80 then
b:=old_cmds[i]
else begin
b:=bp^;
inc(bp);
end;
old_cmds[i]:=b;
if b=$ff then begin { meta event ? }
meta:=bp^;
inc(bp);
{ laenge des meta-events holen }
il:=ord(bp^) AND $7F;
while ord(bp^)>127 do begin
il:=il SHL 7;
inc(bp);
il:=il+(ord(bp^) AND $7F);
end;
inc(bp);
case meta of
$2f: begin { track ende }
if debug then begin
gotoxy(15,4+i);
write('---- (track ende)');
end;
trk_finished[i]:=true;
inc(finished_t);
if finished_t=tracks then
finished:=true;
end;
$51: begin { set tempo }
il:=ord(bp^);
inc(bp);
il:=(il SHL 8)+ord(bp^);
inc(bp);
il:=(il SHL 8)+ord(bp^);
inc(bp);
bpm:=word(60000000 DIV il);
ints:=word((longint(deltaticks)*
longint(bpm)) div 60);
if debug then begin
gotoxy(10,1);
write('Speed: ',bpm:3,
' BPM ;',deltaticks:4,
' Ticks/Quarternote');
end;
{ Interrupt-Frequenz aendern }
modint(longint(ints));
end;
$03: begin { Trackname }
if debug then begin
gotoxy(20,4+i);
for jl:=1 to 11 do
write(' ');
gotoxy(20,4+i);
for jl:=1 to il do begin
write(char(bp^));
inc(bp);
end;
end else
for jl:=1 to il do
inc(bp);
end;
else for jl:=1 to il do
inc(bp); { meta-event uebergehen
}
end;
end { if b=$ff }
else begin
il:=event_lens[b]; { laenge des events }
if ((b=$f0) or (b=$f7)) then begin { sysex ?
}
il:=ord(bp^) AND $7F; { laenge holen }
while ord(bp^)>127 do begin
il:=il SHL 7;
inc(bp);
il:=il+(ord(bp^) AND $7F);
end;
inc(bp);
{ anzahl bytes in il }
if b=$f0 then begin { beginn eines sysex ?
}
while port[StatusPort] and DRR <> 0 do;
port[Dataport] := $0f0; {sysex senden}
end;
for jl:=1 to il do begin
while port[StatusPort] and DRR <> 0
do;
port[Dataport] := bp^;
inc(bp);
end;
end else begin
{ Byte in b }
(*
while port[StatusPort] and DRR <> 0 do;
port[Dataport] := b;
*)
asm
mov dx,StatusPort
@1:
in al,dx
and al,DRR
jnz @1
mov al,[b]
dec dx { DataPort = StatusPort - 1 }
out dx,al
end;
if il>=2 then begin {Event mit 2 oder 3
Bytes}
b:=bp^; { Byte holen }
(*
while port[StatusPort] and DRR <> 0
do;
port[Dataport] := bp^;
*)
asm
mov dx,StatusPort
@2:
in al,dx
and al,DRR
jnz @2
mov al,[b]
dec dx
out dx,al
end;
inc(bp);
if il=3 then begin { 3. Byte ?? }
b:=bp^; { Byte holen }
(*
while port[StatusPort] and DRR<>0
do;
port[Dataport] := bp^;
*)
asm
mov dx,StatusPort
@2:
in al,dx
and al,DRR
jnz @2
mov al,[b]
dec dx
out dx,al
end;
inc(bp);
end; { if il=3 }
end; { if il>=2 }
end; { if ((b=$f0) or (b=$f7)) -- else end }
end; { if b=$ff -- else end}
if not trk_finished[i] then begin
il:=ord(bp^) AND $7F;
while ord(bp^)>127 do begin
il:=il SHL 7;
inc(bp);
il:=il+(ord(bp^) AND $7F);
end;
inc(bp);
play_delta[i]:=play_delta[i]+il;
play_ptr[i]:=bp;
if debug then begin
gotoxy(15,4+i);
write(hex4(ofs(play_ptr[i]^)));
end;
end;
end; { while play_delta[i]=0 }
end; { for i:=1 to tracks }
end; { if not finished }
if keypressed then begin
c:=readkey;
if c=#0 then readkey;
end;
until ((c=#27) or (finished));
{ Interrupt wiederherstellen }
endint;
{ bringen wir das Midi-Equipment mal zur Ruhe }
for b:=0 to 15 do begin
sendMidiByte($B0+b); { All notes off }
sendMidiByte($7B);
sendMidiByte($00);
sendMidiByte($B0+b); { All sounds off }
sendMidiByte($78);
sendMidiByte($00);
sendMidiByte($B0+b); { Reset all controllers }
sendMidiByte($79);
sendMidiByte($00);
end;
{ Speicher freigeben }
for i:=1 to 64 do
if track_ptr[i]<>nil then
FreeMem(track_ptr[i],track_size[i]);
TextMode(CO80);
{ und last but not least: wir bringen die Systemzeit in Ordnung :-) }
asm
XOR AL,AL
OUT 70h,AL
IN AL,71h
MOV DH,AL
AND DH,15
SHR AL,4
MOV DL,10
MUL DL
ADD DH,AL
MOV AL,2
OUT 70h,AL
IN AL,71h
MOV CL,AL
AND CL,15
SHR AL,4
MOV DL,10
MUL DL
ADD CL,AL
MOV AL,4
OUT 70h,AL
IN AL,71h
MOV CH,AL
AND CH,15
SHR AL,4
MOV DL,10
MUL DL
ADD CH,AL
XOR DL,DL
MOV AH,2Dh
INT 21h
MOV AL,7
OUT 70h,AL
IN AL,71h
MOV DL,AL
AND DL,15
SHR AL,4
MOV CH,10
MUL CH
ADD DL,AL
MOV AL,8
OUT 70h,AL
IN AL,71h
MOV DH,AL
AND DH,15
SHR AL,4
MOV CH,10
MUL CH
ADD DH,AL
MOV AL,4
OUT 70h,AL
IN AL,71h
MOV CL,AL
AND CL,15
SHR AL,4
MOV CH,10
MUL CH
ADD CL,AL
XOR CH,CH
ADD CX,1900
MOV AH,2Bh
INT 21h
end;
end.
               (
geocities.com/~franzglaser)