{****************************************************************************}
{                                                                            }
{ MODULE:         ModCommands                                                }
{                                                                            }
{ DESCRIPTION:    This UNIT implements the routines that interpret the       }
{                 various commands that appear in a MOD file`s partiture     }
{                                                                            }
{                 It's independent of the number of channels used.           }
{                                                                            }
{                 Designed for use from within the PlayMod UNIT.             }
{                                                                            }
{ AUTHOR:         Juan Carlos Ar‚valo                                        }
{                                                                            }
{ MODIFICATIONS:  Nobody (yet... ;-)                                         }
{                                                                            }
{ HISTORY:        02-Jan-1993 Creation/definition. It used to be part of the }
{                             PlayMod UNIT, but it was getting too big.      }
{                                                                            }
{ (C) 1992 VangeliSTeam                                                      }
{____________________________________________________________________________}

UNIT ModCommands;

INTERFACE

USES SongUnit, SongElements;




{ Configuration. }

CONST
  MyLoopMod          : BOOLEAN       = FALSE;  { If TRUE, then the MOD will loop if it was so defined.  }
  PermitFilterChange : BOOLEAN       = FALSE;  { TRUE if the partiture is allowed to change the filter. }
  BPMDivider         : WORD          = 125;
  BPMIncrement       : WORD          = 125;
  BPMCount           : WORD          = 0;
  MyFirstPattern     : WORD          = 0;
  MyRepStart         : WORD          = 0;
  MySongLen          : WORD          = 0;
  FirstPattern       : WORD          = 0;
  RepStart           : WORD          = 0;
  SongLen            : WORD          = 0;




{ Values common to all the channels. }

CONST
  FilterIsOn         : BOOLEAN       = FALSE;  { Position of the filter (FALSE = OFF).                  }
  Tempo              : BYTE          = 6;      { Number of ticks in the current note.                   }




{ Values set from outside this UNIT, apart from this one. }

CONST
  NextNote           : WORD          = 1;      { Next note in the pattern.                              }
  NextSeq            : WORD          = 1;      { Next pattern index (for the next note).                }
                                               { They both must have been set BEFORE calling this UNIT. }

  TempoCt            : BYTE          = 0;      { Number of the actual tick. Not changed in this UNIT.   }




{ General definition of the state of a channel. }

TYPE                       { Channel state definition. }
  PCanal = ^TCanal;
  TCanal = RECORD
    Note       : TFullNote;      { Note being played in the channel.   }
    Instrument : PInstrumentRec; { Pointer to the instrument data.     }
    Volume     : BYTE;           { Actual volume (0 - 64).             }
    Period     : WORD;           { Actual Period.                      }
    RealPeriod : WORD;           { Actual adjusted Period.             }

    PeriodIncr,                  { Note portamento increment.          }
    PeriodDest : INTEGER;        { Note portamento destination.        }

    arpct      : BYTE;           { Arpeggio count.                     }
    arp0,                        { Arpeggio 1st Period.                }
    arp1,                        { Arpeggio 2nd Period.                }
    arp2       : WORD;           { Arpeggio 3rd Period.                }

    VibWave,                     { Vibrato wave form.                  }
    VibPos,                      { Vibrato position.                   }
    VibWidth,                    { Vibrato width (period).             }
    VibDepth   : BYTE;           { Vibrato depth (amplitude).          }
    VibReset   : BOOLEAN;        { Vibrato reset.                      }

    TPortaIncr : INTEGER;        { Tone portamento increment.          }
    VolumeIncr : SHORTINT;       { Volume increment.                   }
    TPortIsFine: BOOLEAN;
    VSldIsFine : BOOLEAN;
  END;




PROCEDURE DoTickCommand; 
PROCEDURE CommandStart  (VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);




IMPLEMENTATION

USES SongUtils, SoundDevices;



{----------------------------------------------------------------------------}
{ Rutinas de comandos, para el comienzo y cada Tick.                         }
{                                                                            }
{ En las del tick, se entra con SI apuntando al TCanal correspondiente.      }
{____________________________________________________________________________}


{ 0 xx }

PROCEDURE StartArpeggio(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  VAR
    f, g: INTEGER;
  BEGIN
    can.arpct := 0;

    f := n.Period;
    IF f = 0 THEN f := can.Note.Period;

    can.arp0 := f;
    g := WORD((n.Parameter SHR  4));
    can.arp1 := PeriodArray[(NoteIdx[f] AND $FF) + g];
    g := WORD((n.Parameter AND $F));
    can.arp2 := PeriodArray[(NoteIdx[f] AND $FF) + g];
  END;

PROCEDURE TickArpeggio;    ASSEMBLER;
  ASM

        INC     TCanal([SI]).arpct
        MOV     AL,TCanal([SI]).arpct
        DEC     AL
        JNZ     @@2
         MOV    AX,TCanal([SI]).arp0
         MOV    TCanal([SI]).Period,AX
        JMP    @@Fin
@@2:    DEC     AL
        JNZ     @@3
         MOV    AX,TCanal([SI]).arp1
         MOV    TCanal([SI]).Period,AX
        JMP    @@Fin
@@3:     MOV    AX,TCanal([SI]).arp2
         MOV    TCanal([SI]).Period,AX
         MOV    TCanal([SI]).arpct,0
@@Fin:
  END;


{ 1 xx , 2 xx }

PROCEDURE StartFinePortaUp  (VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote); FORWARD;

PROCEDURE StartFinePortaDown(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote); FORWARD;

PROCEDURE StartTPortUp(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter <> 0 THEN
      BEGIN
        can.TPortaIncr  := WORD(n.Parameter);
        can.TPortIsFine := FALSE;
      END
    ELSE IF can.TPortIsFine THEN
      BEGIN
        can.Note.Command := mcFinePortaUp;
        StartFinePortaUp(Song, can, n);
      END;
  END;

PROCEDURE StartTPortDown(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter <> 0 THEN
      BEGIN
        can.TPortaIncr  := WORD(n.Parameter);
        can.TPortIsFine := FALSE;
      END
    ELSE IF can.TPortIsFine THEN
      BEGIN
        can.Note.Command := mcFinePortaDn;
        StartFinePortaDown(Song, can, n);
      END;
  END;

PROCEDURE TickTPortUp; ASSEMBLER;
  ASM

        MOV     AX,TCanal([SI]).Note.Period
        SUB     AX,TCanal([SI]).TPortaIncr
        MOV     TCanal([SI]).Note.Period,AX
        MOV     TCanal([SI]).Period,AX

  END;


PROCEDURE TickTPortDown; ASSEMBLER;
  ASM

        MOV     AX,TCanal([SI]).Note.Period
        ADD     AX,TCanal([SI]).TPortaIncr
        MOV     TCanal([SI]).Note.Period,AX
        MOV     TCanal([SI]).Period,AX

  END;


{ 3 xy }

PROCEDURE StartNPortamento(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Period <> 0 THEN
      can.PeriodDest := n.Period
    ELSE IF can.PeriodDest = 0 THEN
      can.PeriodDest := can.Period;

    IF n.Parameter = 0 THEN BEGIN
      IF can.PeriodIncr > 0 THEN n.Parameter := BYTE( can.PeriodIncr)
                        ELSE n.Parameter := BYTE(-can.PeriodIncr);
    END;

    IF INTEGER(can.PeriodDest - can.Period) >= 0 THEN
      can.PeriodIncr :=  INTEGER(n.Parameter)
    ELSE
      can.PeriodIncr := -INTEGER(n.Parameter);

    can.Note.Period := can.Period;
  END;

PROCEDURE TickNPortamento; ASSEMBLER; 
  ASM

        MOV     AX,TCanal([SI]).PeriodDest
        AND     AX,AX
        JZ      @@Fin
        MOV     AX,TCanal([SI]).PeriodIncr
        AND     AX,AX
        JZ      @@Fin
        MOV     BX,TCanal([SI]).Note.Period
        ADD     BX,AX
        TEST    AH,80h
        JNZ      @@neg
         CMP    BX,TCanal([SI]).PeriodDest
         JA     @@pneg
        JMP     @@cnt
@@neg:   TEST   BH,80h
         JNZ    @@pneg
         CMP    BX,TCanal([SI]).PeriodDest
         JAE    @@cnt
@@pneg:  MOV    BX,TCanal([SI]).PeriodDest
@@cnt:  MOV     TCanal([SI]).Note.Period,BX
        MOV     TCanal([SI]).Period,BX
@@Fin:

  END;


{ 4 xy }

PROCEDURE StartVibrato(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  VAR
    f : WORD;
  BEGIN
    f := n.Parameter AND $F;
    IF f <> 0 THEN
      CASE can.VibWave OF
        0: can.VibWidth := f;
        1: can.VibWidth := f;
        2: can.VibWidth := (f*255) SHR 7;
      END;

    f := n.Parameter SHR  4;
    IF f <> 0 THEN can.VibDepth := f SHL 2;

    IF (n.Period <> 0) AND can.VibReset THEN
      can.VibPos := 0;
  END;

PROCEDURE TickVibrato;     ASSEMBLER; 
  CONST
    VibTabla : ARRAY[0..31] OF BYTE = (   { Sinus table for the vibrato. }
        0, 24, 49, 74, 97,120,141,161,
      180,197,212,224,235,244,250,253,
      255,253,250,244,235,224,212,197,
      180,161,141,120, 97, 74, 49, 24
    );
  ASM

        MOV     AH,TCanal([SI]).VibWave
        MOV     DH,TCanal([SI]).VibWidth
        MOV     DL,TCanal([SI]).VibPos
        MOV     AL,DL
        AND     AH,AH
        JNZ     @@nosn
         SHR    AL,2
         AND    AL,1Fh
         MOV    BX,OFFSET VibTabla
         XLAT
         JMP    @@set
@@nosn: DEC     AH
        JNZ     @@notr
         SHL    AL,1
         JNC    @@set
          NOT   AL
@@set:   MUL    DH
         SHL    AX,1
         MOV    AL,AH
         XOR    AH,AH
        JMP     @@calc
@@notr:  MOV    AL,DH
         XOR    AH,AH
@@calc: MOV     BX,TCanal([SI]).Note.Period
        TEST    DL,80h
        JZ      @@mas
         NEG    AX
@@mas:  ADD     BX,AX
        MOV     TCanal([SI]).Period,BX
        ADD     DL,TCanal([SI]).VibDepth
        MOV     TCanal([SI]).VibPos,DL

  END;


{ 5 xy }

PROCEDURE TickVolSlide; FORWARD;

PROCEDURE TickT_VSlide; ASSEMBLER;
  ASM

        CALL    TickNPortamento
        JMP     TickVolSlide

  END;


{ 6 xy }

PROCEDURE TickVib_VSlide; ASSEMBLER; 
  ASM

        CALL    TickVibrato
        JMP     TickVolSlide

  END;


{ 7 xy }

PROCEDURE TickTremolo;     ASSEMBLER; 
  ASM
  END;


{ 8 xx }

PROCEDURE TickNPI1;        ASSEMBLER; 
  ASM
  END;


{ 9 xx }

PROCEDURE TickSampleOffs;  ASSEMBLER; 
  ASM
  END;


{ A xy }

PROCEDURE StartVolSlide(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter <> 0 THEN
      BEGIN
        can.VSldIsFine := FALSE;
        IF n.Parameter > $F THEN can.VolumeIncr := n.Parameter SHR 4
                            ELSE can.VolumeIncr := -SHORTINT(n.Parameter AND $F);
      END
    ELSE
      BEGIN
        IF can.VSldIsFine THEN
          BEGIN
            can.VSldIsFine := FALSE;
            ASM
                MOV     SI,WORD PTR can
                CALL    TickVolSlide
            END;
            can.VSldIsFine := TRUE;
          END;
      END;

  END;

PROCEDURE TickVolSlide; ASSEMBLER;
  ASM

        MOV     AL,TCanal([SI]).VSldIsFine
        AND     AL,AL
        JNZ     @@Sale

        MOV     AL,TCanal([SI]).Volume
        MOV     AH,TCanal([SI]).VolumeIncr
        ADD     AL,AH
        CMP     AL,64
        JBE     @@Fin
         XOR    AL,AL
         TEST   AH,80h
         JNZ    @@Fin
          MOV   AL,64
@@Fin:  MOV     TCanal([SI]).Volume,AL
@@Sale:
  END;



{ B xx }

PROCEDURE StartJumpPattern(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    NextSeq := n.Parameter;
    IF NextSeq >= MySongLen THEN
      NextNote := $FFFF
    ELSE
      NextNote := 1;
  END;


{ C xx }

PROCEDURE StartSetVolume(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter > 64 THEN n.Parameter := 64;
    can.Volume := n.Parameter;
  END;


{ D xx }

PROCEDURE StartEndPattern(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF NextNote <> 1 THEN INC(NextSeq);
    IF NextSeq > MySongLen THEN
      NextNote := $FFFF
    ELSE BEGIN
      NextNote := (n.Parameter AND $0F) +
                  (n.Parameter SHR   4)*10 + 1;
    END;
    IF NextNote <> $FFFF THEN NextNote := 1;
  END;


{ F xx }

PROCEDURE StartSetTempo(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter  > $30 THEN
      BPMIncrement := n.Parameter
    ELSE
      IF n.Parameter <>  0  THEN Tempo := n.Parameter;
  END;


{ E 0x }

PROCEDURE StartSetFilter(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF PermitFilterChange THEN
      FilterIsOn := n.Parameter <> 0;
  END;

PROCEDURE TickSetFilter;   ASSEMBLER; 
  ASM
  END;


{ E 1x , E 2x }

PROCEDURE StartFinePortaUp(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter <> 0 THEN
      BEGIN
        can.TPortaIncr  := WORD(n.Parameter);
        can.TPortIsFine := TRUE;
      END
    ELSE IF NOT can.TPortIsFine THEN
      BEGIN
        can.Note.Command := mcTPortUp;
      END;

    IF can.TPortIsFine THEN
      ASM
        MOV       SI,WORD PTR [can]
        CALL      TickTPortUp
      END;
  END;

PROCEDURE StartFinePortaDown(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter <> 0 THEN
      BEGIN
        can.TPortaIncr  := WORD(n.Parameter);
        can.TPortIsFine := TRUE;
      END
    ELSE IF NOT can.TPortIsFine THEN
      BEGIN
        can.Note.Command := mcTPortDown;
      END;

    IF can.TPortIsFine THEN
      ASM
        MOV       SI,WORD PTR [can]
        CALL      TickTPortDown
      END;
  END;

PROCEDURE TickFPortUpDown; ASSEMBLER;
  ASM
  END;


{ E 3x }

PROCEDURE TickGlissCtrl;   ASSEMBLER; 
  ASM
  END;


{ E 4x }

PROCEDURE TickVibCtrl;     ASSEMBLER; 
  ASM
  END;


{ E 5x }

PROCEDURE TickFineTune;    ASSEMBLER; 
  ASM
  END;


{ E 6x }

PROCEDURE TickJumpLoop;    ASSEMBLER; 
  ASM
  END;


{ E 7x }

PROCEDURE TickTremCtrl;    ASSEMBLER; 
  ASM
  END;


{ E 8x }

PROCEDURE TickNPI2;        ASSEMBLER; 
  ASM
  END;


{ E 9x }

PROCEDURE TickRetrigNote;  ASSEMBLER; 
  ASM
  END;


{ E Ax y E Bx }

PROCEDURE StartVolFineUp(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter <> 0 THEN
      BEGIN
        can.VSldIsFine := TRUE;
        can.VolumeIncr := n.Parameter AND $F;
      END;

    IF can.VSldIsFine THEN
      BEGIN
        can.VSldIsFine := FALSE;
          ASM
                MOV     SI,WORD PTR can
                CALL    TickVolSlide
          END;
        can.VSldIsFine := TRUE;
      END;
  END;

PROCEDURE StartVolFineDown(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  BEGIN
    IF n.Parameter <> 0 THEN
      BEGIN
        can.VSldIsFine := TRUE;
        can.VolumeIncr := -SHORTINT(n.Parameter AND $F);
      END;

    IF can.VSldIsFine THEN
      BEGIN
        can.VSldIsFine := FALSE;
          ASM
                MOV     SI,WORD PTR can
                CALL    TickVolSlide
          END;
        can.VSldIsFine := TRUE;
      END;
  END;

PROCEDURE TickVolFineUpDn; ASSEMBLER; 
  ASM
                JMP     TickVolSlide
  END;


{ E Cx }

PROCEDURE TickNoteCut;     ASSEMBLER; 
  ASM
  END;


{ E Dx }

PROCEDURE TickNoteDelay;   ASSEMBLER; 
  ASM
  END;


{ E Ex }

PROCEDURE TickPattDelay;   ASSEMBLER; 
  ASM
  END;


{ E Fx }

PROCEDURE TickFunkIt;      ASSEMBLER; 
  ASM
  END;


{ 0 00 }

PROCEDURE TickNone;        ASSEMBLER; 
  ASM
  END;


PROCEDURE StartOktArp(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  VAR
    f, g: INTEGER;
  BEGIN
    can.arpct := 0;

    f := n.Period;
    IF f = 0 THEN f := can.Note.Period;

    can.arp1 := f;
    g := WORD((n.Parameter SHR  4));
    can.arp0 := PeriodArray[(NoteIdx[f] AND $FF) - g];
    g := WORD((n.Parameter AND $F));
    can.arp2 := PeriodArray[(NoteIdx[f] AND $FF) + g];
  END;



PROCEDURE TickArpeggio2;   ASSEMBLER;
  ASM

        INC     TCanal([SI]).arpct
        MOV     AL,TCanal([SI]).arpct
        DEC     AL
        JNZ     @@2
         MOV    AX,TCanal([SI]).arp1
         MOV    TCanal([SI]).Period,AX
        JMP    @@Fin
@@2:    DEC     AL
        JNZ     @@3
         MOV    AX,TCanal([SI]).arp2
         MOV    TCanal([SI]).Period,AX
        JMP    @@Fin
@@3:    DEC     AL
        JNZ     @@4
         MOV    AX,TCanal([SI]).arp1
         MOV    TCanal([SI]).Period,AX
        JMP    @@Fin
@@4:     MOV    AX,TCanal([SI]).arp0
         MOV    TCanal([SI]).Period,AX
         MOV    TCanal([SI]).arpct,0
@@Fin:
  END;





CONST
  TickCommOfs : ARRAY[mcNone..mcLast] OF WORD = (
    OFS(TickNone),       { 0 00 }

    OFS(TickArpeggio),   { 0 xx }
    OFS(TickTPortUp),    { 1 xx }
    OFS(TickTPortDown),  { 2 xx }
    OFS(TickNPortamento),{ 3 xy }
    OFS(TickVibrato),    { 4 xy }
    OFS(TickT_VSlide),   { 5 xy }
    OFS(TickVib_VSlide), { 6 xy }
    OFS(TickTremolo),    { 7 xy }
    OFS(TickNPI1),       { 8 xx }
    OFS(TickSampleOffs), { 9 xx }
    OFS(TickVolSlide),   { A xy }
    OFS(TickNone),       { B xx }
    OFS(TickNone),       { C xx }
    OFS(TickNone),       { D xx }
    OFS(TickNone),       { E xy }
    OFS(TickNone),       { F xx }

    OFS(TickSetFilter),  { E 0x }
    OFS(TickFPortUpDown),{ E 1x }
    OFS(TickFPortUpDown),{ E 2x }
    OFS(TickGlissCtrl),  { E 3x }
    OFS(TickVibCtrl),    { E 4x }
    OFS(TickFineTune),   { E 5x }
    OFS(TickJumpLoop),   { E 6x }
    OFS(TickTremCtrl),   { E 7x }
    OFS(TickNPI2),       { E 8x }
    OFS(TickRetrigNote), { E 9x }
    OFS(TickVolFineUpDn),{ E Ax }
    OFS(TickVolFineUpDn),{ E Bx }
    OFS(TickNoteCut),    { E Cx }
    OFS(TickNoteDelay),  { E Dx }
    OFS(TickPattDelay),  { E Ex }
    OFS(TickFunkIt),     { E Fx }

    OFS(TickArpeggio),   { Okt  }
    OFS(TickArpeggio2),  { Okt  }

    OFS(TickNone)        { 0 00 }

  );

PROCEDURE DoTickCommand; ASSEMBLER;
  ASM

                CMP     BX,mcLast*2
                JNC     @@1
                ADD     BX,OFFSET TickCommOfs
                CALL    WORD PTR [BX]
@@1:
  END;




PROCEDURE CommandStart(VAR Song: TSong; VAR can: TCanal; VAR n: TFullNote);
  CONST
    f   : INTEGER      = 0;
    g   : INTEGER      = 0;
  BEGIN

    IF (can.Note.Command    = mcArpeggio)  OR
       (((can.Note.Command  = mcVibrato) OR
         (can.Note.Command  = mcVib_VSlide)) AND
        ((       n.Command <> mcVibrato) AND
         (       n.Command <> mcVib_VSlide))) THEN can.Period := can.Note.Period;

    IF (n.Period <> 0) THEN
      can.Note.Period := n.Period;

    can.Note.Command   := n.Command;
    can.Note.Parameter := n.Parameter;

    CASE n.Command OF
      mcArpeggio:      StartArpeggio     (Song, can, n);
      mcTPortUp:       StartTPortUp      (Song, can, n);
      mcTPortDown:     StartTPortDown    (Song, can, n);
      mcNPortamento:   StartNPortamento  (Song, can, n);
      mcVibrato:       StartVibrato      (Song, can, n);
      mcJumpPattern:   StartJumpPattern  (Song, can, n);
      mcT_VSlide,
      mcVib_VSlide,
      mcVolSlide:      StartVolSlide     (Song, can, n);
      mcSetVolume:     StartSetVolume    (Song, can, n);
      mcEndPattern:    StartEndPattern   (Song, can, n);
      mcSetTempo:      StartSetTempo     (Song, can, n);
      mcSetFilter:     StartSetFilter    (Song, can, n);
      mcVolFineUp:     StartVolFineUp    (Song, can, n);
      mcVolFineDown:   StartVolFineDown  (Song, can, n);
      mcFinePortaUp:   StartFinePortaUp  (Song, can, n);
      mcFinePortaDn:   StartFinePortaDown(Song, can, n);
      mcOktArp:        StartOktArp       (Song, can, n);
      mcOktArp2:       StartOktArp       (Song, can, n);
    END;

    IF (n.Period <> 0) AND (n.Command <> mcNPortamento) THEN
      can.Period := n.Period;

  END;




END.
