lovebyte: add music to plasma

This commit is contained in:
Vince Weaver 2024-02-04 01:20:16 -05:00
parent 7f98b47120
commit 67d993a9ed
12 changed files with 1063 additions and 0 deletions

View File

@ -0,0 +1,31 @@
include ../../../Makefile.inc
DOS33 = ../../../utils/dos33fs-utils/dos33
TOKENIZE = ../../../utils/asoft_basic-utils/tokenize_asoft
LINKERSCRIPTS = ../../../linker_scripts
EMPTYDISK = ../../../empty_disk/empty.dsk
all: plasmag.dsk
plasmag.dsk: HELLO PLASMAG_TINY
cp $(EMPTYDISK) plasmag.dsk
$(DOS33) -y plasmag.dsk SAVE A HELLO
$(DOS33) -y plasmag.dsk BSAVE -a 0x4000 PLASMAG_TINY
###
HELLO: hello.bas
$(TOKENIZE) < hello.bas > HELLO
###
PLASMAG_TINY: plasmag_tiny.o
ld65 -o PLASMAG_TINY plasmag_tiny.o -C $(LINKERSCRIPTS)/apple2_4000.inc
plasmag_tiny.o: plasmag_tiny.s gr_gbascalc.s make_tables.s
ca65 -o plasmag_tiny.o plasmag_tiny.s -l plasmag_tiny.lst
###
clean:
rm -f *~ *.o *.lst HELLO PLASMAG_TINY

View File

@ -0,0 +1,4 @@
optimize music code:
+ depending on alignment can hard-code the high value for track0/track1
+ check the frequencies, high freq might always be 0

View File

@ -0,0 +1,34 @@
;=====================
;=====================
;=====================
; ay3 write regs
;=====================
;=====================
;=====================
; write all 14 registers at AY_REGS
ay3_write_regs:
ldx #13
ay3_write_reg_loop:
lda #MOCK_AY_LATCH_ADDR ; latch_address for PB1 ; 2
ldy #MOCK_AY_INACTIVE ; go inactive ; 2
stx MOCK_6522_ORA1 ; put address on PA1 ; 4
sta MOCK_6522_ORB1 ; latch_address on PB1 ; 4
sty MOCK_6522_ORB1 ; 4
; value
lda AY_REGS,X
sta MOCK_6522_ORA1 ; put value on PA1 ; 4
lda #MOCK_AY_WRITE ; ; 2
sta MOCK_6522_ORB1 ; write on PB1 ; 4
sty MOCK_6522_ORB1 ; 4
dex
bpl ay3_write_reg_loop
; rts

View File

@ -0,0 +1,68 @@
;================================
;================================
; 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
; Note: the IIc is much more complicated
; its firmware tries to decode the proper source
; based on various things, including screen hole values
; we bypass that by switching out ROM and replacing the
; $fffe vector with this, but that does mean we have
; to be sure status flag and accumulator set properly
interrupt_handler:
php ; save status flags
cld ; clear decimal mode
pha ; save A ; 3
; A is saved in $45 by firmware
txa
pha ; save X
tya
pha ; save Y
inc $0404 ; debug (flashes char onscreen)
ay3_irq_handler:
bit MOCK_6522_T1CL ; clear 6522 interrupt by reading T1C-L ; 4
.include "play_frame.s"
.include "ay3_write_regs.s"
;=================================
; Finally done with this interrupt
;=================================
done_ay3_irq_handler:
pla
tay ; restore Y
pla
tax ; restore X
pla ; restore a ; 4
; on II+/IIe (but not IIc) we need to do this?
interrupt_smc:
lda $45 ; restore A
plp ; restore flags
rti ; return from interrupt ; 6
;============
; typical
; ???? cycles

View File

