' Program Name : MKJMKR.BAS
' Description  : A program that converts a song into a MKJamz file. (v1.1)
' Look at This : The MakeJamFile SUBroutine and main module code.
'                Also, the Instrument name list.

' Note : For best results, make sure that your song ends with an "X" wherever
'   in the song you want it to end to ensure compatibility with all
'         loaders/players and for much easier use in games.


' Written in 1996 by Molnar \ Kucalaba Productions


DECLARE SUB PlayMusic ()
DECLARE SUB SBInit ()
DECLARE SUB CloseChannels ()
DECLARE SUB MakeJamFile (File$)
DECLARE SUB LoadIns (FileName$)
DECLARE SUB SetIns (Channel%)
DECLARE SUB WriteReg (Reg%, value%)
DECLARE SUB Translate (x$, Channel%)
DECLARE FUNCTION SpaceLess$ (x$)
DECLARE FUNCTION Stringify$ (Array() AS INTEGER)

CONST BasePort% = &H220 ' Change this for other sound cards

CONST MaxChannel% = 3

CONST MaxNotes% = 400


TYPE InsType
 MMult AS INTEGER     ' Modulator's Multiple
 MLevel AS INTEGER    ' Modulator's Level
 MAttack AS INTEGER   ' Modulator's Attack
 MSustain AS INTEGER  ' Modulator's Sustain
 CMult AS INTEGER     ' Carrier's Multiple
 CLevel AS INTEGER    ' Carrier's Level
 CAttack AS INTEGER   ' Carrier's Attack
 CSustain AS INTEGER  ' Carrier's Sustain
END TYPE

TYPE ChannelType
 Defined AS INTEGER   ' Has the user defined the channel and it's notes? (1 if yes)
 Speed AS SINGLE      ' The duration of each note
 SongPtr AS INTEGER   ' A pointer to the current song position
 PStat AS SINGLE      ' Pause counter 2
 Octave AS INTEGER    ' The Octave of each channel
 WaveForm AS INTEGER  ' The Wave Form of each channel
 Flag AS INTEGER      ' Misc control flag
 Flag2 AS INTEGER     ' Number of times to play song
 IsPlaying AS INTEGER ' Is a note playing (1=yes)
 IsDone AS INTEGER    ' Has the note stopped? (1=yes)
END TYPE


DIM SHARED InstrumentList(9) AS STRING


DIM SHARED ChannelStat(1 TO MaxChannel%) AS ChannelType ' Allocate a buffer
                                  ' for holding the status of each channel.

DIM SHARED Songbuf(1 TO (MaxChannel% + 1) * MaxNotes%) AS INTEGER' Allocate a
                           ' buffer or holding nearly all the music data.

DIM SHARED CurrentIns AS InsType ' We use this buffer for loading and setting FM
                                 ' instrument types.

DIM SHARED SongBuffer AS STRING


CLS

' Theses are the (approximate) notes for "Stairway to Heaven"
Channel1$ = "T(.29)o2ao3c#eabecbo4c#o3eco4c#o3f#do2ao3f#ec#o2ao3t(.35)c#p(.12)t(.29)ec#o2agat(.4)ap(.4)"
CALL Translate(Channel1$, 1)

Channel2$ = "T(.29)p(18)o3abo4cego5ef#do4ao5eo4bao3abo5co4geo5cgo4bgo5gt(.13)gf#t(.28)f#t(.4)f#T(.29)p(18)o3abo4cego5ef#do4ao5eo4bao3abo5co4geo5cgo4bgo5gt(.13)gf#t(.28)f#t(.4)f# P(27.9)"
CALL Translate(Channel2$, 2)

Channel3$ = "T(.29) p(26.2) 02 ab 04 ceg o5 cf#f o4 a o5 f#ec o4 a o5 ee o4 a o2 ab o3 ceg o4 c o3 da o4 df#ga t(.5) a"
CALL Translate(Channel3$, 3)


SBInit

LoadIns "xphone.ins"

InstrumentList(1) = "xphone.ins"    ' Include this instrument for channel 1
                                    ' in the MKJ file.

SetIns 1

LoadIns "standard.ins"
InstrumentList(2) = "standard.ins"  ' Include this instrument for channel 1
SetIns 2                            ' in the MKJ file.

LoadIns "kewlbass.ins"
InstrumentList(3) = "kewlbass.ins"  ' Include this instrument for channel 1
SetIns 3                            ' in the MKJ file.


MakeJamFile "STAIRWAY.MKJ"          ' Pretty simple, huh?

PRINT "MK Jamz File has been successfully created."

DO

 PlayMusic

LOOP WHILE INKEY$ = ""

CloseChannels

SBInit

SYSTEM

