pt3_lib: update to version 0.2

includes more documentation and Mockingboard slot detection
This commit is contained in:
Vince Weaver 2019-12-28 22:35:14 -05:00
parent 85e8388035
commit ebd8329600
8 changed files with 266 additions and 38 deletions

View File

@ -1,17 +1,106 @@
The PT3_player Library
~~~~~~~~~~~~~~~~~~~~~~
The PT3_player Library version 0.2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
by Vince "Deater" Weaver
7 June 2019
http://www.deater.net/weave/vmwprod/pt3_player/
by Vince "Deater" Weaver <vince@deater.net>
http://www.deater.net/weave/vmwprod/pt3_lib/
Last Update: 28 December 2019
Plays Vortex Tracker II .pt3 files on the Apple II
Background:
~~~~~~~~~~~
This code is meant as a relatively simple, reasonably optimized version
of the PT3 Vortex-Tracker player for use in other programs.
The orignal player code can be found in ../pt3_player/
That codebase is being *extremely* optimized to the point it's no longer
A much more optimized version can be found in ../pt3_player/
That codebase has been *extremely* optimized to the point it's no longer
very straightforward to reuse the code.
For some more background on this you can watch the talk I gave
at Demosplash 2019 on this topic.
What is a PT3 file?
~~~~~~~~~~~~~~~~~~~
A PT3 file is a tracker format with a compact file size used on systems
with AY-3-8910 based audio. This is most commonly the ZX-spectrum
and Atari ST machines.
Originally most PT3 players were in z80 assembly language for use on Z80
based machines. I have written code that will play the files on modern
systems (using C) and also the included code designed for the 6502-based
Apple II machines with Mockingboard sound cards installed.
You can find many pt3 files on the internet, or you can use the
VortexTracker tracker to write your own.
Using the Code (irq driven):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See the "pt3_test.s" example.
The code is in cc65 6502 assembly language but should be relatively
easy to port to other assemblers.
To get a pt3 file playing:
+ Optionally include "pt3_lib_mockingboard_detect.s" and
call "mockingboard_detect" and "mockingboard_patch" if
you want to auto-detect which slot the mockingboard is in.
Otherwise it will default to Slot#4
The patch code does a vaguely unsafe find/replace of $C4
live patch of the slot values, if you want a safer (but much
larger) version you can go into the file and ifdef out
the right code.
+ Be sure to either include the pt3 file as a binary, or load
it from disk to a buffer pointed to by PT3_LOC.
Not the beginning of the song needs to be aligned on
a page boundary (this makes the decode code a bit
more simple)
+ If you want to make the code more compact but use a lot of
the zero page, you can set PT3_USE_ZERO_PAGE in
"pt3_lib_core.s" This will use zp $80-$FF
but make the pt3 code a bit faster/smaller
+ You can set the interrupt speed in pt3_lib_mockingboard_setup.s
Generally files you find online are 50Hz.
For less overhead you can set something like 25Hz but
in that case you'll want to adjust the speed in the
tracker otherwise the songs will play at the wrong speed.
+ Vortex tracker by default assumes a system with a 1.77MHz
clock and sets frequencies accordingly. The Mockingboard
runs at 1MHz, so the pt3_lib converts on the fly.
For less overhead you can have the tracker generate
1MHz music and strip out the 1.77MHz conversion code.
+ If you want the music to Loop then set the LOOP value to 1.
Using the Code (cycle-counted):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I started work on a cycle-counted (deterministic cycle count) pt3
decoder, but it turned out to be large and complex enough to not be
worth the trouble.
You can still use pt3 files in cycle-counted demos. See the
../demosplash2019/ directory for an example. What this code does
is decode the pt3 files to memory during non-cycle-counted times,
and then use a deterministic playback function to play back this music.
Each frame of music decodes to 11bytes of register info, which means
at 60Hz you can get roughly 4s of music per 3kB of RAM.
Overhead:
~~~~~~~~~
It depends exactly on what features you use, but in general it will use
around 3KB of RAM plus the size of the PT3 file (which is often a few K).
Playback overhead depends on the complexity of the sound file but is typically
in the 10% to 15% range when playing back at 50Hz.
The player also uses 26 zero-page locations. More compact/faster code
can be generated if you're willing to sacrifice 128+ zero page locations.

View File

@ -1,2 +1,2 @@
10 PRINT "PT3 LIB TEST V0.1"
10 PRINT "PT3 LIB TEST V0.2"
100 PRINT CHR$ (4)"BRUN PT3_TEST"

Binary file not shown.

View File

@ -91,7 +91,7 @@ NOTE_TONE_SLIDE_TO_STEP =39
NOTE_STRUCT_SIZE=40
.ifdef USE_ZERO_PAGE
.ifdef PT3_USE_ZERO_PAGE
note_a = $80
note_b = $80+(NOTE_STRUCT_SIZE*1)
note_c = $80+(NOTE_STRUCT_SIZE*2)
@ -100,7 +100,7 @@ begin_vars=$80
end_vars=$80+(NOTE_STRUCT_SIZE*3)
.else ; !USE_ZERO_PAGE
.else ; !PT3_USE_ZERO_PAGE
begin_vars:
note_a: ; reset?

