Tracker.asm and tracker.mac

From Intellivision Wiki

Jump to: navigation, search


These are two files that work together to provide a "music tracker". tracker.mac provides the macros that allow to write music code as expected by the tracker, in a rather easily readable form. tracker.asm provides the actual tracker functions.

Contents

Functions Provided

tracker.mac

MacroAction performedNotes
DRUM t_per, n_per, tone, noise, volDefines drum data for one frame.A drum is fully defined by 8 frames.
NOTES n0, n1, n2, n3Defines 4 notes at onceThis helps to write pattern data in a more compact manner.
NOTE nDefines one noteExpected format for n: "NnO IVL". See comments in source file for details.

tracker.asm

Entry pointFunction provided
TRKINITGlobal tracker initialization.
TRKSNGINITInitialize a new song.
TRKSNGINIT.1Alternate entry point.
TRKPLAYTicks the tracker and updates the PSG.
TRKPATINIT(internal function)
TRKCHUPD(internal function)
TRKPSGUPD(internal function)



Source Code

tracker.mac

;* ======================================================================== *;
;*  This code is placed into the public domain by its author.               *;
;*  All copyright rights are hereby relinquished on the code and data in    *;
;*  this file.  -- Arnauld Chevallier, 2008                                 *;
;* ======================================================================== *;

;; ======================================================================== ;;
;;  DRUM        Drum definition                                             ;;
;; ======================================================================== ;;
MACRO DRUM(t_per, n_per, tone, noise, vol)
      DECLE %t_per%, %n_per%, $39 - %tone% - 8 * %noise%, %vol%
ENDM

;; ======================================================================== ;;
;;  NOTES       Defines 4 notes at once                                     ;;
;; ======================================================================== ;;
MACRO NOTES(n0, n1, n2, n3)
      ;
      LISTING "code"
      NOTE(%n0%)
      NOTE(%n1%)
      NOTE(%n2%)
      NOTE(%n3%)
      LISTING "prev"
ENDM

;; ======================================================================== ;;
;;  NOTE        Defines one note                                            ;;
;; ======================================================================== ;;
;;  Format:                                                                 ;;
;;    "NnO IVL"                                                             ;;
;;  where:                                                                  ;;
;;    N = Note (C, D, E, F, G, A, B)                                        ;;
;;    n = '-' or '#'                                                        ;;
;;    O = Octave (1 to 7)                                                   ;;
;;    I = new Instrument (1 to F, or 0 for unchanged)                       ;;
;;    V = Volume (0 to F)                                                   ;;
;;    L = Length (0 to F)                                                   ;;
;;                                                                          ;;
;;  The following strings can be used instead of the NnO parameters:        ;;
;;    "DRM" --> invokes a drum                                              ;;
;;    "NUL" --> no note                                                     ;;
;;                                                                          ;;
;;  Examples:                                                               ;;
;;    "C-3 1F1", "D#4 0F1", "DRM 1F1", "NUL 0F1"                            ;;
;; ======================================================================== ;;
MACRO NOTE(n)
      ;
      IF STRLEN(%n%) > 0
        ; instrument
_Hex2Dec(_i, ASC(%n%, 4))
        ; volume
_Hex2Dec(_v, ASC(%n%, 5))
        ; length
_Hex2Dec(_l, ASC(%n%, 6))
        ; "DRM" ?
        IF ASC(%n%, 0) = 68 AND ASC(%n%, 1) = 82 AND ASC(%n%, 2) = 77
      DECLE (84+_i) * $100 + _l * $10 + (15-_v)
        ELSE
          ; "NUL" ?
          IF ASC(%n%, 0) = 78 AND ASC(%n%, 1) = 85 AND ASC(%n%, 2) = 76
            IF _i <> 0
      DECLE  $8000 + _l * $10 + (15-_v)
      DECLE (_i - 1) * 3
            ELSE
      DECLE  _l * $10 + (15-_v)
            ENDI
          ELSE
            _InsertNote(%n%, _i, _v, _l)
          ENDI
        ENDI
      ENDI