SUB CloseChannels
IF MaxChannel% > 4 THEN FirstLoop% = 3 ELSE FirstLoop% = MaxChannel%
 FOR Channel% = 0 TO FirstLoop%
  WriteReg &HB0 + Channel%, 0
 NEXT
 IF FirstLoop% = MaxChannel% THEN EXIT SUB
 FOR Channel% = Channel% TO MaxChannel%
  WriteReg &HB0 + Channel% + 5, 0
 NEXT
END SUB

SUB LoadIns (FileName$)
OPEN FileName$ FOR BINARY AS #2
 IF LOF(2) < 2 THEN
  BEEP
  CLOSE
  KILL FileName$
  EXIT SUB
 END IF
 GET #2, , CurrentIns
CLOSE #2
END SUB

SUB MakeJamFile (File$)

OPEN File$ FOR BINARY AS #1
 IF LOF(1) < 2 THEN
   CLOSE
   KILL File$
 END IF
CLOSE #1

Id$ = "MKJamz"

StupidQB% = MaxChannel%
StupidQB2% = MaxNotes%
Version! = 1.1

OPEN File$ FOR BINARY AS #1
 PUT #1, , Id$
 PUT #1, , Version!
 PUT #1, , StupidQB%   ' # of Channels

  FOR x% = 1 TO MaxChannel%
   LoadIns InstrumentList(x%)
   PUT #1, , CurrentIns      ' Intrument data
  NEXT

 PUT #1, , StupidQB2%  ' Notes number
 
  FOR x% = 1 TO MaxChannel%
   PUT #1, , ChannelStat(x%).Defined  ' Channel definitions
  NEXT

  FOR x% = 1 TO UBOUND(Songbuf)
     PUT #1, , Songbuf(x%)        ' Song data
  NEXT

CLOSE #1
END SUB

SUB PlayMusic

FOR Channel% = 1 TO MaxChannel%

  IF ChannelStat(Channel%).Defined = 0 THEN GOTO NoGotosPlease

  RChan% = Channel% - 1

  IF ChannelStat(Channel%).IsDone = 1 THEN
   ChannelStat(Channel%).PStat = TIMER + ChannelStat(Channel%).Speed
   ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
   ChannelStat(Channel%).IsDone = 0
   ChannelStat(Channel%).IsPlaying = 0
  END IF

   IF ChannelStat(Channel%).IsPlaying = 0 AND Songbuf(ChannelStat(Channel%).SongPtr + Channel% - 1) <> 0 THEN
    ChannelStat(Channel%).PStat = TIMER + ChannelStat(Channel%).Speed
     SELECT CASE Songbuf(ChannelStat(Channel%).SongPtr + RChan%)
       CASE 68   ' "D"
         WriteReg &HA0 + RChan%, &H81
         WriteReg &HB0 + RChan%, &H21 + 4 * ChannelStat(Channel%).Octave
       CASE 69   ' "E"
         WriteReg &HA0 + RChan%, &HB0
         WriteReg &HB0 + RChan%, &H21 + 4 * ChannelStat(Channel%).Octave
       CASE 70   ' "F"
         WriteReg &HA0 + RChan%, &HCA
         WriteReg &HB0 + RChan%, &H21 + 4 * ChannelStat(Channel%).Octave
       CASE 71    ' "G"
         WriteReg &HA0 + RChan%, &H2
         WriteReg &HB0 + RChan%, &H22 + 4 * ChannelStat(Channel%).Octave
       CASE 65 ' "A"
         WriteReg &HA0 + RChan%, &H41
         WriteReg &HB0 + RChan%, &H22 + 4 * ChannelStat(Channel%).Octave
       CASE 66   ' "B"
         WriteReg &HA0 + RChan%, &H87
         WriteReg &HB0 + RChan%, &H22 + 4 * ChannelStat(Channel%).Octave
       CASE 67  '  "C"
         WriteReg &HA0 + RChan%, &HAE
         WriteReg &HB0 + RChan%, &H22 + 4 * ChannelStat(Channel%).Octave
       CASE 17 ' "C#"
         WriteReg &HA0 + RChan%, &H6B
         WriteReg &HB0 + RChan%, &H21 + 4 * ChannelStat(Channel%).Octave
       CASE 18 ' "D#"
         WriteReg &HA0 + RChan%, &H98
         WriteReg &HB0 + RChan%, &H21 + 4 * ChannelStat(Channel%).Octave
       CASE 20 ' F#"
         WriteReg &HA0 + RChan%, &HE5
         WriteReg &HB0 + RChan%, &H21 + 4 * ChannelStat(Channel%).Octave
       CASE 21 ' "G#"
         WriteReg &HA0 + RChan%, &H20
         WriteReg &HB0 + RChan%, &H22 + 4 * ChannelStat(Channel%).Octave
       CASE 15 ' "A#"
         WriteReg &HA0 + RChan%, &H63
         WriteReg &HB0 + RChan%, &H22 + 4 * ChannelStat(Channel%).Octave
       
       CASE 255
         ChannelStat(Channel%).IsPlaying = 1
         ChannelStat(Channel%).PStat = TIMER + Songbuf(ChannelStat(Channel%).SongPtr + Channel% - 1 + MaxChannel%) / 100
         ChannelStat(Channel%).Flag = -1
       CASE 254
         ChannelStat(Channel%).Octave = Songbuf(ChannelStat(Channel%).SongPtr + Channel% - 1 + MaxChannel%)
         ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
       CASE 253
         ChannelStat(Channel%).Speed = Songbuf(ChannelStat(Channel%).SongPtr + Channel% - 1 + MaxChannel%) / 100
         ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
       CASE 252
         ChannelStat(Channel%).WaveForm = Songbuf(ChannelStat(Channel%).SongPtr + Channel% - 1 + MaxChannel%) - 300
         ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
         IF RChan% > 2 THEN RRChan% = RChan% + 6 ELSE RRChan% = 0
         WriteReg &HE0 + RChan% + RRChan%, ChannelStat(Channel%).WaveForm
       CASE 251
         ChannelStat(1).Flag2 = -1
          FOR MaxChan% = 1 TO MaxChannel%
           ChannelStat(MaxChan%).SongPtr = 1
           ChannelStat(MaxChan%).IsPlaying = 0
           ChannelStat(MaxChan%).IsDone = 0
          NEXT
         CloseChannels
         EXIT SUB
     END SELECT
   END IF

    IF ChannelStat(Channel%).IsPlaying = 1 THEN
     IF TIMER >= ChannelStat(Channel%).PStat THEN
      WriteReg &HB0 + RChan%, 0
      ChannelStat(Channel%).IsDone = 1
       IF ChannelStat(Channel%).Flag = -1 THEN
        ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
        ChannelStat(Channel%).Flag = 0
       END IF
     END IF
    END IF
  
   IF ChannelStat(Channel%).SongPtr > MaxChannel% + 1 THEN
     IF Songbuf(ChannelStat(Channel%).SongPtr + RChan%) <> 0 AND Songbuf(ChannelStat(Channel%).SongPtr - MaxChannel% + RChan%) < 250 THEN
       IF ChannelStat(Channel%).IsPlaying = 0 THEN ChannelStat(Channel%).IsPlaying = 1
     END IF
   END IF

