; program: vco.asm
; UID = 000029 - unique id to eliminate conflicts between variables
; sram not used
; mono data in on left channel, identical stereo out

; constant definitions
;
.equ maxvalue_000029 = $0200 ; storage location in internal sram for
; envelope max value, as ive run out of registers.
.equ decay_000029 = $10 ; decay amount per sample period for envelope

; program overview
;
; a sinewave is generated internally from a lookup table in flash.  the
; frequency can be set by either an external voltage applied on the left
; channel input, or via the adc input.  the rotary encoder changes the
; functionality, from adc input with varying max values (86Hz, 171Hz, 343Hz,
; 686Hz, 1.4KHz, 2.8kHz, 5.5kHz, 11kHz), to analog input (in/1, in/2, in/4,
; in/8, in/16, in/32, in/64, in/128), to envelope control (8 adc input
; controls, and 8 analog input controls, with the same values as above).  in
; envelope control mode, the envelope of the input signal also sets the
; envelope of the vco output. the sinewave is generated from a lookup table
; in program memory, and is interpolated to get non-integer multiples of
; the base frequency. it uses a 512 sample, 16b lookup table.  the analog
; input is lowpassed at ~1kHz to give smoother response, and the adc is
; oversampled 256 times and deadbanded to remove glitches.  the envelope is
; generated by keeping track of the iput voltage max value, and decaying
; that value with time.

; register usage - may be redefined in other sections
;
; r0  multiply result lsb
; r1  multiply result msb
; r2  low pass accumulation fractional byte
; r3  low pass accumulation lsb
; r4  right/left lsb out/accumulation lsb
; r5  right/left msb out/accumulation msb
; r6  left lsb in
; r7  left msb in
; r8  low pass accumulation msb
; r9  adc accumulation msb
; r10 adc accumulation fractional byte
; r11 adc accumulation lsb
; r12 vco control signal lsb
; r13 vco control signal msb
; r14 rotary encoder counter
; r15 switch/adc sample counter
; r16 temporary swap register
; r17 temporary swap register
; r18 temporary swap register
; r19 temporary swap register
; r20 temporary swap register
; r21 vco function register
; r22 null register
; r23 sinetable lookup address fractional byte
; r24 sinetable lookup address lsb
; r25 sinetable lookup address msb
; r26 adc last value lsb
; r27 adc last value msb
; r28 read/write address lsb
; r29 read/write address msb
; r30 jump location for interrupt lsb
; r31 jump location for interrupt msb

;program starts here first time and after buffer changes
ldi r30,$19 ; set jump location to program start
clr r24 ; clear write register
clr r25
clr r22 ; setup r22 as null register for carry addition and ddr setting
ldi r17,$ff ; setup r17 for ddr setting

clear_000029: ; clear delay buffer
; eliminates static when first switching to the delay setting
out portd,r24 ; set address
sts porth,r25
out portg,r22 ; pull ce low,we low,and set high bits of address
out ddra,r17 ; set porta as output for data write
out ddrc,r17 ; set portc as output for data write
out porta,r22 ; set data
out portc,r22 ; r18 is cleared above
sbi portg,portg2 ; pull we high to write
out ddra,r22 ; set porta as input for data lines
out ddrc,r22 ; set portc as input for data lines
inc r24 ; increment write register - only clears first 256 bytes
brne clear_000029 ; continue until end of buffer reached

cleardone_000029: ; reset registers

ldi r28,$20 ; set buffer size for lowpass (44.1kHz/value = cutoff frequency)
clr r29
clr r2 ; initialize accumulation registers for lowpass
clr r3
clr r8
ldi r21,$18 ; initialize vco state to envelope mod and analog input
reti ; finish with initialization and wait for next interrupt

; program starts here every time but first
; initiate data transfer to codec
sbi portb,portb0 ; toggle slave select pin
out spdr,r5 ; send out left channel msb
cbi portb,portb0

;increment sram addreses
adiw r29:r28,$01 ; increment read/write address
movw r17:r16,r29:r28 ; move to temporary register
subi r16,$20 ; remove buffer size for read address
sbc r17,r22 ; r22 is cleared above

wait1_000029: ; check if byte has been sent

in r7,spsr
sbrs r7,spif
rjmp wait1_000029
in r7,spdr ; recieve in left channel msb
out spdr,r4 ; send out left channel lsb

;get delayed data
out portd,r16 ; set address
sts porth,r17
nop ; wait input latch time of 2 clock cycles
nop ; wait input latch time of 2 clock cycles
in r0,pina ; get data
in r1,pinc ; get data

wait2_000029: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait2_000029
in r6,spdr ; recieve in left channel lsb
out spdr,r5 ; send out right channel msb