@ -0,0 +1,167 @@
; Made with version 1.0 (2024 Lovebyte)
peasant_song:
; register init
track0:
; A: 'A 5' 57
; B: 'A 5' 57
.byte $04 ; frame=2 A=0 L=2
; A: 'C 6' 60
.byte $05 ; frame=4 B=0 L=2
; B: 'C 6' 60
.byte $14 ; frame=6 A=1 L=2
; A: 'F 5' 53
.byte $15 ; frame=8 B=1 L=2
; B: 'F 5' 53
.byte $24 ; frame=10 A=2 L=2
; A: 'G 5' 55
.byte $2D ; frame=16 B=2 L=6
; B: 'G 5' 55
.byte $34 ; frame=18 A=3 L=2
; A: 'E 5' 52
.byte $35 ; frame=20 B=3 L=2
; A: 'E 5' 50
; B: 'E 5' 52
.byte $44 ; frame=22 A=4 L=2
; A: 'D 5' 57
; B: 'D 5' 50
.byte $50 ; frame=24 A=5 L=0
.byte $45 ; frame=24 B=4 L=2
; B: 'A 5' 57
.byte $00 ; frame=26 A=0 L=0
.byte $55 ; frame=26 B=5 L=2
; A: 'E 5' 52
.byte $05 ; frame=28 B=0 L=2
; B: 'E 5' 52
.byte $44 ; frame=30 A=4 L=2
; A: 'A 5' 57
.byte $45 ; frame=32 B=4 L=2
; B: 'A 5' 57
.byte $04 ; frame=34 A=0 L=2
; A: 'C 6' 60
.byte $05 ; frame=36 B=0 L=2
; B: 'C 6' 60
.byte $14 ; frame=38 A=1 L=2
; A: 'F 5' 53
.byte $15 ; frame=40 B=1 L=2
; B: 'F 5' 53
.byte $24 ; frame=42 A=2 L=2
; A: 'G 5' 55
.byte $2D ; frame=48 B=2 L=6
; B: 'G 5' 55
.byte $34 ; frame=50 A=3 L=2
; A: 'E 5' 52
.byte $35 ; frame=52 B=3 L=2
; A: 'E 5' 50
; B: 'E 5' 52
.byte $44 ; frame=54 A=4 L=2
; A: 'D 5' 45
; B: 'D 5' 50
.byte $50 ; frame=56 A=5 L=0
.byte $45 ; frame=56 B=4 L=2
; B: 'A 4' 45
.byte $60 ; frame=58 A=6 L=0
.byte $55 ; frame=58 B=5 L=2
; A: 'E 4' 40
.byte $65 ; frame=60 B=6 L=2
; B: 'E 4' 40
.byte $74 ; frame=62 A=7 L=2
; last: a=-1 b=7 len=2
.byte $75 ; frame=64 B=7 L=2
.byte $ff
track1:
; A: 'A 4' 57
; B: 'A 4' 45
; A: 'A 4' 60
; B: 'A 4' 45
.byte $00 ; frame=4 A=0 L=0
.byte $69 ; frame=4 B=6 L=4
; B: 'B 4' 47
.byte $10 ; frame=6 A=1 L=0
.byte $65 ; frame=6 B=6 L=2
; A: 'C 5' 53
; B: 'C 5' 48
.byte $85 ; frame=8 B=8 L=2
; B: 'A 4' 45
.byte $20 ; frame=10 A=2 L=0
.byte $95 ; frame=10 B=9 L=2
; B: 'D 5' 50
.byte $69 ; frame=14 B=6 L=4
; A: 'G 5' 55
.byte $55 ; frame=16 B=5 L=2
; B: 'D 5' 50
.byte $34 ; frame=18 A=3 L=2
; A: 'C 5' 52
; B: 'C 5' 48
.byte $55 ; frame=20 B=5 L=2
; A: 'B 4' 50
; B: 'B 4' 47
.byte $40 ; frame=22 A=4 L=0
.byte $95 ; frame=22 B=9 L=2
; A: 'C 5' 57
; B: 'C 5' 48
.byte $50 ; frame=24 A=5 L=0
.byte $85 ; frame=24 B=8 L=2
; B: 'A 4' 45
.byte $00 ; frame=26 A=0 L=0
.byte $95 ; frame=26 B=9 L=2
; A: 'E 5' 52
.byte $65 ; frame=28 B=6 L=2
; A: 'A 4' 57
; B: 'A 4' 45
.byte $48 ; frame=32 A=4 L=4
; A: 'A 4' 60
; B: 'A 4' 45
.byte $00 ; frame=36 A=0 L=0
.byte $69 ; frame=36 B=6 L=4
; B: 'B 4' 47
.byte $10 ; frame=38 A=1 L=0
.byte $65 ; frame=38 B=6 L=2
; A: 'C 5' 53
; B: 'C 5' 48
.byte $85 ; frame=40 B=8 L=2
; B: 'A 4' 45
.byte $20 ; frame=42 A=2 L=0
.byte $95 ; frame=42 B=9 L=2
; B: 'A 4' 45
.byte $69 ; frame=46 B=6 L=4
; B: 'B 4' 47
.byte $63 ; frame=47 B=6 L=1
; A: 'D 5' 55
; B: 'D 5' 50
.byte $83 ; frame=48 B=8 L=1
; B: 'D 5' 50
.byte $30 ; frame=50 A=3 L=0
.byte $55 ; frame=50 B=5 L=2
; A: 'C 5' 52
; B: 'C 5' 48
.byte $55 ; frame=52 B=5 L=2
; A: 'B 4' 50
; B: 'B 4' 47
.byte $40 ; frame=54 A=4 L=0
.byte $95 ; frame=54 B=9 L=2
; A: 'A 4' 45
; B: 'A 4' 45
.byte $50 ; frame=56 A=5 L=0
.byte $85 ; frame=56 B=8 L=2
; A: 'E 4' 40
.byte $60 ; frame=60 A=6 L=0
.byte $69 ; frame=60 B=6 L=4
; last: a=7 b=-1 len=4
.byte $78 ; frame=64 A=7 L=4
.byte $FF ; end
; Octave 0 : 0 0 0 0 0 0 0 0 0 0 0 0
; Octave 1 : 0 0 0 0 0 0 0 0 0 0 0 0
; Octave 2 : 0 0 0 0 0 0 0 0 0 0 0 0
; Octave 3 : 0 0 0 0 3 0 0 0 0 12 0 5
; Octave 4 : 5 0 10 0 9 6 0 6 0 9 0 0
; Octave 5 : 6 0 0 0 0 0 0 0 0 0 0 0
; 10 notes allocated
;.byte 57,60,53,55,52,50,45,40,47,48,
frequencies_low:
.byte $48,$3D,$5B,$51,$60,$6C,$91,$C1,$81,$7A
frequencies_high:
.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00
; total len=21