View File

@ -28,7 +28,7 @@
; if card was found, X = #$Cn where n is the slot number of the card
; C clear if no Mockingboard found
; other flags clobbered
; zp $85-$87 clobbered
; zp $65-$67 clobbered
; A/Y clobbered
;------------------------------------------------------------------------------
@ -70,27 +70,124 @@ mb_timer_check_done:
;===================================================================
; code to patch mockingboard if not in slot#4
;===================================================================
; this is the brute force version, we have to patch 39 locations
; see further below if you want to try a smaller, more dangerous, patch
.if 0
mockingboard_patch:
lda MB_ADDR_H
sta pt3_irq_smc1+2
sta pt3_irq_smc1+2 ; 1
sta pt3_irq_smc2+2
sta pt3_irq_smc2+5
sta pt3_irq_smc2+2 ; 2
sta pt3_irq_smc2+5 ; 3
sta pt3_irq_smc3+2
sta pt3_irq_smc3+5
sta pt3_irq_smc3+2 ; 4
sta pt3_irq_smc3+5 ; 5
sta pt3_irq_smc4+2
sta pt3_irq_smc4+5
sta pt3_irq_smc4+2 ; 6
sta pt3_irq_smc4+5 ; 7
sta pt3_irq_smc5+2 ; 8
sta pt3_irq_smc5+5 ; 9
sta pt3_irq_smc6+2 ; 10
sta pt3_irq_smc6+5 ; 11
sta pt3_irq_smc7+2 ; 12
sta pt3_irq_smc7+5 ; 13
sta mock_init_smc1+2 ; 14
sta mock_init_smc1+5 ; 15
sta mock_init_smc2+2 ; 16
sta mock_init_smc2+5 ; 17
sta reset_ay_smc1+2 ; 18
sta reset_ay_smc2+2 ; 19
sta reset_ay_smc3+2 ; 20
sta reset_ay_smc4+2 ; 21
sta write_ay_smc1+2 ; 22
sta write_ay_smc1+5 ; 23
sta write_ay_smc2+2 ; 24
sta write_ay_smc2+5 ; 25
sta write_ay_smc3+2 ; 26
sta write_ay_smc3+5 ; 27
sta write_ay_smc4+2 ; 28
sta write_ay_smc4+5 ; 29
sta write_ay_smc5+2 ; 30
sta write_ay_smc5+5 ; 31
sta write_ay_smc6+2 ; 32
sta write_ay_smc6+5 ; 33
sta setup_irq_smc1+2 ; 34
sta setup_irq_smc2+2 ; 35
sta setup_irq_smc3+2 ; 36
sta setup_irq_smc4+2 ; 37
sta setup_irq_smc5+2 ; 38
sta setup_irq_smc6+2 ; 39
rts
.endif
;===================================================================
; dangerous code to patch mockingboard if not in slot#4
;===================================================================
; this code patches any $C4 value to the proper slot# if not slot4
; this can be dangerous, it might over-write other important values
; that should be $C4
; safer ways to do this:
; only do this if 2 bytes after a LDA/STA/LDX/STX
; count total and if not 39 then print error message
mockingboard_patch:
; from mockingboard_init $1BBF
; to done_pt3_irq_handler $1D85
ldx MB_ADDR_H
ldy #0
lda #<mockingboard_init
sta MB_ADDR_L
lda #>mockingboard_init
sta MB_ADDR_H
mb_patch_loop:
lda (MB_ADDR_L),Y
cmp #$C4
bne mb_patch_nomatch
txa
sta (MB_ADDR_L),Y
mb_patch_nomatch:
inc MB_ADDR_L
lda MB_ADDR_L
bne mb_patch_oflo
inc MB_ADDR_H
mb_patch_oflo:
lda MB_ADDR_H
cmp #>done_pt3_irq_handler
bne mb_patch_loop
lda MB_ADDR_L
cmp #<done_pt3_irq_handler
bne mb_patch_loop
mb_patch_done:
rts
.if 0

View File

