Metropoli BBS
VIEWER: loadmod.pas MODE: TEXT (ASCII)
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.
[ RETURN TO DIRECTORY ]