View File

@ -0,0 +1,21 @@
init_addresses:
.byte <MOCK_6522_DDRB1,<MOCK_6522_DDRA1 ; set the data direction for all pins of PortA/PortB to be output
.byte <MOCK_6522_ACR,<MOCK_6522_IER ; Continuous interrupts, clear all interrupts
.byte <MOCK_6522_IFR,<MOCK_6522_IER ; enable interrupt on timer overflow
.byte <MOCK_6522_T1CL,<MOCK_6522_T1CH ; set oflow value, start counting
.byte <MOCK_6522_ORB1,<MOCK_6522_ORB1 ; reset ay-3-8910
; note, terminated by the $ff below
init_values:
.byte $ff,$ff ; set the data direction for all pins of PortA/PortB to be output
.byte $40,$7f
.byte $C0,$C0
.byte $e7,$4f ; c7ce / 1.023e6 = .050s, 20Hz
.byte MOCK_AY_RESET,MOCK_AY_INACTIVE
; c7ce / 1.023e6 = .050s, 20Hz
; 9c40 / 1.023e6 = .040s, 25Hz
; 4fe7 / 1.023e6 = .020s, 50Hz
; 411a / 1.023e6 = .016s, 60Hz

View File

@ -0,0 +1,119 @@
; 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
MOCK_6522_T1CL = $C404 ; 6522 #1 t1 low order latches
MOCK_6522_T1CH = $C405 ; 6522 #1 t1 high order counter
MOCK_6522_T1LL = $C406 ; 6522 #1 t1 low order latches
MOCK_6522_T1LH = $C407 ; 6522 #1 t1 high order latches
MOCK_6522_T2CL = $C408 ; 6522 #1 t2 low order latches
MOCK_6522_T2CH = $C409 ; 6522 #1 t2 high order counters
MOCK_6522_SR = $C40A ; 6522 #1 shift register
MOCK_6522_ACR = $C40B ; 6522 #1 auxilliary control register
MOCK_6522_PCR = $C40C ; 6522 #1 peripheral control register
MOCK_6522_IFR = $C40D ; 6522 #1 interrupt flag register
MOCK_6522_IER = $C40E ; 6522 #1 interrupt enable register
MOCK_6522_ORANH = $C40F ; 6522 #1 port a data no handshake
; 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
;========================
;========================
; Left channel only
mockingboard_init:
;=========================
; Initialize the 6522s
; Reset Left AY-3-8910
;===========================
; ldx #$FF
; stx MOCK_6522_DDRB1
; stx MOCK_6522_DDRA1
; inx ; #MOCK_AY_RESET $0
; stx MOCK_6522_ORB1
; ldx #MOCK_AY_INACTIVE ; $4
; stx MOCK_6522_ORB1
; sei ; disable interrupts, is this necessary?
;=========================
; Setup Interrupt Handler
;=========================
; NOTE: we don't support IIc as it's a hack
; traditionally Mockingboard on IIc was rare
;========================
; set up interrupt
; Vector address goes to 0x3fe/0x3ff
; can save 10 bytes if we load in memory so this
; is in the right place automatically
lda #<interrupt_handler ; 2
sta $03fe ; 3
lda #>interrupt_handler ; 2
sta $03ff ; 3
;=========
; 10
;=========================
; Initialize the 6522s
; Reset Left AY-3-8910
;===========================
; entries=10
; 14 + 2*entries = 34 bytes
; assume Y=0 on entry?
ldy #0 ; 2
init_it_loop:
lda init_values,Y ; 3
ldx init_addresses,Y ; 3
bmi doneit ; 2
iny ; 1
sta $c400,X ; 3
bne init_it_loop ; 2
doneit:

