15 NEW WAVEFORMS FOR THE VIC-20 Written by viznut/pwp in the early June 2003 Most VIC-20 people assume that the waveform of the VIC-I pulse channels ($900a..$900c) is generated by a simple flip-flop. This does not seem to be the case. By changing an oscillator's "on/off" bit very rapidly it is possible to get it in states that give out waveforms different to the normal 1:1 pulse waveform you have learned to expect from the VIC-20. An example: ; assume that the content of $900c has been $7e for a while ; (at least a couple of hundred cycles) ldx #$7e ; oscillator off, maximum shift rate (4 clocks) ldy #$fe ; oscillator on, maximum shift rate (4 clocks) lda #152 ; the initial frequency of the sound (can be any) sei ; the following stuff needs exact timing sty $900c ; push 1 to oscillator sty $900c ; push 1 to oscillator stx $900c ; push 0 to oscillator sta $900c ; and let it rotate on its own cli This gives out the waveform later referred to as the "110" wave. The following is my current theory about the VIC-I sound: Each VIC-I voice has: - an 8-bit shift register - a 7-bit counter - a 7-bit frequency value - on/off bit The clock rates for each voice: - $900c: cpuclock/4 - $900b: cpuclock/8 - $900a: cpuclock/16 - $900d: cpuclock/32 (?) When a voice gets a clock cycle: - The counter value is incremented by 1. - If the incremented value is all ones ($7F), the counter is reset to the frequency value. Also, the channel's shift register is shifted. The shift function for pulsewave channels: - Shift all bits left by one position. - If the voice is ON: the lowest bit becomes the complement of the previous top bit. - If the voice is OFF: the lowest bit becomes zero. - The lowest bit of the shift register is the output - In formal C-like code: reg = (reg<<1) | ( ((reg>>7)^1) & voiceon); The shift function for noise is probably something akin to that of the TED chip used in the C-16 and the Plus-4. Waveforms: These are the 16 possible waveforms according to the theory: NAME SHAPE default 0000000011111111 "10" 0000001011111101 "100" 0000010011111011 "110" 0000011011111001 "1000" 0000100011110111 "1010" 0000101011110101 "1011" 0000110011110011 "1110" 0000111011110001 "10010" 0001001011101101 "10100" 0001010011101011 "10110" 0001011011101001 "11000" 0001100011100111 "11010" 0001101011100101 "100100" 0010010011011011 "101010" 0010101011010101 "101100" 0010110011010011 A short generic routine for setting any shift register value for any pulse channel in about 150 cpu clocks. Use it freely. setwave: ; USAGE: y = channel ($0a..$0c) ; x = initial frequency ; a = shift register contents ; ; WARNING for purists: self-modifying code, illegal opcodes. ; ; code align assertion: make sure that the loop is within a page. ; oscillator assertion: make sure that the channel has been at $7e ; for some time before calling this function. ; put TMP and TMP2 in the zero page. stx .initfreq ; 4 sty .ch0 ; 4 sty .ch1 ; 4 ldx .ldfqmasks-$a,y ; 4 sta TMP ; 3 ora #$7f ; 2 axs $900c ; 4 [$900c] = a AND x .ch0=*-2 sty TMP2 ; 3 ldy #7 ; 2 .l0: lda #$7f ; 2 aso TMP ; 5 asl tmp; a = [tmp] OR $7f axs $900c ; 4 [$900c] = a AND x .ch1=*-2 dey ; 2 bne .l0 ; 3 lda #128 ; 2 .initfreq=*-1 nop ; 2 ldy TMP2 ; 3 .noset: sta $9000,y ; 5 rts ; 6 total clocks 11+4+3+2+16*7+16+6 eq 154 .ldfqmasks: !byte $fe ; $900a - 1 x 16 clocks/bit !byte $fd ; $900b - 2 x 8 clocks/bit !byte $fb ; $900c - 4 x 4 clocks/bit