; ============================================================================== ; 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