;write left channel data to sram
out portd,r28 ; set address
sts porth,r29
out portg,r22 ; pull ce low,we low,and set high bits of address
ldi r17,$ff
out ddra,r17 ; set porta as output for data write
out ddrc,r17 ; set portc as output for data write
out porta,r6 ; set data
out portc,r7
sbi portg,portg2 ; pull we high to write
out ddra,r22 ; set porta as input for data lines
out ddrc,r22 ; set portc as input for data lines

wait3_000029: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait3_000029
in r17,spdr ; recieve in right channel msb
out spdr,r4 ; send out right channel lsb

wait4_000029: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait4_000029
in r17,spdr ; recieve in right channel lsb

;accumulate samples for lowpass
add r2,r6 ; add in current sample
adc r3,r7
sbrc r7,$07 ; check if data is negative
ldi r22,$ff ; set high bits if it is
adc r8,r22 ; else r22 is cleared above
clr r22 ; reset null register
sub r2,r0 ; remove last sample in buffer
sbc r3,r1
sbrc r1,$07 ; check if data is negative
ldi r22,$ff ; set high bits if it is
sbc r8,r22 ; else r22 is cleared above
movw r7:r6,r3:r2 ; move data to temporary register
mov r22,r8
asr r22 ; divide by 32 to normalize output
ror r7
ror r6
asr r22
ror r7
ror r6
asr r22
ror r7
ror r6
asr r22
ror r7
ror r6
asr r22
ror r7
ror r6
clr r22 ; reset null register

; vco generation
movw r17:r16,r31:r30 ; store z register
;get sample 1
add r23,r12 ; increment sinetable address
adc r24,r13
adc r25,r22 ; r22 is cleared above
movw r31:r30,r25:r24 ; move to z register for data fetch
lsl r30 ; adjust pointer for 16b fetch
rol r31
andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
ori r31,$48 ; set to memory address location where table is stored
lpm r18,z+ ; get sine value lsb, increment z register
lpm r19,z ; get sine value msb
sbrc r25,$01 ; flip sign for half of the values
rjmp interpolate_000029 ; dont invert if even
neg r18 ; invert if odd
adc r19,r22 ; r22 is cleared above
neg r19

interpolate_000029: ; multiply sample 1 by distance

mov r20,r23 ; get distance from sample 1
com r20 ; invert distance for sample magnitude
mulsu r19,r20 ; (signed)ah * b
movw r5:r4,r1:r0
mul r18,r20 ; al * b
add r4,r1
adc r5,r22 ; r22 is cleared above
mov r20,r0

;get sample 2
adiw r25:r24,$01 ; set to next sample
movw r31:r30,r25:r24 ; move to z register for data fetch
lsl r30 ; adjust pointer for 16b fetch
rol r31
andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
ori r31,$48 ; set to memory address location where table is stored
lpm r18,z+ ; get sine value lsb, increment z register
lpm r19,z ; get sine value msb
sbrc r25,$01 ; flip sign for half of the values
rjmp interpolate1_000029 ; dont invert if even
neg r18 ; invert if odd
adc r19,r22 ; r22 is cleared above
neg r19

interpolate1_000029: ; multiply sample 2 by distance

movw r31:r30,r17:r16 ; restore z register
sbiw r25:r24,$01 ; reset address
mulsu r19,r23 ; (signed)ah * b
add r4,r0 ; accumulate samples
adc r5,r1
mul r18,r23 ; al * b
add r20,r0 ; accumulate samples
adc r4,r1
adc r5,r22 ; r22 is cleared above

;find envelope value
movw r17:r16,r7:r6 ; move lowpassed value to temporary register
tst r17 ; check if value is negative
brpl envelope_000029 ; dont invert if positive
com r16 ; invert negative values
com r17 ; using ones complement to avoid problem at $8000

envelope_000029: ; compare current value to max value

lsl r16 ; convert current value to unsigned number
rol r17 ; this is done to increase output volume
lds r18,maxvalue_000029 ; fetch current max value
lds r19,(maxvalue_000029 + 1)
cp r16,r18 ; compare current value to max value
cpc r17,r19
brlo envelope_dec_000029 ; decrement envelope if below
movw r19:r18,r17:r16 ; move value to max value if same or higher
rjmp restore_000029 ; dont decrement if just incremented

envelope_dec_000029: ; decay the envelope

subi r18,decay_000029 ; decay the envelope
sbc r19,r22 ; r22 is cleared above
brcc restore_000029 ; re-store new max value if no underflow
clr r18 ; set envelope to bottom
clr r19

restore_000029: ; re-store new max value

sts maxvalue_000029,r18 ; re-store new max value
sts (maxvalue_000029 + 1),r19

rotary_000029: ; check rotary encoder and adjust function
; rotary encoder is externally debounced, so that is not done here.
; pin1 is sampled on a transition from high to low on pin0.  if pin1 is
; high, a left turn occured, if pin1 is low, a right turn occured.
dec r14 ; reduce the sampling rate to help with debounce
brne adcsample_000029
ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
mov r14,r17
lds r17,pinj ; get switch data
sbrs r17,$00 ; check if pin0 is low
rjmp edge_000029 ; check if pin0 was low on previous sample
clt ;  clear state register if back high
rjmp adcsample_000029 ; finish off