NoGotosPlease:
NEXT Channel%


FOR Channel% = 1 TO MaxChannel%

 IF ChannelStat(Channel%).IsPlaying = 0 THEN
  ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
  IF ChannelStat(Channel%).SongPtr >= UBOUND(Songbuf) / MaxChannel% THEN
   ChannelStat(Channel%).SongPtr = 1
  END IF
 END IF
NEXT Channel%

END SUB

SUB SBInit
   FOR z% = 1 TO &HF5
     CALL WriteReg(z%, 0)
   NEXT z%
END SUB

SUB SetIns (Channel%)
CarChan% = Channel% - 1

IF Channel% > 3 THEN CarChan% = CarChan% + 5
IF Channel% > 6 THEN CarChan% = CarChan% + 5


WriteReg &H20 + CarChan%, CurrentIns.CMult  'Plays carrier note at specified octave ch. 1
WriteReg &H23 + CarChan%, CurrentIns.MMult  'Plays modulator note at specified octave ch. 1
WriteReg &H40 + CarChan%, CurrentIns.CLevel   'Set carrier total level to softest ch. 1
WriteReg &H43 + CarChan%, CurrentIns.MLevel   'Set modulator level to loudest ch. 1
WriteReg &H60 + CarChan%, CurrentIns.CAttack  'Set carrier attack and decay ch. 1
WriteReg &H63 + CarChan%, CurrentIns.MAttack  'Set modulator attack and decay ch. 1
WriteReg &H80 + CarChan%, CurrentIns.CSustain 'Set carrier sustain and release ch. 1
WriteReg &H83 + CarChan%, CurrentIns.MSustain 'Set modulator sustain and release ch. 1

END SUB

FUNCTION SpaceLess$ (x$)
FOR C% = 1 TO LEN(x$)
 IF MID$(x$, C%, 1) <> " " THEN k$ = k$ + MID$(x$, C%, 1)
NEXT
SpaceLess$ = UCASE$(k$)
END FUNCTION

FUNCTION Stringify$ (Array() AS INTEGER)
' This function takes an integer array and converts it to a string.
' Although it isn't used, it still works and may be useful to someone.

