Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Here's a brief summary of the SID demo:
- What it plays: A continuously looping three-voice piece. Voice 1 carries a short melody that cycles through the SID's three main waveforms each repeat — triangle (soft), sawtooth (bright/buzzy), pulse (hollow) — so you hear the chip's tone colors. Voice 2 holds a sawtooth bassline, and Voice 3 plays a sustained harmony a fifth up, routed through the filter with a sweeping cutoff for that signature SID "wah."
- How it works: I poke the SID's registers at $D400–$D418 directly from assembly. Each voice gets a frequency, a waveform, and an ADSR envelope (attack/decay/sustain/release — the swell-and-fade shape of a note). A note sounds when I set the frequency and flip the voice's gate bit on; clearing the gate triggers the release. Timing comes from a raster-synced tick (wait_frame) that fires once per video frame (~50Hz), the standard heartbeat for C64 music. The filter sweep is just nudging the cutoff register up and down a little each frame.
- ; sid_demo.asm - three-voice SID sound demonstration for the C64
- ;
- ; Assemble: acme -f cbm -o sid_demo.prg sid_demo.asm
- ; Run: x64sc sid_demo.prg
- ;
- ; What it does:
- ; Voice 1 (melody) - plays a short phrase, switching WAVEFORM every pass so
- ; you hear triangle -> sawtooth -> pulse -> back again.
- ; Voice 2 (bass) - a steady low sawtooth bassline under the melody.
- ; Voice 3 (harmony) - sustained notes a fifth above, with the SID FILTER
- ; sweeping open and closed so you hear the filter effect.
- ;
- ; Timing is done with a simple frame-counted delay loop driven off the VIC-II
- ; raster (a crude ~50Hz tick). No CIA/IRQ player - kept readable on purpose.
- ;
- ; SID register map (base $D400):
- ; +0/+1 voice1 freq lo/hi +7/+8 voice2 freq lo/hi +14/+15 v3 freq
- ; +2/+3 voice1 pulse width ...
- ; +4 voice1 control (waveform + gate)
- ; +5/+6 voice1 attack/decay , sustain/release
- ; (voices 2 and 3 follow at +7 and +14 offsets)
- ; +21/+22 filter cutoff lo/hi +23 filter resonance/routing +24 vol+mode
- SID = $D400
- V1FREQ = SID+0
- V1PW = SID+2
- V1CTRL = SID+4
- V1AD = SID+5
- V1SR = SID+6
- V2FREQ = SID+7
- V2PW = SID+9
- V2CTRL = SID+11
- V2AD = SID+12
- V2SR = SID+13
- V3FREQ = SID+14
- V3PW = SID+16
- V3CTRL = SID+18
- V3AD = SID+19
- V3SR = SID+20
- FCUTLO = SID+21
- FCUTHI = SID+22
- FRESON = SID+23
- FVOLMODE = SID+24
- RASTER = $D012
- ; zero page scratch
- note_idx = $02 ; index into melody table
- wave_sel = $03 ; which waveform voice1 currently uses (control byte hi bits)
- sweep_dir= $04 ; filter sweep direction (0 = up, 1 = down)
- sweep_val= $05 ; current filter cutoff (hi byte)
- tmp = $06
- * = $0801
- !byte $0b,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00 ; 10 SYS 2061
- start:
- sei
- ; ---- clear all SID registers ----
- lda #0
- ldx #$18
- clr: sta SID,x
- dex
- bpl clr
- ; ---- master volume = max, filter mode set later ----
- lda #$0f
- sta FVOLMODE
- ; ---- envelopes ----
- ; voice1 melody: quick attack, medium decay, mid sustain, short release
- lda #$09 ; attack=0 (fast), decay=9
- sta V1AD
- lda #$80 ; sustain=8, release=0
- sta V1SR
- ; voice2 bass: punchy
- lda #$0a
- sta V2AD
- lda #$90
- sta V2SR
- ; voice3 harmony: slow swell so the filter sweep is audible
- lda #$48 ; attack=4 (slowish), decay=8
- sta V3AD
- lda #$a0 ; sustain=A, release=0
- sta V3SR
- ; voice1 + voice3 pulse width (used when pulse waveform selected) ~50%
- lda #$00
- sta V1PW
- lda #$08
- sta V1PW+1
- lda #$00
- sta V3PW
- lda #$08
- sta V3PW+1
- ; ---- filter: route voice3 through it, low-pass, some resonance ----
- lda #%00000100 ; bit2 = filter voice3
- sta FRESON ; (low nibble = routing; high nibble = resonance)
- lda #%11110100 ; resonance=$F (high), and re-assert voice3 routing
- sta FRESON
- ; FVOLMODE: low nibble volume=$F, bit4=low-pass enable ($10)
- lda #$1f
- sta FVOLMODE
- ; ---- init state ----
- lda #0
- sta note_idx
- sta sweep_dir
- lda #$10 ; voice1 waveform start = triangle ($10)
- sta wave_sel
- lda #$40
- sta sweep_val
- cli
- ; ================= MAIN LOOP =================
- mainloop:
- ; --- play current melody note on voice 1 ---
- ldx note_idx
- lda mel_lo,x
- sta V1FREQ
- lda mel_hi,x
- sta V1FREQ+1
- ; gate ON with current waveform
- lda wave_sel
- ora #$01 ; set gate bit
- sta V1CTRL
- ; --- bass note on voice 2 (sawtooth), one per two melody notes ---
- lda bass_lo,x
- sta V2FREQ
- lda bass_hi,x
- sta V2FREQ+1
- lda #$21 ; sawtooth ($20) + gate ($01)
- sta V2CTRL
- ; --- harmony on voice 3 (triangle through filter) ---
- lda harm_lo,x
- sta V3FREQ
- lda harm_hi,x
- sta V3FREQ+1
- lda #$11 ; triangle ($10) + gate ($01)
- sta V3CTRL
- ; --- hold the note for a while, sweeping the filter as we wait ---
- jsr note_delay
- ; --- gate OFF (release) all voices ---
- lda wave_sel
- sta V1CTRL ; gate bit cleared -> release
- lda #$20
- sta V2CTRL
- lda #$10
- sta V3CTRL
- ; short gap
- jsr short_delay
- ; --- advance to next note ---
- inc note_idx
- ldx note_idx
- lda mel_lo,x
- cmp #$ff ; $ff in lo table marks end of phrase
- bne mainloop
- ; phrase finished: reset index and switch voice1 waveform
- lda #0
- sta note_idx
- jsr next_waveform
- jmp mainloop
- ; ---- cycle voice1 waveform: triangle -> saw -> pulse -> triangle ... ----
- next_waveform:
- lda wave_sel
- cmp #$10
- bne nw_try_saw
- lda #$20 ; triangle -> sawtooth
- jmp nw_set
- nw_try_saw:
- cmp #$20
- bne nw_pulse
- lda #$40 ; sawtooth -> pulse
- jmp nw_set
- nw_pulse:
- lda #$10 ; pulse -> back to triangle
- nw_set:
- sta wave_sel
- rts
- ; ---- note_delay: hold ~24 frames, advancing the filter sweep each frame ----
- note_delay:
- ldy #24
- nd_loop:
- jsr wait_frame
- ; sweep filter cutoff up and down
- lda sweep_dir
- bne nd_down
- inc sweep_val
- lda sweep_val
- cmp #$f0
- bcc nd_apply
- lda #1
- sta sweep_dir
- jmp nd_apply
- nd_down:
- dec sweep_val
- lda sweep_val
- cmp #$20
- bcs nd_apply
- lda #0
- sta sweep_dir
- nd_apply:
- lda sweep_val
- sta FCUTHI
- dey
- bne nd_loop
- rts
- short_delay:
- ldy #6
- sd_loop:
- jsr wait_frame
- dey
- bne sd_loop
- rts
- ; ---- wait_frame: spin until the raster reaches line $FA (250), which happens
- ; exactly once per frame -> a clean ~50Hz (PAL) / 60Hz (NTSC) tick.
- ; We first wait for it to LEAVE 250 (in case we're already there), then
- ; wait for it to come back, guaranteeing one full frame elapses.
- wait_frame:
- wf_leave:
- lda RASTER
- cmp #$fa
- beq wf_leave ; wait until we are NOT on line 250
- wf_reach:
- lda RASTER
- cmp #$fa
- bne wf_reach ; wait until raster hits line 250
- rts
- ; ================= NOTE DATA =================
- ; Frequencies are 16-bit SID values (PAL). $ff in mel_lo ends the phrase.
- ; Melody: a little rising/falling phrase.
- mel_lo:
- !byte $25,$1c,$d6,$48,$d6,$1c,$25,$00,$ff
- mel_hi:
- !byte $11,$15,$11,$1c,$11,$15,$11,$00,$00
- ; Bass: lower octave roots
- bass_lo:
- !byte $25,$25,$d6,$d6,$d6,$25,$25,$25,$00
- bass_hi:
- !byte $08,$08,$06,$06,$06,$08,$08,$08,$00
- ; Harmony: a fifth above the melody, sustained
- harm_lo:
- !byte $b8,$ad,$1c,$8e,$1c,$ad,$b8,$00,$00
- harm_hi:
- !byte $19,$1f,$15,$23,$15,$1f,$19,$00,$00
Advertisement
Add Comment
Please, Sign In to add comment