View File

@ -0,0 +1,210 @@
; Plasma Tiny
; originally based on Plasmagoria code by French Touch
; trying to see how small I can get it
; note can use $F000 (or similar) for color lookup to get passable
; effect on fewer bytes
; 347 bytes -- initial with FAC
; 343 bytes -- optimize init
; 340 bytes -- move Table1 + Table2 to zero page
; 331 bytes -- init not necessary
; 319 bytes -- inline precalc + display + init lores colors
; 316 bytes -- fallthrough in make_tiny
.include "hardware.inc"
.include "zp.inc"
;Table1 = $8000
;Table2 = $8000+64
lores_colors_fine=$8100
;======================================
; start of code
;======================================
plasma_tiny:
jsr HGR ; have table gen appear on hgr page1
bit FULLGR
;=================
; init music
lda #0
sta FRAME
sta WHICH_TRACK
;===================
; music Player Setup
tracker_song = peasant_song
; assume mockingboard in slot#4
; inline mockingboard_init
.include "mockingboard_init.s"
.include "tracker_init.s"
cli ; start music
jsr make_tables
bit LORES ; set lo-res
; ============================================================================
; init lores colors (inline)
; ============================================================================
init_lores_colors:
ldx #0
ldy #0
init_lores_colors_loop:
lda lores_colors_lookup,X
sta lores_colors_fine,Y
iny
sta lores_colors_fine,Y
iny
sta lores_colors_fine,Y
iny
sta lores_colors_fine,Y
iny
beq done_init_lores_colors
inx
txa
and #$f
tax
jmp init_lores_colors_loop
done_init_lores_colors:
;====================================
; do plasma
;====================================
do_plasma:
; init
; lda #02
; ldx #5
;init_loop:
; sta COMPT1,X
; dex
; bne init_loop
BP3:
; ============================================================================
; Precalculate some values (inlined)
; ROUTINES PRE CALCUL
; ============================================================================
precalc:
lda PARAM1 ; self modify various parts
sta pc_off1+1
lda PARAM2
sta pc_off2+1
lda PARAM3
sta pc_off3+1
lda PARAM4
sta pc_off4+1
; Table1(X) = sin1(PARAM1+X)+sin2(PARAM1+X)
; Table2(X) = sin3(PARAM3+X)+sin1(PARAM4+X)
ldx #$28 ; 40
pc_b1:
pc_off1:
lda sin1
pc_off2:
adc sin2
sta Table1,X
pc_off3:
lda sin3
pc_off4:
adc sin1
sta Table2,X
inc pc_off1+1
inc pc_off2+1
inc pc_off3+1
inc pc_off4+1
dex
bpl pc_b1
inc PARAM1
inc PARAM1
dec PARAM2
inc PARAM3
dec PARAM4
; ============================================================================
; Display Routines
; ROUTINES AFFICHAGES
; ============================================================================
; Display "Normal"
; AFFICHAGE "NORMAL"
display_normal:
ldx #23 ; lines 0-23 lignes 0-23
display_line_loop:
txa
jsr GBASCALC
ldy #39 ; col 0-39
lda Table2,X ; setup base sine value for row
sta display_row_sin_smc+1
display_col_loop:
lda Table1,Y ; load in column sine value
display_row_sin_smc:
adc #00 ; add in row value
sta display_lookup_smc+1 ; patch in low byte of lookup
display_lookup_smc:
lda lores_colors_fine ; attention: must be aligned
sta (GBASL),Y
dey
bpl display_col_loop
dex
bpl display_line_loop
; ============================================================================
inc COMPT1
bne BP3
dec COMPT2
bne BP3
beq do_plasma ; bra
lores_colors_lookup:
.byte $00,$88,$55,$99,$ff,$bb,$33,$22,$66,$77,$44,$cc,$ee,$dd,$99,$11
.include "make_tables.s"
.include "interrupt_handler.s"
.include "mockingboard_constants.s"
; music
.include "mA2E_2.s"

