Browse Source

menu: SPC player (necronomfive, blargg)

Maximilian Rehkopf 12 years ago
parent
commit
f9c8e62f10
8 changed files with 810 additions and 2 deletions
  1. 1 1
      snes/Makefile
  2. 20 0
      snes/const.a65
  3. 6 1
      snes/data.a65
  4. 33 0
      snes/main.a65
  5. 11 0
      snes/memmap.i65
  6. 30 0
      snes/menu.a65
  7. 61 0
      snes/spc700.a65
  8. 648 0
      snes/spcplay.a65

+ 1 - 1
snes/Makefile

@@ -1,4 +1,4 @@
-OBJS = header.ips reset.o65 main.o65 font.o65 palette.o65 data.o65 const.o65 logo.o65 logospr.o65 text.o65 dma.o65 menu.o65 pad.o65 time.o65 mainmenu.o65 sysinfo.o65 # gfx.o65 # vars.o65
+OBJS = header.ips reset.o65 main.o65 font.o65 palette.o65 data.o65 const.o65 logo.o65 logospr.o65 text.o65 dma.o65 menu.o65 pad.o65 time.o65 mainmenu.o65 sysinfo.o65 spc700.o65 spcplay.o65 # gfx.o65 # vars.o65
 
 all: clean menu.bin map
 

+ 20 - 0
snes/const.a65

@@ -168,4 +168,24 @@ text_mm_vmode_game	.byt "Game video mode", 0
 text_mm_sysinfo		.byt "System Information", 0
 text_statusbar_keys	.byt "A:Select B:Back X:Menu", 0
 text_last		.byt "Run previous ROM: Press Start again to confirm", 0
+text_system		.byt "PPU1 Rev.: x  PPU2 Rev.: y  CPU Rev.: z",0
 
+
+text_spcplay		.byt "SPC Music Player", 0
+spcplay_win_x		.byt 15
+spcplay_win_y		.byt 15
+spcplay_win_w		.byt 33
+spcplay_win_h		.byt 5
+
+text_spcload		.byt "Loading SPC data to SPC700...", 0
+text_spcstarta		.byt "**** Now playing SPC tune ****", 0
+text_spcstartb		.byt "Name:  ",0
+text_spcstartc		.byt "Song:  ",0
+text_spcstartd		.byt "Artist:",0
+
+spcstart_win_x		.byt 10
+spcstart_win_y		.byt 13
+spcstart_win_w		.byt 44
+spcstart_win_h		.byt 9
+
+text_spcid		.byt "SNES-SPC700"

+ 6 - 1
snes/data.a65

@@ -183,5 +183,10 @@ direntry_xscroll
 	.word	0
 direntry_xscroll_wait
 	.word	0
-infloop		.byt 0,0  ; to be filled w/ 80 FE
+infloop	.byt 0,0  ; to be filled w/ 80 FE
+;------------------------
+saved_sp
+	.word 0
+warm_signature
+	.word 0
 wram_fadeloop	.byt 0

+ 33 - 0
snes/main.a65

@@ -11,6 +11,35 @@ GAME_MAIN:
 	sta @AVR_PARAM+2
 	sep #$20 : .as
 	stz $4200 ; inhibit VBlank NMI
+
+	rep #$20 : .al
+	lda @warm_signature	; Was CMD_RESET issued before reset?
+	cmp #$fa50		; If yes, then perform warm boot procedure
+	bne coldboot
+	lda #$0000
+	sta @warm_signature
+	lda @saved_sp		; Restore previous stack pointer
+	tcs
+	sep #$20 : .as
+
+	jsr killdma		; The following initialization processes must not touch memory
+	jsr waitblank		; structures used by the main menu !
+	jsr snes_init
+	lda #$01
+	sta $420d 		; fast cpu
+	jsr setup_gfx
+	jsr colortest
+	jsr setup_hdma
+	jsr tests
+
+	jmp @set_bank		; Set bios bank, just to be sure
+set_bank:
+	plp			; Restore processor state
+	rts			; Jump to the routine which called the sub-routine issuing CMD_RESET
+
+coldboot:							; Regular, cold-start init
+	sep #$20 : .as
+
 	jsr killdma
 	jsr waitblank
 	jsr snes_init
@@ -70,12 +99,15 @@ killdma:
 
 
 waitblank:
+	php
+	sep #$30 : .as : .xs
 -	lda $4212
 	and #$80
 	bne -
 -	lda $4212
 	and #$80
 	beq -