ENDM

;; ======================================================================== ;;
;;  _InsertNote Inserts one note                                            ;;
;; ======================================================================== ;;
;;  This was put outside the "Note" macro for readability.                  ;;
;; ======================================================================== ;;
MACRO _InsertNote(n, i, v, l)
      ; note
_n0   SET   ASC(%n%, 0)
      IF (_n0 < 65) OR (_n0 > 71)
        ERR "Invalid note"
      ENDI
      ; conversion letter -> value
      ;          ABCDEFG
_n1   SET   ASC("JLACEFH", _n0-65) - 64
      ; sharp ?
      IF ASC(%n%, 1) = 35
_n1   SET   _n1 + 1
      ELSE
        ; minus ?
        IF ASC(%n%, 1) <> 45
          ERR "Invalid note"
        ENDI
      ENDI
      ; octave
_o    SET   ASC(%n%, 2)
      IF (_o < 49) OR (_o > 55)
        ERR "Invalid octave"
      ENDI
      ; value
      IF %i% <> 0
      DECLE $8000 + (_n1 + 12 * (_o - 49)) * $100 + %l% * $10 + (15-%v%)
      DECLE (%i% - 1) * 3
      ELSE
      DECLE (_n1 + 12 * (_o - 49)) * $100 + %l% * $10 + (15-%v%)
      ENDI
ENDM

;; ======================================================================== ;;
;;  _Hex2Dec    Hexa to decimal conversion                                  ;;
;; ======================================================================== ;;
MACRO _Hex2Dec(var, dec)
      ;
      IF (%dec% >= 48) AND (%dec% <= 57)
%var% SET   %dec% - 48
      ELSE
        IF (%dec% >= 65) AND (%dec% <= 70)
%var% SET   %dec% - 55
        ELSE
          ERR "Invalid hexa value"
        ENDI
      ENDI
ENDM

;; ======================================================================== ;;
;;  End of File:  tracker.mac                                               ;;
;; ======================================================================== ;;

tracker.asm

;* ======================================================================== *;
;*  These routines are placed into the public domain by their author.  All  *;
;*  copyright rights are hereby relinquished on the routines and data in    *;
;*  this file.  -- Arnauld Chevallier, 2008                                 *;
;* ======================================================================== *;

;; ======================================================================== ;;
;;  TRKINIT       Global tracker initialization                             ;;
;;  TRKSNGINIT    Song initialization                                       ;;
;;  TRKSNGINIT.1  Alternate entry point                                     ;;
;;  TRKPLAY       Ticks the tracker and updates the PSG                     ;;
;;                                                                          ;;
;;  TRKPATINIT    (internal) Pattern initialization                         ;;
;;  TRKCHUPD      (internal) Updates a channel                              ;;
;;  TRKPSGUPD     (internal) Updates the PSG                                ;;
;;                                                                          ;;
;;  AUTHOR                                                                  ;;
;;      Arnauld Chevallier <a_chevallier AT yahoo.com>                      ;;
;;                                                                          ;;
;;  REVISION HISTORY                                                        ;;
;;      07-Sep-2008 Initial Revision                                        ;;
;;                                                                          ;;
;;  INPUTS for TRKINIT                                                      ;;
;;      none                                                                ;;
;;                                                                          ;;
;;  INPUTS for TRKSONGINIT                                                  ;;
;;      R5    Pointer to invocation record, followed by return address      ;;
;;            Song base address            1 DECLE                          ;;
;;                                                                          ;;
;;  INPUTS for TRKSONGINIT.1                                                ;;
;;      R1    Song base address                                             ;;
;;                                                                          ;;
;;  INPUTS for TRKPLAY                                                      ;;
;;      none                                                                ;;
;;                                                                          ;;
;;  CODESIZE                                                                ;;
;;      524 words                                                           ;;
;; ======================================================================== ;;

