*-------------------------------
*
* playerd
*
* Like playerc2, version D reads a variable number of
* instructions ahead.
*
* Player D features nested REP/ENDREP loops, and two
* new instructions: SYNC and WAIT.  WAIT will simply
* stop reading data until a SYNC occurs from another
* voice -- macros are still active, though.
*
* More new instructions: CYC, which cycles the test bit,
*   and SLUR and SLOFF -- in SLUR mode, macros are not
*   restarted on new notes.
*
* STOP should work fine now, i.e. process any preceding
* instructions.
*
         ORG $0FE0

         DA MACBEGIN
         DA FIELDEND
         DS ^

*
* Constants
*
SID      = $D400
NUMINST  = 14             ;14 Instruments total (arbitrary)

* Player directives
* See PROCDATA

HOLD     = $F0            ;Special note (hold current note)

*
* My little kernal jump table
*
KERNAL   
* JMP MYINIT ;Be sure to comment this out :)
         JMP MAINPLAY
         JMP INIT
         JMP SETIRQ
         JMP LOADINST     ;Load instrument
         JMP GETP         ;Get current voice pointer
         JMP SETP         ;Set current voice pointer
         JMP V1GETP       ;Get voice 1 pointer
         JMP V2GETP
         JMP V3GETP
         JMP V1SETP       ;Set voice 1 pointer
         JMP V2SETP
         JMP V3SETP
RETURN                    ;This is what macros call to exit.
         PLA              ;Get the exit address
         STA MACLO,Y
         PLA
         STA MACHI,Y
         LDX CURFIELD     ;Restore .X
         PLA              ;Active macros/set Z flag
         RTS              ;That's it!


         TXT 'player d slj'

:SNEAKY  DS KERNAL+61-:SNEAKY ;Fill up to $1000 + 61

V1LOOK   DFB 02           ;How far to look ahead
V2LOOK   DFB 02           ;instruction-wise
V3LOOK   DFB 02

         ERR *-1/$1040    ;Break if past $1000 + 64

*
* Variables, tables, etc.
*

*
* These are the variables and such that must stay fixed
* so that the compiler and editor don't screw everything
* up.
*

FREQLO   DS 192           ;Low byte, frequencies
* $1100
FREQHI   DS 192           ;High byte freq, page boundary

MARKERS  DS 64            ;Table of 32 marker locations
* $1200
MACADR   DS 48            ;Macro starting addresses
                          ;stored seqentially, i.e.
                          ;lo hi lo hi ... thus may
                          ;be jumped to via JMP()
                          ;MUST start on page boundary
GLOBVAR  DS 16            ;Global variables
SHADOW   DS 25            ;Shadow SID
DURTAB   DS 24            ;Table of initial durations
INSTAB                    ;Instrument table
         DS 14*9          ;126 bytes
TEMPY    DS 1             ;Temporary storage
LOCOFF   DS 1             ;(local) ariable offset
                          ;8*current voice
COFFSET  DS 1             ;SID offset (0 7 14) of current voice
CURVOICE DS 1             ;Current voice
CURFIELD DS 1             ;Current field
LASTNOTE DS 3             ;Last note read in for voice

V1FIELD  DS 1             ;Voice 1 field
V2FIELD  DS 1             ;Voice 2 field
V3FIELD  DS 1             ;field associated with Voice 3
DURATION DS 6             ;Duration for current field
* $1300
TIMERUPT DA $42C6         ;Interrupt timer setting (1/60th sec.)
STOPME   DS 3             ;Stop this voice
CURINST  DS 6             ;Current instrument for this field
FIELDS                    ;Pointers to start of fields.
         DA FIELD1
         DA FIELD2
         DA FIELD3
         DS 6
MACBYTE1 DS 7             ;Active macros, lowest byte
MACBYTE2 DS 7             ;1 for each field
MACBYTE3 DS 7             ;MUST be sequential!
LOCALVAR DS 32            ;3+1 sets of 8 local variables

* Missing things:
TIMES24                   ;x*24 -- macro addr access
         DFB 0,24,48,72

MACLO    
         DS 24            ;Global macro entry points
                          ;Treated as field #0
         DS 72            ;Macro entry/exit points low byte
                          ;24 macros * 3 fields = 72
MACHI    
         DS 24            ;Global entry/exit
         DS 72            ;Macro entry/exit addresses

FOFFSET  DS 3             ;Offset into fields

REPLO    DS 3             ;Repeats, lo address
REPHI    DS 3             ;Hi address
REPTIMES DS 3             ;Number of times to repeat
REPFLAG  DS 3             ;If currently in a repeat loop
                          ;can push stuff on stack

BITP                      ;Table of 2^(x mod 8)
         DFB 1,2,4,8,16,32,64,128
         DFB 1,2,4,8,16,32,64,128
         DFB 1,2,4,8,16,32,64,128
