diff --git a/monkey/Makefile b/monkey/Makefile index b03be5a2..2e4d172d 100644 --- a/monkey/Makefile +++ b/monkey/Makefile @@ -36,7 +36,9 @@ loader.o: loader.s TITLE: title.o ld65 -o TITLE title.o -C ../linker_scripts/apple2_2000.inc -title.o: title.s +title.o: title.s \ + graphics_intro/title_graphics.inc \ + interrupt_handler.s mockingboard.s ym_play.s ca65 -o title.o title.s -l title.lst @@ -59,6 +61,9 @@ monkey.o: monkey.s zp.inc hardware.inc common_defines.inc \ graphics/graphics.inc: cd graphics && make +graphics_intro/title_graphics.inc: + cd graphics_intro && make + #### clean: diff --git a/monkey/interrupt_handler.s b/monkey/interrupt_handler.s new file mode 100644 index 00000000..85f3a7fb --- /dev/null +++ b/monkey/interrupt_handler.s @@ -0,0 +1,301 @@ +; This plays KRG files, stripped down ym5 files +; this is a limited format: the envelope values are ignored +; the fields with don't-care values are packed together +; they are played at 25Hz + +; FRAME0 = AFINE (r0) +; FRAME1 = BFINE (r2) +; FRAME2 = CFINE (r4) +; FRAME3 = NOISE PERIOD (r6) +; FRAME4 = ENABLE (r7) +; FRAME5 = ACOARSE/BCOARSE (r1/r3) +; FRAME6 = CCOARSE/AAMP (r5/r8) +; FRAME7 = BAMP/CAMP (r9/r10) + + ;================================ + ;================================ + ; mockingboard interrupt handler + ;================================ + ;================================ + ; On Apple II/6502 the interrupt handler jumps to address in 0xfffe + ; This is in the ROM, which saves the registers + ; on older IIe it saved A to $45 (which could mess with DISK II) + ; newer IIe doesn't do that. + ; It then calculates if it is a BRK or not (which trashes A) + ; Then it sets up the stack like an interrupt and calls 0x3fe + +CHUNKSIZE = 15 ; hardcoded, based on krg file + +interrupt_handler: + php + pha ; save A ; 3 + txa + pha + tya + pha + + inc $0404 ; debug (flashes char onscreen) + + bit $C404 ; clear 6522 interrupt by reading T1C-L ; 4 + + + lda DONE_PLAYING ; 3 + beq mb_play_music ; if song done, don't play music ; 3/2nt + jmp done_interrupt ; 3 + ;============ + ; 13 + +mb_play_music: + + + ;====================================== + ; Write frames to Mockingboard + ;====================================== + ; actually plays frame loaded at end of + ; last interrupt, so 20ms behind? + +mb_write_frame: + + ;================================== + ; loop through the 11 registers + ; reading the value, then write out + ;================================== + ldx #0 ; set up reg count ; 2 + +mb_write_loop_left: + lda REGISTER_DUMP,X ; load register value ; 4 + cmp REGISTER_OLD,X ; compare with old values ; 4 + beq mb_no_write_left ; 3/2nt + ;============= + ; typ 11 + + ; address + stx MOCK_6522_ORA1 ; put address on PA1 ; 4 + lda #MOCK_AY_LATCH_ADDR ; latch_address for PB1 ; 2 + sta MOCK_6522_ORB1 ; latch_address on PB1 ; 4 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 4 + + ; value + lda REGISTER_DUMP,X ; load register value ; 4 + sta MOCK_6522_ORA1 ; put value on PA1 ; 4 + lda #MOCK_AY_WRITE ; ; 2 + sta MOCK_6522_ORB1 ; write on PB1 ; 4 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 4 + + ;=========== + ; 36 +mb_no_write_left: + inx ; point to next register ; 2 + cpx #11 ; if 11 we're done ; 2 + bmi mb_write_loop_left ; otherwise, loop ; 3/2nt + ;============ + ; 7 + + ldx #0 ; set up reg count ; 2 +mb_write_loop_right: + lda REGISTER_DUMP,X ; load register value ; 4 + cmp REGISTER_OLD,X ; compare with old values ; 4 + beq mb_no_write_right ; 3/2nt + ;============= + ; typ 11 + + ; address + stx MOCK_6522_ORA2 ; put address on PA2 ; 4 + lda #MOCK_AY_LATCH_ADDR ; latch_address for PB1 ; 2 + sta MOCK_6522_ORB2 ; latch_address on PB2 ; 4 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB2 ; 4 + + ; value + lda REGISTER_DUMP,X ; load register value ; 4 + sta MOCK_6522_ORA2 ; put value on PA2 ; 4 + lda #MOCK_AY_WRITE ; ; 2 + sta MOCK_6522_ORB2 ; write on PB2 ; 4 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB2 ; 4 + + ;=========== + ; 36 +mb_no_write_right: + inx ; point to next register ; 2 + cpx #11 ; if 11 we're done ; 2 + bmi mb_write_loop_right ; otherwise, loop ; 3/2nt + ;============ + ; 7 + + ;===================================== + ; Copy registers to old + ;===================================== + ; 11 coming in + + ldx #10 ; 2 +mb_reg_copy: + lda REGISTER_DUMP,X ; load register value ; 4 + sta REGISTER_OLD,X ; compare with old values ; 4 + dex ; 2 + bpl mb_reg_copy ; 2nt/3 + ;============= + ; 171 + + ;=================================== + ; Load all 11 registers in advance + ;=================================== + ; note, assuming not cross page boundary, not any slower + ; then loading from zero page? + +mb_load_values: + + ldy MB_CHUNK_OFFSET ; get chunk offset ; 3 + + ; afine + lda (MB_ADDRL),y ; load register value ; 5 + sta A_FINE_TONE ; 3 + clc ; point to next interleaved ; 2 + lda MB_ADDRH ; page by adding CHUNKSIZE ; 3 + adc #CHUNKSIZE ; 3 + sta MB_ADDRH ; 3 + + ; bfine + lda (MB_ADDRL),y ; load register value ; 5 + sta B_FINE_TONE ; 3 + clc ; point to next interleaved ; 2 + lda MB_ADDRH ; page by adding CHUNKSIZE ; 3 + adc #CHUNKSIZE ; 3 + sta MB_ADDRH ; 3 + + ; cfine + lda (MB_ADDRL),y ; load register value ; 5 + sta C_FINE_TONE ; 3 + clc ; point to next interleaved ; 2 + lda MB_ADDRH ; page by adding CHUNKSIZE ; 3 + adc #CHUNKSIZE ; 3 + sta MB_ADDRH ; 3 + + ; noise + lda (MB_ADDRL),y ; load register value ; 5 + sta NOISE ; 3 + clc ; point to next interleaved ; 2 + lda MB_ADDRH ; page by adding CHUNKSIZE ; 3 + adc #CHUNKSIZE ; 3 + sta MB_ADDRH ; 3 + + ; enable + lda (MB_ADDRL),y ; load register value ; 5 + sta ENABLE ; 3 + clc ; point to next interleaved ; 2 + lda MB_ADDRH ; page by adding CHUNKSIZE ; 3 + adc #CHUNKSIZE ; 3 + sta MB_ADDRH ; 3 + + ; acoarse/bcoarse + lda (MB_ADDRL),y ; load register value ; 5 + and #$f ; 2 + sta B_COARSE_TONE ; 3 + lda (MB_ADDRL),y ; load register value ; 5 + lsr ; 2 + lsr ; 2 + lsr ; 2 + lsr ; 2 + sta A_COARSE_TONE ; 3 + clc ; point to next interleaved ; 2 + lda MB_ADDRH ; page by adding CHUNKSIZE ; 3 + adc #CHUNKSIZE ; 3 + sta MB_ADDRH ; 3 + + ; CCOARSE/AAMP + lda (MB_ADDRL),y ; load register value ; 5 + and #$f ; 2 + sta A_VOLUME ; 3 + lda (MB_ADDRL),y ; load register value ; 5 + lsr ; 2 + lsr ; 2 + lsr ; 2 + lsr ; 2 + sta C_COARSE_TONE ; 3 + clc ; point to next interleaved ; 2 + lda MB_ADDRH ; page by adding CHUNKSIZE ; 3 + adc #CHUNKSIZE ; 3 + sta MB_ADDRH ; 3 + inx ; point to next register ; 2 + + ; BAMP/CAMP + lda (MB_ADDRL),y ; load register value ; 5 + and #$f ; 2 + sta C_VOLUME ; 3 + lda (MB_ADDRL),y ; load register value ; 5 + lsr ; 2 + lsr ; 2 + lsr ; 2 + lsr ; 2 + sta B_VOLUME ; 3 + + ;========================================= + ; if NOISE is $ff then we are done + + lda NOISE ; 3 + cmp #$ff + bne mb_not_done +; bpl mb_not_done ; 3/2nt + +; lda #1 + sta DONE_PLAYING + jsr clear_ay_both + + jmp done_interrupt ; 3 + ;=========== + ; typ 6 +mb_not_done: + + ;============================================== + ; incremement offset. If 0 move to next chunk + ;============================================== + +increment_offset: + + inc MB_CHUNK_OFFSET ; increment offset ; 5 + bne increment_done ; if not zero, done ; 3/2nt + + inc WHICH_CHUNK + lda WHICH_CHUNK + cmp #CHUNKSIZE + bne increment_done + + lda #0 + sta WHICH_CHUNK + +increment_done: + + lda #>music_start + clc + adc WHICH_CHUNK + sta MB_ADDRH + + ;================================= + ; Finally done with this interrupt + ;================================= + +done_interrupt: + + +exit_interrupt: + pla + tay + pla + tax + + pla + + lda $45 + plp + + rti ; return from interrupt ; 6 + + ;============ + ; typical + ; ???? cycles + + +REGISTER_OLD: + .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0 diff --git a/monkey/keyboard.s b/monkey/keyboard.s index 74c0d36e..ce976c0d 100644 --- a/monkey/keyboard.s +++ b/monkey/keyboard.s @@ -348,3 +348,4 @@ done_split: lda #$c ; load to page $c00 jsr decompress_lzsa2_fast + rts diff --git a/monkey/mockingboard.s b/monkey/mockingboard.s new file mode 100644 index 00000000..a9a5c44d --- /dev/null +++ b/monkey/mockingboard.s @@ -0,0 +1,162 @@ +; Mockingboad programming: +; + Has two 6522 I/O chips connected to two AY-3-8910 chips +; + Optionally has some speech chips controlled via the outport on the AY +; + Often in slot 4 +; TODO: how to auto-detect? +; References used: +; http://macgui.com/usenet/?group=2&id=8366 +; 6522 Data Sheet +; AY-3-8910 Data Sheet + +;======================== +; Mockingboard card +; Essentially two 6522s hooked to the Apple II bus +; Connected to AY-3-8910 chips +; PA0-PA7 on 6522 connected to DA0-DA7 on AY +; PB0 on 6522 connected to BC1 +; PB1 on 6522 connected to BDIR +; PB2 on 6522 connected to RESET + + +; left speaker +MOCK_6522_ORB1 = $C400 ; 6522 #1 port b data +MOCK_6522_ORA1 = $C401 ; 6522 #1 port a data +MOCK_6522_DDRB1 = $C402 ; 6522 #1 data direction port B +MOCK_6522_DDRA1 = $C403 ; 6522 #1 data direction port A + +; right speaker +MOCK_6522_ORB2 = $C480 ; 6522 #2 port b data +MOCK_6522_ORA2 = $C481 ; 6522 #2 port a data +MOCK_6522_DDRB2 = $C482 ; 6522 #2 data direction port B +MOCK_6522_DDRA2 = $C483 ; 6522 #2 data direction port A + +; AY-3-8910 commands on port B +; RESET BDIR BC1 +MOCK_AY_RESET = $0 ; 0 0 0 +MOCK_AY_INACTIVE = $4 ; 1 0 0 +MOCK_AY_READ = $5 ; 1 0 1 +MOCK_AY_WRITE = $6 ; 1 1 0 +MOCK_AY_LATCH_ADDR = $7 ; 1 1 1 + + + ;======================== + ; Mockingboard Init + ;======================== + ; Initialize the 6522s + ; set the data direction for all pins of PortA/PortB to be output + +mockingboard_init: + lda #$ff ; all output (1) + sta MOCK_6522_DDRB1 + sta MOCK_6522_DDRA1 + sta MOCK_6522_DDRB2 + sta MOCK_6522_DDRA2 + rts + + ;====================== + ; Reset Left AY-3-8910 + ;====================== +reset_ay_both: + lda #MOCK_AY_RESET + sta MOCK_6522_ORB1 + lda #MOCK_AY_INACTIVE + sta MOCK_6522_ORB1 + + ;====================== + ; Reset Right AY-3-8910 + ;====================== +;reset_ay_right: + lda #MOCK_AY_RESET + sta MOCK_6522_ORB2 + lda #MOCK_AY_INACTIVE + sta MOCK_6522_ORB2 + rts + + +; Write sequence +; Inactive -> Latch Address -> Inactive -> Write Data -> Inactive + + ;========================================= + ; Write Right/Left to save value AY-3-8910 + ;========================================= + ; register in X + ; value in MB_VALUE + +write_ay_both: + ; address + stx MOCK_6522_ORA1 ; put address on PA1 ; 3 + stx MOCK_6522_ORA2 ; put address on PA2 ; 3 + lda #MOCK_AY_LATCH_ADDR ; latch_address on PB1 ; 2 + sta MOCK_6522_ORB1 ; latch_address on PB1 ; 3 + sta MOCK_6522_ORB2 ; latch_address on PB2 ; 3 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 3 + sta MOCK_6522_ORB2 ; 3 + + ; value + lda MB_VALUE ; 3 + sta MOCK_6522_ORA1 ; put value on PA1 ; 3 + sta MOCK_6522_ORA2 ; put value on PA2 ; 3 + lda #MOCK_AY_WRITE ; ; 2 + sta MOCK_6522_ORB1 ; write on PB1 ; 3 + sta MOCK_6522_ORB2 ; write on PB2 ; 3 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 3 + sta MOCK_6522_ORB2 ; 3 + + rts ; 6 + ;=========== + ; 53 + ;======================================= + ; clear ay -- clear all 14 AY registers + ; should silence the card + ;======================================= +clear_ay_both: + ldx #14 + lda #0 + sta MB_VALUE +clear_ay_left_loop: + jsr write_ay_both + dex + bpl clear_ay_left_loop + rts + + ;======================================= + ; Detect a Mockingboard card in Slot4 + ;======================================= + ; Based on code from the French Touch "Pure Noise" Demo + ; Attempts to time an instruction sequence with a 6522 + ; + ; MB_ADDRL:MB_ADDRH has address of Mockingboard + ; returns X=0 if not found, X=1 if found + +mockingboard_detect_slot4: + lda #0 + sta MB_ADDRL + +mb4_detect_loop: ; self-modifying + lda #$04 ; we're only looking in Slot 4 + ora #$C0 ; make it start with C + sta MB_ADDRH + ldy #04 ; $CX04 + ldx #02 ; 2 tries? +mb4_check_cycle_loop: + lda (MB_ADDRL),Y ; timer 6522 (Low Order Counter) + ; count down + sta TEMP ; 3 cycles + lda (MB_ADDRL),Y ; + 5 cycles = 8 cycles + ; between the two accesses to the timer + sec + sbc TEMP ; subtract to see if we had 8 cycles + cmp #$f8 ; -8 + bne mb4_not_in_this_slot + dex ; decrement, try one more time + bne mb4_check_cycle_loop ; loop detection + inx ; Mockingboard found (X=1) +done_mb4_detect: + rts ; return + +mb4_not_in_this_slot: + ldx #00 + beq done_mb4_detect + diff --git a/monkey/title.s b/monkey/title.s index f18f1701..8d0a3d85 100644 --- a/monkey/title.s +++ b/monkey/title.s @@ -7,6 +7,8 @@ .include "hardware.inc" .include "common_defines.inc" +music_start = $4800 + title_start: ;=================== ; init screen @@ -17,12 +19,30 @@ title_start: bit SET_GR bit PAGE0 bit LORES - bit TEXTGR + bit FULLGR + + lda #0 + sta clear_all_color+1 + jsr clear_all lda #0 sta ANIMATE_FRAME sta FRAMEL sta FRAMEH + sta DRAW_PAGE + sta DISP_PAGE + +setup_music: + ; decompress music + + lda #theme_lzsa + sta LZSA_SRC_HI + lda #$48 ; load to page $4800 + jsr decompress_lzsa2_fast + + jsr mockingboard_detect title_loop: @@ -30,6 +50,14 @@ title_loop: ; load LF logo ;==================================== + lda #logo_lzsa + sta LZSA_SRC_HI + lda #$c ; load to page $c00 + jsr decompress_lzsa2_fast + + jsr gr_copy_to_current logo_loop: @@ -64,6 +92,30 @@ room_frame_no_oflo: jsr wait_until_keypressed + ;==================================== + ; load LF logo + ;==================================== + + lda #title_lzsa + sta LZSA_SRC_HI + lda #$c ; load to page $c00 + jsr decompress_lzsa2_fast + + + jsr gr_copy_to_current + + jsr wait_until_keypressed + + ;========================== + ; turn off music + ;========================== + + sei ; clear interrupts + + jsr clear_ay_both + ;========================== ; load main program ;========================== @@ -78,22 +130,20 @@ room_frame_no_oflo: ;========================== ; level graphics -; .include "graphics/graphics.inc" + .include "graphics_intro/title_graphics.inc" - ; level data -; .include "leveldata_monkey.inc" ; .include "end_level.s" ; .include "text_print.s" .include "gr_offsets.s" -; .include "gr_fast_clear.s" + .include "gr_fast_clear.s" ; .include "keyboard.s" .include "gr_copy.s" ; .include "gr_putsprite_crop.s" ; .include "joystick.s" ; .include "gr_pageflip.s" -; .include "decompress_fast_v2.s" + .include "decompress_fast_v2.s" ; .include "draw_pointer.s" ; .include "common_sprites.inc" ; .include "guy.brush" @@ -101,10 +151,24 @@ room_frame_no_oflo: ; .include "monkey_actions.s" ; .include "update_bottom.s" + .include "ym_play.s" + .include "interrupt_handler.s" + .include "mockingboard.s" + wait_until_keypressed: lda KEYPRESS - bmi wait_until_keypressed + bpl wait_until_keypressed bit KEYRESET rts + + +; music is compressed +; decompressed it is 30720 bytes +; we decompress to $4800 +; so total size of our code can't be biggr than $2800 = 10k + +theme_lzsa: +.incbin "music/theme.lzsa" + diff --git a/monkey/ym_play.s b/monkey/ym_play.s new file mode 100644 index 00000000..e075ec67 --- /dev/null +++ b/monkey/ym_play.s @@ -0,0 +1,87 @@ + +mockingboard_detect: + + ;================================ + ; Mockingboard detect + ;================================ + + jsr mockingboard_detect_slot4 ; call detection routine + stx MB_DETECTED + + ;================================ + ; Mockingboard start + ;================================ + +mockingboard_setup: + sei ; disable interrupts just in case + + jsr mockingboard_init + jsr reset_ay_both + jsr clear_ay_both + + ;========================= + ; Setup Interrupt Handler + ;========================= + ; Vector address goes to 0x3fe/0x3ff + ; FIXME: should chain any existing handler + + lda #interrupt_handler + sta $03ff + + ;============================ + ; Enable 50Hz clock on 6522 + ;============================ + + lda #$40 ; Continuous interrupts, don't touch PB7 + sta $C40B ; ACR register + lda #$7F ; clear all interrupt flags + sta $C40E ; IER register (interrupt enable) + + lda #$C0 + sta $C40D ; IFR: 1100, enable interrupt on timer one oflow + sta $C40E ; IER: 1100, enable timer one interrupt + + lda #$e7 + sta $C404 ; write into low-order latch + lda #$4f + sta $C405 ; write into high-order latch, + ; load both values into counter + ; clear interrupt and start counting + + ; 4fe7 / 1e6 = .020s, 50Hz + ; 9c40 / 1e6 = .040s, 25Hz + + ;============================ + ; Start Playing + ;============================ +main_loop: + lda MB_DETECTED + beq mockingboard_setup_done + + lda #0 + sta DONE_PLAYING + sta WHICH_CHUNK + sta MB_CHUNK_OFFSET + sta MB_ADDRL ; we are aligned, so should be 0 + + lda #>music_start + sta MB_ADDRH + + ;===================================== + ; clear register area + ;===================================== + ldx #13 ; 2 + lda #0 ; 2 +mb_setup_clear_reg: + sta REGISTER_DUMP,X ; clear register value ; 4 + sta REGISTER_OLD,X ; clear old values ; 4 + dex ; 2 + bpl mb_setup_clear_reg ; 2nt/3 + + cli ; start interrupts + +mockingboard_setup_done: + + rts diff --git a/monkey/zp.inc b/monkey/zp.inc index 5785d46f..9d0aff33 100644 --- a/monkey/zp.inc +++ b/monkey/zp.inc @@ -22,11 +22,12 @@ MASK = $2E COLOR_MASK = $2F COLOR = $30 - SEEDL = $4e SEEDH = $4f XMAX = $50 + + ; MIST zero page addresses FRAMEL = $60 @@ -45,6 +46,7 @@ BTC_L = $6C BTC_H = $6D ; pt3 player registers +REGISTER_DUMP = $70 AY_REGISTERS = $70 A_FINE_TONE = $70 A_COARSE_TONE = $71 @@ -61,8 +63,9 @@ C_VOLUME = $7A ENVELOPE_FINE = $7B ENVELOPE_COARSE = $7C ENVELOPE_SHAPE = $7D -PATTERN_L = $7E -PATTERN_H = $7F +COPY_OFFSET = $7E +DECODER_STATE = $7F + ; note 70-7f also used by disk code @@ -114,23 +117,15 @@ DISP_PAGE = $ED ; ALL DRAW_PAGE = $EE ; ALL ; rest of pt3_player -PT3_TEMP = $EF -ORNAMENT_L = $F0 -ORNAMENT_H = $F1 -SAMPLE_L = $F2 -SAMPLE_H = $F3 -LOOP = $F4 -MB_VALUE = $F5 -MB_ADDR_L = $F6 -MB_ADDR_H = $F7 -DONE_PLAYING = $F8 -DONE_SONG = $F9 - -;TINL = $F0 -;TINH = $F1 -;BINL = $F2 -;BINH = $F3 -;SCROLL_COUNT = $F9 +MB_DETECTED = $EF +WHICH_CHUNK = $F0 +MB_CHUNK_OFFSET = $F1 +LOOP = $F4 +MB_VALUE = $F5 +MB_ADDRL = $F6 +MB_ADDRH = $F7 +DONE_PLAYING = $F8 +DONE_SONG = $F9 TEMP = $FA TEMPY = $FB