UNIT StmLoader;

INTERFACE

USES Objects, SongUnit;




PROCEDURE LoadStmFileFormat(VAR Song: TSong; VAR St: TStream; VAR Header: TSongHeader);




IMPLEMENTATION

USES SongElements, SongUtils, Heaps, AsciiZ;




{----------------------------------------------------------------------------}
{ Internal definitions. Format of the files.                                 }
{____________________________________________________________________________}

TYPE
  TStmFileMagic = ARRAY[1..8] OF CHAR;

CONST
  MagicStm : TStmFileMagic = ( '!', 'S', 'c', 'r', 'e', 'a', 'm', '!' );

TYPE

  TStmInstrument =
    RECORD
      Name      : ARRAY[1..14] OF CHAR;
      fill1     : WORD;
      Size      : WORD;
      RepStart  : WORD;
      RepEnd    : WORD;
      Volume    : WORD;
      NAdj      : WORD;
      fill2     : ARRAY[1..6] OF BYTE;
    END;

  TStmHeader =
    RECORD
      Name        : ARRAY[1..20] OF CHAR;
      Magic       : TStmFileMagic;
      fill1       : LONGINT;
      Tempo       : BYTE;
      NPatterns   : BYTE;
      Volume      : BYTE;
      fill2       : ARRAY[1..13]  OF BYTE;
      Instruments : ARRAY[1..31]  OF TStmInstrument;
      Sequence    : ARRAY[1..128] OF BYTE;
    END;

  TStmPattern = ARRAY[1..64, 1..4] OF
    RECORD
      b1, b2,
      b3, b4 : BYTE;
    END;




PROCEDURE ProcessPatterns(VAR Song: TSong; VAR St: TStream; Num: WORD);
  VAR
    Patt      : TStmPattern;
    FullTrack : TFullTrack;
    Pattern   : PPattern;
    Track     : PTrack;
    c         : BYTE;
    i, j      : WORD;
    n, t      : WORD;
    Row       : WORD;
    Size      : WORD;
    NAdj      : WORD;
    Perd      : WORD;
    l         : LONGINT;
  BEGIN
    t := 1;
    FOR n := 1 TO Num DO
      BEGIN
{WriteLn('Patt ', n : 3, FullHeap.HMaxAvail : 10, FullHeap.HMemAvail : 10);}
        Pattern := Song.GetPattern(n);
        IF Pattern = NIL THEN
          BEGIN
            Song.Status := msOutOfMemory;
            EXIT;
          END;

        WITH Pattern^.Patt^ DO
          BEGIN
            NNotes := 64;
            NChans := Song.NumChannels;
            Tempo  := 0;
            BPM    := 0;
          END;

        St.Read(Patt, SizeOf(Patt));

        IF St.Status <> stOk THEN
          BEGIN
            Song.Status := msFileTooShort;
            EXIT;
          END;

        FOR j := 1 TO Song.NumChannels DO
          BEGIN
            FillChar(FullTrack, SizeOf(FullTrack), 0);

            FOR i := 1 TO 64 DO
              WITH FullTrack[i-1], Patt[i][j] DO
                BEGIN
                  FillChar(FullTrack[i-1], SizeOf(FullTrack[0]), 0);

                  IF b1 <> $FF THEN
                    BEGIN
                      Period := b1;
                      IF ((Period AND $F0) > $70) OR
                         ((Period AND $F0) < $00) OR
                         ((Period AND $0F) > $0B) THEN
                        Period := 0;
                      Instrument := b2 SHR 3;
                    END;

                  Volume := ((b3 AND $F0) SHR 1) + (b2 AND $07);

                  IF Volume > 64 THEN
                    Volume := 0
                  ELSE IF Volume < 64 THEN
                    INC(Volume);

                  Parameter := b4;
                  CASE b3 AND $F OF
                     0 : Command := mcNone;
                     1 : BEGIN
                           Command   := mcSetTempo;
                           Parameter := b4 SHR 4;
                         END;
                     2 : BEGIN
                           Command := mcJumpPattern;
                           INC(Parameter);
                         END;
                     3 : Command := mcEndPattern;
                     4 : Command := mcVolSlide;
                     5 : Command := mcTPortDown;
                     6 : Command := mcTPortUp;
                     7 : Command := mcNPortamento;
                     8 : Command := mcVibrato;
                    10 : Command := mcArpeggio;
                  ELSE
                    Command := TModCommand(ORD(mcLast) + (b3 AND $F));
                  END;

                  IF ((Command = mcEndPattern) OR (Command = mcJumpPattern)) AND
                     (Pattern^.Patt^.NNotes > i) THEN
                    Pattern^.Patt^.NNotes := i;

                  IF Period <> 0 THEN
                    BEGIN
{
                      IF (Song.GetInstrument(Instrument)        = NIL) OR
                         (Song.GetInstrument(Instrument)^.Instr = NIL) THEN
                        Dadj := NAdj
                      ELSE
                        DAdj := Song.GetInstrument(Instrument)^.Instr^.DAdj;
}
                      Perd := PeriodSet[(Period SHR 4), Period AND 15];
{
                      IF DAdj > $3E7 THEN
                        ASM
                          MOV     AX,Perd
                          MOV     BX,$20AB
                          MUL     BX
                          MOV     BX,DAdj
                          DIV     BX
                          MOV     Perd,AX
                        END;
}
                      Period := Perd;
                    END;
                END;

            Track := Song.GetTrack(t);
            IF Track = NIL THEN
              BEGIN
                Song.Status := msOutOfMemory;
                EXIT;
              END;

            Track^.SetFullTrack(FullTrack);

            Pattern^.Patt^.Channels[j] := t;

            INC(t);
          END;

      END;
  END;


