[Back to SOUND SWAG index]  [Back to Main SWAG index]  [Original]

{
> Umm, I don't think you understood my question...  I don't know how
> to play 8 or 16 bit sound  (Like a mod file), I CAN do FM...
> I just want help on making the routines to play things like a
> mod...
> Gotta, go...  Could you please see if you could help me on this,
> though? Justin Greer

 Here is some Pascal source code to Read a (Standard) MOD 4 Tracks:

 READ it's not play, that is another story...

 Here is some example files that you sould see:
 ==============================================
 DEMOVT15.ZIP    MOD's 8 Borland C/ MS C/ (Real Mode/Protected Mode)
                 PAS/C/Assembly
 GOLDPALY.ZIP    MOD's 4 tracks Assembly/Pascal
 MDSS031A.ZIP    MOD's e S3M's  C/Pascal/Assembly
 PPS.ZIP         MOD's 4 tracks Assembly
 TNYPL212.ZIP    MOD's 8 tracks C/Assembly (Real Mode/Protected Mode)
 VTSRC12B.ZIP    MOD's 8 Pascal source of a player
}

Unit MODTool;
{* Reads information from a Soundtracker module. *}
INTERFACE
CONST
   MODToolVersion = 'v1.0';
   MaxIns = 31;
   Octaves : ARRAY[1..36] OF WORD =
      (856,808,762,720,678,640,604,570,538,508,480,453,
       428,404,381,360,339,320,302,285,269,254,240,226,
       214,202,190,180,170,160,151,143,135,127,120,113);
TYPE
   InstrumentType = RECORD
      SampName : ARRAY[0..21] OF CHAR;
      SampLen  : WORD;
      SampTune : BYTE;
      SampAmp  : BYTE;
      SampRepS : WORD;
      SampRepL : WORD;
      END;
   MODHeaderType = RECORD
      MODName  : ARRAY[0..19] OF CHAR;
      MODInstr : ARRAY[1..MaxIns] OF InstrumentType;
      MODLen   : BYTE;
      MODMisc  : BYTE;
      MODPattr : ARRAY[1..128] OF BYTE;
      MODSign  : ARRAY[1..4] OF CHAR;
      END;
   NoteType    = ARRAY[1..4] OF BYTE;
   PatternLine = RECORD
      Channel1, Channel2,
      Channel3, Channel4  : NoteType;
      END;
   PatternType = ARRAY[1..64] OF PatternLine;
PROCEDURE BuildModScript(ModFilename, ScriptFilename : STRING);
IMPLEMENTATION
FUNCTION ConvertString(Source : Pointer; Size : BYTE):String;
{* INPUT   : Pointer to an ARRAY OF CHAR, length in BYTES
 * OUTPUT  : Pascal string in Size bytes length
 * PURPOSE : Convertor, e.g., converts string ending in NULL to a Pascal
              string. This routine can convert any other memory range
              into a string. }
VAR
   WorkStr : String;
BEGIN
   Move(Source^,WorkStr[1],Size);
   WorkStr[0] := CHR(Size);
   ConvertString := WorkStr;
   END;
FUNCTION Words(FalseWord : WORD):WORD;
{* INPUT   : Word variable with rotated high/low-byte
 * OUTPUT  : Restored Word, multiplied by 2
 * PURPOSE : Gets a Word value from the MOD file and restores the proper
             high-byte/low-byte sequence; also multiplies the result by
             2, as this function must store the information as Word units
             to coincide with sample length. }
BEGIN
   Words := (Hi(FalseWord)+Lo(FalseWord)*256)*2;
   END;

FUNCTION NoteName(Period : WORD):String;
{* INPUT   : Note length value as WORD
 * OUTPUT  : Note in text as string
 * PURPOSE : Using tuning table, converts pitch values to note names. }
CONST
   NNames : ARRAY[0..11] OF String[2] =
      ('C-','C#','D-','D#','E-','F-','F#','G-','G#','A-','A#','B-');
VAR
   WorkStr : String;
   NCount  : BYTE;
BEGIN
   NCount := 1;
   IF (Period = 0) THEN BEGIN
      NCount := 37;
      NoteName := '----';
      END;
   WHILE (NCount <= 36) DO BEGIN
      IF (Period = Octaves[NCount]) THEN BEGIN
         Dec(Ncount);
         Str((NCount DIV 12)+1:2,WorkStr);
         NoteName := NNames[NCount-(NCount DIV 12)*12]+WorkStr;
         NCount := 37;
         END;
      Inc(NCount);
      END;
   END;
PROCEDURE BuildModScript(ModFilename, ScriptFilename : STRING);
{* INPUT   : Module name and name of desired script file
 * OUTPUT  : None
 * PURPOSE : Reads a SoundTracker module and writes the most important
             information to a text file.  The module _must_ contain 31
             instruments, or incorrect results will occur.
             Patterns are stored in sequence. }
VAR
   ModFile  : File;
   ScrFile  : TEXT;
   Result   : WORD;
   Header   : ModHeaderType;
   DummyStr : String;
   InsCount : BYTE;
   PatCount : BYTE;
   Pattern  : PatternType;
   HiPatt   : BYTE;
   Counter  : WORD;