View File

@ -0,0 +1,182 @@
play_frame:
;===============================
;===============================
; things that happen every frame
;===============================
;===============================
;========================================
; patch b channel volumes based on track
lda WHICH_TRACK
and #$2
bne switch_to_b1
lda #<channel_b0_volume
.byte $2C ; bit trick
switch_to_b1:
lda #<channel_b1_volume
sta chb_smc1+1
sta chb_smc2+1
;==============================
; countdown for the volumes
lda FRAME
and #$f
bne no_inc_countdowns
inc A_COUNTDOWN
inc B_COUNTDOWN
no_inc_countdowns:
;=================================
; inc frame counter
inc FRAME
;=================================
; rotate through channel A volume
ldy A_COUNTDOWN
lda channel_a_volume,Y
sta AY_REGS+8
;=================================
; rotate through channel B volume
ldy B_COUNTDOWN
chb_smc1:
lda channel_b0_volume,Y
sta AY_REGS+9
;============================
; see if still counting down
lda SONG_COUNTDOWN
bpl done_update_song
set_notes_loop:
;==================
; load next byte
ldy SONG_OFFSET
track_smc:
lda track0,Y
;==================
; see if hit end
cmp #$ff
bne not_end
;====================================
; if at end, loop back to beginning
inc WHICH_TRACK
ldy WHICH_TRACK
cpy #4 ; looping, hard coded
bne no_wrap
ldy #0
sty WHICH_TRACK
no_wrap:
lda tracks_l,Y
sta track_smc+1
lda tracks_h,Y
sta track_smc+2
lda #0
sta SONG_OFFSET
beq set_notes_loop ; bra
not_end:
; NNNNEEEC -- c=channel, e=end, n=note
pha ; save note
and #1
tax
bne start_b_note
; reset A countdown
start_a_note:
ldy channel_a_volume
bne done_start_note ; bra
start_b_note:
chb_smc2:
ldy channel_b0_volume
done_start_note:
; set initial note volume
sty AY_REGS+8,X ; $08 set volume A or B
; reset countdown
ldy #0
sty A_COUNTDOWN,X
asl
tax ; mul channel offset by 2 for later
; as frequency registers 2 wide
pla ; restore note
pha
and #$E ; get length
asl ; it's NNNNEEEC
asl ; we want EEE * 8
sta SONG_COUNTDOWN ;
pla
lsr
lsr
lsr ; get note in A
lsr
tay ; lookup in table
lda frequencies_high,Y
sta AY_REGS+1,X
lda frequencies_low,Y
sta AY_REGS,X ; set proper register value
; visualization
blah_urgh:
sta $400,Y
inc blah_urgh+1
;============================
; point to next
; assume less than 256 bytes
inc SONG_OFFSET
done_update_song:
dec SONG_COUNTDOWN
bmi set_notes_loop
bpl skip_data
channel_a_volume:
.byte $D,$C,$A,$9
channel_b0_volume:
.byte $9,$5,$4,$3
channel_b1_volume:
.byte $F,$C,$B,$A
tracks_l:
.byte <track0,<track0,<track1,<track1
tracks_h:
.byte >track0,>track0,>track1,>track1
skip_data:

View File

@ -0,0 +1,22 @@
tracker_init:
; setup initial ay-3-8910 values (this depends on song)
init_registers_to_zero:
ldx #$13 ; zero $70--$83
lda #0
; sta SONG_OFFSET ; also init song stuff
; sta SONG_COUNTDOWN
init_loop:
sta AY_REGS,X
dex
bpl init_loop
lda #$38
sta AY_REGS+7 ; $07 mixer (ABC on)
; lda #$0E
; sta AY_REGS+8 ; $08 volume A
; lda #$0C
; sta AY_REGS+9 ; $09 volume B
; sta AY_REGS+10 ; $0A volume C

View File

