diff --git a/pt3_player/Makefile b/pt3_player/Makefile index b0617817..b6a33bb1 100644 --- a/pt3_player/Makefile +++ b/pt3_player/Makefile @@ -8,12 +8,13 @@ all: pt3_player.dsk $(DOS33): cd ../dos33fs-utils && make -pt3_player.dsk: PT3_PLAYER PT3_DUMPER HELLO DUMP +pt3_player.dsk: PT3_PLAYER PT3_DUMPER PT3_TIMER HELLO DUMP cp empty.dsk pt3_player.dsk $(DOS33) -y pt3_player.dsk SAVE A HELLO $(DOS33) -y pt3_player.dsk SAVE A DUMP $(DOS33) -y pt3_player.dsk BSAVE -a 0x1000 PT3_DUMPER $(DOS33) -y pt3_player.dsk BSAVE -a 0x2000 PT3_PLAYER + $(DOS33) -y pt3_player.dsk BSAVE -a 0x2000 PT3_TIMER $(DOS33) -y pt3_player.dsk BSAVE -a 0x4000 EA.PT3 $(DOS33) -y pt3_player.dsk BSAVE -a 0x4000 VC.PT3 $(DOS33) -y pt3_player.dsk BSAVE -a 0x4000 SR.PT3 @@ -42,18 +43,26 @@ HELLO: hello.bas DUMP: dump.bas ../asoft_basic-utils/tokenize_asoft < dump.bas > DUMP + +# + PT3_PLAYER: pt3_player.o ld65 -o PT3_PLAYER pt3_player.o -C ../linker_scripts/apple2_2000.inc pt3_player.o: pt3_player.s \ gr_fast_clear.s pt3_lib.s interrupt_handler.s zp.inc -# mockingboard.s \ -# keypress_minimal.s \ -# qkumba_rts.s \ -# rasterbars.s volume_bars.s interrupt_handler.s \ -# song_list.inc chip_title.inc zp.inc chip_title_uncompressed.inc ca65 -o pt3_player.o pt3_player.s -l pt3_player.lst +# + +PT3_TIMER: pt3_timer.o + ld65 -o PT3_TIMER pt3_timer.o -C ../linker_scripts/apple2_2000.inc + +pt3_timer.o: pt3_timer.s \ + gr_fast_clear.s pt3_lib.s interrupt_handler.s zp.inc + ca65 -o pt3_timer.o pt3_timer.s -l pt3_timer.lst + +# PT3_DUMPER: pt3_dumper.o ld65 -o PT3_DUMPER pt3_dumper.o -C ../linker_scripts/apple2_1000.inc @@ -65,5 +74,5 @@ pt3_dumper.o: pt3_dumper.s \ clean: rm -f *~ TITLE.GR *.o *.lst \ - PT3_PLAYER + PT3_PLAYER PT3_TIMER PT3_DUMPER diff --git a/pt3_player/README.pt3 b/pt3_player/README.pt3_player similarity index 100% rename from pt3_player/README.pt3 rename to pt3_player/README.pt3_player diff --git a/pt3_player/mockingboard_a.s b/pt3_player/mockingboard_a.s new file mode 100644 index 00000000..fff7ca78 --- /dev/null +++ b/pt3_player/mockingboard_a.s @@ -0,0 +1,206 @@ +; 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 EQU $C400 ; 6522 #1 port b data +MOCK_6522_ORA1 EQU $C401 ; 6522 #1 port a data +MOCK_6522_DDRB1 EQU $C402 ; 6522 #1 data direction port B +MOCK_6522_DDRA1 EQU $C403 ; 6522 #1 data direction port A + +; right speaker +MOCK_6522_ORB2 EQU $C480 ; 6522 #2 port b data +MOCK_6522_ORA2 EQU $C481 ; 6522 #2 port a data +MOCK_6522_DDRB2 EQU $C482 ; 6522 #2 data direction port B +MOCK_6522_DDRA2 EQU $C483 ; 6522 #2 data direction port A + +; AY-3-8910 commands on port B +; RESET BDIR BC1 +MOCK_AY_RESET EQU $0 ; 0 0 0 +MOCK_AY_INACTIVE EQU $4 ; 1 0 0 +MOCK_AY_READ EQU $5 ; 1 0 1 +MOCK_AY_WRITE EQU $6 ; 1 1 0 +MOCK_AY_LATCH_ADDR EQU $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 + ;======================================= + ; Based on code from the French Touch "Pure Noise" Demo + ; Attempts to time an instruction sequence with a 6522 + ; + ; If found, puts in bMB + ; MB_ADDRL:MB_ADDRH has address of Mockingboard + ; returns X=0 if not found, X=1 if found + +mockingboard_detect: + lda #0 + sta MB_ADDRL + +mb_detect_loop: ; self-modifying + lda #$07 ; we start in slot 7 ($C7) and go down to 0 ($C0) + ora #$C0 ; make it start with C + sta MB_ADDRH + ldy #04 ; $CX04 + ldx #02 ; 2 tries? +mb_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 mb_not_in_this_slot + dex ; decrement, try one more time + bne mb_check_cycle_loop ; loop detection + inx ; Mockingboard found (X=1) +done_mb_detect: + ;stx bMB ; store result to bMB + rts ; return + +mb_not_in_this_slot: + dec mb_detect_loop+1 ; decrement the "slot" (self_modify) + bne mb_detect_loop ; loop down to one + ldx #00 + beq done_mb_detect + + + ;======================================= + ; 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/pt3_player/pt3_player.s b/pt3_player/pt3_player.s index 5dfc56e2..328ab763 100644 --- a/pt3_player/pt3_player.s +++ b/pt3_player/pt3_player.s @@ -584,7 +584,7 @@ song_list: ;========= .include "../asm_routines/gr_offsets.s" .include "text_print.s" -.include "../asm_routines/mockingboard_a.s" +.include "mockingboard_a.s" .include "gr_fast_clear.s" .include "../asm_routines/pageflip.s" ;.include "../asm_routines/gr_unrle.s" diff --git a/pt3_player/pt3_timer.s b/pt3_player/pt3_timer.s new file mode 100644 index 00000000..79028a30 --- /dev/null +++ b/pt3_player/pt3_timer.s @@ -0,0 +1,463 @@ +; VMW Chiptune Player + +.include "zp.inc" + +PT3_LOC = $4000 + +NUM_FILES EQU 15 + + + ;============================= + ; Setup + ;============================= +pt3_setup: + jsr HOME + jsr TEXT + + ; Init disk code + + jsr rts_init + + ; init variables + + lda #0 + sta DRAW_PAGE + sta DONE_PLAYING + sta WHICH_FILE + + jsr mockingboard_detect_slot4 ; call detection routine + cpx #$1 + beq mockingboard_found + + +mockingboard_found: + + ;============================ + ; Init the Mockingboard + ;============================ + + 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 + ;============================ + + sei ; disable interrupts just in case + + 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 + + ;================== + ; load first song + ;================== + + jsr new_song + + ;============================ + ; Enable 6502 interrupts + ;============================ + + cli ; clear interrupt mask + + + ;============================ + ; Loop forever + ;============================ +main_loop: + ; play song as fast as possible + + jsr interrupt_simulator + +check_done: + lda #$ff + bit DONE_PLAYING + beq main_loop ; if was all zeros, loop + ; right pressed + + +done_play: + sei ; disable interrupts + + ; print results + + jsr CROUT + jsr CROUT + lda timer_seconds_h + jsr PRBYTE + lda timer_seconds_l + jsr PRBYTE + lda #'.'+$80 + jsr COUT + lda timer_fractions + jsr PRBYTE + jsr CROUT + jsr CROUT + + lda #0 + sta DONE_PLAYING + + +forever_loop: + jmp forever_loop + + + + ;================= + ; load a new song + ;================= + +new_song: + + ;========================= + ; Init Variables + ;========================= + + lda #$0 + sta FRAME_COUNT + + + ;=========================== + ; Load in PT3 file + ;=========================== + + jsr get_filename + + ; needs to be space-padded $A0 30-byte filename + + lda #readfile_filename + sta namhi + + ldy #0 + ldx #30 ; 30 chars +name_loop: + lda (INL),Y + beq space_loop + ora #$80 + sta (namlo),Y + iny + dex + bne name_loop + beq done_name_loop +space_loop: + lda #$a0 ; pad with ' ' + sta (namlo),Y + iny + dex + bne space_loop + +done_name_loop: + + ; open and read a file + ; loads to whatever it was BSAVED at (default is $2000) + + jsr read_file ; read PT3 file from disk + + + ;========================= + ; Print Info + ;========================= + + ; re-init, as we've run through it + lda #0 + sta DONE_PLAYING + + jsr pt3_init_song + + rts + + + + + + ;================== + ; Get filename + ;================== + ; WHICH_FILE holds number + ; MAX_FILES has max + ; Scroll through until find + ; point INH:INL to it +get_filename: + + ldy #0 + ldx WHICH_FILE + + lda #song_list + sta INH + +get_filename_loop: + cpx #0 + beq filename_found + +inner_loop: + iny + lda (INL),Y + bne inner_loop + + iny + + dex + jmp get_filename_loop + +filename_found: + tya + clc + adc INL + sta INL + lda INH + adc #0 + sta INH + + rts + + ;=============================== + ; Increment file we want to load + ;=============================== +increment_file: + inc WHICH_FILE + lda WHICH_FILE + cmp #NUM_FILES + bne done_increment + lda #0 + sta WHICH_FILE +done_increment: + rts + + ;=============================== + ; Decrement file we want to load + ;=============================== +decrement_file: + dec WHICH_FILE + bpl done_decrement + lda #(NUM_FILES-1) + sta WHICH_FILE +done_decrement: + rts + + +;========== +; filenames +;========== + +song_list: + +.include "song_list.inc" + +;========= +;routines +;========= +.include "mockingboard_a.s" +.include "qkumba_rts.s" +.include "pt3_lib.s" + + + + +TIME_OFFSET EQU 13 + + + +timer_fractions: .byte $0 +timer_seconds_l: .byte $0 +timer_seconds_h: .byte $0 + + ;====================================== + ; simply time things with a 50Hz clock + +interrupt_handler: +; pha ; save A ; 3 + ; A is saved in $45 by firmware + txa + pha ; save X + tya + pha ; save Y + + bit $C404 ; clear 6522 interrupt by reading T1C-L ; 4 + +count_time: + + inc timer_fractions + lda timer_fractions + cmp #50 ; 50Hz + + bne done_count_time + + lda #$0 + sta timer_fractions + + inc timer_seconds_l + bne done_count_time + + inc timer_seconds_h + +done_count_time: + + +exit_interrupt_handler: + +; pla ; restore a ; 4 + + pla + tay ; restore Y + pla + tax ; restore X + lda $45 ; restore A + + + rti ; return from interrupt ; 6 + + + + + + + + +interrupt_simulator: +; pha ; save A ; 3 + ; A is saved in $45 by firmware + txa + pha ; save X + tya + pha ; save Y + +; bit $C404 ; clear 6522 interrupt by reading T1C-L ; 4 + + lda DONE_PLAYING ; 3 + beq pt3_play_music ; if song done, don't play music ; 3/2nt + jmp check_keyboard ; 3 + ;============ + ; 13 + +pt3_play_music: + + jsr pt3_make_frame + + ;====================================== + ; Write frames to Mockingboard + ;====================================== + ; actually plays frame loaded at end of + ; last interrupt, so 20ms behind? + +mb_write_frame: + + + ldx #0 ; set up reg count ; 2 + ;============ + ; 2 + + ;================================== + ; loop through the 14 registers + ; reading the value, then write out + ;================================== + +mb_write_loop: + lda REGISTER_DUMP,X ; load register value ; 4 + + ; special case R13. If it is 0xff, then don't update + ; otherwise might spuriously reset the envelope settings + + cpx #13 ; 2 + bne mb_not_13 ; 3/2nt + cmp #$ff ; 2 + beq mb_skip_13 ; 3/2nt + ;============ + ; typ 5 +mb_not_13: + + + ; address + stx MOCK_6522_ORA1 ; put address on PA1 ; 4 + stx MOCK_6522_ORA2 ; put address on PA2 ; 4 + lda #MOCK_AY_LATCH_ADDR ; latch_address for PB1 ; 2 + sta MOCK_6522_ORB1 ; latch_address on PB1 ; 4 + sta MOCK_6522_ORB2 ; latch_address on PB2 ; 4 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 4 + sta MOCK_6522_ORB2 ; 4 + + ; value + lda REGISTER_DUMP,X ; load register value ; 4 + sta MOCK_6522_ORA1 ; put value on PA1 ; 4 + sta MOCK_6522_ORA2 ; put value on PA2 ; 4 + lda #MOCK_AY_WRITE ; ; 2 + sta MOCK_6522_ORB1 ; write on PB1 ; 4 + sta MOCK_6522_ORB2 ; write on PB2 ; 4 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 4 + sta MOCK_6522_ORB2 ; 4 + ;=========== + ; 62 +mb_no_write: + inx ; point to next register ; 2 + cpx #14 ; if 14 we're done ; 2 + bmi mb_write_loop ; otherwise, loop ; 3/2nt + ;============ + ; 7 +mb_skip_13: + + + ;================================= + ; Finally done with this interrupt + ;================================= + +done_interrupt: + +update_time: + +done_time: + +check_keyboard: + jmp exit_interrupt + +quiet_exit: + sta DONE_PLAYING + jsr clear_ay_both + +exit_interrupt: + +; pla ; restore a ; 4 + + pla + tay ; restore Y + pla + tax ; restore X + lda $45 ; restore A + + + rts ; return from interrupt ; 6 + + + +