P*------------------------------- * * playerc2 * * Like playerc, except can read a variable number of * instructions ahead. * * Note that STOP works funny -- the composer has to * plan ahead, and not have any other instructions * before the STOP. * * SLJ 11/8/97 * 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 'playerc2 slj' V1LOOK DFB 02 ;How far to look ahead V2LOOK DFB 02 ;instruction-wise V3LOOK DFB 02 :SNEAKY DS KERNAL+64-:SNEAKY ;Fill up to $1000 + 64 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 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 SHADMAC1 DS 7 ;MACBYTE1/2/3 shadow vars SHADMAC2 DS 7 SHADMAC3 DS 7 * * 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 HICODE = $8F ;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 STACK DS 24 ;Stack for JSR, 4-deep 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 DEX STX STOPME STX STOPME+1 STX STOPME+2 TXA 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 STA SID+4 ;Clear gate bits STA SID+$0B STA SID+$12 STA GSTACKP STA LSTACKP STA LSTACKP+1 STA LSTACKP+2 STA STACKP LDA #8 STA STACKP+1 LDA #16 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 TXA STA REPTIMES-1,Y ;Number of times to repeat JSR GETP ;Get current pointer * GETP leaves Y unchanged * LDY CURFIELD ;returns (A X) = (lo hi) STA REPLO-1,Y ;Low byte TXA STA REPHI-1,Y ;Hi byte SNEAKY RTS ENDR ;Repeat LDX CURFIELD DEC REPTIMES-1,X BEQ SNEAKY LDY CURFIELD LDA REPLO-1,Y ;Restore index LDX REPHI-1,Y JMP SETP ;Set current voice to (A X 0) 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! RTS 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 *-- 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 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 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 STOPME BEQ V1LOOP JV2 JMP VOICE2 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 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 STOPME+1 BEQ V2LOOP JMP VOICE3 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 :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 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 :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