asid/c64/asid.asm
Stefano D'Angelo 46706b6f3f initial import
2022-06-03 10:47:05 +02:00

565 lines
14 KiB
NASM

; A-SID - C64 bandpass filter + LFO
;
; Copyright (C) 2022 Orastron srl unipersonale
;
; A-SID is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, version 3 of the License.
;
; A-SID is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with A-SID. If not, see <http://www.gnu.org/licenses/>.
;
; File author: Stefano D'Angelo
!cpu 6502
!to "asid.prg",cbm
; memory map
; $fb,$fc: used for indirect Y addressing
; $0801-$1fff: BASIC code
; $2000-$2eff: machine language code
; $2f00-$2fff: machine code paddle read routine
; $3000-$3fff: lfo scaling data (16 sets of 256 elements)
; $4000-$43e7: colormap 1 (green)
; $4400-$47e7: colormap 2 (blue)
; $4800-$4be7: colormap 3 (violet)
; $4c00-$4fe7: colormap 4 (red)
; $6000-$7f3f: bitmap
; $7f40-$7f43: parameter colors
; $7f50-$7f5f: lfo phase increments
; $8000-$80ff: paddle mapping data (256 elements, set by boot program)
; $8100-$81ff: bar length in pixels, lookup data (256 elements)
; $8200-$83ff: cutoff to SID mapping lookup table (256 low byte entries followed by corresponding 256 high byte entries)
; $c000: cutoff frequency parameter (0-15)
; $c001: lfo amount parameter (0-15)
; $c002: lfo speed parameter (0-15)
; $c003: current parameter index (0-2)
; $c004: current number of update iterations in main loop
; $c005: number of iterations left to trigger left joystick event
; $c006: number of iterations left to trigger right joystick event
; $c008-$c00a: parameters for routines
; $c00c: current modulated cutoff value (0-255)
; $c00e: current colormap index
; $c00f: current parameter color
; $c010: lfo phase (0-255)
; $c011: lfo output (signed)
; $c014: 1 if paddle is present, 0 if not (set by boot program)
; $c015: paddle min value (set by boot program)
; $c016: paddle max value (set by boot program)
; $c017: mapped paddle value [-127,127]
; $c018: bar length in pixels
; $c019: number of bar lines left to draw
; $c01a: unmapped paddle value
* = $0801 ; BASIC code
!binary "boot.prg",,2
* = $2000 ; machine language code
.vic_setup:
; set hi res mode
lda #$3b
sta $d011
lda #$08
sta $d016
; set VIC bank
lda $dd00
and #%11111100
ora #%00000010
sta $dd00
.sid_setup:
; full volume, bp filter mode
lda #$2f
sta $d418
; res=15, external input to filter
lda #$f8
sta $d417
.initial_setup:
lda #$f
sta $c000 ; default cutoff frequency = 15
lda #$0
sta $c001 ; default lfo amount = 0
lda #$7
sta $c002 ; default lfo speed = 7
lda #$00
sta $c003 ; default parameter = 0 (cutoff)
lda #$00
sta $c010 ; intiial lfo phase = 0
; set 8 iterations left per left/right joystick events
lda #$03
sta $c005
sta $c006
.main_loop:
ldy $dc01 ; read joystick port 1
.joystick_left:
tya ; put joystick byte in A
and #$04 ; zero if left
bne .joystick_left_reset ; if != 0 reset and skip
dec $c005 ; decrement event counter
bne .joystick_right ; if != 0 skip
lda $c003 ; get current parameter index -> A
beq .joystick_left_wrap ; if = 0, see below...
sec
sbc #$01 ; if != 0, subtract 1
jmp .joystick_left_end
.joystick_left_wrap:
lda #$02 ; ... wrap
.joystick_left_end:
sta $c003 ; save current parameter index
.joystick_left_reset:
lda #$03
sta $c005 ; reset event counter
.joystick_right:
tya ; put joystick byte in A
and #$08 ; zero if right
bne .joystick_right_reset ; if != 0 reset and skip
dec $c006 ; decrement event counter
bne .joystick_up ; if != 0 skip
lda $c003 ; get current parameter index -> A
cmp #$02
beq .joystick_right_wrap ; if = 2, see below...
clc
adc #$01 ; if != 2, add 1
jmp .joystick_right_end
.joystick_right_wrap:
lda #$00 ; ... wrap
.joystick_right_end:
sta $c003 ; save current parameter index
.joystick_right_reset:
lda #$03
sta $c006 ; reset event counter
.joystick_up:
tya ; put joystick byte in A
and #$01 ; zero if up
bne .joystick_down ; if non-zero skip
ldx $c003 ; get current parameter index -> X
lda $c000,X ; get current value -> A
cmp #$f
beq .joystick_down ; if max skip
inc $c000,X ; increment parameter value
.joystick_down:
tya ; put joystick byte in A
and #$02 ; zero if down
bne .paddle ; if non-zero skip
ldx $c003 ; get current parameter index -> X
lda $c000,X ; get current value
beq .paddle ; if zero skip
dec $c000,X ; decrement parameter value
.paddle:
lda #$0
sta $c017 ; store 0 as paddle value
lda $c014 ; get paddle is present -> A
beq .io_end ; if not present skip
jsr $2f00 ; call paddle read routine
lda $c01a ; get paddle x value -> A
.paddle_min:
cmp $c015 ; compare with minimum
bcs .paddle_max ; if >= minimum jump ahead
lda $c015 ; put minimum -> A
jmp .paddle_map ; jump to mapping code
.paddle_max:
cmp $c016 ; compare with maximum
bcc .paddle_map ; if < maximum jump ahead
lda $c016 ; put maximum -> A
.paddle_map:
tax ; copy A -> X
lda $8000,X ; get mapped value -> A
sta $c017 ; store mapped paddle value
.io_end:
lda #$04
sta $c004 ; set 4 number of update iterations in main loop
.lfo:
lda $c010 ; get lfo phase -> A
ldx $c002 ; get lfo speed -> X
clc
adc $7f50,X ; add lfo phase increment
sta $c010 ; store lfo phase
tay ; and also store it in Y
lda $c001 ; get lfo amount -> A
clc
adc #$30 ; add #$30
sta $fc ; and store result into $fc
lda #$0
sta $fb ; store 0 into $fb
lda ($fb),Y ; get scaled lfo -> A
sta $c011 ; and store it into $c011
.cutoff_compute:
lda $c000 ; get cutoff frequency parameter -> A
asl
asl
asl
asl ; << 4
ora #$08 ; | 8
tax ; copy A -> X
.cutoff_compute_paddle:
lda $c017 ; get mapped paddle value -> A
cmp #$80 ; compare with #$80
bcs .cutoff_compute_paddle_neg ; if gte then paddle is negative and jump below
txa ; copy X -> A
clc
adc $c017 ; add paddle value
bcc .cutoff_compute_paddle_end ; if no overflow jump below
lda #$ff ; put #$ff -> A
jmp .cutoff_compute_paddle_end ; jump below
.cutoff_compute_paddle_neg:
txa ; copy X -> A
clc
adc $c017 ; add paddle value
bcs .cutoff_compute_paddle_end ; if overflow jump below
lda #$00 ; put 0 -> A
.cutoff_compute_paddle_end:
tax ; copy A -> X
.cutoff_compute_lfo:
lda $c011 ; get lfo output -> A
cmp #$80 ; compare with #$80
bcs .cutoff_compute_lfo_neg ; if gte then lfo is negative and jump below
txa ; copy X -> A
clc
adc $c011 ; add lfo output
bcc .cutoff_compute_end ; if no overflow jump to end
lda #$ff ; put #$ff -> A
jmp .cutoff_compute_end ; jump to end
.cutoff_compute_lfo_neg:
txa ; copy X -> A
clc
adc $c011 ; add lfo output
bcs .cutoff_compute_end ; if overflow jump to end
lda #$00 ; put 0 -> A
.cutoff_compute_end:
sta $c00c ; put result in $c00c
.bar_draw:
ldx $c00c ; get current modulated cutoff -> X
lda $8100,X ; get bar length in pixels -> A
sta $c018 ; store value in $c018
ldx #$60
stx $fb
ldx #$66
stx $fc ; put upper left corner=$6660, in $fb, $fc
lda #$78 ; put 120 -> A
sta $c019 ; put A -> $c019
.bar_draw_clear_loop:
cmp $c018 ; compare with bar length
beq .bar_draw_fill_loop ; if A = $c018 then jump below
sec
sbc #$8 ; A - 8 -> A
bcc .color_compute ; if overflow, done, move to next task
cmp $c018 ; compare with bar length
sta $c019 ; put number of lines left -> $c019
bcc .bar_draw_mixed ; if A < $c018 then jump below
ldy #$8 ; put 8 -> Y
lda #$40 ; put $40 -> A
.bar_draw_clear_inner_loop:
dey
sta ($fb),y
bne .bar_draw_clear_inner_loop ; loop
; update memory pointer
lda $fb ; get low byte of memory pointer -> A
clc
adc #$40 ; add $40
sta $fb ; store low byte
lda $fc ; get high byte -> A
adc #$01 ; add 1 + carry
sta $fc ; store high byte
lda $c019 ; get back number of lines left -> A
jmp .bar_draw_clear_loop ; loop
.bar_draw_mixed:
lda $c018 ; put bar length -> A
sec
sbc $c019 ; subtract number of lines left (-8)
tax ; copy A -> X
ldy #$8 ; put 8 -> Y
lda #$5f ; put $5f -> A
.bar_draw_mixed_fill_loop:
dey
dex
sta ($fb),y
bne .bar_draw_mixed_fill_loop ; loop
lda #$40 ; put $40 -> A
.bar_draw_mixed_clear_loop:
dey
sta ($fb),y
bne .bar_draw_mixed_clear_loop ; loop
; update memory pointer
lda $fb ; get low byte of memory pointer -> A
clc
adc #$40 ; add $40
sta $fb ; store low byte
lda $fc ; get high byte -> A
adc #$01 ; add 1 + carry
sta $fc ; store high byte
.bar_draw_fill_loop:
lda $c019 ; get number of lines left -> A
beq .color_compute ; if A = 0, then done, move to next task
sec
sbc #$8 ; A - 8 -> A
sta $c019 ; put number of lines left -> $c019
ldy #$8 ; put 8 -> Y
lda #$5f ; put $5f -> A
.bar_draw_fill_inner_loop:
dey
sta ($fb),y
bne .bar_draw_fill_inner_loop ; loop
; update memory pointer
lda $fb ; get low byte of memory pointer -> A
clc
adc #$40 ; add $40
sta $fb ; store low byte
lda $fc ; get high byte -> A
adc #$01 ; add 1 + carry
sta $fc ; store high byte
lda $c019 ; get back number of lines left -> A
jmp .bar_draw_fill_loop ; loop
.color_compute:
lda $c00c ; get current modulated cutoff -> A
lsr
lsr
lsr
lsr
lsr
lsr ; >> 6
sta $c00e ; store current colormap index
tax ; copy A -> X
lda $7f40,X ; get current parameter color
sta $c00f ; and store it
.cutoff_update:
ldy $c00f ; parameter color -> Y
lda $c003 ; get current parameter index -> A
beq .cutoff_update_color_ok ; if selected don't set to grey
ldy #$b1 ; grey color -> Y
.cutoff_update_color_ok:
sty $c008 ; set Y -> color
lda #$00
sta $c009 ; set 0 -> x offset
lda $c000 ; get cutoff parameter value -> A
sta $c00a ; set A -> parameter value
jsr .param_update
.lfo_amount_update:
ldy $c00f ; parameter color -> Y
lda $c003 ; get current parameter index -> A
sec
sbc #$01 ; subtract 1
beq .lfo_amount_update_color_ok ; if selected don't set to grey
ldy #$b1 ; grey color -> Y
.lfo_amount_update_color_ok:
sty $c008 ; set Y -> color
lda #$09
sta $c009 ; set 9 -> x offset
lda $c001 ; get lfo amount parameter value -> A
sta $c00a ; set A -> parameter value
jsr .param_update
.lfo_speed_update:
ldy $c00f ; parameter color -> Y
lda $c003 ; get current parameter index -> A
sec
sbc #$02 ; subtract 2
beq .lfo_speed_update_color_ok ; if selected don't set to grey
ldy #$b1 ; grey color -> Y
.lfo_speed_update_color_ok:
sty $c008 ; set Y -> color
lda #$12
sta $c009 ; set 18 -> x offset
lda $c002 ; get lfo speed parameter value -> A
sta $c00a ; set A -> parameter value
jsr .param_update
.sid_update:
ldx $c00c ; get current modulated cutoff -> X
lda $8200,X ; get mapped low byte -> A
sta $d415 ; set low byte
lda $8300,X ; get mapped high byte -> A
sta $d416 ; set high byte
; doing it here to get colormap updated before showing it (avoids giltches)
.vic_update:
; set bitmap addresses
lda $c00c ; get current modulated cutoff -> A
lsr
lsr ; >> 2
ora #$08 ; A | 8
sta $d018 ; set colormap address
.main_loop_end:
dec $c004 ; decrement number of update iterations
beq .main_loop_loop ; if == 0 restart main loop (needed because far jump)
jmp .lfo ; otherwise update again
.main_loop_loop:
jmp .main_loop
; routine that updates colormap for parameter
; inputs:
; $c008: color byte
; $c009: x offset (number of cells)
; $c00a: parameter value (0-15)
.param_update:
; upper left corner of first parameter = 40a0/44a0/48a0/4ca0
; here we put 40a0 + x offset into $fb, $fc for indirect addressing
lda $c009 ; put x offset -> A
clc
adc #$a0 ; add low byte
sta $fb ; store low byte in $fb
lda $c00e ; get current colormap index -> A
asl
asl ; * 4
adc #$40 ; add 40 (with carry)
sta $fc ; store high byte in $fc
ldx #$15 ; 21 lines left -> X
; $c00a = 15 - $c00a (number of white cells)
lda #$0f
sec
sbc $c00a
sta $c00a
.param_update_loop:
lda $c008 ; get color byte -> A
; put color data in 3 consecutive bytes
ldy #$00 ; put 0 -> Y
sta ($fb),y ; 1
iny
sta ($fb),y ; 2
iny
sta ($fb),y ; 3
iny
; put color data or all white for middle cell
cpx #$15
beq .param_update_mcell ; if first line use color data
lda $c00a ; get number of white cells -> A
bne .param_update_mdec ; if !=0 jump
lda $c008 ; get color byte -> A
jmp .param_update_mcell ; jump
.param_update_mdec:
dec $c00a ; decrement
lda #$11 ; use white
.param_update_mcell:
sta ($fb),y ; 4
iny
lda $c008 ; get color byte -> A
; put color data in 3 consecutive bytes
sta ($fb),y ; 5
iny
sta ($fb),y ; 6
iny
sta ($fb),y ; 7
dex ; decrement lines left
beq .param_update_end ; end loop if no lines left
; update memory pointer
lda $fb ; get low byte of memory pointer -> A
clc
adc #$28 ; add 40
sta $fb ; store low byte
lda $fc ; get high byte -> A
adc #$00 ; add carry
sta $fc ; store high byte
jmp .param_update_loop ; loop
.param_update_end:
rts ; return
; routines that reads paddle x value in port 2
; adapted from C64 programming reference, essentially a normal read with some waiting
* = $2f00
.paddle_read:
sei ; disable interrupts
lda $dc02 ; get CIA 1 data direction port A
tay ; copy it in Y
lda #$c0
sta $dc02 ; put #$c0 -> CIA 1 data direction port A
lda #$80
sta $dc00 ; put #$80 -> CIA 1 data port A (choose paddle in port 2)
ldx #$80 ; put #$80 -> X
.paddle_read_wait_loop:
nop
dex
bpl .paddle_read_wait_loop ; loop
lda $d419 ; get value -> A
sta $c01a ; store it in $c01a
sty $dc02 ; restore CIA 1 data direction port A
cli ; re-enable interrupts
rts
; lfo scaling
* = $3000
!binary "lfoscaling.dat"
; colormap 1 (green)
* = $4000
!binary "colormap1.dat"
; colormap 2 (blue)
* = $4400
!binary "colormap2.dat"
; colormap 3 (violet)
* = $4800
!binary "colormap3.dat"
; colormap 4 (red)
* = $4c00
!binary "colormap4.dat"
; bitmap
* = $6000
!binary "bitmap.dat"
; parameter colors
* = $7f40
!8 $51, $61, $41, $21
; lfo increments
* = $7f50
!8 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 16, 21, 28, 37, 49, 64
; bar mapping
* = $8100
!binary "barmapping.dat"
; cutoff mapping
* = $8200
!binary "cutoffmapping.dat"