UNIT LoadMOD;
{ Amiga 'MOD' module loader for use with the UltraSound SDK.
This unit provides facilities to load and unload MOD files
from memory.
This file is copyright <C> 1993, by Kurt Kennett of Ingenuity Software.
Modification of it is a violation of copyright.
The MOD file format is public domain. Please see the enclosed file
MODFIL10.TXT for more information about the MOD file format and MOD
players.
}
INTERFACE
CONST
MaxTracks = 8; { *Maximum* # of Tracks that can be in a file }
TYPE
Note = RECORD
InstNum : BYTE; { Instrument Number }
NoteName : STRING[3]; { Name OF the Note }
Period : WORD; { Note Period (Amiga) }
Effect : BYTE; { Effect Number 0-15 }
EffArg : BYTE; { Effect Argument }
END;
Track = ARRAY[1..MaxTracks] OF Note; { A single Line of a pattern }
Pattern = ARRAY[0..63] OF Track;
PPattern = ^Pattern;
Sheet = RECORD { A single Pattern-Number, pointer-to-pattern pair }
Num : BYTE;
Patt : PPattern;
END;
Script = ARRAY[0..127] OF Sheet; { The Song, made up of pattern #s }
Instrument = RECORD
IName : STRING[22]; { Instrument's name }
ILen : LONGINT; { Length in bytes }
ITune : BYTE; { FineTune options }
IAmpl : WORD; { Aplitude (Amiga volume) }
IMode : BYTE; { GUS Play Mode BYTE }
IRepSt : LONGINT; { GUS Repeat start position }
IRepNd : LONGINT; { GUS Repeat END position }
IMemLoc : LONGINT; { GUS Sample location }
END;
MODFile = RECORD
Name : STRING[20];
Instr : ARRAY[0..30] OF Instrument;
SongLen : BYTE; { # OF valid sheets in the score }
Score : Script;
MChans : BYTE; { 4, 6, or 8 channels }
M31Inst : BOOLEAN; { file has 31 instruments? }
TagField : STRING[4];
EndJump : BYTE;
FHandle : File;
END;
PModFile = ^ModFile;
FUNCTION LoadModFile(VAR ModF : MODFile;
FileName : STRING) : BOOLEAN;
IMPLEMENTATION
USES
UltraDrv; { Ultrasound driver unit }
VAR
SampSizes : LONGINT; { Holder for calculation of Sample Sizes. }
FUNCTION DetermineMODType(VAR ModF : MODFile) : BOOLEAN;
CONST
NumTags = 8;
TagStrs : ARRAY[1..NumTags] OF STRING[4] = ('M.K.','FLT4','4CHN','M!K!',
'FLT8','8CHN','OCTA','6CHN');
VAR
Check : BYTE;
BEGIN
WITH ModF DO BEGIN
IF FileSize(FHandle) < 1084 THEN
EXIT; { File not long enough to be a MOD file }
Seek(FHandle,1080);
BlockRead(FHandle, TagField[1], 4);
IF IOResult <> 0 THEN EXIT;
TagField[0] := Chr(4);
Check := 1;
WHILE Check <= NumTags DO
IF TagField=TagStrs[Check] THEN
Inc(Check,100)
ELSE
Inc(Check);
{ Could put in more determination logic here }
IF Check < 100 THEN Check := 1 ELSE Dec(Check,100);
{ Default to 4 chans IF no valid tag found }
IF Check < 8 THEN
BEGIN
IF Check < 5 THEN
MChans := 4
ELSE
MChans := 8;
END
ELSE
MChans := 6;
M31Inst := TRUE;
{ Default. Could put more determination logic here }
Seek(FHandle,0); { Go to beginning OF file }
END;
END;
FUNCTION WFlip(W : WORD) : WORD;
BEGIN
WFlip := WORD(Lo(W)) SHL 8 + Hi(W);
END;
FUNCTION LoadSampleInfo(VAR ModF : MODFile) : BOOLEAN;
VAR
LCount,
MaxRead : BYTE;
AWLoad : WORD;
BEGIN
LoadSampleInfo := FALSE;
WITH ModF DO BEGIN
IF M31Inst THEN MaxRead := 31 ELSE MaxRead := 15;
FOR LCount := 0 to MaxRead-1 DO
WITH Instr[LCount] DO
BEGIN
{Instrument Name }
BlockRead(FHandle, IName[1], 22);
IF IOResult <> 0 THEN EXIT;
IName[0] := Chr(22);
IF Pos(#0,IName) <> 0 THEN
IName[0] := Chr(Pos(#0,IName)-1);
{ Instrument Length }
BlockRead(FHandle, AWLoad, 2); { Sample length stays the same }
IF IOResult <> 0 THEN EXIT;
ILen := WFlip(AWLoad)*2;
Inc(SampSizes,ILen);
{ Instrument FineTune }
BlockRead(FHandle, ITune, 1);
IF IOResult <> 0 THEN EXIT;
{ Instrument Max Volume 0-64 }
BlockRead(FHandle, IAmpl, 1);
IF IOResult <> 0 THEN EXIT;
{ Instrument Repeat Offset }
BlockRead(FHandle, AWLoad, 2);
IF IOResult <> 0 THEN EXIT;
IRepSt := WFlip(AWLoad)*2;
{ Instrument Repeat Length }
BlockRead(FHandle, AWLoad, 2);
IF IOResult <> 0 THEN EXIT;
IRepNd := WFlip(AWLoad)*2;
IF IRepNd < 3 THEN
BEGIN
IRepSt := 0;
IRepNd := 0;
IMode := 0;
END
ELSE
BEGIN
IF IRepSt > 1 THEN Dec(IRepSt,2);
Dec(IRepNd,2);
IMode := Loop_Voice;
END;
IMemLoc := -1;
END;
END;
LoadSampleInfo := TRUE;
END;
FUNCTION LoadSampleData(VAR ModF : MODFile) : BOOLEAN;
CONST
SizeLBlock = 32 * 1024; { 32k load buffer }
VAR
LCount,
MaxRead : BYTE;
LoadSize : LONGINT;
SizeDone : LONGINT;
BlockSize : WORD;
LBlock : Pointer;
HoldBWord : WORD;
BEGIN
LoadSampleData := FALSE;
GetMem(LBlock, SizeLBlock+2);
WITH ModF DO BEGIN
IF M31Inst THEN MaxRead := 31 ELSE MaxRead := 15;
FOR LCount := 0 to MaxRead-1 DO
WITH Instr[LCount] DO
IF ILen > 0 THEN
BEGIN
IF Not UltraMemAlloc(ILen,IMemLoc) THEN
BEGIN
IMemLoc := -1;
FreeMem(LBlock, SizeLBlock+2);
EXIT; { Out OF GUS RAM }
END;
LoadSize := ILen;
SizeDone := 2;
{ Get Rid OF junk bytes }
BlockRead(FHandle, LBlock^, 2);
IF IOResult <> 0 THEN
BEGIN
FreeMem(LBlock, SizeLBlock+2);
EXIT; { File Read Error }
END;
WHILE SizeDone < ILen DO
BEGIN
IF ((LoadSize-SizeDone) > SizeLBlock) THEN
BlockSize := SizeLBlock
ELSE
BlockSize := LoadSize-SizeDone;
BlockRead(FHandle, LBlock^, BlockSize);
IF IOResult <> 0 THEN
BEGIN
FreeMem(LBlock, SizeLBlock+2);
EXIT; { File Read error }
END;
IF Not UltraDownLoad(LBlock, 0, IMemLoc+SizeDone-2,
BlockSize, TRUE) THEN
BEGIN
FreeMem(LBlock, SizeLBlock+2);
EXIT; { Error Downloading sample fragment }
END;
Inc(SizeDone, BlockSize);
END;
IF IRepSt > 0 THEN
BEGIN
HoldBWord := UltraPeekData(Ultra_Config.Base_Port, IMemLoc+IRepSt);
IRepSt := IRepSt + IMemLoc;
IRepNd := IRepNd + IMemLoc;
END
ELSE
HoldBWord := 0;
HoldBWord := HoldBWord SHL 8;
UltraDownLoad(@HoldBWord, 0, IMemLoc+LoadSize-2,
2, TRUE);
END;
END;
FreeMem(LBlock, SizeLBlock+2);
LoadSampleData := TRUE;
END;
FUNCTION LoadModFile(VAR ModF : MODFile;
FileName : STRING) : BOOLEAN;
TYPE
TrackLoad = ARRAY[1..4] OF BYTE;
VAR
LineLoad : ARRAY[1..8] OF TrackLoad;
PatArray : ARRAY[1..128] OF BYTE;
PatSize : WORD;
Calc : LONGINT;
PCount : WORD;
TCount,
LCount : BYTE;
NewPat : PPattern;
PatUsed : BOOLEAN;
BEGIN
LoadMODFile := FALSE;
FillChar(MODF, SizeOf(ModF), 0);
IF Not Ultra_Installed THEN
EXIT; { Ultrasound not available }
WITH MODF DO BEGIN
Assign(FHandle, FileName);
Reset(FHandle,1);
IF IOResult <> 0 THEN
EXIT; { File (as given) does not exist }
IF Not DetermineMODType(MODF) THEN
BEGIN
Close(FHandle);
EXIT; { TYPE determination error }
END;
{ Song name }
BlockRead(FHandle, Name[1], 20);
IF IOResult <> 0 THEN
BEGIN
Close(FHandle);
EXIT; { File read error }
END;
Name[0] := Chr(20);
IF Pos(#0,Name) <> 0 THEN
Name[0] := Chr(Pos(#0,Name)-1);
{ Sample Information }
SampSizes := 0;
IF Not LoadSampleInfo(ModF) THEN
BEGIN
Close(FHandle);
EXIT; { File Read Error }
END;
{ Number OF Patterns in song }
BlockRead(FHandle, SongLen, 1);
IF IOResult <> 0 THEN
BEGIN
Close(FHandle);
EXIT; { File Read Error }
END;
{ END Jump Position }
BlockRead(FHandle, EndJump, 1);
IF IOResult <> 0 THEN
BEGIN
Close(FHandle);
EXIT; { File Read Error }
END;
{ Pattern Table }
BlockRead(FHandle, PatArray[1], 128);
IF IOResult <> 0 THEN
BEGIN
Close(FHandle);
EXIT; { File Read Error }
END;
FOR LCount := 1 to 128 DO
Score[LCount-1].Num := PatArray[LCount];
{ File Format Tag }
BlockRead(FHandle, TagField[1], 4);
IF IOResult <> 0 THEN
BEGIN
Close(FHandle);
EXIT; { File Read Error }
END;
TagField[0] := Chr(4);
{ Patterns : compute # and load }
PatSize := WORD(MChans) SHL 8;
Calc := ((FileSize(FHandle)-FilePos(FHandle))-SampSizes) div PatSize;
FOR PCount := 0 to Calc-1 DO
BEGIN
New(NewPat);
FOR LCount := 0 to 63 DO
BEGIN
BlockRead(FHandle, LineLoad[1], MChans*4);
IF IOResult <> 0 THEN
BEGIN
Close(FHandle);
EXIT;
END;
FOR TCount := 1 to MChans DO
BEGIN
NewPat^[LCount][TCount].InstNum :=
(LineLoad[TCount][1] and $F0) or
(((LineLoad[TCount][3] SHR 4)) AND $0F);
NewPat^[LCount][TCount].Period :=
(WORD(LineLoad[TCount][1] and $0F) SHL 8) or
LineLoad[TCount][2];
NewPat^[LCount][TCount].Effect :=
(LineLoad[TCount][3] and $0F);
NewPat^[LCount][TCount].EffArg := LineLoad[TCount][4];
END;
END;
PatUsed := FALSE;
FOR LCount := 0 to 127 DO
IF Score[LCount].Num=PCount THEN
BEGIN
Score[LCount].Patt := NewPat;
PatUsed := TRUE;
END;
IF Not PatUsed THEN
Dispose(NewPat);
END;
{ Sample Data }
IF Not LoadSampleData(ModF) THEN
BEGIN
Close(FHandle);
IF M31Inst THEN TCount := 31 ELSE TCount := 15;
FOR LCount := 1 to TCount DO
IF Instr[LCount].IMemLoc <> -1 THEN
UltraMemFree(Instr[LCount].IMemLoc, Instr[LCount].ILen);
EXIT; { Error loading sample data }
END;
Close(FHandle);
END;
LoadMODFile := TRUE;
END;
END.