DIV8T7                    ;x/8 * 7 (for MACBYTE indexing)
         DFB 0,0,0,0,0,0,0,0
         DFB 7,7,7,7,7,7,7,7
         DFB 14,14,14,14,14,14,14,14

COUNT    DFB 00           ;Temporary counter (could use
                          ;TEMPX etc. instead)

*-- Variables for playerb.
*-- Flags and shadow variables

NEWVOL   DFB $FF          ;Volume flag
NEWINST  DFB $FF,$FF,$FF  ;New instrument flags
NEWGATE  DFB 0,0,0        ;Gate flags
NEWCYC   DFB 0,0,0        ;Cycle test bit
SHADMAC1 DS 7             ;MACBYTE1/2/3 shadow vars
SHADMAC2 DS 7
SHADMAC3 DS 7

SLURFLAG DFB 0,0,0        ;Slur mode on
SYNCFLAG DFB 00           ;Sync activated
SYNCVAL  DFB 00           ;Sync value
V1WAIT   DFB 00           ;Voice 1 wait for sync
V2WAIT   DFB 00
V3WAIT   DFB 00

*
* Code!
*

SETIRQ   SEI              ;Otherwise, set up simple IRQ player
         JSR INIT
         LDA #<IRQ
         STA $0314
         LDA #>IRQ
         STA $0315
         LDA TIMERUPT
         STA $DC04
         LDA TIMERUPT+1
         STA $DC05
         CLI
         RTS
IRQ                       ;Simple IRQ player.
         JSR MAINPLAY
IRQBLAH  JMP $EA31

*
* (Merlin) Macros
*

*
* The macro caller.  There are two entry points,
* INITMAC and CALLMAC.  INITMAC will enter a macro
* at the beginning.  CALLMAC will enter a macro
* where it last left off.
*
* On entry:
*   C is set!
*   .X is assumed to contain CURFIELD
*
* In all cases A and Y are preserved.  The last line is
* PLA, thus the zero flag is set appropriately on exit.
*
* Both expect .Y to contain the macro number +
*   24*CURFIELD (i.e. to be the correct offset into the
*   address table)
*
* These routines exit through RETURN, above, which
* restores X=CURFIELD and sets Z appropriately.
*

INITMAC  
         PHA
         TYA
         SBC TIMES24,X    ;We just want the macro number
         ASL              ;Double it...
         STA :ADDR+1      ;to get offset into address table
:ADDR    JMP (MACADR)

CALLMAC  
         PHA
         LDA MACHI,Y      ;MACLO etc. contain last exit
         PHA              ;locations.
         LDA MACLO,Y
         PHA
         RTS              ;Neat, huh?

*
* The (music) macro handler.  Shift bits until an active
* macro is found, and go from there!  If a macro active byte
* is zero, then it may be skipped completely.
*
*    CURFIELD -- Current voice (0 1 2 or 3)
*          (0 = global macros)
*    The correct index into macro address tables is
*          computed as 24*CURFIELD
*    X is set to the current field
*
* On exit, Z is zet.
*

X24      DFB $FF,23,47,71 ;24*X-1 (-1 for INY)
X24P8    DFB 7,31,55,79   ;plus 8
X24P16   DFB 15,39,63,87  ;plus 16

NEWMAC   
         LDX CURFIELD
         LDA MACBYTE1,X   ;Active macros -- 3 bytes
         LDY X24,X        ;Y is macro index

:L1      INY
         LSR              ;Check
         BCC :CONT1       ;Carry set -> call macro
:CALL1   JSR INITMAC      ;Enters macros at initial point
:CONT1   BNE :L1          ;Last line of INITMAC is PLA

         LDY X24P8,X      ;Advance to next macro chunk
                          ;(the INY will advance Y to Y+8)
         LDA MACBYTE2,X
:L2      INY
         LSR
         BCC :CONT2
         JSR INITMAC
:CONT2   BNE :L2

         LDY X24P16,X
         LDA MACBYTE3,X
:L3      INY
         LSR
         BCC :CONT3
         JSR INITMAC
:CONT3   BNE :L3
         RTS              ;That's it!


OLDMAC                    ;Identical to above, with CALLMAC
         LDX CURFIELD
         LDA MACBYTE1,X   ;Active macros -- 3 bytes
         LDY X24,X        ;Y is macro index

:L1      INY
         LSR              ;Check
         BCC :CONT1       ;Carry set -> call macro
:CALL1   JSR CALLMAC      ;Enters macros at initial point
:CONT1   BNE :L1          ;Last line of INITMAC is PLA

         LDY X24P8,X      ;Advance to next macro chunk
                          ;(the INY will advance Y to Y+8)
         LDA MACBYTE2,X
