=== MICrODEC: Stock Functions === ==== VCO / VCA ==== This function implements a voltage controlled oscillator (VCO), with the ability to control its amplitude (voltage controlled amplitude - VCA). The control voltage is taken from left channel input, and the output is presented on both the left and right channels. The pot (MOD1) set the oscillators base frequency, and the rotary encoder (MOD2) adjusts the functionality of the VCO. In its simplest form, it can be used as a very low distortion audio sine wave generator, which has been extremely useful for us a number of times already. The main oscillator is a sinusoid generated from a look-up table in program memory. This consists of a 512 sample, 16 bit, half sine wave. The other half of the sine wave is generated by reading through the table a second time, and inverting all the values. If you were to play through this table at normal rate (44.1ks/s), it would produce a tone of 43Hz. To get other frequencies, it is either read through at slower or faster rate. The problem with reading through at faster rates, is that you need samples from points in time when you did not take a sample. The most common method of dealing with this problem is interpolation. Interpolation is a method of guessing what a value might have been if we actually had sampled at that point in time. For this pitch-shifter function, we use a linear interpolation. This means we draw a straight line between the two adjacent samples from where we want data, and assume our value is on that line. So if we're closer in time to one sample versus the other, than our output value is closer in value to that sample (the output is a sum of the two values, weighted by their distance to our sample point). The pot (MOD1) adjusts this playback rate from 0Hz all the way to a max value, which is determined by MOD2. Each 'click' of the rotary encoder indexes the current VCO/VCA mode. It has 8 consecutive oscillator modes. This means it just outputs a sinusoid, and does neither the VCO or VCA mode. These have max values of 86Hz, 171Hz, 343Hz, 686Hz, 1.4KHz, 2.8kHz, 5.5kHz, and 11kHz. The next 8 modes are simple VCO's, with the frequency of the output being determined by the sum of MOD1 and the analog input voltage on the left channel. In these modes, adjusting MOD2 varies the amount that the input signal is allowed to change the frequency of the VCO. The VCO has a range of 0Hz to 11Hz for all VCO modes. The next 8 modes are VCA modes, which means the input on the left channel determines the amplitude of the oscillator. This is done by taking the long term average of the peak input signal (called envelope stripping), and multiplying it with the oscillator. Setting the output volume to match the input peak values, rather than just multiplying the two together, allows the output to maintain a much more pure tone, as multiplying two signals directly adds their harmonics together. Basically, its setting the internal oscillator volume to match the input signal's volume. The VCA mode uses the same 8 max oscillator values as the simple oscillator mode described above. The last 8 modes are both VCO and VCA, with the output signal's frequency and amplitude determined by the input signal. They work in the same fashion as above, except the frequency range is fixed at 0Hz to 11kHz. In all of the VCO modes, the frequency is limited to this range, so values do not wrap around, i.e. an input command for a lower value than 0Hz does not become and output of 11kHz. The MICrODEC starts in this mode when switched to the VCO/VCA function. All of the input control voltages are first low-passed at approximately 1Khz before being sent to the VCO or VCA. This is done to keep the output less jittery, and free of higher harmonics added by the input signal. It is often difficult to tell exactly which mode the VCO/VCA function is in, and an external readout on the serial port would be a good mod. [[attachment:vco.asm|vco.asm]] ---- {{{#!highlight nasm ; 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 }}}