*------------------------------- * * 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 $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