;; ======================================================================== ;;
;;  VARIABLES IN SCRATCH RAM USED BY THESE ROUTINES                         ;;
;; ======================================================================== ;;
; TRKSCRACTH  ORG         $01DC ; (or anywhere else)
; G_FAD       RMB         1
; REF_M       RMB         1
; NOTE_A      RMB         1
; NOTE_B      RMB         1
; NOTE_C      RMB         1
; REF_A       RMB         1
; REF_B       RMB         1
; REF_C       RMB         1
; VOL_A       RMB         1
; VOL_B       RMB         1
; VOL_C       RMB         1
; INSTR_A     RMB         1
; INSTR_B     RMB         1
; INSTR_C     RMB         1
; COUNT_A     RMB         1
; COUNT_B     RMB         1
; COUNT_C     RMB         1
; COUNT_M     RMB         1
; COUNT_P     RMB         1
; PAT         RMB         1

;; ======================================================================== ;;
;;  VARIABLES IN SYSTEM RAM USED BY THESE ROUTINES                          ;;
;; ======================================================================== ;;
; TRKSYSTEM   ORG         $035B ; (or anywhere else)
; SONG        RMB         1
; INS_PTR     RMB         1
; POS_A       RMB         1
; POS_B       RMB         1
; POS_C       RMB         1

;; ======================================================================== ;;
;;  CONSTANTS USED BY THESE ROUTINES                                        ;;
;; ======================================================================== ;;
RF2POS      EQU     ((POS_A - REF_A)   AND $FFFF)
POS2NT      EQU     ((POS_A - NOTE_A)  AND $FFFF)
POS2IN      EQU     ((INSTR_A - POS_A) AND $FFFF)
IN2PER      EQU     (($01F0 - INSTR_A) AND $FFFF)
PER2CN      EQU     ((COUNT_A - $01F4) AND $FFFF)
V2V         EQU     (($01FB - VOL_A)   AND $FFFF)

;; ======================================================================== ;;
;;  TRKSNGINIT    Song initialization                                       ;;
;; ======================================================================== ;;
TRKSNGINIT  PROC

            MVI@    R5,     R1          ; read song address
@@1         MVO     R1,     SONG        ; save song address

            ADDI    #2,     R1          ; read pointer to instruments
            MVI@    R1,     R1
            MVO     R1,     INS_PTR     ; and save it

            CLRR    R0                  ; initialize variables
            MVO     R0,     REF_M
            MVO     R0,     COUNT_M
            MVO     R0,     PAT
            NOP
            MVO     R0,     REF_A
            MVO     R0,     REF_B
            MVO     R0,     REF_C

            COMR    R0
            MVO     R0,     COUNT_A
            MVO     R0,     COUNT_B
            MVO     R0,     COUNT_C

            MVII    #1,     R0
            MVO     R0,     G_FAD       ; default global volume fading

            ENDP

;; ======================================================================== ;;
;;  TRKPATINIT    Pattern initialization                                    ;;
;; ======================================================================== ;;
TRKPATINIT  PROC

            MVI     SONG,   R4
            INCR    R4
            MVI@    R4,     R2          ; R2 = address of 1st pattern
            INCR    R4
            MVI     PAT,    R0          ; R0 = position in patterns order table
            ADDR    R0,     R4

            MVI@    R4,     R1          ; R1 = pattern number
            TSTR    R1
            BPL     @@pat_ok            ; end of patterns ? ...

            CMPI    #$F000, R1          ; ... yes : stop replay ?
            BEQ     TRKINIT

@@restart   ADDR    R1,     R0          ; ... no : jump to restart position ...
            ADDR    R1,     R4
            DECR    R4
            MVI@    R4,     R1          ; ... and read again