@ -60,19 +60,30 @@ MOCK_AY_LATCH_ADDR = $7 ; 1 1 1
mockingboard_init:
lda #$ff ; all output (1)
mock_init_smc1:
sta MOCK_6522_DDRB1
sta MOCK_6522_DDRA1
mock_init_smc2:
sta MOCK_6522_DDRB2
sta MOCK_6522_DDRA2
rts
;===================================
;===================================
; Reset Both AY-3-8910s
;===================================
;===================================
;======================
; Reset Left AY-3-8910
;======================
reset_ay_both:
lda #MOCK_AY_RESET
reset_ay_smc1:
sta MOCK_6522_ORB1
lda #MOCK_AY_INACTIVE
reset_ay_smc2:
sta MOCK_6522_ORB1
;======================
@ -81,8 +92,10 @@ reset_ay_both:
;reset_ay_right:
;could be merged with both
lda #MOCK_AY_RESET
reset_ay_smc3:
sta MOCK_6522_ORB2
lda #MOCK_AY_INACTIVE
reset_ay_smc4:
sta MOCK_6522_ORB2
rts
@ -98,22 +111,29 @@ reset_ay_both:
write_ay_both:
; address
write_ay_smc1:
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
write_ay_smc2:
sta MOCK_6522_ORB1 ; latch_address on PB1 ; 3
sta MOCK_6522_ORB2 ; latch_address on PB2 ; 3
ldy #MOCK_AY_INACTIVE ; go inactive ; 2
write_ay_smc3:
sty MOCK_6522_ORB1 ; 3
sty MOCK_6522_ORB2 ; 3
; value
lda MB_VALUE ; 3
write_ay_smc4:
sta MOCK_6522_ORA1 ; put value on PA1 ; 3
sta MOCK_6522_ORA2 ; put value on PA2 ; 3
lda #MOCK_AY_WRITE ; ; 2
write_ay_smc5:
sta MOCK_6522_ORB1 ; write on PB1 ; 3
sta MOCK_6522_ORB2 ; write on PB2 ; 3
write_ay_smc6:
sty MOCK_6522_ORB1 ; 3
sty MOCK_6522_ORB2 ; 3
@ -206,17 +226,23 @@ done_apple_detect:
sei ; disable interrupts just in case
lda #$40 ; Continuous interrupts, don't touch PB7
setup_irq_smc1:
sta MOCK_6522_ACR ; ACR register
lda #$7F ; clear all interrupt flags
setup_irq_smc2:
sta MOCK_6522_IER ; IER register (interrupt enable)
lda #$C0
setup_irq_smc3:
sta MOCK_6522_IFR ; IFR: 1100, enable interrupt on timer one oflow
setup_irq_smc4:
sta MOCK_6522_IER ; IER: 1100, enable timer one interrupt
lda #$E7
setup_irq_smc5:
sta MOCK_6522_T1CL ; write into low-order latch
lda #$4f
setup_irq_smc6:
sta MOCK_6522_T1CH ; write into high-order latch,
; load both values into counter
; clear interrupt and start counting

View File

@ -15,8 +15,7 @@
PT3_LOC = song
; the below will make for more compact code, at the expense
; of using $80 - $ff by our routines. You'll also need to
; grab the zp.inc file from the pt3_player code
; of using $80 - $ff zero page addresses by the decoder.
; PT3_USE_ZERO_PAGE = 1
@ -34,24 +33,32 @@ pt3_setup:
lda #0
sta DONE_PLAYING
sta LOOP
sta LOOP ; change to 1 to loop forever
;=======================
; Detect mockingboard
;========================
jsr print_mockingboard_detect
jsr print_mockingboard_detect ; print message
jsr mockingboard_detect ; call detection routine
bcs mockingboard_found
jsr print_mocking_notfound
;jmp forever_loop
; can't detect on IIc so just run with it anyway
; possibly can't detect on IIc so just try with slot#4 anyway
; even if not detected
jmp setup_interrupt
mockingboard_found:
; print found message
; modify message to print slot value
lda MB_ADDR_H
sec
sbc #$10
@ -60,6 +67,13 @@ mockingboard_found:
jsr print_mocking_found
setup_interrupt:
;==================================================
; patch the playing code with the proper slot value
;==================================================
jsr mockingboard_patch
;=======================
; Set up 50Hz interrupt
;========================
@ -170,9 +184,9 @@ found_message: .asciiz "FOUND SLOT#4"
.include "pt3_lib_core.s"
.include "pt3_lib_init.s"
.include "pt3_lib_mockingboard_setup.s"
.include "pt3_lib_mockingboard_detect.s"
.include "interrupt_handler.s"
; if you're self patching, detect has to be after interrupt_handler.s
.include "pt3_lib_mockingboard_detect.s"
;=============
; include song

View File

@ -1,5 +1,18 @@
;; Zero page addresses
ORNAMENT_L = $60
ORNAMENT_H = $61
SAMPLE_L = $62
SAMPLE_H = $63
LOOP = $64
MB_ADDR_L = $65
MB_ADDR_H = $66
MB_VALUE = $67
DONE_PLAYING = $68
DONE_SONG = $69
PT3_TEMP = $6A
AY_REGISTERS = $70
A_FINE_TONE = $70
A_COARSE_TONE = $71
@ -19,15 +32,4 @@ ENVELOPE_SHAPE = $7D
PATTERN_L = $7E
PATTERN_H = $7F
ORNAMENT_L = $80
ORNAMENT_H = $81
SAMPLE_L = $82
SAMPLE_H = $83
LOOP = $84
MB_ADDR_L = $85
MB_ADDR_H = $86
MB_VALUE = $87
DONE_PLAYING = $88
DONE_SONG = $89
PT3_TEMP = $8A