:L2      INY
         LSR
         BCC :CONT2
         JSR CALLMAC
:CONT2   BNE :L2

         LDY X24P16,X
         LDA MACBYTE3,X
:L3      INY
         LSR
         BCC :CONT3
         JSR CALLMAC
:CONT3   BNE :L3
         RTS              ;That's it!


* Subroutine to read in new data from the current field.
* The field pointers are stored inside the routine,
* so each voice stores its own pointers.  To switch
* fields within a voice, these pointers must be
* swapped out and the new pointers swapped in.
*
* Parameters:
*   ]1 -- Current offset into the field data.
*   ]2 -- Current voice # (index for current note/dur)
*         (1 2 or 3)
*   ]3 -- SID offset for current voice (0 7 or 14)
*
* Due to space considerations (i.e. the lite version
* probably shouldn't take up 3k :), this is now
* a genuine procedure.  As a result, the two
* variables ]2 and ]3 must be set up elsewhere,
* in curfield and coffset.  Also required is
* LOCOFF, which tells the current offset into
* the local variables.

* Player instructions

SETVOL   = $80            ;Set volume
ACTGLOB  = $81            ;Activate global macro
DACTGLOB = $82            ;Deactivate global macro
ACTLOC   = $83            ;Activate local macro
DACTLOC  = $84            ;Deactivate local macro
LINST    = $85            ;Load instrument
REPEAT   = $86            ;Repeat loop begin
ENDREP   = $87            ;Go to last REPEAT and repeat!
STOP     = $88            ;Halts the field at that point
GATEON   = $89            ;Turn gate bit on
GATEOFF  = $8A            ;Turn gate bit off
RESTART  = $8B            ;Restart player (JMP INIT)
JUMP     = $8C            ;Jump to marker
JSUB     = $8D            ;Subroutine to marker
RSUB     = $8E            ;Return from subroutine
WAIT     = $8F            ;Wait for SYNC
SYNC     = $90            ;Synchronize
CYCTEST  = $91            ;Cycle test bit
SLUR     = $92            ;Slur mode on
SLOFF    = $93            ;Slur mode off
HICODE   = $94            ;Always last instruction

LOADGLOB = %11000000      ;Load global variable (mask)
LOADLOC  = %11100000      ;Load local variable

* Table of handler routine addresses for above

TABLE1   
         DA SETV
         DA ACTG
         DA DACTG
         DA ACTL
         DA DACTL
         DA LDINST
         DA REP
         DA ENDR
         DA STO
         DA GON
         DA GOFF
         DA PLAINIT
         DA JMPMARK
         DA JSRMARK
         DA RTSMARK
         DA WAIT1
         DA SYNC1
         DA CYCTEST1
         DA SLUR1
         DA SLOFF1

STACK    DS 42            ;Stack for JSR and REP
STACKP   DS 3             ;Stack index for each field

GNSTACK  DS 16            ;Global variable stack, var #
GVSTACK  DS 16            ;glob var, value
GSTACKP  DS 1

LNSTACK  DS 24            ;Local variable stack, var #
LVSTACK  DS 24            ;local var, value
LSTACKP  DS 3

* Macro to pop variable info off of the stacks
* ]1 = voice # (0,1,2)

POPLOC   MAC
         LDX LSTACKP+]1
         BEQ SKIP
L1       LDY 8*]1+LNSTACK-1,X ;Variable #
         LDA 8*]1+LVSTACK-1,X ;value
         STA LOCALVAR,Y
         DEX
         BNE L1
         STX LSTACKP+]1
SKIP     <<<

PROCDATA 
IHANDLE  
         CMP #LOADGLOB    ;Load variable has bit 6 set
         BCC NOLOAD
         CMP #LOADLOC     ;Less than => global load
         BCC GLOBAL       ;Load local is probably the most
                          ;frequent directive.
         AND #%00011111   ;Strip out the variable number
         CLC
         ADC LOCOFF       ;Local variable offset
         PHA              ;for current voice
         LDY CURFIELD
         LDA LSTACKP-1,Y
         ADC #1           ;Advance (C clear)
         STA LSTACKP-1,Y
         ADC LOCOFF       ;8*CURFIELD, so 8 too much!
         TAY
         TXA
         STA LVSTACK-9,Y  ;value (-1 for advance above)
         PLA
         STA LNSTACK-9,Y  ;variable number
         RTS

GLOBAL   AND #%00011111
         LDY GSTACKP
         STA GNSTACK,Y    ;variable number
         TXA
         STA GVSTACK,Y    ;value
         INC GSTACKP
         RTS

