[Back to SOUND SWAG index] [Back to Main SWAG index] [Original]
UNIT CMFTool;
{** Unit - uses SBFMDRV.COM **}
INTERFACE
USES Dos;
TYPE
CMFFileTyp = FILE;
CMFDataTyp = Pointer;
CMFHeader = RECORD
CMFFileID : ARRAY[0..3] OF CHAR;
CMFVersion : WORD;
CMFInstrBlockOfs : WORD;
CMFMusicBlockOfs : WORD;
CMFTickPerBeat : WORD;
CMFClockTicksPS : WORD;
CMFFileTitleOfs : WORD;
CMFComposerOfs : WORD;
CMFMusicRemarkOfs : WORD;
CMFChannelsUsed : ARRAY[0..15] OF CHAR;
CMFInstrNumber : WORD;
CMFBasicTempo : WORD;
END;
CONST
CMFToolVersion = 'v1.0';
VAR
CMFStatusByte : BYTE;
CMFErrStat : WORD;
CMFDriverInstalled : BOOLEAN;
CMFDriverIRQ : WORD;
CMFSongPaused : BOOLEAN;
OldExitProc : Pointer;
PROCEDURE PrintCMFErrMessage;
FUNCTION CMFGetSongBuffer(VAR CMFBuffer : Pointer; CMFFile : STRING):BOOLEAN;
FUNCTION CMFFreeSongBuffer (VAR CMFBuffer : Pointer):BOOLEAN;
FUNCTION CMFInitDriver : BOOLEAN;
FUNCTION CMFGetVersion : WORD;
PROCEDURE CMFSetStatusByte;
FUNCTION CMFSetInstruments(VAR CMFBuffer : Pointer):BOOLEAN;
FUNCTION CMFSetSingleInstruments(VAR CMFInstrument:Pointer; No:WORD):BOOLEAN;
PROCEDURE CMFSetSysClock(Frequency : WORD);
PROCEDURE CMFSetDriverClock(Frequency : WORD);
PROCEDURE CMFSetTransposeOfs (Offset : INTEGER);
FUNCTION CMFPlaySong(VAR CMFBuffer : Pointer) : BOOLEAN;
FUNCTION CMFStopSong : BOOLEAN;
FUNCTION CMFResetDriver:BOOLEAN;
FUNCTION CMFPauseSong : BOOLEAN;
FUNCTION CMFContinueSong : BOOLEAN;
IMPLEMENTATION
TYPE
TypeCastTyp = ARRAY [0..6000] of Char;
VAR
Regs : Registers;
CMFIntern : ^CMFHeader; { Internal pointer to CMF structure }
PROCEDURE PrintCMFErrMessage;
{ PURPOSE : Displays SB error as text; no change to error status. }
BEGIN
CASE CMFErrStat OF
100 : Write(' SBFMDRV sound driver not found ');
110 : Write(' Driver reset successful ');
200 : Write(' CMF file not found ');
210 : Write(' No memory free for CMF file ');
220 : Write(' File not in CMF format ');
300 : Write(' Memory allocation error occurred ');
400 : Write(' Too many instruments defined ');
500 : Write(' CMF data could not be played ');
510 : Write(' CMF data could not be stopped ');
520 : Write(' CMF data could not be paused ');
530 : Write(' CMF data could not be continued ');
END;
END;
FUNCTION Exists (Filename : STRING):BOOLEAN;
{ PURPOSE : Checks for the existence of a file, and returns a Boolean exp. }
VAR
F : File;
BEGIN
Assign(F,Filename);
{$I-}
Reset(F);
Close(F);
{$I+}
Exists := (IoResult = 0) AND (Filename <> '');
END;
PROCEDURE AllocateMem (VAR Pt : Pointer; Size : LongInt);
{ Reserves as many bytes as Size allows, then sets the pointer in the
Pt variable. If not enough memory is available, Pt is set to NIL. }
VAR
SizeIntern : WORD;
BEGIN
Inc(Size,15);
SizeIntern := (Size shr 4);
Regs.AH := $48;
Regs.BX := SizeIntern;
MsDos(Regs);
IF (Regs.BX <> SizeIntern) THEN Pt := NIL
ELSE Pt := Ptr(Regs.AX,0);
END;
FUNCTION CheckFreeMem (VAR CMFBuffer : Pointer; CMFSize : LongInt):BOOLEAN;
{ Ensures that enough memory has been allocated for CMF file. }
BEGIN
AllocateMem(CMFBuffer,CMFSize);
CheckFreeMem := CMFBuffer <> NIL;
END;
FUNCTION CMFGetSongBuffer(VAR CMFBuffer : Pointer; CMFFile : STRING):BOOLEAN;
{ Loads file into memory; returns TRUE if load successful, FALSE if not. }
CONST
FileCheck : STRING[4] = 'CTMF';
VAR
CMFFileSize : LongInt;
FPresent : BOOLEAN;
VFile : CMFFileTyp;
Segs : WORD;
Read : WORD;
Checkcount : BYTE;
BEGIN
FPresent := Exists(CMFFile);
{ CMF file could not be found }
IF Not(FPresent) THEN BEGIN
CMFGetSongBuffer := FALSE;
CMFErrStat := 200;
EXIT
END;
Assign(VFile,CMFFile);
Reset(VFile,1);
CMFFileSize := Filesize(VFile);
AllocateMem(CMFBuffer,CMFFileSize);
{ Insufficient memory for CMF file }
IF (CMFBuffer = NIL) THEN BEGIN
Close(VFile);
CMFGetSongBuffer := FALSE;
CMFErrStat := 210;
EXIT;
END;
Segs := 0;
REPEAT
Blockread(VFile,Ptr(seg(CMFBuffer^)+4096*Segs,Ofs(CMFBuffer^))^,$FFFF,Read
);
Inc(Segs);
UNTIL Read = 0;
Close(VFile);
{ File not in CMF format }
CMFIntern := CMFBuffer;
CheckCount := 1;
REPEAT
IF FileCheck[CheckCount] = CMFIntern^.CMFFileID[CheckCount-1]
THEN Inc(CheckCount)
ELSE CheckCount := $FF;
UNTIL CheckCount >= 3;
IF NOT(CheckCount = 3) THEN BEGIN
CMFGetSongBuffer := FALSE;
CMFErrStat := 220;
EXIT;
END;
{ Load was successful }
CMFGetSongBuffer := TRUE;
CMFErrStat := 0;
END;
FUNCTION CMFFreeSongBuffer (VAR CMFBuffer : Pointer):BOOLEAN;
{ Frees memory allocated for CMF file. }
BEGIN
Regs.AH := $49;
Regs.ES := seg(CMFBuffer^);
MsDos(Regs);
CMFFreeSongBuffer := TRUE;
IF (Regs.AX = 7) OR (Regs.AX = 9) THEN BEGIN
CMFFreeSongBuffer := FALSE;
CMFErrStat := 300
END;
END;
FUNCTION CMFInitDriver : BOOLEAN;
{ Checks for SBFMDRV.COM resident in memory, and resets driver }
CONST
DriverCheck :STRING[5] = 'FMDRV';
VAR
ScanIRQ,
CheckCount : BYTE;
IRQPtr,
DummyPtr : Pointer;
BEGIN
{ Possible SBFMDRV interrupts lie in range $80 - $BF }
FOR ScanIRQ := $80 TO $BF DO BEGIN
GetIntVec(ScanIRQ, IRQPtr);
DummyPtr := Ptr(Seg(IRQPtr^), $102);
{ Check for string 'FMDRV' in interrupt program. }
CheckCount := 1;
REPEAT
IF DriverCheck[CheckCount] = TypeCastTyp(DummyPtr^)[CheckCount]
THEN Inc(CheckCount)
ELSE CheckCount := $FF;
UNTIL CheckCount >= 5;
IF (CheckCount = 5) THEN BEGIN
{ String found; reset executed }
Regs.BX := 08;
CMFDriverIRQ := ScanIRQ;
Intr(CMFDriverIRQ, Regs);
IF Regs.AX = 0 THEN
CMFInitDriver := TRUE
ELSE BEGIN
CMFInitDriver := FALSE;
CMFErrStat := 110;
END;
Exit;
END
ELSE BEGIN
{ String not found }
CMFInitDriver := FALSE;
CMFErrStat := 100;
END;
END;
END;
FUNCTION CMFGetVersion : WORD;
{ Gets version number from SBFMDRV driver. }
BEGIN
Regs.BX := 0;
Intr(CMFDriverIRQ,Regs);
CMFGetVersion := Regs.AX;
END;
PROCEDURE CMFSetStatusByte;
{ Place driver status byte in CMFStatusByte variable. }
BEGIN
Regs.BX:= 1;
Regs.DX:= Seg(CMFStatusByte);
Regs.AX:= Ofs(CMFStatusByte);
Intr(CMFDriverIRQ, Regs);
END;
FUNCTION CMFSetInstruments(VAR CMFBuffer : Pointer):BOOLEAN;
{ Sets SB card FM registers to instrumentation stated in CMF file. }
BEGIN
CMFIntern := CMFBuffer;
IF CMFIntern^.CMFInstrNumber > 128 THEN BEGIN
CMFErrStat := 400;
CMFSetInstruments := FALSE;
Exit;
END;
Regs.BX := 02;
Regs.CX := CMFIntern^.CMFInstrNumber;
Regs.DX := Seg(CMFBuffer^);
Regs.AX := Ofs(CMFBuffer^)+CMFIntern^.CMFInstrBlockOfs;
Intr(CMFDriverIRQ, Regs);
CMFSetInstruments := TRUE;
END;
FUNCTION CMFSetSingleInstruments(VAR CMFInstrument:Pointer; No:WORD):BOOLEAN;
{ Sets SB FM registers to instrument values corresponding to the
data structure following the CMFInstrument pointer. }
BEGIN
IF No > 128 THEN BEGIN
CMFErrStat := 400;
CMFSetSingleInstruments := FALSE;
Exit;
END;
Regs.BX := 02;
Regs.CX := No;
Regs.DX := Seg(CMFInstrument^);
Regs.AX := Ofs(CMFInstrument^);
Intr(CMFDriverIRQ, Regs);
CMFSetSingleInstruments := TRUE;
END;
PROCEDURE CMFSetSysClock(Frequency : WORD);
{ Sets default value of timer 0 to new value. }
BEGIN
Regs.BX := 03;
Regs.AX := (1193180 DIV Frequency);
Intr(CMFDriverIRQ, Regs);
END;
PROCEDURE CMFSetDriverClock(Frequency : WORD);
{ Sets driver timer frequency to new value. }
BEGIN
Regs.BX := 04;
Regs.AX := (1193180 DIV Frequency);
Intr(CMFDriverIRQ, Regs);
END;
PROCEDURE CMFSetTransposeOfs (Offset : INTEGER);
{ Transposes all notes in the CMF file by "Offset." }
BEGIN
Regs.BX := 05;
Regs.AX := Offset;
Intr(CMFDriverIRQ, Regs);
END;
FUNCTION CMFPlaySong(VAR CMFBuffer : Pointer) : BOOLEAN;
{ Initializes all important parameters and starts song playback. }
VAR
Check : BOOLEAN;
BEGIN
CMFIntern := CMFBuffer;
{ Set driver clock frequency }
CMFSetDriverClock(CMFIntern^.CMFClockTicksPS);
{ Set instruments }
Check := CMFSetInstruments(CMFBuffer);
IF Not(Check) THEN Exit;
Regs.BX := 06;
Regs.DX := Seg(CMFIntern^);
Regs.AX := Ofs(CMFIntern^)+CMFIntern^.CMFMusicBlockOfs;
Intr(CMFDriverIRQ, Regs);
IF Regs.AX = 0 THEN BEGIN
CMFPlaySong := TRUE;
CMFSongPaused := FALSE;
END
ELSE BEGIN
CMFPlaySong := FALSE;
CMFErrStat := 500;
END;
END;
FUNCTION CMFStopSong : BOOLEAN;
{ Attempts to stop song playback. }
BEGIN
Regs.BX := 07;
Intr(CMFDriverIRQ, Regs);
IF Regs.AX = 0 THEN
CMFStopSong := TRUE
ELSE BEGIN
CMFStopSong := FALSE;
CMFErrStat := 510;
END;
END;
FUNCTION CMFResetDriver:BOOLEAN;
{ Resets driver to starting status. }
BEGIN
Regs.BX := 08;
Intr(CMFDriverIRQ, Regs);
IF Regs.AX = 0 THEN
CMFResetDriver := TRUE
ELSE BEGIN
CMFResetDriver := FALSE;
CMFErrStat := 110;
END;
END;
FUNCTION CMFPauseSong : BOOLEAN;
{ Attempts to pause song playback. If pause is possible, this
function sets the CMFSongPaused variable to TRUE. }
BEGIN
Regs.BX := 09;
Intr(CMFDriverIRQ, Regs);
IF Regs.AX = 0 THEN BEGIN
CMFPauseSong := TRUE;
CMFSongPaused := TRUE;
END
ELSE BEGIN
CMFPauseSong := FALSE;
CMFErrStat := 520;
END;
END;
FUNCTION CMFContinueSong : BOOLEAN;
{ Attempts to continue playback of a paused song. If continuation
is possible, this function sets CMFSongPaused to FALSE. }
BEGIN
Regs.BX := 10;
Intr(CMFDriverIRQ, Regs);
IF Regs.AX = 0 THEN BEGIN
CMFContinueSong := TRUE;
CMFSongPaused := FALSE;
END
ELSE BEGIN
CMFContinueSong := FALSE;
CMFErrStat := 530;
END;
END;
{$F+}
PROCEDURE CMFToolsExitProc;
{$F-}
{ Resets the status byte address, allowing this program to exit.}
BEGIN
Regs.BX:= 1;
Regs.DX:= 0;
Regs.AX:= 0;
Intr(CMFDriverIRQ, Regs);
ExitProc := OldExitProc;
END;
BEGIN
{ Reset old ExitProc to the Tool unit proc }
OldExitProc := ExitProc;
ExitProc := @CMFToolsExitProc;
{ Initialize variables }
CMFErrStat := 0;
CMFSongPaused := FALSE;
{ Initialize driver }
CMFDriverInstalled := CMFInitDriver;
IF CMFDriverInstalled THEN BEGIN
CMFStatusByte := 0;
CMFSetStatusByte;
END;
END.
{ --------------------- DEMO PROGRAM ----------------- }
Program CMFDemo;
{* Demo program for CMFTOOL unit *}
{$M 16384,0,65535}
Uses CMFTool,Crt;
VAR
Check : BOOLEAN;
SongName : String;
SongBuffer : CMFDataTyp;
PROCEDURE TextNumError;
{* INPUT : None; data comes from CMFErrStat global variable
* OUTPUT : None
* PURPOSE : Displays SB error as text, including error number. }
BEGIN
Write(' Error #',CMFErrStat:3,': ');
PrintCMFErrMessage;
WriteLn;
Halt(CMFErrStat);
END;
BEGIN
ClrScr;
{ Displays error if SBFMDRV driver has not been installed }
IF Not (CMFDriverInstalled) THEN TextNumError;
{ If no song name is included with command line parameters,
program searches for the default name (here STARFM.CMF). }
IF ParamCount = 0 THEN SongName := 'STARFM.CMF'
ELSE SongName := ParamStr(1);
{ Display driver's version and subversion numbers }
GotoXY(28,5);
Write ('SBFMDRV Version ',Hi(CMFGetVersion):2,'.');
WriteLn(Lo(CMFGetVersion):2,' loaded');
{ Display interrupt number in use }
GotoXY(24,10);
Write ('System interrupt (IRQ) ');
WriteLn(CMFDriverIRQ:3,' in use');
GotoXY(35,15);
WriteLn('Song Status');
GotoXY(31,23);
WriteLn('Song name: ',SongName);
{ Load song file }
Check := CMFGetSongBuffer(SongBuffer,SongName);
IF NOT(Check) THEN TextNumError;
{ CMFSetTransposeOfs() controls transposition down or up of the loaded song
(positive values transpose up, negative values transpose down). The value
0 plays the loaded song in its original key. }
CMFSetTransposeOfs(0); { Experiment with this value }
{ Play song }
Check := CMFPlaySong(SongBuffer);
IF NOT(Check) THEN TextNumError;
{ During playback, display status byte }
REPEAT
GotoXY(41,17);Write(CMFStatusByte:3);
UNTIL (KeyPressed OR (CMFStatusByte = 0));
{ Stop playback if user presses a key }
IF KeyPressed THEN BEGIN
Check := CMFStopSong;
IF NOT(Check) THEN TextNumError;
END;
{ Re-initialize driver }
Check := CMFResetDriver;
IF NOT(Check) THEN TextNumError;
{ Free song file memory }
Check := CMFFreeSongBuffer(SongBuffer);
IF NOT(Check) THEN TextNumError;
END.
[Back to SOUND SWAG index] [Back to Main SWAG index] [Original]