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.
1 ; program: vco.asm
2 ; UID = 000029 - unique id to eliminate conflicts between variables
3 ; sram not used
4 ; mono data in on left channel, identical stereo out
5
6 ; constant definitions
7 ;
8 .equ maxvalue_000029 = $0200 ; storage location in internal sram for
9 ; envelope max value, as ive run out of registers.
10 .equ decay_000029 = $10 ; decay amount per sample period for envelope
11
12 ; program overview
13 ;
14 ; a sinewave is generated internally from a lookup table in flash. the
15 ; frequency can be set by either an external voltage applied on the left
16 ; channel input, or via the adc input. the rotary encoder changes the
17 ; functionality, from adc input with varying max values (86Hz, 171Hz, 343Hz,
18 ; 686Hz, 1.4KHz, 2.8kHz, 5.5kHz, 11kHz), to analog input (in/1, in/2, in/4,
19 ; in/8, in/16, in/32, in/64, in/128), to envelope control (8 adc input
20 ; controls, and 8 analog input controls, with the same values as above). in
21 ; envelope control mode, the envelope of the input signal also sets the
22 ; envelope of the vco output. the sinewave is generated from a lookup table
23 ; in program memory, and is interpolated to get non-integer multiples of
24 ; the base frequency. it uses a 512 sample, 16b lookup table. the analog
25 ; input is lowpassed at ~1kHz to give smoother response, and the adc is
26 ; oversampled 256 times and deadbanded to remove glitches. the envelope is
27 ; generated by keeping track of the iput voltage max value, and decaying
28 ; that value with time.
29
30 ; register usage - may be redefined in other sections
31 ;
32 ; r0 multiply result lsb
33 ; r1 multiply result msb
34 ; r2 low pass accumulation fractional byte
35 ; r3 low pass accumulation lsb
36 ; r4 right/left lsb out/accumulation lsb
37 ; r5 right/left msb out/accumulation msb
38 ; r6 left lsb in
39 ; r7 left msb in
40 ; r8 low pass accumulation msb
41 ; r9 adc accumulation msb
42 ; r10 adc accumulation fractional byte
43 ; r11 adc accumulation lsb
44 ; r12 vco control signal lsb
45 ; r13 vco control signal msb
46 ; r14 rotary encoder counter
47 ; r15 switch/adc sample counter
48 ; r16 temporary swap register
49 ; r17 temporary swap register
50 ; r18 temporary swap register
51 ; r19 temporary swap register
52 ; r20 temporary swap register
53 ; r21 vco function register
54 ; r22 null register
55 ; r23 sinetable lookup address fractional byte
56 ; r24 sinetable lookup address lsb
57 ; r25 sinetable lookup address msb
58 ; r26 adc last value lsb
59 ; r27 adc last value msb
60 ; r28 read/write address lsb
61 ; r29 read/write address msb
62 ; r30 jump location for interrupt lsb
63 ; r31 jump location for interrupt msb
64
65 ;program starts here first time and after buffer changes
66 ldi r30,$19 ; set jump location to program start
67 clr r24 ; clear write register
68 clr r25
69 clr r22 ; setup r22 as null register for carry addition and ddr setting
70 ldi r17,$ff ; setup r17 for ddr setting
71
72 clear_000029: ; clear delay buffer
73 ; eliminates static when first switching to the delay setting
74 out portd,r24 ; set address
75 sts porth,r25
76 out portg,r22 ; pull ce low,we low,and set high bits of address
77 out ddra,r17 ; set porta as output for data write
78 out ddrc,r17 ; set portc as output for data write
79 out porta,r22 ; set data
80 out portc,r22 ; r18 is cleared above
81 sbi portg,portg2 ; pull we high to write
82 out ddra,r22 ; set porta as input for data lines
83 out ddrc,r22 ; set portc as input for data lines
84 inc r24 ; increment write register - only clears first 256 bytes
85 brne clear_000029 ; continue until end of buffer reached
86
87 cleardone_000029: ; reset registers
88
89 ldi r28,$20 ; set buffer size for lowpass (44.1kHz/value = cutoff frequency)
90 clr r29
91 clr r2 ; initialize accumulation registers for lowpass
92 clr r3
93 clr r8
94 ldi r21,$18 ; initialize vco state to envelope mod and analog input
95 reti ; finish with initialization and wait for next interrupt
96
97 ; program starts here every time but first
98 ; initiate data transfer to codec
99 sbi portb,portb0 ; toggle slave select pin
100 out spdr,r5 ; send out left channel msb
101 cbi portb,portb0
102
103 ;increment sram addreses
104 adiw r29:r28,$01 ; increment read/write address
105 movw r17:r16,r29:r28 ; move to temporary register
106 subi r16,$20 ; remove buffer size for read address
107 sbc r17,r22 ; r22 is cleared above
108
109 wait1_000029: ; check if byte has been sent
110
111 in r7,spsr
112 sbrs r7,spif
113 rjmp wait1_000029
114 in r7,spdr ; recieve in left channel msb
115 out spdr,r4 ; send out left channel lsb
116
117 ;get delayed data
118 out portd,r16 ; set address
119 sts porth,r17
120 nop ; wait input latch time of 2 clock cycles
121 nop ; wait input latch time of 2 clock cycles
122 in r0,pina ; get data
123 in r1,pinc ; get data
124
125 wait2_000029: ; check if byte has been sent
126
127 in r17,spsr
128 sbrs r17,spif
129 rjmp wait2_000029
130 in r6,spdr ; recieve in left channel lsb
131 out spdr,r5 ; send out right channel msb
132
133 ;write left channel data to sram
134 out portd,r28 ; set address
135 sts porth,r29
136 out portg,r22 ; pull ce low,we low,and set high bits of address
137 ldi r17,$ff
138 out ddra,r17 ; set porta as output for data write
139 out ddrc,r17 ; set portc as output for data write
140 out porta,r6 ; set data
141 out portc,r7
142 sbi portg,portg2 ; pull we high to write
143 out ddra,r22 ; set porta as input for data lines
144 out ddrc,r22 ; set portc as input for data lines
145
146 wait3_000029: ; check if byte has been sent
147
148 in r17,spsr
149 sbrs r17,spif
150 rjmp wait3_000029
151 in r17,spdr ; recieve in right channel msb
152 out spdr,r4 ; send out right channel lsb
153
154 wait4_000029: ; check if byte has been sent
155
156 in r17,spsr
157 sbrs r17,spif
158 rjmp wait4_000029
159 in r17,spdr ; recieve in right channel lsb
160
161 ;accumulate samples for lowpass
162 add r2,r6 ; add in current sample
163 adc r3,r7
164 sbrc r7,$07 ; check if data is negative
165 ldi r22,$ff ; set high bits if it is
166 adc r8,r22 ; else r22 is cleared above
167 clr r22 ; reset null register
168 sub r2,r0 ; remove last sample in buffer
169 sbc r3,r1
170 sbrc r1,$07 ; check if data is negative
171 ldi r22,$ff ; set high bits if it is
172 sbc r8,r22 ; else r22 is cleared above
173 movw r7:r6,r3:r2 ; move data to temporary register
174 mov r22,r8
175 asr r22 ; divide by 32 to normalize output
176 ror r7
177 ror r6
178 asr r22
179 ror r7
180 ror r6
181 asr r22
182 ror r7
183 ror r6
184 asr r22
185 ror r7
186 ror r6
187 asr r22
188 ror r7
189 ror r6
190 clr r22 ; reset null register
191
192 ; vco generation
193 movw r17:r16,r31:r30 ; store z register
194 ;get sample 1
195 add r23,r12 ; increment sinetable address
196 adc r24,r13
197 adc r25,r22 ; r22 is cleared above
198 movw r31:r30,r25:r24 ; move to z register for data fetch
199 lsl r30 ; adjust pointer for 16b fetch
200 rol r31
201 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
202 ori r31,$48 ; set to memory address location where table is stored
203 lpm r18,z+ ; get sine value lsb, increment z register
204 lpm r19,z ; get sine value msb
205 sbrc r25,$01 ; flip sign for half of the values
206 rjmp interpolate_000029 ; dont invert if even
207 neg r18 ; invert if odd
208 adc r19,r22 ; r22 is cleared above
209 neg r19
210
211 interpolate_000029: ; multiply sample 1 by distance
212
213 mov r20,r23 ; get distance from sample 1
214 com r20 ; invert distance for sample magnitude
215 mulsu r19,r20 ; (signed)ah * b
216 movw r5:r4,r1:r0
217 mul r18,r20 ; al * b
218 add r4,r1
219 adc r5,r22 ; r22 is cleared above
220 mov r20,r0
221
222 ;get sample 2
223 adiw r25:r24,$01 ; set to next sample
224 movw r31:r30,r25:r24 ; move to z register for data fetch
225 lsl r30 ; adjust pointer for 16b fetch
226 rol r31
227 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
228 ori r31,$48 ; set to memory address location where table is stored
229 lpm r18,z+ ; get sine value lsb, increment z register
230 lpm r19,z ; get sine value msb
231 sbrc r25,$01 ; flip sign for half of the values
232 rjmp interpolate1_000029 ; dont invert if even
233 neg r18 ; invert if odd
234 adc r19,r22 ; r22 is cleared above
235 neg r19
236
237 interpolate1_000029: ; multiply sample 2 by distance
238
239 movw r31:r30,r17:r16 ; restore z register
240 sbiw r25:r24,$01 ; reset address
241 mulsu r19,r23 ; (signed)ah * b
242 add r4,r0 ; accumulate samples
243 adc r5,r1
244 mul r18,r23 ; al * b
245 add r20,r0 ; accumulate samples
246 adc r4,r1
247 adc r5,r22 ; r22 is cleared above
248
249 ;find envelope value
250 movw r17:r16,r7:r6 ; move lowpassed value to temporary register
251 tst r17 ; check if value is negative
252 brpl envelope_000029 ; dont invert if positive
253 com r16 ; invert negative values
254 com r17 ; using ones complement to avoid problem at $8000
255
256 envelope_000029: ; compare current value to max value
257
258 lsl r16 ; convert current value to unsigned number
259 rol r17 ; this is done to increase output volume
260 lds r18,maxvalue_000029 ; fetch current max value
261 lds r19,(maxvalue_000029 + 1)
262 cp r16,r18 ; compare current value to max value
263 cpc r17,r19
264 brlo envelope_dec_000029 ; decrement envelope if below
265 movw r19:r18,r17:r16 ; move value to max value if same or higher
266 rjmp restore_000029 ; dont decrement if just incremented
267
268 envelope_dec_000029: ; decay the envelope
269
270 subi r18,decay_000029 ; decay the envelope
271 sbc r19,r22 ; r22 is cleared above
272 brcc restore_000029 ; re-store new max value if no underflow
273 clr r18 ; set envelope to bottom
274 clr r19
275
276 restore_000029: ; re-store new max value
277
278 sts maxvalue_000029,r18 ; re-store new max value
279 sts (maxvalue_000029 + 1),r19
280
281 rotary_000029: ; check rotary encoder and adjust function
282 ; rotary encoder is externally debounced, so that is not done here.
283 ; pin1 is sampled on a transition from high to low on pin0. if pin1 is
284 ; high, a left turn occured, if pin1 is low, a right turn occured.
285 dec r14 ; reduce the sampling rate to help with debounce
286 brne adcsample_000029
287 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
288 mov r14,r17
289 lds r17,pinj ; get switch data
290 sbrs r17,$00 ; check if pin0 is low
291 rjmp edge_000029 ; check if pin0 was low on previous sample
292 clt ; clear state register if back high
293 rjmp adcsample_000029 ; finish off
294
295 edge_000029: ; check for falling edge
296
297 brts adcsample_000029 ; do nothing if the edge was already detected
298 set ; set state register to indicate a falling edge occured
299 sbrs r17,$01 ; check if pin1 is high
300 rjmp increment_000029 ; increment playback if right rotation
301 dec r21
302 rjmp adcsample_000029 ; finish off
303
304 increment_000029: ; increment playback speed
305
306 inc r21
307
308 adcsample_000029: ; get loop setting
309
310 lds r17,adcsra ; get adc control register
311 sbrs r17,adif ; check if adc conversion is complete
312 rjmp done_000029 ; skip adc sampling
313 lds r16,adcl ; get low byte adc value
314 lds r17,adch ; get high byte adc value
315 add r10,r16
316 adc r11,r17 ; accumulate adc samples
317 adc r9,r22 ; accumulate adc samples - r22 is cleared above
318 ldi r17,$f7
319 sts adcsra,r17 ; clear interrupt flag
320 dec r15 ; countdown adc sample clock
321 brne done_000029 ; move adc value to loop setting after 256 samples
322 lsr r9 ; divide value by 4 to get a 16b value
323 ror r11
324 ror r10
325 lsr r9
326 ror r11
327 ror r10
328
329 ;check if value changed enough to warrant updating
330 movw r17:r16,r27:r26 ; make a copy of last adc value for comparison
331 sub r16,r10 ; find difference between current and last value
332 sbc r17,r11
333 brcc deadband_000029 ; see if difference is large enough to indicate a change
334 neg r16 ; invert difference if negative
335 adc r17,r22 ; r22 is cleared above
336 neg r17
337
338 deadband_000029: ; see if pot has moved or if its just noise
339
340 cpi r16,$40 ; see if difference is greater than 1 lsb
341 cpc r17,r22 ; r22 is cleared above
342 brlo nochange_000029 ; dont update loop time if difference is not large enough
343 sbrc r21,$03 ; check if in analog mode
344 rjmp update_000029 ; dont adjust frequency range if in analog mode
345 mov r17,r21 ; move vco function to temporary register
346 andi r17,$07 ; mask off lower 3b
347 breq update_000029 ; check if no multiply required
348
349 frequencyshift1_000029: ; adjust frequency range
350
351 lsr r11 ; adjust frequency range
352 ror r10
353 dec r17
354 brne frequencyshift1_000029 ; keep adjusting till done
355
356 update_000029: ; update loop time register
357
358 movw r27:r26,r11:r10 ; move adc value to last value register
359 sbrs r21,$03 ; check if in analog mode
360 movw r13:r12,r27:r26 ; move adc value to vco increment register
361
362 nochange_000029: ; clear accumulation registers
363
364 clr r10 ; empty accumulation registers
365 clr r11
366 clr r9
367
368 switchsample_000029: ; check rotary switch state
369
370 lds r16,pinj ; get switch data
371 andi r16,$78 ; mask off rotary switch
372 lsr r16 ; adjust switch position to program memory location
373 lsr r16
374 ldi r17,$02
375 add r16,r17
376 cpse r16,r31 ; check if location has changed
377 clr r30 ; reset jump register to intial state
378 mov r31,r16
379
380 done_000029:
381
382 sbrs r21,$03 ; check if in analog mode
383 rjmp done1_000029 ; finish if not
384 mov r17,r21 ; move vco function to temporary register
385 andi r17,$07 ; mask off lower 3b
386 breq offset_000029 ; check if no divide required
387
388 frequencyshift_000029: ; adjust frequency range
389
390 asr r7 ; adjust frequency range that input signal effects
391 ror r6
392 dec r17
393 brne frequencyshift_000029 ; keep adjusting till done
394
395 offset_000029: ; add in offset from adc
396
397 tst r7 ; check if input voltage is negative
398 brmi subtract_000029 ; subtract value and check for low bound
399 add r6,r26 ; else add input voltage to adc value to get frequency
400 adc r7,r27
401 brcc move_000029 ; check if overflow
402 ldi r17,$ff ; set value to max if overflow
403 mov r6,r17
404 mov r7,r17
405 rjmp move_000029
406
407 subtract_000029:
408
409 add r6,r26 ; add input voltage to adc value to get frequency
410 adc r7,r27 ; adding a negative value is the same as subtracting
411 brcs move_000029 ; check if underflow
412 clr r6 ; set value to min if underflow
413 clr r7
414
415 move_000029: ; finish off by moving value to vco increment register
416
417 movw r13:r12,r7:r6 ; move combined value to vco increment register
418
419 done1_000029:
420
421 sbrc r21,$04 ; check if in envelope mode
422 rjmp done2_000029 ; add envelope
423
424 ;reduce output volume because its too loud
425 asr r5 ; divide by 2
426 ror r4
427 reti ; return to waiting if not envelope mode
428
429 done2_000029: ; add envelope
430
431 ;multiply data by envelope -
432 movw r17:r16,r5:r4 ; move signal to multiply register
433 mulsu r17,r19 ; (signed)ah * (unsigned)bh
434 movw r5:r4,r1:r0
435 mul r18,r16 ; (unsigned)al * (unsigned)bl
436 movw r7:r6,r1:r0
437 mulsu r17,r18 ; (signed)ah * (unsigned)bl
438 sbc r5,r22 ; r22 is cleared above
439 add r7,r0
440 adc r4,r1
441 adc r5,r22
442 mul r19,r16 ; (unsigned)bh * (unsigned)al
443 add r7,r0
444 adc r4,r1
445 adc r5,r22
446 reti ; return to waiting
447