NOLOAD   
         CMP #HICODE      ;Restart on anything above
         BCS PLAINIT
         ASL              ;Double it
         TAY
         LDA TABLE1,Y     ;Get address of routine
         STA :JMP+1
         LDA TABLE1+1,Y   ;hi byte
         STA :JMP+2
:JMP     JMP SETV
PLAINIT  PLA
         PLA

*
* Initialization routine
*
* Reset field pointers, macros
* inactive, fields for voice 1 2 and 3
*
INIT     
         LDX #01          ;Start all durations at 1
         STX DURATION
         STX DURATION+1
         STX DURATION+2
         INX
         LDA #00
:LS      STA STOPME,X
         STA V1WAIT,X
         STA LSTACKP,X
         STA REPFLAG,X
         STA SLURFLAG,X
* STA NEWGATE,X
* STA NEWCYC,X
         DEX
         BPL :LS
         STA SYNCFLAG     ;Reset SYNC

         LDX #3           ;Reset macros
:L2      STA SHADMAC1,X
         STA SHADMAC2,X
         STA SHADMAC3,X
         DEX
         BPL :L2
         STA MACBYTE1     ;Globals
         STA MACBYTE2
         STA MACBYTE3
         STX NEWVOL       ;Flags
         STX NEWINST

         LDX #8
         STX SID+4        ;Reset test bits
         STA SID+4        ;Clear gate bits
         STX SID+$B
         STA SID+$0B
         STX SID+$12
         STA SID+$12

         STA GSTACKP
         STA STACKP
         LDA #14
         STA STACKP+1
         LDA #28
         STA STACKP+2

         DO 0             ;Not necessary for lite routine
         LDX #1
         STX V1FIELD
         INX
         STX V2FIELD
         INX
         STX V3FIELD
         FIN              ;-----------------------

         LDA FIELDS       ;Initialize the field pointers
         LDX FIELDS+1     ;voice 1
         JSR V1SETP

         LDA FIELDS+2     ;Voice 2 = field 2
         LDX FIELDS+3
         JSR V2SETP

         LDA FIELDS+4
         LDX FIELDS+5
         JSR V3SETP

         JMP VOICE1       ;Continue into player, skipping
                          ;global macros.


SETV                      ;Set volume
         STX NEWVOL       ;(shadowed variable)
         RTS

REP                       ;Repeat begin
         LDY CURFIELD
         LDA REPFLAG-1,Y  ;Currently in a repeat loop?
         BEQ :CONT
         STX TEMPY
         LDA STACKP-1,Y   ;If so, push old stuff on stack
         CLC
         ADC #3
         STA STACKP-1,Y
         TAX
         LDA REPLO-1,Y
         STA STACK-1,X    ;lo
         LDA REPHI-1,Y
         STA STACK-2,X    ;hi
         LDA REPTIMES-1,Y
         STA STACK-3,X
         LDX TEMPY
         LDA REPFLAG-1,Y

:CONT    CLC
         ADC #01
         STA REPFLAG-1,Y  ;Measures the repeat loop depth
         TXA
         STA REPTIMES-1,Y ;Number of times to repeat
         JSR GETP         ;Get current pointer
         STA REPLO-1,Y    ;Low byte
         TXA
         STA REPHI-1,Y    ;Hi byte
SNEAKY   RTS

ENDR                      ;Repeat
         LDX CURFIELD
         LDA REPFLAG-1,X
         BEQ :OOPS
         DEC REPTIMES-1,X
         BEQ :NEXT
         LDY CURFIELD
         LDA REPLO-1,Y    ;Restore index
         LDX REPHI-1,Y
         JMP SETP         ;Set current voice to (A X 0)

:NEXT    DEC REPFLAG-1,X  ;This loop is done
         BEQ SNEAKY       ;If zero, all loops are done
         LDY STACKP-1,X   ;Otherwise, get previous
         LDA STACK-1,Y    ;loop info off of stack
         STA REPLO-1,X
         LDA STACK-2,Y
         STA REPHI-1,X
         LDA STACK-3,Y
         STA REPTIMES-1,X
         TYA
         SEC
         SBC #3
         STA STACKP-1,X
         RTS

:OOPS    INC REPFLAG-1,X  ;END without any REP
         RTS

ACTG                      ;Activate global macro
         LDA BITP,X       ;Get the bit!
         LDY DIV8T7,X     ;x/8*7, gives MACBYTE1 2 or 3
         ORA SHADMAC1,Y   ;automatically
         STA SHADMAC1,Y   ;Global=voice 0
         TXA              ;Reset entry address
         ASL
         TAY
         LDA MACADR,Y
         STA MACLO,X
         LDA MACADR+1,Y
         STA MACHI,X
         RTS

DACTG                     ;Deactivate
         LDA BITP,X
         EOR #$FF
         LDY DIV8T7,X
         AND SHADMAC1,Y
         STA SHADMAC1,Y
         RTS