@ -0,0 +1,46 @@
; zero page
; pre-defined applesoft vars
CH = $24
CV = $25
GBASL = $26
GBASH = $27
BASL = $28
BASH = $29
COMPT1 = $30
COMPT2 = $31
PARAM1 = $60
PARAM2 = $61
PARAM3 = $62
PARAM4 = $63
AY_REGS = $70
; through = $7F
SONG_L = $80
SONG_H = $81
SONG_OFFSET = $82
SONG_COUNTDOWN = $83
OCTAVE = $84
REGISTER = $85
A_COUNTDOWN = $86
B_COUNTDOWN = $87
Table1 = $A0 ; 40 bytes ($28)
Table2 = $D0 ; 40 bytes ($28)
OUR_ROT = $A5
HGR_X = $E0
HGR_XH = $E1
HGR_Y = $E2
HGR_COLOR = $E4
HGR_PAGE = $E6
HGR_SCALE = $E7
COUNT = $FB
FRAME = $FC
WHICH_TRACK = $FD

View File

@ -0,0 +1,159 @@
; De-compressor for ZX02 files
; ----------------------------
;
; Decompress ZX02 data (6502 optimized format), optimized for speed and size
; 138 bytes code, 58.0 cycles/byte in test file.
;
; Compress with:
; zx02 input.bin output.zx0
;
; (c) 2022 DMSC
; Code under MIT license, see LICENSE file.
;ZP=$80
;offset = ZP+0
;ZX0_src = ZP+2
;ZX0_dst = ZP+4
;bitr = ZP+6
;pntr = ZP+7
; Initial values for offset, source, destination and bitr
;zx0_ini_block:
; .byte $00, $00, <comp_data, >comp_data, <out_addr, >out_addr, $80
;--------------------------------------------------
; Decompress ZX0 data (6502 optimized format)
zx02_full_decomp:
; ; Get initialization block
; ldy #7
;
;copy_init: lda zx0_ini_block-1, y
; sta offset-1, y
; dey
; bne copy_init
sta ZX0_dst+1 ; page to output to in A
zx_src_l:
ldy #$dd
sty ZX0_src
zx_src_h:
ldy #$dd
sty ZX0_src+1
ldy #$80
sty bitr
ldy #0
sty offset
sty offset+1
sty ZX0_dst ; always on even page
; Decode literal: Ccopy next N bytes from compressed file
; Elias(length) byte[1] byte[2] ... byte[N]
decode_literal:
jsr get_elias
cop0: lda (ZX0_src), y
inc ZX0_src
bne plus1
inc ZX0_src+1
plus1: sta (ZX0_dst),y
inc ZX0_dst
bne plus2
inc ZX0_dst+1
plus2: dex
bne cop0
asl bitr
bcs dzx0s_new_offset
; Copy from last offset (repeat N bytes from last offset)
; Elias(length)
jsr get_elias
dzx0s_copy:
lda ZX0_dst
sbc offset ; C=0 from get_elias
sta pntr
lda ZX0_dst+1
sbc offset+1
sta pntr+1
cop1:
lda (pntr), y
inc pntr
bne plus3
inc pntr+1
plus3: sta (ZX0_dst),y
inc ZX0_dst
bne plus4
inc ZX0_dst+1
plus4: dex
bne cop1
asl bitr
bcc decode_literal
; Copy from new offset (repeat N bytes from new offset)
; Elias(MSB(offset)) LSB(offset) Elias(length-1)
dzx0s_new_offset:
; Read elias code for high part of offset
jsr get_elias
beq exit ; Read a 0, signals the end
; Decrease and divide by 2
dex
txa
lsr ; @
sta offset+1
; Get low part of offset, a literal 7 bits
lda (ZX0_src), y
inc ZX0_src
bne plus5
inc ZX0_src+1
plus5:
; Divide by 2
ror ; @
sta offset
; And get the copy length.
; Start elias reading with the bit already in carry:
ldx #1
jsr elias_skip1
inx
bcc dzx0s_copy
; Read an elias-gamma interlaced code.
; ------------------------------------
get_elias:
; Initialize return value to #1
ldx #1
bne elias_start
elias_get: ; Read next data bit to result
asl bitr
rol ; @
tax
elias_start:
; Get one bit
asl bitr
bne elias_skip1
; Read new bit from stream
lda (ZX0_src), y
inc ZX0_src
bne plus6
inc ZX0_src+1
plus6: ;sec ; not needed, C=1 guaranteed from last bit
rol ;@
sta bitr
elias_skip1:
txa
bcs elias_get
; Got ending bit, stop reading
exit:
rts