mirror of
https://github.com/deater/dos33fsprogs.git
synced 2025-01-13 07:29:54 +00:00
pt3_lib: update to version 0.2
includes more documentation and Mockingboard slot detection
This commit is contained in:
parent
85e8388035
commit
ebd8329600
@ -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.
|
||||
|
||||
|
||||
|
@ -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.
@ -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?
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user