ACTL                      ;Activate local macro
         LDA DIV8T7,X     ;Macro byte offset
         CLC
         ADC CURFIELD     ;offset further by current voice
         TAY
         LDA BITP,X
         ORA SHADMAC1,Y
         STA SHADMAC1,Y
         RTS

DACTL    
         LDA DIV8T7,X
         CLC
         ADC CURFIELD
         TAY
         LDA BITP,X
         EOR #$FF
         AND SHADMAC1,Y
         STA SHADMAC1,Y
         RTS

LDINST                    ;Load instrument
         TXA
         LDX CURFIELD
         STA NEWINST-1,X  ;store to be handled later
         RTS              ;at new note time.

STO                       ;Stop voice
         LDX CURFIELD
         STA STOPME-1,X   ;And that's the end of that!
         JMP UNSHAD       ;Process remaining instructions

GON                       ;Turn gate on
         LDX CURFIELD
         LDA #$01
         STA NEWGATE-1,X
         RTS

GOFF                      ;Turn gate off
         LDX CURFIELD
         LDA #$FE
         STA NEWGATE-1,X
         RTS

JMPMARK                   ;X contains marker # index
         LDA MARKERS,X
         PHA
         LDA MARKERS+1,X
         TAX
         PLA
         JMP SETP

JSRMARK  
         LDY CURFIELD     ;1 2 or 3
         LDA STACKP-1,Y
         CLC
         ADC #2
         STA STACKP-1,Y
         TAY
         LDA MARKERS,X
         PHA
         LDA MARKERS+1,X
         PHA
         JSR GETP
         STA STACK-1,Y    ;lo
         TXA
         STA STACK-2,Y    ;hi
         PLA              ;Destination hi
         TAX
         PLA
         JMP SETP

RTSMARK  
         LDY CURFIELD
         LDA STACKP-1,Y
         SEC
         SBC #2
         STA STACKP-1,Y
         TAY
         LDA STACK,Y
         TAX              ;High byte
         LDA STACK+1,Y
         JMP SETP

WAIT1    LDX CURFIELD     ;Set WAIT flag for this voice
         INC V1WAIT-1,X
         BNE UNSHAD       ;And process any preceding
                          ;instructions.

SYNC1    LDY CURFIELD
         LDA DURATION-1,Y
         STA SYNCVAL
         STA SYNCFLAG
         RTS

CYCTEST1 
         LDX CURFIELD
         INC NEWCYC-1,X
         RTS

SLUR1                     ;Activate slur mode
         LDX CURFIELD
         LDA #$FF
         STA SLURFLAG-1,X
         RTS

SLOFF1   
         LDX CURFIELD
         LDA #00
         STA SLURFLAG-1,X
         RTS



*-- Out of the shadows and into the light, or at least
*-- the SID.
*--
*-- Calling routine should store .X in TEMPX

TEMPX    DFB 00

UNSHAD   
         LDA NEWVOL       ;First check for new volume
         BMI :NOVOL
         LDA SHADOW+24
         AND #$F0
         ORA NEWVOL
         STA SHADOW+24
         STA SID+24
         LDA #$FF         ;Reset flag
         STA NEWVOL

:NOVOL   LDY CURFIELD
         LDA NEWINST-1,Y  ;Then for new instrument
         BMI :NOINST
         TAX              ;instrument number
         LDA #$FF
         STA NEWINST-1,Y  ;clear shadow var
         JSR LOADINST
:NOINST                   ;Copy the active macro bits
         LDA SHADMAC1,Y   ;.Y = CURFIELD
         STA MACBYTE1,Y
         LDA SHADMAC2,Y
         STA MACBYTE2,Y
         LDA SHADMAC3,Y
         STA MACBYTE3,Y
         LDA SHADMAC1     ;(Global macros)
         STA MACBYTE1
         LDA SHADMAC2
         STA MACBYTE2
         LDA SHADMAC3
         STA MACBYTE3

         LDX COFFSET      ;Set/Clear gate bits
         LDA NEWGATE-1,Y  ;$01 or $FE for on/off
         BEQ :SKIP1
         BMI :OFF
         ORA SHADOW+4,X   ;set gate
         BNE :STORE
:OFF     AND SHADOW+4,X   ;clear gate
:STORE   STA SHADOW+4,X
         STA SID+4,X
         LDA #00
         STA NEWGATE-1,Y
:SKIP1   
         LDA NEWCYC-1,Y   ;Cycle test bit
         BEQ :SKIP2
         LDA #08
         STA SID+4,X
         LDA SHADOW+4,X
         STA SID+4,X
         LDA #00
         STA NEWCYC-1,Y
