monkey: play some music

This commit is contained in:
Vince Weaver 2020-09-17 01:12:40 -04:00
parent c1ee40a69b
commit 489a9e5d52
7 changed files with 643 additions and 28 deletions

View File

@ -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:

301
monkey/interrupt_handler.s Normal file
View File

@ -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

View File

@ -348,3 +348,4 @@ done_split:
lda #$c ; load to page $c00
jsr decompress_lzsa2_fast
rts

162
monkey/mockingboard.s Normal file
View File

@ -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

View File

@ -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_LO
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_LO
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_LO
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"

87
monkey/ym_play.s Normal file
View File

@ -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 $03fe
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

View File

@ -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