edge_000029: ; check for falling edge

brts adcsample_000029 ; do nothing if the edge was already detected
set ; set state register to indicate a falling edge occured
sbrs r17,$01 ; check if pin1 is high
rjmp increment_000029 ; increment playback if right rotation
dec r21
rjmp adcsample_000029 ; finish off

increment_000029: ; increment playback speed

inc r21

adcsample_000029: ; get loop setting

lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp done_000029 ; skip adc sampling
lds r16,adcl ; get low byte adc value
lds r17,adch ; get high byte adc value
add r10,r16
adc r11,r17 ; accumulate adc samples
adc r9,r22 ; accumulate adc samples - r22 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r15 ; countdown adc sample clock
brne done_000029 ; move adc value to loop setting after 256 samples
lsr r9 ; divide value by 4 to get a 16b value
ror r11
ror r10
lsr r9
ror r11
ror r10

;check if value changed enough to warrant updating
movw r17:r16,r27:r26 ; make a copy of last adc value for comparison
sub r16,r10 ; find difference between current and last value
sbc r17,r11
brcc deadband_000029 ; see if difference is large enough to indicate a change
neg r16 ; invert difference if negative
adc r17,r22 ; r22 is cleared above
neg r17

deadband_000029: ; see if pot has moved or if its just noise

cpi r16,$40 ; see if difference is greater than 1 lsb
cpc r17,r22 ; r22 is cleared above
brlo nochange_000029 ; dont update loop time if difference is not large enough
sbrc r21,$03 ; check if in analog mode
rjmp update_000029 ; dont adjust frequency range if in analog mode
mov r17,r21 ; move vco function to temporary register
andi r17,$07 ; mask off lower 3b
breq update_000029 ; check if no multiply required

frequencyshift1_000029: ; adjust frequency range

lsr r11 ; adjust frequency range
ror r10
dec r17
brne frequencyshift1_000029 ; keep adjusting till done

update_000029: ; update loop time register

movw r27:r26,r11:r10 ; move adc value to last value register
sbrs r21,$03 ; check if in analog mode
movw r13:r12,r27:r26 ; move adc value to vco increment register

nochange_000029: ; clear accumulation registers

clr r10 ; empty accumulation registers
clr r11
clr r9

switchsample_000029: ; check rotary switch state

lds r16,pinj ; get switch data
andi r16,$78 ; mask off rotary switch
lsr r16 ; adjust switch position to program memory location
lsr r16
ldi r17,$02
add r16,r17
cpse r16,r31 ; check if location has changed
clr r30 ; reset jump register to intial state
mov r31,r16

done_000029:

sbrs r21,$03 ; check if in analog mode
rjmp done1_000029 ; finish if not
mov r17,r21 ; move vco function to temporary register
andi r17,$07 ; mask off lower 3b
breq offset_000029 ; check if no divide required

frequencyshift_000029: ; adjust frequency range

asr r7 ; adjust frequency range that input signal effects
ror r6
dec r17
brne frequencyshift_000029 ; keep adjusting till done

offset_000029: ; add in offset from adc

tst r7 ; check if input voltage is negative
brmi subtract_000029 ; subtract value and check for low bound
add r6,r26 ; else add input voltage to adc value to get frequency
adc r7,r27
brcc move_000029 ; check if overflow
ldi r17,$ff ; set value to max if overflow
mov r6,r17
mov r7,r17
rjmp move_000029

subtract_000029:

add r6,r26 ; add input voltage to adc value to get frequency
adc r7,r27 ; adding a negative value is the same as subtracting
brcs move_000029 ; check if underflow
clr r6 ; set value to min if underflow
clr r7

move_000029: ; finish off by moving value to vco increment register

movw r13:r12,r7:r6 ; move combined value to vco increment register

done1_000029:

sbrc r21,$04 ; check if in envelope mode
rjmp done2_000029 ; add envelope

;reduce output volume because its too loud
asr r5 ; divide by 2
ror r4
reti ; return to waiting if not envelope mode

done2_000029: ; add envelope

;multiply data by envelope - 
movw r17:r16,r5:r4 ; move signal to multiply register
mulsu r17,r19 ; (signed)ah * (unsigned)bh
movw r5:r4,r1:r0
mul	r18,r16	; (unsigned)al * (unsigned)bl
movw r7:r6,r1:r0
mulsu r17,r18 ; (signed)ah * (unsigned)bl
sbc	r5,r22 ; r22 is cleared above
add	r7,r0
adc	r4,r1
adc	r5,r22
mul r19,r16 ; (unsigned)bh * (unsigned)al
add	r7,r0
adc	r4,r1
adc	r5,r22
reti ; return to waiting