:SKIP2   
         LDX GSTACKP      ;Copy any global variables
         BEQ :SKIP
:L1      LDY GNSTACK-1,X  ;var #
         LDA GVSTACK-1,X  ;value
         STA GLOBVAR,Y
         DEX
         BNE :L1
         STX GSTACKP

:SKIP    RTS


*
* Load instrument routine
*
* On entry: X contains the instrument to be loaded
*
* On exit: Y is preserved
*
TIMES9   DFB 0,9,18,27,36,45,54,63,72,81,90,99,108,117

LOADINST 
         STY TEMPY
         LDY CURVOICE     ;1 2 or 3
         TXA
         STA CURINST-1,Y
         LDA TIMES9,X
         TAX
         LDY COFFSET      ;Current voice offset
         LDA INSTAB,X     ;Pulse lo
         STA SHADOW+2,Y
         STA SID+2,Y
         LDA INSTAB+1,X   ;Pulse hi
         STA SHADOW+3,Y
         STA SID+3,Y
         LDA INSTAB+2,X   ;Control register
         STA SHADOW+4,Y
         STA SID+4,Y
         LDA INSTAB+3,X   ;Attack/decay
         STA SHADOW+5,Y
         STA SID+5,Y
         LDA INSTAB+4,X   ;Sustain/release
         STA SHADOW+6,Y
         STA SID+6,Y

         LDY CURVOICE     ;1 2 or 3
         LDA INSTAB+8,X   ;Filter mode, 0 means no filter
         BEQ :NOFILTER    ;Zero means no filter
         LDA SHADOW+$18   ;Volume, filter mode
         AND #$0F
         ORA INSTAB+8,X
         STA SHADOW+$18
         STA SID+$18
         LDA SHADOW+$17   ;Next up: resonance
         AND #$0F         ;Other voices may be filtered
         ORA BITP-1,Y
         ORA INSTAB+7,X   ;Grab resonance
         STA SHADOW+$17
         STA SID+$17
         LDA INSTAB+6,X   ;Cutoff hi
         STA SHADOW+$16
         STA SID+$16
         LDA INSTAB+5,X   ;Cutoff lo
         STA SHADOW+$15
         STA SID+$15
         LDY TEMPY
         RTS
:NOFILTER                 ;Don't filter this voice
         LDA BITP-1,Y
         EOR #$FF         ;Set bit to 0
         AND SHADOW+$17
         STA SHADOW+$17
         STA SID+$17
         LDY TEMPY
         RTS

*
* These routines will either fetch or set the current
* data (field) pointers for voice1, voice2, and voice3.
* Data is passed in/out via (A X) = (lo hi)
*
* The index is added into the pointer.
*
GETP                      ;Figure out which one to call
         LDA CURVOICE
         CMP #2
         BEQ V2GETP
         BPL V3GETP

V1GETP   
         LDX V1POINT+5
         LDA FOFFSET
         CLC
         ADC V1POINT+4
         BCC :RTS
         INX
:RTS     RTS
V2GETP   
         LDX V2POINT+5
         LDA FOFFSET+1
         CLC
         ADC V2POINT+4
         BCC :RTS
         INX
:RTS     RTS
V3GETP   
         LDX V3POINT+5
         LDA FOFFSET+2
         CLC
         ADC V3POINT+4
         BCC :RTS
         INX
:RTS     RTS

SETP                      ;Again figure which to use
         PHA
         LDA CURVOICE
         CMP #2
         BEQ V2SETPLA     ;Special branch
         BPL V3SETPLA
V1SETPLA PLA
V1SETP   
         STA V1POINT+4
         STX V1POINT+5
         STA V1POINT+8
         STX V1POINT+9
         LDA #00
         STA FOFFSET
         RTS

V2SETPLA PLA
V2SETP   
         STA V2POINT+4
         STX V2POINT+5
         STA V2POINT+8
         STX V2POINT+9
         LDA #00
         STA FOFFSET+1
         RTS

V3SETPLA PLA
V3SETP   
         STA V3POINT+4
         STX V3POINT+5
         STA V3POINT+8
         STX V3POINT+9
         LDA #00
         STA FOFFSET+2
         RTS

*
* Main interrupt routine:
*   If Shadow SID active, then copy it over.
*   DEC duration
*   If zero then read in new data
*   Process macros
*   Repeat for other voices
*
MAINPLAY 
* LDA COPYSHAD ;Zero means active
* BNE GLOBMAC
* LDX #24
*:L1 LDA SHADOW,X
* STA SID,X
* DEX
* BPL :L1
         LDX #$FF
         LDA SYNCFLAG     ;Has sync been activated?
         BEQ :CONT        ;If not, then syncval=$FF
         LDX SYNCVAL
         DEX
         BNE :CONT
         STX SYNCFLAG     ;If at zero, deactivate sync