BEGIN
{ Make sure that desired MOD file is available }
{$I-}
   Assign(ModFile,ModFilename);
   Reset(ModFile);
   Close(ModFile);
{$I+}
   IF (IOResult <> 0) THEN BEGIN
      Writeln('Cannot find MOD file: ',ModFilename:12,'. Sorry.');
      HALT(100);
      END;
{ Read MOD header data into Header variable }
   Reset(ModFile,1);
   BlockRead(ModFile,Header,SizeOf(Header),Result);
{ Analyze data in Header }
   WITH Header DO BEGIN
      Assign(ScrFile,ScriptFilename);
      ReWrite(ScrFile);
      WriteLn(ScrFile,'Soundtracker module script file ',ModFilename);
      WriteLn(ScrFile);
{ Write module name }
      DummyStr := ConvertString(Addr(MODName),SizeOf(MODName));
      WriteLn(ScrFile,'Module name  : ',DummyStr);
{ Write module length (valid numbers = 1 - 128) }
      Str(MODLen,DummyStr);
      Write  (ScrFile,'Module length  : ',DummyStr, ' Pattern(s),');
{ Search for highest pattern number }
      HiPatt := 0;
      FOR PatCount := 1 TO MODLen DO
         IF ModPattr[PatCount] >= HiPatt THEN
            HiPatt := ModPattr[PatCount];
      Str(HiPatt,DummyStr);
      WriteLn(ScrFile,' - highest pattern number is ',DummyStr);
      WriteLn(ScrFile);
{ Write instrument information }
      FOR InsCount := 1 TO MaxIns DO BEGIN
         WITH MODInstr[InsCount] DO BEGIN
            DummyStr := ConvertString(Addr(SampName),SizeOf(SampName));
            WriteLn(ScrFile,'Instrument # ',InsCount:2,' = ',DummyStr);
            Str(Words(SampLen):6,DummyStr);
            WriteLn(ScrFile,'Length in bytes = ',DummyStr);
            Str(SampTune:6,DummyStr);
            WriteLn(ScrFile,'Fine tune       = ',DummyStr);
            Str(SampAmp:6,DummyStr);
            WriteLn(ScrFile,'Volume          = ',DummyStr);
            Str(Words(SampRepS):6,DummyStr);
            WriteLn(ScrFile,'Repeat start    = ',DummyStr);
            Str(Words(SampRepL):6,DummyStr);
            WriteLn(ScrFile,'Repeat length   = ',DummyStr);
            WriteLn(ScrFile,'----------------------------------------');
            END;
      END;
{ Read patterns and write note values into script file }
{ (parentheses following pitch name contain sample number of note) }
      FOR PatCount := 1 TO HiPatt+1 DO BEGIN
         IF NOT(EOF(ModFile)) THEN
            Blockread(ModFile,Pattern,SizeOf(Pattern),Result);
         WriteLn('Read pattern ',PatCount-1:3);
         WriteLn(ScrFile,'Pattern number : ',PatCount-1:3);
         WriteLn(ScrFile,'Lines #   Chan.1     Chan.2     Chan.3     Chan.4');
         FOR Counter := 1 TO 64 DO BEGIN
            Write(ScrFile,'  ',Counter:2,'    ');
            WITH Pattern[Counter] DO BEGIN
{ Display note and sample number for channel 1 }
               DummyStr := NoteName((Channel1[1] AND $0F)*256+(Channel1[2]));
               Write(ScrFile,' ',DummyStr);
               Write(ScrFile,'(',((Channel1[1] AND $F0)+
                                  (Channel1[3] SHR 4)):2,')  ');
{ Display note and sample number for channel 2 }
               DummyStr := NoteName((Channel2[1] AND $0F)*256+(Channel2[2]));
               Write(ScrFile,' ',DummyStr);
               Write(ScrFile,'(',((Channel2[1] AND $F0)+
                                  (Channel2[3] SHR 4)):2,')  ');
{ Display note and sample number for channel 3 }
               DummyStr := NoteName((Channel3[1] AND $0F)*256+(Channel3[2]));
               Write(ScrFile,' ',DummyStr);
               Write(ScrFile,'(',((Channel3[1] AND $F0)+
                                  (Channel3[3] SHR 4)):2,')  ');
{ Display note and sample number for channel 4 }
               DummyStr := NoteName((Channel4[1] AND $0F)*256+(Channel4[2]));
               Write(ScrFile,' ',DummyStr);
               Write(ScrFile,'(',((Channel4[1] AND $F0)+
                                  (Channel4[3] SHR 4)):2,')  ');
               WriteLn(ScrFile);
               END;
            END;
         WriteLn(ScrFile,'-------------------------------------------------');
         WriteLn(ScrFile);
         END;
{ Close script file and MOD file }
      Close(ScrFile);
      CLose(ModFile);
      END;
   END;
END.

{

MAIN FILE to test MODTOOL.PAS
-------------------------------- MODSCRIP.MOD ------------------------------
}
Program MODScript;
{* Demo program for MODTOOL unit: Creating a script file *}
Uses MODTool; { Use MODTool unit }
VAR
   WorkStr : String; { String for processing provided filename }
BEGIN
   WriteLn('MODScript v1.0 (C) 1992 Abacus -  Author: Axel Stolz');
{ No command line parameter given? display syntax }
   IF (ParamCount = 0) THEN BEGIN
      WriteLn('Syntax : MODSCRIP module[.MOD]');
      HALT(0);
      END;
{ Pass command line paramters }
   WorkStr := ParamStr(1);
{ If user enters filename and extension, replace extension with ".MOD"}
   IF Pos('.',WorkStr) > 0 THEN
      WorkStr := Copy(WorkStr,1,Pos('.',WorkStr)-1);
   WriteLn('Create a script file from a sound module ',WorkStr,'.MOD !');
{ Create script file }
   BuildModScript(WorkStr+'.MOD',WorkStr+'.TXT');
   WriteLn('Ready. Result stored in ',WorkStr,'.TXT.');
END.

[Back to SOUND SWAG index]  [Back to Main SWAG index]  [Original]