PROCEDURE ProcessInstruments(VAR Song: TSong; VAR St: TStream; VAR Hdr: TStmHeader);
  VAR
    Instrument : TInstrumentRec;
    Instr      : PInstrument;
    i, w       : WORD;
    Signo      : LONGINT;
    NoSigno    : LONGINT;
  BEGIN
    FOR i := 1 TO 31 DO
      WITH Instrument DO
        BEGIN
          FillChar(Instrument, SizeOf(Instrument), 0);

          Instr := Song.GetInstrument(i);
          IF Instr = NIL THEN
            BEGIN
              Song.Status := msOutOfMemory;
              EXIT;
            END;

          Instr^.SetName(StrASCIIZ(Hdr.Instruments[i].Name, 14));

          Len  := Hdr.Instruments[i].Size;

          IF Len > 0 THEN
            BEGIN

              IF (Hdr.Instruments[i].RepStart <>     0) OR
                 (Hdr.Instruments[i].RepEnd   <> 65535) THEN
                BEGIN
                  Reps := Hdr.Instruments[i].RepStart;
                  Repl := Hdr.Instruments[i].RepEnd - Reps;
                END
              ELSE
                BEGIN
                  Reps := 0;
                  Repl := 0;
                END;

              Vol  := Hdr.Instruments[i].Volume;
              Dadj := Hdr.Instruments[i].Nadj;
              NAdj := $2100;

              IF Vol > $40 THEN
                Vol := $40;

              IF Repl        > Len THEN Repl := Len;
              IF Reps + Repl > Len THEN Repl := Len - Reps;

              Instr^.Change(@Instrument);
            END
          ELSE
            Instr^.Change(NIL);
        END;
  END;



PROCEDURE ProcessSamples(VAR Song: TSong; VAR St: TStream);
  VAR
    Instr      : PInstrument;
    i, w       : WORD;
  BEGIN
    FOR i := 1 TO 31 DO
      BEGIN
{WriteLn('Instr ', i : 3, FullHeap.HMaxAvail : 10, FullHeap.HMemAvail : 10);}
        Instr := Song.GetInstrument(i);

        IF (Instr^.Instr     <> NIL) AND
           (Instr^.Instr^.Len > 0)   THEN
          WITH Instr^.Instr^ DO
            BEGIN
              IF Len <= MaxSample THEN
                BEGIN
                  FullHeap.HGetMem(POINTER(Data), Len);
                  IF Data = NIL THEN BEGIN
                    Song.Status := msOutOfMemory;
                    EXIT;
                  END;

                  St.Read(Data^, Len);

                  IF St.Status <> stOk THEN BEGIN
                    Song.Status := msFileDamaged;
                    EXIT;
                  END;
{
                  FOR w := 0 TO Len - 1 DO
                    INC(Data^[w], 128);
}
                END
              ELSE
                BEGIN
                  FullHeap.HGetMem(POINTER(Data), MaxSample);
                  FullHeap.HGetMem(POINTER(Xtra), Len-MaxSample);

                  IF (Data = NIL) OR (Xtra = NIL) THEN BEGIN
                    Song.Status := msOutOfMemory;
                    EXIT;
                  END;

                  St.Read(Data^, MaxSample);
                  St.Read(Xtra^, Len-MaxSample);

                  IF St.Status <> 0 THEN BEGIN
                    Song.Status := msFileDamaged;
                    EXIT;
                  END;
                END;
            END;

        IF LowQuality THEN
          Instr^.Desample;

      END;
  END;

PROCEDURE LoadStmFileFormat(VAR Song: TSong; VAR St: TStream; VAR Header: TSongHeader);
  VAR
    Hdr        : TStmHeader ABSOLUTE Header;
    InitialPos : LONGINT;
    i          : WORD;
  BEGIN
    Song.FileFormat := mffStm;

    InitialPos := St.GetPos;

    St.Seek(InitialPos + SizeOf(TStmHeader));

    IF Hdr.Magic <> MagicStm THEN
      BEGIN
        Song.Status := msNotLoaded;
        EXIT;
      END;

    Song.Status := msOK;

    Song.Name := FullHeap.HNewStr(StrAsciiZ(Hdr.Name, 20));

    IF Hdr.Volume = 64 THEN
      Hdr.Volume := 63;

    Song.FirstTick    := TRUE;
    Song.InitialTempo := Hdr.Tempo SHR 4;
    Song.InitialBPM   := 125;
    Song.Volume       := Hdr.Volume SHL 2;
    Song.NumChannels  := 4;

    Song.SequenceLength := 0;
    FOR i := 1 TO 128 DO
      IF Hdr.Sequence[i] < 99 THEN
        Song.SequenceLength := i;

    Song.SequenceRepStart := 1;
    Move(Hdr.Sequence, Song.PatternSequence^, Song.SequenceLength);

    FOR i := 1 TO Song.SequenceLength DO
      INC(Song.PatternSequence^[i]);


    { Processing of the instruments }

    ProcessInstruments(Song, St, Hdr);
    IF Song.Status > msOk THEN EXIT;


    { Processing of the patterns (the partiture) }

    ProcessPatterns(Song, St, Hdr.NPatterns);
    IF Song.Status > msOk THEN EXIT;


    { Processing of the samples }

    ProcessSamples(Song, St);
    IF Song.Status > msFileTooShort THEN EXIT;
  END;




END.