:CONT    STX SYNCVAL      ;Sync occurs at SYNCVAL=0

GLOBMAC                   ;Take care of global macros
         LDX #00          ;1-6 are normal fields, 0 global
         STX CURFIELD
         STX LOCOFF       ;Local variable offset
         JSR OLDMAC

VOICE1   
         LDA V1LOOK       ;Instruction lookahead depth
         STA COUNT
         LDA #08
         STA LOCOFF
         LDX #00
         STX COFFSET      ;SID offset
         INX
         STX CURVOICE     ;Current voice

* LDX V1FIELD ;Current field voice 1
         STX CURFIELD     ;Current field

V1STOP   LDA V1WAIT
         BEQ :CONT
         LDA SYNCVAL
         BEQ :C2
         JSR OLDMAC       ;Otherwise deal with macros.
:JMP     JMP VOICE2
:C2      STA V1WAIT       ;We have synced
:CONT    LDA STOPME
         BNE :JMP

V1POINT  
V1LOOP   LDY FOFFSET      ;v1offset, v2offset, etc.
:FP1     LDX $1000,Y      ;First byte: note or data
         INY
:FP2     LDA $1000,Y      ;Second byte: inst or duration
         DEC DURATION
         BEQ :NEW
                          ;If dur nonzero, then process if
                          ;possible and call old macs.
         AND #$FF
         BPL :OLDMAC      ;High bit set means instruction

         CMP #RESTART     ;Can't process
         BEQ :OLDMAC
         CMP #STOP
         BEQ :OLDMAC
         CMP #WAIT
         BEQ :OLDMAC

         INY              ;Advance pointers
         STY FOFFSET
         BNE :CONT1
         INC :FP1+2       ;Increment the high bytes
         INC :FP2+2       ;Simple!
