; ==============================================================================
; VECTOR OSCILLOSCOPE FROM HELL (Commodore 128)
; A high-speed, mutating Lissajous/Alien signal generator with phosphor trails.
; ==============================================================================
!cpu 6510
!to "hell.prg",cbm
; --- Zero Page Variables ---
zp_acc_0 = $02
zp_acc_1 = $03
zp_acc_2 = $04
zp_i_lo = $05
zp_i_hi = $06
zp_add_0 = $07
zp_add_1 = $08
ptr_ring_x_lo = $09
ptr_ring_x_hi = $0B
ptr_ring_y = $0D
ring_idx = $0F
old_x_lo = $10
old_x_hi = $11
old_y = $12
new_x_lo = $13
new_x_hi = $14
new_y = $15
zp_scr_lo = $16
zp_scr_hi = $17
zp_mul_a = $18
zp_mul_b = $19
sum_lo = $1A
sum_hi = $1B
zp_t1 = $1C
zp_t2 = $1D
; --- State / Math Variables ---
phase_x_lo = $20
phase_x_hi = $21
phase_y_lo = $22
phase_y_hi = $23
phase_m_lo = $24
phase_m_hi = $25
freq_x = $26
freq_y = $27
freq_m = $28
amp_x = $29
amp_y = $2A
amp_m = $2B
t_freq_x = $2C
t_freq_y = $2D
t_freq_m = $2E
t_amp_x = $2F
t_amp_y = $30
t_amp_m = $31
curr_mode = $32
mode_timer = $33
pts_counter = $34
; --- Memory Map ---
; $1C01 - $1FFF : Code and Loader
; $2000 - $20FF : Sine Table
; $2100 - $21C7 : Row Address Lo
; $2200 - $22C7 : Row Address Hi
; $2300 - $2307 : Pixel Bitmasks
; $3000 - $33FF : Ring Buffer X_lo (1024 bytes)
; $3400 - $37FF : Ring Buffer X_hi (1024 bytes)
; $3800 - $3BFF : Ring Buffer Y (1024 bytes)
; $4000 - $43E7 : Screen RAM (Color Matrix)
; $6000 - $7F3F : VIC-II Bitmap
; $8000 - $81FF : Quarter Squares Table
row_addr_lo = $2100
row_addr_hi = $2200
bit_mask = $2300
bit_inv = $2308
sqr_hi_0 = $8000
sqr_hi_1 = $8100
; ==============================================================================
; BASIC SYS LOADER (Loads at $1C01 in C128 mode)
; ==============================================================================
* = $1C01
!byte $0C,$1C,$0A,$00,$9E,$37,$31,$38,$31,$00,$00,$00 ; 10 SYS 7181
* = $1C0D
jmp init
; ==============================================================================
; INITIALIZATION
; ==============================================================================
init:
sei
; 1. Setup MMU for pure RAM in Bank 0, but keep I/O visible
lda #$00
sta $FF00 ; Configuration register (RAM Bank 0, I/O at $D000)
; 2. Point VIC-II to Bank 1 ($4000 - $7FFF)
lda $DD00
and #$FC
ora #$02
sta $DD00
; 3. Setup VIC-II Hires Bitmap Mode
lda #$3B ; Bitmap mode, Screen ON, 25 rows
sta $D011
lda #$08 ; Hires (No Multi-color)
sta $D016
lda #$08 ; Video matrix at $4000 (offset $0000), Bitmap at $6000 (offset $2000)
sta $D018
; Colors
lda #$00
sta $D020 ; Border Black
sta $D021 ; Background Black
; 4. Clear Video Matrix to Green ($50)
ldx #$00
lda #$50 ; Color 5 (Green) foreground, 0 (Black) background
.clr_scr:
sta $4000,x
sta $4100,x
sta $4200,x
sta $4300,x
inx
bne .clr_scr
; 5. Clear Bitmap to Black ($00)
lda #$00
ldx #$00
.clr_bmp:
sta $6000,x
sta $6100,x
sta $6200,x
sta $6300,x
sta $6400,x
sta $6500,x
sta $6600,x
sta $6700,x
sta $6800,x
sta $6900,x
sta $6A00,x
sta $6B00,x
sta $6C00,x
sta $6D00,x
sta $6E00,x
sta $6F00,x
sta $7000,x
sta $7100,x
sta $7200,x
sta $7300,x
sta $7400,x
sta $7500,x
sta $7600,x
sta $7700,x
sta $7800,x
sta $7900,x
sta $7A00,x
sta $7B00,x
sta $7C00,x
sta $7D00,x
sta $7E00,x
sta $7F00,x
inx
bne .clr_bmp
; 6. Build Tables
jsr build_row_table
jsr build_sqr_table
jsr build_bitmasks
; 7. Init State
lda #$30
sta ptr_ring_x_lo+1
lda #$34
sta ptr_ring_x_hi+1
lda #$38
sta ptr_ring_y+1
lda #$00
sta ptr_ring_x_lo
sta ptr_ring_x_hi
sta ptr_ring_y
sta ring_idx
sta phase_x_lo
sta phase_x_hi
sta phase_y_lo
sta phase_y_hi
sta phase_m_lo
sta phase_m_hi
; Initial mode targets
lda #0
sta curr_mode
jsr set_mode_targets
; Force immediate morph for first frame
lda t_freq_x
sta freq_x
lda t_amp_x
sta amp_x
lda t_amp_y
sta amp_y
; ==============================================================================
; MAIN DEMO LOOP
; ==============================================================================
main_loop:
ldx #128 ; Process 128 points per visual frame update
.point_loop:
stx pts_counter
; --- 1. ERASE OLD POINT ---
ldy ring_idx
lda (ptr_ring_x_lo), y
sta old_x_lo
lda (ptr_ring_x_hi), y
sta old_x_hi
lda (ptr_ring_y), y
sta old_y
jsr erase_point
; --- 2. CALCULATE NEW POINT ---
jsr calc_point
; --- 3. PLOT NEW POINT ---
jsr plot_point
; --- 4. SAVE TO RING BUFFER ---
ldy ring_idx
lda new_x_lo
sta (ptr_ring_x_lo), y
lda new_x_hi
sta (ptr_ring_x_hi), y
lda new_y
sta (ptr_ring_y), y
; --- 5. ADVANCE PHASES ---
lda phase_x_lo
clc
adc freq_x
sta phase_x_lo
bcc +
inc phase_x_hi
+ lda phase_y_lo
clc
adc freq_y
sta phase_y_lo
bcc +
inc phase_y_hi
+ lda phase_m_lo
clc
adc freq_m
sta phase_m_lo
bcc +
inc phase_m_hi
+
; --- 6. ADVANCE RING INDEX ---
iny
sty ring_idx
bne .skip_wrap
inc ptr_ring_x_lo+1
inc ptr_ring_x_hi+1
inc ptr_ring_y+1
lda ptr_ring_y+1
cmp #$3C
bne .skip_wrap
lda #$30
sta ptr_ring_x_lo+1
lda #$34
sta ptr_ring_x_hi+1
lda #$38
sta ptr_ring_y+1
.skip_wrap:
ldx pts_counter
dex
bne .point_loop
; --- UPDATE ANIMATION / MORPH ---
jsr morph_parameters
dec mode_timer
bne +
jsr next_mode
+
; --- KEYBOARD SCAN ---
lda #$7F
sta $DC00
lda $DC01
and #$10 ; SPACE BAR
bne +
jsr next_mode
+
jmp main_loop
; ==============================================================================
; CALCULATE POINT
; X = 160 + (sin(px)*amp_x) + (sin(pm)*amp_m)
; Y = 100 + (sin(py)*amp_y) + (sin(pm+64)*amp_m)
; ==============================================================================
calc_point:
; -- Compute X --
ldx phase_x_hi
ldy amp_x
jsr get_scaled_sin
tax
bmi .negx1
lda #0
sta zp_t2
jmp .addx1
.negx1:
lda #$FF
sta zp_t2
.addx1:
txa
clc
adc #160 ; center X
sta new_x_lo
lda zp_t2
adc #0
sta new_x_hi
; add X modulation
ldx phase_m_hi
ldy amp_m
jsr get_scaled_sin
tax
bmi .negx2
lda #0
sta zp_t2
jmp .addx2
.negx2:
lda #$FF
sta zp_t2
.addx2:
txa
clc
adc new_x_lo
sta new_x_lo
lda zp_t2
adc new_x_hi
sta new_x_hi
; -- Compute Y --
ldx phase_y_hi
ldy amp_y
jsr get_scaled_sin
clc
adc #100 ; center Y
sta new_y
; add Y modulation (phase + 64 to create circular twisting)
lda phase_m_hi
clc
adc #64
tax
ldy amp_m
jsr get_scaled_sin
clc
adc new_y
sta new_y
rts
; ==============================================================================
; PLOT POINT (X: new_x_lo/hi, Y: new_y)
; ==============================================================================
plot_point:
ldx new_y
cpx #200
bcs .end_plot
lda row_addr_lo, x
sta zp_scr_lo
lda row_addr_hi, x
clc
adc new_x_hi ; High byte addition effectively adds 256
sta zp_scr_hi
lda new_x_lo
and #$F8
clc
adc zp_scr_lo
sta zp_scr_lo
bcc +
inc zp_scr_hi
+
lda new_x_lo
and #7
tax
lda bit_mask, x
ldy #0
ora (zp_scr_lo), y
sta (zp_scr_lo), y
.end_plot:
rts
; ==============================================================================
; ERASE POINT (X: old_x_lo/hi, Y: old_y)
; ==============================================================================
erase_point:
ldx old_y
cpx #200
bcs .end_erase
lda row_addr_lo, x
sta zp_scr_lo
lda row_addr_hi, x
clc
adc old_x_hi
sta zp_scr_hi
lda old_x_lo
and #$F8
clc
adc zp_scr_lo
sta zp_scr_lo
bcc +
inc zp_scr_hi
+
lda old_x_lo
and #7
tax
lda bit_inv, x
ldy #0
and (zp_scr_lo), y
sta (zp_scr_lo), y
.end_erase:
rts
; ==============================================================================
; GET SCALED SINE (A = sin[X] * Y)
; Uses quarter square multiplication. Returns signed 8-bit result in A.
; ==============================================================================
get_scaled_sin:
lda sin_table, x
sta zp_mul_a
sty zp_mul_b
clc
adc zp_mul_b
sta sum_lo
lda #0
adc #0
sta sum_hi
sec
lda zp_mul_a
sbc zp_mul_b
bcs .mul_pos
eor #$FF
adc #1
.mul_pos:
tax
ldy sum_hi
beq .sum_sm
ldy sum_lo
lda sqr_hi_1, y
sec
sbc sqr_hi_0, x
jmp .mul_done
.sum_sm:
ldy sum_lo
lda sqr_hi_0, y
sec
sbc sqr_hi_0, x
.mul_done:
lsr zp_mul_b
sec
sbc zp_mul_b
rts
; ==============================================================================
; MORPH PARAMETERS
; ==============================================================================
!macro morph .var, .target {
lda .var
cmp .target
beq +
bcc ++
dec .var
jmp +
++ inc .var
+
}
morph_parameters:
+morph freq_x, t_freq_x
+morph freq_y, t_freq_y
+morph freq_m, t_freq_m
+morph amp_x, t_amp_x
+morph amp_y, t_amp_y
+morph amp_m, t_amp_m
rts
next_mode:
lda #200
sta mode_timer
inc curr_mode
lda curr_mode
cmp #6
bcc +
lda #0
sta curr_mode
+
set_mode_targets:
lda curr_mode
asl
asl
tax
; wait, each mode has 6 bytes. We can just do A * 6
lda curr_mode
sta zp_t1
asl
clc
adc zp_t1
asl
tax
lda mode_data, x
sta t_freq_x
lda mode_data+1, x
sta t_freq_y
lda mode_data+2, x
sta t_amp_x
lda mode_data+3, x
sta t_amp_y
lda mode_data+4, x
sta t_freq_m
lda mode_data+5, x
sta t_amp_m
rts
; FX, FY, AX, AY, FM, AM
mode_data:
!byte 3, 4, 130, 95, 0, 0 ; 0: Classic Lissajous
!byte 2, 2, 90, 90, 7, 50 ; 1: Knots
!byte 5, 6, 120, 80, 11,55 ; 2: Flower
!byte 1, 1, 15, 15, 0, 0 ; 3: Collapse (Shrinks to center)
!byte 13,17,140, 90, 23,80 ; 4: Overload (Chaotic Alien Pattern)
!byte 7, 9, 120, 90, 5, 30 ; 5: Slow Drift
; ==============================================================================
; STARTUP TABLE BUILDERS
; ==============================================================================
build_row_table:
ldx #0
.yloop:
txa
pha
lsr
lsr
lsr
sta zp_t1
lsr
lsr
adc zp_t1
adc #$60
sta zp_scr_hi
lda zp_t1
asl
asl
asl
asl
asl
asl
sta zp_scr_lo
pla
pha
and #7
clc
adc zp_scr_lo
sta row_addr_lo, x
lda zp_scr_hi
adc #0
sta row_addr_hi, x
pla
tax
inx
cpx #200
bne .yloop
rts
build_sqr_table:
lda #0
sta zp_acc_0
sta zp_acc_1
sta zp_acc_2
sta zp_i_lo
sta zp_i_hi
ldy #0
.sq_loop:
lda zp_i_hi
beq .p0
lda zp_acc_2
sta sqr_hi_1, y
jmp .dst
.p0:
lda zp_acc_2
sta sqr_hi_0, y
.dst:
lda zp_i_lo
asl
sta zp_add_0
lda zp_i_hi
rol
sta zp_add_1
inc zp_add_0
bne +
inc zp_add_1
+ lda zp_acc_0
clc
adc zp_add_0
sta zp_acc_0
lda zp_acc_1
adc zp_add_1
sta zp_acc_1
lda zp_acc_2
adc #0
sta zp_acc_2
inc zp_i_lo
bne +
inc zp_i_hi
+ iny
cpy #0
bne .sq_loop
lda zp_i_hi
cmp #2
bne .sq_loop
rts
build_bitmasks:
ldx #0
lda #$80
.bm:
sta bit_mask, x
pha
eor #$FF
sta bit_inv, x
pla
lsr
inx
cpx #8
bne .bm
rts
; ==============================================================================
; SINE TABLE (0 to 254)
; ==============================================================================
* = $2000
sin_table:
!byte 127,130,133,136,139,142,145,148,152,155,158,161,164,167,170,173
!byte 176,179,182,185,188,190,193,196,199,201,204,206,209,211,214,216
!byte 218,221,223,225,227,229,231,233,235,237,238,240,242,243,245,246
!byte 247,248,250,251,251,252,253,253,254,254,254,254,254,254,254,254
!byte 254,254,254,254,254,254,254,254,254,253,253,252,251,251,250,248
!byte 247,246,245,243,242,240,238,237,235,233,231,229,227,225,223,221
!byte 218,216,214,211,209,206,204,201,199,196,193,190,188,185,182,179
!byte 176,173,170,167,164,161,158,155,152,148,145,142,139,136,133,130
!byte 127,124,121,118,115,112,109,106,102,99,96,93,90,87,84,81
!byte 78,75,72,69,66,64,61,58,55,53,50,48,45,43,40,38
!byte 36,33,31,29,27,25,23,21,19,17,16,14,12,11,9,8
!byte 7,6,4,3,3,2,1,1,0,0,0,0,0,0,0,0
!byte 0,0,0,0,0,0,0,0,0,1,1,2,3,3,4,6
!byte 7,8,9,11,12,14,16,17,19,21,23,25,27,29,31,33
!byte 36,38,40,43,45,48,50,53,55,58,61,64,66,69,72,75
!byte 78,81,84,87,90,93,96,99,102,106,109,112,115,118,121,124