+	plp
 	rts
 
 colortest:
@@ -225,6 +257,7 @@ tests:
 	sta $212e
 	sta $212f
 	stz $2121
+	jsr waitblank
 	lda #$0f
 	sta $2100		;screen on, full brightness
 	lda #9

+ 11 - 0
snes/memmap.i65

@@ -6,6 +6,11 @@
 /* These must be defined as constants, because they're used
  * in calculation that is sent to PPU as parameters */
 
+#define APUIO0		$2140
+#define APUIO1		$2141
+#define APUIO2		$2142
+#define APUIO3		$2143
+
 #define BG1_TILE_BASE	$5800
 #define BG2_TILE_BASE	$5000
 
@@ -27,3 +32,9 @@
 #define ROOT_DIR	$C10000
 
 #define CMD_SYSINFO	$03
+#define CMD_LOADSPC	$05
+#define CMD_RESET	$06
+
+#define SPC_DATA	$DD0000
+#define SPC_HEADER	$DE0000
+#define SPC_DSP_REGS	$DE0100

+ 30 - 0
snes/menu.a65

@@ -272,6 +272,10 @@ dirent_is_file
 	lda #$0000
 	bra dirent_type_cont
 +
+	cmp #$0003  ;SPC -> palette 2
+	bne +
+	lda #$0002
+	bra dirent_type_cont
 	cmp #$0004	;IPS -> palette 2 (green)
 	bne +
         lda #$0002
@@ -502,6 +506,8 @@ select_item:
 	lda [dirptr_addr], y
 	cmp #$01
 	beq sel_is_file
+	cmp #$03
+	beq sel_is_spc
 	cmp #$04
 	beq sel_is_file
 	cmp #$80
@@ -519,6 +525,9 @@ sel_is_parent
 sel_is_dir
 	jsr select_dir
 	bra select_item_cont
+sel_is_spc
+	jsr select_spc
+	bra select_item_cont
 
 select_file:
 	; have avr load the rom
@@ -635,6 +644,27 @@ select_parent:
 	sta @menu_dirty
 	rts
 
+select_spc:
+	dey
+	rep #$20 : .al
+	lda [dirptr_addr], y
+	and #$00ff
+	sta @AVR_PARAM+2
+	dey
+	dey
+	lda [dirptr_addr], y
+	sta @AVR_PARAM
+	sep #$20 : .as
+	lda #CMD_LOADSPC
+	sta @AVR_CMD
+wait_spc:
+	lda @AVR_CMD
+	cmp #$00
+	bne wait_spc
+	jsr spcplayer
+	jsr restore_screen
+	rts
+
 menu_key_x:
 	jsr mainmenu
 	rts

+ 61 - 0
snes/spc700.a65

@@ -0,0 +1,61 @@
+; All SPC700 routines in SPC700 machine code
+; SPC loader & transfer routines by Shay Green <gblargg@gmail.com>
+
+loader				; .org $0002
+	.byt $F8,$21		;	mov x,@loader_data
+	.byt $BD		;	mov sp,x
+	.byt $CD,$22		;	mov x,#@loader_data+1
+
+	; Push PC and PSW from SPC header
+	.byt $BF		;	mov a,(x)+
+	.byt $2D		;	push a
+	.byt $BF		;	mov a,(x)+
+	.byt $2D		;	push a
+	.byt $BF		;	mov a,(x)+
+	.byt $2D		;	push a
+
+	; Set FLG to $60 rather than value from SPC
+	.byt $E8,$60		;	mov a,#$60
+	.byt $D4,$6C		;	mov FLG+x,a
+
+	; Restore DSP registers
+	.byt $8D,$00		;	mov y,#0
+	.byt $BF		; next:	mov a,(x)+
+	.byt $CB,$F2		;	mov $F2,y
+	.byt $C4,$F3		;	mov $F3,a
+	.byt $FC		;	inc y
+	.byt $10,-8		;	bpl next
+
+	.byt $8F,$6C,$F2	;	mov $F2,#FLG	; set for later
+
+	; Rerun loader
+	.byt $5F,$C0,$FF	;	jmp $FFC0
+
+;---------------------------------------
+
+transfer			; .org $0002
+
+	.byt $CD,$FE		; mov x,#$FE		; transfer 254 pages
+
+	; Transfer four-byte chunks
+	.byt $8D,$3F		; page: mov y,#$3F
+	.byt $E4,$F4		; quad: mov a,$F4
+	.byt $D6,$00,$02	; mov0: mov !$0200+y,a
+	.byt $E4,$F5		;	mov a,$F5
+	.byt $D6,$40,$02	; mov1: mov !$0240+y,a
+	.byt $E4,$F6		;	mov a,$F6
+	.byt $D6,$80,$02	; mov2: mov !$0280+y,a
+	.byt $E4,$F7		;	mov a,$F7	; tell S-CPU we're ready for more
+	.byt $CB,$F7		;	mov $F7,Y
+	.byt $D6,$C0,$02	; mov3: mov !$02C0+y,a
+	.byt $DC 		;	dec y
+	.byt $10,-25		;	bpl quad
+	; Increment MSBs of addresses
+	.byt $AB,$0A		;	inc mov0+2
+	.byt $AB,$0F		;	inc mov1+2
+	.byt $AB,$14		;	inc mov2+2
+	.byt $AB,$1B		;	inc mov3+2
+	.byt $1D    		;	dec x
+	.byt $D0,-38		;	bne page
+	; Rerun loader
+	.byt $5F,$C0,$FF	;	jmp $FFC0