:CONT1   
         JSR PROCDATA
         INC DURATION     ;What a kludge :(
         DEC COUNT        ;Grab another instruction
         BNE V1LOOP
         DEC DURATION
:OLDMAC  JSR OLDMAC       ;Otherwise deal with macros.
:JV2     JMP VOICE2

*--- Duration has hit zero.  Process remaining insts+note

:NEW     
         INY
         STY FOFFSET
         BNE :CONT2
         INC :FP1+2       ;Increment the high bytes
         INC :FP2+2       ;Simple!
:CONT2   
         AND #$FF
         BPL :LNOTE       ;If an instruction, then
         JSR PROCDATA     ;process,
         INC DURATION     ;increment for loop around
         BNE V1STOP       ;and loop around!

:LNOTE                    ;Otherwise, load frequency and
                          ;duration data
                          ;X is note number i.e. freq index
         TAY              ;A is duration index
         LDA DURTAB,Y
         STA DURATION     ;New duration
         STX TEMPX
         JSR UNSHAD       ;Fix up any stuff in the shadows
         >>> POPLOC,0     ;Fix up any local vars
         LDX TEMPX

         CPX #HOLD        ;Slur current note?
         BEQ :OLDMAC      ;If so, then do it!
         STX LASTNOTE     ;Last note read for v1, etc.
         LDA FREQLO,X
         STA SHADOW
         STA SID
         LDA FREQHI,X
         STA SHADOW+1
         STA SID+1
         JSR NEWMAC       ;Restart macros


VOICE2   
         LDA V2LOOK
         STA COUNT
         LDX CURVOICE
         INX
         STX CURVOICE
         STX CURFIELD
         LDA #16
         STA LOCOFF
         LDA #07
         STA COFFSET

V2STOP   LDA V2WAIT
         BEQ :CONT
         LDA SYNCVAL
         BEQ :C2
         JSR OLDMAC
:JMP     JMP VOICE3
:C2      STA V2WAIT       ;We have synced
:CONT    LDA STOPME+1
         BNE :JMP

V2POINT  
V2LOOP   LDY FOFFSET+1    ;v1offset, v2offset, etc.
                          ;An offset into the data; less
                          ;self-modifying code this way
                          ;(i.e. more saved cycles)
:FP1     LDX $1000,Y      ;First byte: inst or duration
         INY
:FP2     LDA $1000,Y      ;Second byte: note or data
         DEC DURATION+1
         BEQ :NEW
                          ;If dur nonzero, then process if
                          ;possible and call old macs.
         AND #$FF
         BPL :OLDMAC      ;High bit set means instruction

         CMP #RESTART
         BEQ :OLDMAC
         CMP #STOP
         BEQ :OLDMAC
         CMP #WAIT
         BEQ :OLDMAC

:PROC    INY              ;Advance pointers
         STY FOFFSET+1
         BNE :CONT1
         INC :FP1+2       ;Increment the high bytes
         INC :FP2+2       ;Simple!
:CONT1   
         JSR PROCDATA
         INC DURATION+1
         DEC COUNT
         BNE V2LOOP
         DEC DURATION+1
:OLDMAC  JSR OLDMAC       ;Otherwise deal with macros.
:JV3     JMP VOICE3

*--- Duration has hit zero.  Process remaining insts+note

:NEW     
         INY
         STY FOFFSET+1
         BNE :CONT2
         INC :FP1+2       ;Increment the high bytes
         INC :FP2+2       ;Simple!
:CONT2   
         AND #$FF
         BPL :LNOTE       ;If an instruction, then
         JSR PROCDATA     ;process,
         INC DURATION+1   ;increment for loop around
         BNE V2STOP       ;and loop around!

:LNOTE                    ;Otherwise, load frequency and
                          ;duration data
                          ;X is note number i.e. freq index
         TAY              ;A is duration index
         LDA DURTAB,Y
         STA DURATION+1   ;New duration
         STX TEMPX
         JSR UNSHAD       ;Fix up any stuff in the shadows
         >>> POPLOC,1
         LDX TEMPX

         CPX #HOLD        ;Slur current note?
         BEQ :OLDMAC      ;If so, then do it!
         STX LASTNOTE+1   ;Last note read for v1, etc.
         LDA FREQLO,X
         STA SHADOW+7
         STA SID+7
         LDA FREQHI,X
         STA SHADOW+8
         STA SID+8
         JSR NEWMAC       ;Restart macros


VOICE3   
         LDA V3LOOK
         STA COUNT
         LDX CURVOICE
         INX
         STX CURVOICE
         STX CURFIELD
         LDA #24
         STA LOCOFF
         LDA #14
         STA COFFSET
* LDX V3FIELD
* STX CURFIELD
* LDA TIMES24,X
* TAY
V3STOP   LDA V3WAIT
         BEQ :CONT
         LDA SYNCVAL
         BEQ :C2
         JMP OLDMAC
:C2      STA V3WAIT       ;We have synced
:CONT    LDA STOPME+2
         BEQ V3LOOP
DONERTS  RTS

V3POINT  
V3LOOP   LDY FOFFSET+2    ;v1offset, v2offset, etc.
                          ;An offset into the data; less
                          ;self-modifying code this way
                          ;(i.e. more saved cycles)
:FP1     LDX $1000,Y      ;First byte: inst or duration
         INY
:FP2     LDA $1000,Y      ;Second byte: note or data
         DEC DURATION+2
         BEQ :NEW
                          ;If dur nonzero, then process if
                          ;possible and call old macs.
         AND #$FF
         BPL :OLDMAC      ;High bit set means instruction

         CMP #RESTART
         BEQ :OLDMAC
         CMP #STOP
         BEQ :OLDMAC
         CMP #WAIT
         BEQ :OLDMAC

:PROC    INY              ;Advance pointers
         STY FOFFSET+2
         BNE :CONT1
         INC :FP1+2       ;Increment the high bytes
         INC :FP2+2       ;Simple!
:CONT1   
         JSR PROCDATA
         INC DURATION+2
         DEC COUNT
         BNE V3LOOP
         DEC DURATION+2
:OLDMAC  JMP OLDMAC       ;Otherwise deal with macros.
                          ;and exit

*--- Duration has hit zero.  Process remaining insts+note

:NEW     
         INY
         STY FOFFSET+2
         BNE :CONT2
         INC :FP1+2       ;Increment the high bytes
         INC :FP2+2       ;Simple!
:CONT2   
         AND #$FF
         BPL :LNOTE       ;If an instruction, then
         JSR PROCDATA     ;process,
         INC DURATION+2   ;increment for loop around
         BNE V3STOP       ;and loop around!

:LNOTE                    ;Otherwise, load frequency and
                          ;duration data
                          ;X is note number i.e. freq index
         TAY              ;A is duration index
         LDA DURTAB,Y
         STA DURATION+2   ;New duration
         STX TEMPX
         JSR UNSHAD       ;Fix up any stuff in the shadows
         >>> POPLOC,2
         LDX TEMPX

         CPX #HOLD        ;Slur current note?
         BEQ :OLDMAC      ;If so, then do it!
         STX LASTNOTE+2   ;Last note read for v1, etc.
         LDA FREQLO,X
         STA SHADOW+14
         STA SID+14
         LDA FREQHI,X
         STA SHADOW+15
         STA SID+15
         JMP NEWMAC       ;Restart macros
                          ;and exit


MACBEGIN 

*
* Now some simple song data -- just a scale.
*
FIELD1   
         DFB RESTART,RESTART
FIELD2   
         DFB STOP,STOP
FIELD3   
         DFB STOP,STOP
FIELDEND 