DIM Byte AS STRING * 1

 FOR x% = 1 TO UBOUND(Array)
  
   DEF SEG = VARSEG(Array)
    
     IntVal% = PEEK(x%)
     Byte = STR$(IntVal%)
     Buffer$ = Buffer$ + Byte

 NEXT

DEF SEG

Stringify$ = Buffer$

END FUNCTION

SUB Translate (x$, Channel%)

ChannelStat(Channel%).SongPtr = 1   ' These values are all just
ChannelStat(Channel%).Octave = 4    ' back ups if the user never defines
ChannelStat(Channel%).Speed = .25   ' them.
ChannelStat(Channel%).WaveForm = 2
IF Channel% > 3 THEN RChan% = Channel% + 4
WriteReg &HE0 + RChan%, ChannelStat(Channel%).WaveForm

TempBuf! = 0       ' We'll use a temporary single precision value in parsing
TempBuffer% = 0    ' ...as well as a temporary integer.
Temp$ = ""         ' ...as well as a temporary string.

x$ = SpaceLess(x$) ' Remove all spaces from the string

FOR StringLen% = 1 TO LEN(x$)
  CurCommand$ = MID$(x$, StringLen%, 1)
 
    SELECT CASE CurCommand$

      CASE "T":  ' Duration command was issued
                Temp$ = ""
                IF MID$(x$, StringLen% + 1, 1) <> "(" THEN
                  PRINT "Invalid duration command issued :  No control parenthesis! (  )"
                  END
                END IF
                TempBuffer% = 2
                DO UNTIL MID$(x$, StringLen% + TempBuffer%, 1) = ")"
                 Temp$ = Temp$ + MID$(x$, StringLen% + TempBuffer%, 1)
                 TempBuffer% = TempBuffer% + 1
                LOOP
                StringLen% = StringLen% + TempBuffer%
                TempBuf! = VAL(Temp$)
                Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1) = 253
                Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1 + MaxChannel%) = TempBuf! * 100
                ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
     
       CASE "X": ' Exit song command was issued
                Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1) = 251
       CASE "W": ' Wave Form command was issued
                Temp$ = ""
                IF MID$(x$, StringLen% + 1, 1) <> "(" THEN
                  PRINT "Invalid wave form command issued :  No control parenthesis! (  )"
                  END
                END IF
                TempBuffer% = 2
                DO UNTIL MID$(x$, StringLen% + TempBuffer%, 1) = ")"
                 Temp$ = Temp$ + MID$(x$, StringLen% + TempBuffer%, 1)
                 TempBuffer% = TempBuffer% + 1
                LOOP
                StringLen% = StringLen% + TempBuffer%
                Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1) = 252
                Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1 + MaxChannel%) = VAL(Temp$) + 300
                ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%

       CASE "P": ' Pause command was issued
                Temp$ = ""
                IF MID$(x$, StringLen% + 1, 1) <> "(" THEN
                  PRINT "Invalid pause command issued :  No control parenthesis! (  )"
                  END
                END IF
                TempBuffer% = 2
                DO UNTIL MID$(x$, StringLen% + TempBuffer%, 1) = ")"
                 Temp$ = Temp$ + MID$(x$, StringLen% + TempBuffer%, 1)
                 TempBuffer% = TempBuffer% + 1
                LOOP
                StringLen% = StringLen% + TempBuffer%
                TempBuf! = VAL(Temp$)
                Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1) = 255
                Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1 + MaxChannel%) = TempBuf! * 100
                ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
       
         ' An octave command was issued
         CASE "O": Temp$ = MID$(x$, StringLen% + 1, 1)
                   Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1) = 254
                   Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1 + MaxChannel%) = VAL(Temp$)
                   StringLen% = StringLen% + 1
                   ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%

      ' A note command was issued
      CASE "C", "D", "E", "F", "G", "A", "B":
      IF MID$(x$, StringLen% + 1, 1) <> "#" THEN
       Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1) = ASC(CurCommand$)
      ELSE
       Songbuf(Channel% + ChannelStat(Channel%).SongPtr - 1) = ASC(CurCommand$) - 50
       StringLen% = StringLen% + 1
      END IF

    END SELECT

ChannelStat(Channel%).SongPtr = ChannelStat(Channel%).SongPtr + MaxChannel%
NEXT

ChannelStat(Channel%).Defined = 1


END SUB

DEFINT A-Z
SUB WriteReg (Reg%, value%)
OUT BasePort% + 8, Reg%
 FOR V% = 1 TO 6
  Buf% = INP(BasePort% + 8)
 NEXT
OUT BasePort% + 9, value%
 FOR V% = 1 TO 34
  Buf% = INP(BasePort% + 9)
 NEXT
END SUB