+ 648 - 0
snes/spcplay.a65

@@ -0,0 +1,648 @@
+#include "memmap.i65"
+
+; SPC Player
+; SPC700 transfer and IO routines by Shay Green <gblargg@gmail.com>
+
+spcplayer:
+	php
+	sep #$30 : .as : .xs
+
+	ldx #$0a	; Check if SPC header is present
+-
+	lda @SPC_HEADER,x
+	cmp @text_spcid,x
+	beq +
+	jmp spc_exit
++
+	dey
+	bne -
+
+	rep #$10 : .xl	; Now draw lots of stuff
+
+	stz bar_wl
+	dec bar_wl
+	stz bar_xl
+	dec bar_xl
+	stz bar_yl
+	dec bar_yl
+	jsr backup_screen
+
+	lda #^text_spcplay	; Loading window
+	sta window_tbank
+	ldx #!text_spcplay
+	stx window_taddr
+	lda @spcplay_win_x
+	sta window_x
+	lda @spcplay_win_y
+	sta window_y
+	lda @spcplay_win_w
+	sta window_w
+	lda @spcplay_win_h
+	sta window_h
+	jsr draw_window
+
+	lda #^text_spcload	; Loading text
+	ldx #!text_spcload
+	sta print_bank
+	stx print_src
+	stz print_pal
+	lda #29
+	sta print_count
+	lda #17
+	sta print_y
+	lda #17
+	sta print_x
+	jsr hiprint
+
+	stz isr_done
+-
+	lda isr_done		; Wait until text is being printed...
+	beq -
+
+	jsr spc700_load		; Load SPC into SPC700
+
+	lda #^text_spcplay
+	sta window_tbank
+	ldx #!text_spcplay
+	stx window_taddr
+	lda @spcstart_win_x
+	sta window_x
+	lda @spcstart_win_y
+	sta window_y
+	lda @spcstart_win_w
+	sta window_w
+	lda @spcstart_win_h
+	sta window_h
+	jsr draw_window
+
+	lda #^text_spcstarta
+	ldx #!text_spcstarta
+	sta print_bank
+	stx print_src
+	lda #$01
+	sta print_pal
+	lda #30
+	sta print_count
+	lda #15
+	sta print_y
+	lda #17
+	sta print_x
+	jsr hiprint
+
+	lda #^text_spcstartb
+	ldx #!text_spcstartb
+	sta print_bank
+	stx print_src
+	lda #$01
+	sta print_pal
+	lda #07
+	sta print_count
+	lda #17
+	sta print_y
+	lda #12
+	sta print_x
+	jsr hiprint
+
+	lda #$de
+	ldx #$004e
+	sta print_bank
+	stx print_src
+	stz print_pal
+	lda #32
+	sta print_count
+	lda #17
+	sta print_y
+	lda #20
+	sta print_x
+	jsr hiprint
+
+	lda #^text_spcstartc
+	ldx #!text_spcstartc
+	sta print_bank
+	stx print_src
+	lda #$01
+	sta print_pal
+	lda #07
+	sta print_count
+	lda #18
+	sta print_y
+	lda #12
+	sta print_x
+	jsr hiprint
+
+	lda #$de
+	ldx #$002e
+	sta print_bank
+	stx print_src
+	stz print_pal
+	lda #32
+	sta print_count
+	lda #18
+	sta print_y
+	lda #20
+	sta print_x
+	jsr hiprint
+
+	lda #^text_spcstartd
+	ldx #!text_spcstartd
+	sta print_bank
+	stx print_src
+	lda #$01
+	sta print_pal
+	lda #07
+	sta print_count
+	lda #19
+	sta print_y
+	lda #12
+	sta print_x
+	jsr hiprint
+
+	lda #$de
+	ldx #$00b1
+	sta print_bank
+	stx print_src
+	stz print_pal
+	lda #32
+	sta print_count
+	lda #19
+	sta print_y
+	lda #20
+	sta print_x
+	jsr hiprint
+
+spc_playloop:
+	lda isr_done	; SPC player loop
+	lsr
+	bcc spc_playloop
+	jsr printtime
+	stz isr_done
+
+	jsr read_pad
+	lda #$80
+	and pad1trig+1
+	bne spc_key_b
+	bra spc_playloop
+
+spc_key_b:
+	rep #$20 : .al
+	tsc
+	sta saved_sp	; Save SP for later re-entry
+	lda #$fa50	; Write reset signature
+	sta @warm_signature
+	sep #$20 : .as
+
+	sei		; Blank screen & issue CMD_RESET command to Microcontroller...
+	stz $2100	; ...this is required, because there is no other way to stop S-SMP & S-DSP
+	lda #CMD_RESET
+	sta @AVR_CMD
+-
+	bra -		; At this point, the SNES waits for an external reset from the Microcontroller
+
+spc_exit:		; Return from player in case of wrong SPC file data
+	plp
+	rts
+
+;---------------------------------------
+spc700_load:
+	php
+	sep #$20 : .as
+	rep #$10 : .xl
+
+	sei			; Disable NMI & IRQ
+	stz $4200		; The SPC player code is really timing sensitive ;)
+	jsr upload_dsp_regs	; Upload S-DSP registers
+	jsr upload_high_ram	; Upload 63.5K of SPC700 ram
+	jsr upload_low_ram	; Upload rest of ram
+	jsr restore_final	; Restore SPC700 state & start execution
+
+	lda #$81		; VBlank NMI + Auto Joypad Read
+	sta $4200		; enable V-BLANK NMI
+	cli
+	plp
+	rts
+;---------------------------------------
+; Uploads DSP registers and some other setup code
+upload_dsp_regs:
+
+; ---- Begin upload
+
+	ldy #$0002
+	jsr spc_begin_upload
+
+; ---- Upload loader
+
+	ldx #$0000
+-
+	lda @loader,x
+	jsr spc_upload_byte
+	inx
+	cpy #31		; size of loader
+	bne -
+
+; ---- Upload SP, PC & PSW
+
+	lda @SPC_HEADER+43
+	jsr spc_upload_byte
+	lda @SPC_HEADER+38
+	jsr spc_upload_byte
+	lda @SPC_HEADER+37
+	jsr spc_upload_byte
+	lda @SPC_HEADER+42
+	jsr spc_upload_byte
+
+; ---- Upload DSP registers
+
+	ldx #$0000
+-
+	lda @SPC_DSP_REGS,x
+	jsr spc_upload_byte
+	inx
+	cpx #128
+	bne -
+
+; --- Upload fixed values for $F1-$F3
+
+	ldy #$00F1
+	jsr spc_next_upload
+
+	lda #$80        ; stop timers
+	jsr spc_upload_byte
+	lda #$6c        ; get dspaddr set for later
+	jsr spc_upload_byte
+	lda #$60
+	jsr spc_upload_byte
+
+; ---- Upload $f8-$1ff
+
+	ldy #$00F8
+	jsr spc_next_upload
+
+	ldx #$00F8
+-
+	lda @SPC_DATA,x
+	jsr spc_upload_byte
+	inx
+	cpx #$200
+	bne -
+
+; ---- Execute loader
+
+	ldy #$0002
+	jsr spc_execute
+	rts
+;---------------------------------------
+upload_high_ram:
+
+	ldy #$0002
+	jsr spc_begin_upload
+
+; ---- Upload transfer routine
+
+	ldx #$0000
+-
+	lda @transfer,x
+	jsr spc_upload_byte
+	inx
+	cpy #43		; size of transfer routine
+	bne -
+
+	ldx #$023f	; prepare transfer address
+
+; ---- Execute transfer routine
+
+	ldy #$0002
+	sty APUIO2
+	stz APUIO1
+	lda APUIO0
+	inc
+	inc
+	sta APUIO0
+; Wait for acknowledgement
+-
+	cmp APUIO0
+	bne -
+
+; ---- Burst transfer of 63.5K using custom routine
+
+outer_transfer_loop:
+	ldy #$003f		; 3
+inner_transfer_loop:
+	lda @SPC_DATA,x		; 5 |
+	sta APUIO0		; 4 |
+	lda @SPC_DATA+$40,x	; 5 |
+	sta APUIO1		; 4 |
+	lda @SPC_DATA+$80,x	; 5 |
+	sta APUIO2		; 4 |
+	lda @SPC_DATA+$C0,x	; 5 |
+	sta APUIO3		; 4 |
+	tya			; 2 >> 38 cycles
+-
+	cmp APUIO3		; 4 |
+	bne -			; 3 |
+	dex			; 2 |
+	dey			; 2 |
+	bpl inner_transfer_loop	; 3 >> 14 cycles
+
+	rep #$21 : .al		; 3 |
+	txa			; 2 |
+	adc #$140		; 3 |
+	tax			; 2 |
+	sep #$20 : .as		; 3 |
+	cpx #$003f		; 3 |
+	bne outer_transfer_loop	; 3 >> 19 cycles
+
+	rts
+
+;---------------------------------------
+upload_low_ram:
+
+; ---- Upload $0002-$00EF
+
+	ldy #$0002
+	jsr spc_begin_upload
+
+	ldx #$0002
+-
+	lda @SPC_DATA,x
+	jsr spc_upload_byte
+	inx
+	cpx #$00F0
+	bne -
+	rts
+;---------------------------------------
+; Executes final restoration code
+restore_final:
+	jsr start_exec_io
+	stz $420d	; SPC700 I/O code requires SLOW timing
+
+; ---- Restore first two bytes of RAM
+
+	lda @SPC_DATA
+	xba
+	lda #$e8	; MOV A,#@SPC_DATA
+	tax
+	jsr exec_instr
+	ldx #$00C4	; MOV $00,A
+	jsr exec_instr
+
+	lda @SPC_DATA+1
+	xba
+	lda #$e8	; MOV A,#@SPC_DATA+1
+	tax
+	jsr exec_instr
+	ldx #$01C4	; MOV $01,A
+	jsr exec_instr
+
+; ---- Restore SP
+
+	lda @SPC_HEADER+43
+	sec
+	sbc #3
+	xba
+	lda #$cd	; MOV X,#@SPC_HEADER+43
+	tax
+	jsr exec_instr
+	ldx #$bd	; MOV SP,X
+	jsr exec_instr
+
+; ---- Restore X
+
+	lda @SPC_HEADER+40
+	xba
+	lda #$cd	; MOV X,#@SPC_HEADER+40
+	tax
+	jsr exec_instr
+
+; ---- Restore Y
+
+	lda @SPC_HEADER+41
+	xba
+	lda #$8d	; MOV Y,#@SPC_HEADER+41
+	tax
+	jsr exec_instr
+
+; ---- Restore DSP FLG register
+
+	lda @SPC_DSP_REGS+$6c
+	xba
+	lda #$e8	; MOV A,#@SPC_DSP_REGS+$6c
+	tax
+	jsr exec_instr
+	ldx #$f3C4	; MOV $f3,A -> $f2 has been set-up before by SPC700 loader
+	jsr exec_instr
+
+; ---- Restore DSP KON register
+
+	lda #$4C
+	xba
+	lda #$e8	; MOV A,#$4c
+	tax
+	jsr exec_instr
+	ldx #$f2C4	; MOV $f2,A
+	jsr exec_instr
+	lda @SPC_DSP_REGS+$4C
+	xba
+	lda #$e8	; MOV A,#@SPC_DSP_REGS+$4c
+	tax
+	jsr exec_instr
+	ldx #$f3C4	; MOV $f3,A
+	jsr exec_instr
+
+; ---- Restore DSP register address
+
+ 	lda @SPC_DATA+$F2
+	xba
+	lda #$e8	; MOV A,#@SPC_DATA+$F2
+	tax
+	jsr exec_instr
+	ldx #$f2C4	; MOV dest,A
+	jsr exec_instr
+
+; ---- Restore CONTROL register
+
+	lda @SPC_DATA+$F1
+	and #$CF	; don't clear input ports
+	xba
+	lda #$e8	; MOV A,#@SPC_DATA+$F1
+	tax
+	jsr exec_instr
+	ldx #$f1C4  	; MOV $F1,A
+	jsr exec_instr
+
+;---- Restore A
+
+	lda @SPC_HEADER+39
+	xba
+	lda #$e8	; MOV A,#@SPC_HEADER+39
+	tax
+	jsr exec_instr
+
+;---- Restore PSW and PC
+
+	ldx #$7F00	; NOP; RTI
+	stx APUIO0
+	lda #$FC	; Patch loop to execute instruction just written
+	sta APUIO3
+
+;---- restore IO ports $f4 - $f7
+
+	rep #$20 : .al
+	lda @SPC_DATA+$F4
+	tax
+	lda @SPC_DATA+$F6
+	sta APUIO2
+	stx APUIO0	; last to avoid overwriting RETI before run
+	sep #$20 : .as
+
+	lda #$01
+	sta $420d	; restore FAST CPU operation
+	rts
+;---------------------------------------
+spc_begin_upload:
+
+	sty APUIO2	; Set address
+
+	ldy #$BBAA	; Wait for SPC
+-
+	cpy APUIO0
+	bne -
+
+	lda #$CC	; Send acknowledgement
+	sta APUIO1
+	sta APUIO0
+
+-       		; Wait for acknowledgement
+	cmp APUIO0
+	bne -
+
+	ldy #0		; Initialize index
+	rts
+;---------------------------------------
+spc_upload_byte:
+	sta APUIO1
+
+	tya		; Signal it's ready
+	sta APUIO0
+-     			; Wait for acknowledgement
+	cmp APUIO0
+	bne -
+
+	iny
+
+	rts
+;---------------------------------------
+spc_next_upload:
+	sty APUIO2
+
+; Send command
+; Special case operation has been fully tested.
+	lda APUIO0
+	inc
+	inc
+	bne +
+	inc
++
+	sta APUIO1
+	sta APUIO0
+
+; Wait for acknowledgement
+-
+	cmp APUIO0
+	bne -
+
+	ldy #0
+	rts
+;---------------------------------------
+spc_execute:
+	sty APUIO2
+
+	stz APUIO1
+
+	lda APUIO0
+	inc
+	inc
+	sta APUIO0
+
+; Wait for acknowledgement
+-
+	cmp APUIO0
+	bne -
+
+	rts
+;---------------------------------------
+start_exec_io:
+; Set execution address
+	ldx #$00F5
+	stx APUIO2
+
+	stz APUIO1      ; NOP
+	ldx #$FE2F      ; BRA *-2
+
+; Signal to SPC that we're ready
+	lda APUIO0
+	inc
+	inc
+	sta APUIO0
+
+; Wait for acknowledgement
+-
+	cmp APUIO0
+	bne -
+
+; Quickly write branch
+	stx APUIO2
+
+	rts
+;---------------------------------------
+exec_instr:
+; Replace instruction
+	stx APUIO0
+	lda #$FC
+	sta APUIO3      ; 30
+
+        ; SPC BRA loop takes 4 cycles, so it reads
+        ; the branch offset every 4 SPC cycles (84 master).
+        ; We must handle the case where it read just before
+        ; the write above, and when it reads just after it.
+        ; If it reads just after, we have at least 7 SPC
+        ; cycles (147 master) to change restore the branch
+        ; offset.
+
+        ; 48 minimum, 90 maximum
+        ora #0
+        ora #0
+        ora #0
+        nop
+        nop
+        nop
+
+        ; 66 delay, about the middle of the above limits
+	phd ;4
+	pld ;5
+
+        ; Give plenty of extra time if single execution
+        ; isn't needed, as this avoids such tight timing
+        ; requirements.
+
+;	phd ;4
+;	pld ;5
+;	phd ;4
+;	pld ;5
+
+        ; Patch loop to skip first two bytes
+	lda #$FE        ; 16
+	sta APUIO3      ; 30
+
+        ; 38 minimum (assuming 66 delay above)
+	phd ; 4
+	pld ; 5
+
+        ; Give plenty of extra time if single execution
+        ; isn't needed, as this avoids such tight timing
+        ; requirements.
+
+	phd
+	pld
+	phd
+	pld
+	rts