@@pat_ok    INCR    R0                  ; increment position
            MVO     R0,     PAT         ; in patterns order table

            SLL     R1,     2           ; R4 = R1 * 4 + R2
            MOVR    R1,     R4          ; (beginning of pattern's details)
            ADDR    R2,     R4

            MVI@    R4,     R0          ; init. pattern counter
            MVO     R0,     COUNT_P

            MVI@    R4,     R0          ; init. position for each channel
            MVO     R0,     POS_A
            MVI@    R4,     R0
            MVO     R0,     POS_B
            MVI@    R4,     R0
            MVO     R0,     POS_C

            JR      R5
            ENDP

;; ======================================================================== ;;
;;  TRKINIT       Global tracker initialization                             ;;
;; ======================================================================== ;;
TRKINIT     PROC

            MVII    #$38,   R0          ; 'Enable Noise/Tone' register
            MVO     R0,     $01F8

            CLRR    R0

            MVO     R0,     SONG        ; no song

            MVII    #$01F0, R4          ; clear channels periods / low
            MVO@    R0,     R4          ; $01F0
            MVO@    R0,     R4          ; $01F1
            MVO@    R0,     R4          ; $01F2

            INCR    R4                  ; clear channels periods / hi
            MVO@    R0,     R4          ; $01F4
            MVO@    R0,     R4          ; $01F5
            MVO@    R0,     R4          ; $01F6

            MVII    #$01FB, R4          ; clear volumes
            MVO@    R0,     R4          ; $01FB
            MVO@    R0,     R4          ; $01FC
            MVO@    R0,     R4          ; $01FD

            JR      R5
            ENDP

;; ======================================================================== ;;
;;  TRKPLAY       Ticks the tracker and updates the PSG                     ;;
;; ======================================================================== ;;
TRKPLAY     PROC
            PSHR    R5

            MVI     SONG,   R4          ; R4 = song base address
            TSTR    R4                  ; is a song actually playing ?
            BEQ     @@done

            MVI     COUNT_M,R0          ; ... yes : increment global music counter
            INCR    R0
            MVO     R0,     COUNT_M

            MVI     REF_M,  R0          ; refresh notes ?
            DECR    R0
            BPL     @@notes_ok

            MVI@    R4,     R0          ; ... yes : read speed
            MVO     R0,     REF_M

            MVII    #REF_A, R3          ; refresh note for each channel
            CALL    TRKCHUPD
            MVII    #REF_B, R3
            CALL    TRKCHUPD
            MVII    #REF_C, R3
            CALL    TRKCHUPD

            MVI     COUNT_P,R0          ; decrement pattern counter
            DECR    R0
            MVO     R0,     COUNT_P
            BNEQ    @@upd_psg           ; jump to next pattern ?

            CALL    TRKPATINIT          ; ... yes
            B       @@upd_psg

@@notes_ok  MVO     R0,     REF_M

@@upd_psg   MVII    #NOTE_A,R3          ; update PSG for each channel
            CALL    TRKPSGUPD
            MVII    #NOTE_B,R3
            CALL    TRKPSGUPD
            MVII    #NOTE_C,R3
            CALL    TRKPSGUPD

@@done      PULR    PC
            ENDP

;; ======================================================================== ;;
;;  TRKCHUPD      Updates a channel                                         ;;
;; ======================================================================== ;;
TRKCHUPD    PROC
            PSHR    R5

            MVI@    R3,     R0          ; (R3 = REF_x)
            SUBI    #$10,   R0
            BMI     @@ch_new

            MVO@    R0,     R3
            PULR    PC

@@ch_new    ADDI    #RF2POS,R3          ; read pos
            MVI@    R3,     R4
            MVI@    R4,     R0          ; read data

            MOVR    R0,     R1          ; extra data to read ? ...
            BPL     @@data_ok

            MVI@    R4,     R2          ; ... yes : R2 = new instrument
            ADDI    #POS2IN,R3
            MVO@    R2,     R3          ; save it
            SUBI    #POS2IN,R3

@@data_ok   MVO@    R4,     R3          ; update pos

            SWAP    R0                  ; save note
            SUBI    #POS2NT,R3
            ANDI    #$7F,   R0
            BEQ     @@skip_sav

            MVO@    R0,     R3

@@skip_sav  ADDI    #3,     R3          ; new refresh value (R3 = REF_x)
            MVO@    R1,     R3

            ANDI    #$F,    R1          ; new volume
            ADDI    #3,     R3          ; (R3 = VOL_x)
            MVO@    R1,     R3

            TSTR    R0                  ; if note = 0,
            BEQ     @@ch_ok             ; don't reset counter

            ADDI    #6,     R3          ; (R3 = COUNT_x)
            CLRR    R0                  ; reset counter
            MVO@    R0,     R3

@@ch_ok     PULR    PC
            ENDP

;; ======================================================================== ;;
;;  TRKPSGUPD     Updates the PSG                                           ;;
;; ======================================================================== ;;
TRKPSGUPD   PROC

            MVI@    R3,     R1          ; read note

            ADDI    #12,    R3          ; (R3 = COUNT_x)
            MVI@    R3,     R2          ; read channel counter -> R2
            CMPI    #$FF,   R2          ; prevents loop after $FF
            BEQ     @@cnt_ok

            INCR    R2                  ; increment counter
            MVO@    R2,     R3
            DECR    R2

@@cnt_ok    CMPI    #85,    R1          ; drum ?
            BGE     @@drum

;; ------------------------------------------------------------------------ ;;
;;  Standard instrument                                                     ;;
;; ------------------------------------------------------------------------ ;;
            SUBI    #3,     R3          ; (R3 = INSTR_x)
            MVI@    R3,     R4          ; R4 = pointer to instrument
            ADD     INS_PTR,R4

            PSHR    R2                  ; apply pitch effect
            ANDI    #3,     R2
            ADD@    R4,     R2
            ADD@    R2,     R1

            ADDI    #@@nt-1,R1          ; read period from notes table
            MVI@    R1,     R0          ; R0 = period

            ADDI    #84,    R1          ; read vibrato amplitude for this note
            MVI@    R1,     R1          ; R1 = vibrato amplitude

            MVI@    R4,     R2          ; R2 = type of vibrato for this channel
            CMPI    #1,     R2
            BLT     @@vibr0             ; no vibrato ?
            BEQ     @@low_vibr          ; low vibrato ?

            CMPI    #2,     R2
            BGT     @@apply_vb          ; high vibrato --> amplitude unchanged

            SLR     R1                  ; medium vibrato --> 1/2 amplitude
            INCR    PC

@@low_vibr  SLR     R1,     2           ; low vibrato --> 1/4 amplitude

@@apply_vb  MVI     COUNT_M,R2          ; apply vibrato
            ANDI    #7,     R2          ; according to current step
            ADDI    #@@v_tb,R2          ; (i.e. COUNT_M % 8)
            MVI@    R2,     PC

@@v_tb      DECLE   @@vibr1, @@vibr2    ; vibrato processing index
            DECLE   @@vibr0, @@vibr3
            DECLE   @@vibr4, @@vibr3
            DECLE   @@vibr0, @@vibr2

@@vibr1     SUBR    R1,     R0          ; - amplitude
            B       @@vibr0

@@vibr2     SLR     R1                  ; - 1/2 amplitude
            SUBR    R1,     R0
            B       @@vibr0

@@vibr3     SLR     R1                  ; + 1/2 amplitude
            ADDR    R1,     R0
            INCR    PC

@@vibr4     ADDR    R1,     R0          ; + amplitude

@@vibr0     ADDI    #IN2PER,R3          ; write period
            MVO@    R0,     R3          ; (low)
            SWAP    R0
            ADDI    #4,     R3
            MVO@    R0,     R3          ; (high)
            ADDI    #PER2CN,R3

            PULR    R0                  ; R0 = channel counter / 2
            SLR     R0

@@env       MVI@    R4,     R4          ; R4 = pointer to envelope

            ADD@    R4,     PC          ; apply speed of envelope
            SLR     R0
            SLR     R0
            SLR     R0

            MOVR    R0,     R1          ; get volume from envelope
            SLR     R0,     2
            ADDR    R0,     R4
            MVI@    R4,     R0
            ANDI    #3,     R1
            ADDI    #@@e_tb,R1
            MVI@    R1,     PC

@@e_tb      DECLE   @@env0, @@env1
            DECLE   @@env2, @@env3

@@env1      SWAP    R0
            B       @@env3

@@env0      SWAP    R0

@@env2      SLR     R0,     2
            SLR     R0,     2

@@env3      ANDI    #$F,    R0

            SUBI    #6,     R3          ; (R3 = VOL_x)
            SUB@    R3,     R0          ; get volume from song
            SUB     G_FAD,  R0          ; apply global volume fading
            BPL     @@upd_vol

            CLRR    R0

@@upd_vol   ADDI    #V2V,   R3          ; apply new volume
            MVO@    R0,     R3

            JR      R5                  ; return

;; ------------------------------------------------------------------------ ;;
;;  Drum                                                                    ;;
;; ------------------------------------------------------------------------ ;;
;;  WARNING: drums are currently supported on channel A only                ;;
;; ------------------------------------------------------------------------ ;;
@@drum      CMPI    #8,     R2          ; end of drum ?
            BGE     @@end_drum

            SUBI    #85-3*4,R1          ; get pointer to drum data
            ADD     INS_PTR,R1
            MVI@    R1,     R4
            SLL     R2,     2
            ADDR    R2,     R4

            MVI@    R4,     R0          ; tone period
            MVO     R0,     $01F0
            SWAP    R0
            MVO     R0,     $01F4

            MVI@    R4,     R0          ; noise period
            MVO     R0,     $01F9

            MVI@    R4,     R0          ; enable tone / noise
            MVO     R0,     $01F8

            MVI@    R4,     R0          ; volume
            SUB     G_FAD,  R0
            BPL     @@drum_vol

@@end_drum  CLRR    R0

@@drum_vol  MVO     R0,     $01FB

            JR      R5

;; ======================================================================== ;;
;;  Periods of the 84 defined notes                                         ;;
;;  (from C-1 to B-7)                                                       ;;
;; ======================================================================== ;;
@@nt        DECLE   $0D5C, $0C9D, $0BE7, $0B3C, $0A9B, $0A02, $0973, $08EB
            DECLE   $086B, $07F2, $0780, $0714, $06AE, $064E, $05F4, $059E
            DECLE   $054D, $0501, $04B9, $0475, $0435, $03F9, $03C0, $038A
            DECLE   $0357, $0327, $02FA, $02CF, $02A7, $0281, $025D, $023B
            DECLE   $021B, $01FC, $01E0, $01C5, $01AC, $0194, $017D, $0168
            DECLE   $0153, $0140, $012E, $011D, $010D, $00FE, $00F0, $00E2
            DECLE   $00D6, $00CA, $00BE, $00B4, $00AA, $00A0, $0097, $008F
            DECLE   $0087, $007F, $0078, $0071, $006B, $0065, $005F, $005A
            DECLE   $0055, $0050, $004C, $0047, $0043, $0040, $003C, $0039
            DECLE   $0035, $0032, $0030, $002D, $002A, $0028, $0026, $0024
            DECLE   $0022, $0020, $001E, $001C

;; ======================================================================== ;;
;;  Vibrato amplitude for each note above                                   ;;
;;  (30% of an half-tone)                                                   ;;
;; ======================================================================== ;;
            DECLE   $003B, $0038, $0035, $0032, $002F, $002C, $002A, $0028
            DECLE   $0025, $0023, $0021, $001F, $001E, $001C, $001A, $0019
            DECLE   $0018, $0016, $0015, $0014, $0013, $0012, $0011, $0010
            DECLE   $000F, $000E, $000D, $000C, $000C, $000B, $000A, $000A
            DECLE   $0009, $0009, $0008, $0008, $0007, $0007, $0007, $0006
            DECLE   $0006, $0006, $0005, $0005, $0005, $0004, $0004, $0004
            DECLE   $0004, $0003, $0003, $0003, $0003, $0003, $0003, $0002
            DECLE   $0002, $0002, $0002, $0002, $0002, $0002, $0002, $0002
            DECLE   $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001
            DECLE   $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001
            DECLE   $0001, $0001, $0001, $0000

            ENDP

;; ======================================================================== ;;
;;  End of File:  tracker.asm                                               ;;
;; ======================================================================== ;;
Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox