MICrODEC: Stock Functions
Chorus
This function implements a chorus. It is mono, taking in data from the left channel, and presenting the mono output on both the left and right channels.
As with a vocal choir, there are a number of voices, all ever so slightly out of time and pitch from one another. This function attempts to replicate this effect by pitch shifting and delaying the input signal, to create a slightly out of time signal, which can be mixed back in with the original. This function adds two such voices, each with a different, predetermined delay. The easiest way to accomplish the pitch shifting is to add in a time varying delay, so at any point, the samples are being read back at a slower or faster rate. But, since its moving back and forth, there is no buffer boundary to deal with. Any time-varying signal can be used, but in this case, we are using a sinusoidal signal, as it introduces the least amount of added frequency content. The two voices generated by this chorus effect use the opposite sides of the same sinusoid, so as one voice is pitching up, the other is pitching down. This sinusoid is created by taking values from a look-up table stored in program memory. The frequency of the sinusoid is set by the pot (MOD1), and is variable from .084Hz to 10.84Hz.
The depth of the low frequency oscillator (LFO), and the resulting pitch shifting of the choir, can be modified by the rotary encoder (MOD2). Rotations to the right increase its amplitude (from 0ms to 6ms). The rotary encoder increments a value from 0-255 at the rate of 1 per click (code lines 350 and 357). As a result, it takes quite a few turns to go from one end to the other, however, this also gives the user very fine control over the resolution.
1 ; program: chorus-16b-sine.asm
2 ; UID = 000042 - unique id to eliminate conflicts between variables
3 ; 16b address space
4 ; mono data in on left channel, identical stereo out
5 ; pot (MOD1) controls lfo frequency
6 ; rotary encoder (MOD2) controls lfo depth
7
8 ; program overview
9 ;
10 ; data is read in from the codec and stored to sram. this data is then read
11 ; back out at a time varying delay. the average delay time is a fixed number
12 ; set at the beginning of the code, and is varied with a sinusoidal lfo. the
13 ; lfo is generated via interpolating a 16b 512s half sinewave lookup table.
14 ; a 32b number is used to index into this lookup table, to allow for very
15 ; slow lfo rates. this lfo is then multiplied by a 16b amplitude signal,
16 ; and the data is fetched from the sram, at that location. the adc is
17 ; oversampled 256 times and deadbanded before updating the lfo rate. the
18 ; rotary encoder is used to increment the amplitude of the lfo. there are
19 ; two voices, each swept with the same lfo signal, but in opposite
20 ; directions. the average delay for each is independent.
21
22 ; constants
23 ;
24 .equ delay1_000042 = $0321 ; chorus average delay time for voice 1
25 ; (1/44.1 ms per unit), min value $0100
26 .equ delay2_000042 = $0444 ; chorus average delay time for voice 2
27 ; (1/44.1 ms per unit), min value $0100
28
29 ; register usage - may be redefined in other sections
30 ;
31 ; r0 multiply result lsb
32 ; r1 multiply result msb
33 ; r2 accumulation lsb
34 ; r3 accumulation mlb
35 ; r4 right/left lsb out/accumulation mhb
36 ; r5 right/left msb out/accumulation msb
37 ; r6 left lsb in
38 ; r7 left msb in
39 ; r8 adc accumulator fractional byte
40 ; r9 adc accumulator lsb
41 ; r10 adc accumulator msb
42 ; r11 rotary encoder counter
43 ; r12 lfo rate lsb
44 ; r13 lfo rate msb
45 ; r14 null register
46 ; r15 switch sample counter
47 ; r16 temporary swap register
48 ; r17 temporary swap register
49 ; r18 sine wave buffer/multiply msb
50 ; r19 sine wave buffer/multiply msb
51 ; r20 multiply swap register
52 ; r21 multiply swap register
53 ; r22 sinetable lookup address lsb
54 ; r23 sinetable lookup address mlb
55 ; r24 write address lsb
56 ; r25 write address msb
57 ; r26 sinetable lookup address mhb
58 ; r27 sinetable lookup address msb
59 ; r28 temporary swap register
60 ; r29 lfo depth
61 ; r30 jump location for interrupt lsb
62 ; r31 jump location for interrupt msb
63 ; t rotary encoder edge detect indicator
64
65 ; program starts here first time
66 ; intialize registers
67 ldi r30,$04 ; increment z pointer to new jump location
68 clr r14 ; clear null register
69 ldi r29,$0e ; initialize lfo depth
70 reti ; finish with initialization and wait for next interrupt
71
72 ; program starts here every time but first
73 ; initiate data transfer to codec
74 sbi portb,portb0 ; toggle slave select pin
75 out spdr,r5 ; send out left channel msb
76 cbi portb,portb0
77
78 adiw r25:r24,$01 ; increment write address
79
80 wait1_000042: ; check if byte has been sent
81
82 in r17,spsr
83 sbrs r17,spif
84 rjmp wait1_000042
85 in r7,spdr ; recieve in left channel msb
86 out spdr,r4 ; send out left channel lsb
87
88 wait2_000042: ; check if byte has been sent
89
90 in r17,spsr
91 sbrs r17,spif
92 rjmp wait2_000042
93 in r6,spdr ; recieve in left channel lsb
94 out spdr,r5 ; send out right channel msb
95
96 ;write left channel datat to sram
97 out portd,r24 ; set address
98 sts porth,r25
99 out portg,r14 ; pull ce low,we low,and set high bits of address
100 ldi r17,$ff
101 out ddra,r17 ; set porta as output for data write
102 out ddrc,r17 ; set portc as output for data write
103 out porta,r6 ; set data
104 out portc,r7
105 sbi portg,portg2 ; pull we high to write
106 out ddra,r14 ; set porta as input for data lines
107 out ddrc,r14 ; set portc as input for data lines
108
109 wait3_000042: ; check if byte has been sent
110
111 in r17,spsr
112 sbrs r17,spif
113 rjmp wait3_000042
114 in r17,spdr ; recieve in right channel msb
115 out spdr,r4 ; send out right channel lsb
116
117 wait4_000042: ; check if byte has been sent
118
119 in r17,spsr
120 sbrs r17,spif
121 rjmp wait4_000042
122 in r17,spdr ; recieve in right channel lsb
123
124 ; vco generation
125 movw r17:r16,r31:r30 ; store z register
126 ;get sample 1
127 add r22,r12 ; increment sinetable address
128 adc r23,r13
129 adc r26,r14 ; r14 is cleared above
130 adc r27,r14
131 movw r31:r30,r27:r26 ; move to z register for data fetch
132 lsl r30 ; adjust pointer for 16b fetch
133 rol r31
134 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
135 ori r31,$48 ; set to memory address location where table is stored
136 lpm r18,z+ ; get sine value lsb, increment z register
137 lpm r19,z ; get sine value msb
138 sbrc r27,$01 ; flip sign for half of the values
139 rjmp interpolate_000042
140 neg r18
141 adc r19,r14 ; r14 is cleared above
142 neg r19
143
144 interpolate_000042: ; multiply sample 1 by distance
145
146 movw r21:r20,r23:r22 ; get distance from sample 1
147 com r20 ; invert distance
148 com r21
149 mulsu r19,r21 ; (signed)Ah * (unsigned)Bh - multiply high bytes
150 movw r5:r4,r1:r0 ; store high bytes result for later
151 mul r18,r20 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
152 movw r3:r2,r1:r0 ; store low byets for later
153 mulsu r19,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
154 sbc r5,r14 ; r14 is cleared above - subtract sign bit
155 add r3,r0 ; accumulate result
156 adc r4,r1
157 adc r5,r14 ; r14 is cleared above
158 mul r21,r18 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
159 add r3,r0 ; accumulate result
160 adc r4,r1
161 adc r5,r14 ; r14 is cleared above
162
163 ;get sample 2
164 adiw r27:r26,$01 ; set to next sample
165 movw r31:r30,r27:r26 ; move to z register for data fetch
166 lsl r30 ; adjust pointer for 16b fetch
167 rol r31
168 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
169 ori r31,$48 ; set to memory address location where table is stored
170 lpm r18,z+ ; get sine value lsb, increment z register
171 lpm r19,z ; get sine value msb
172 sbrc r27,$01 ; flip sign for half of the values
173 rjmp interpolate1_000042
174 neg r18
175 adc r19,r14 ; r14 is cleared above
176 neg r19
177
178 interpolate1_000042: ; multiply sample 2 by distance
179
180 sbiw r27:r26,$01 ; reset address
181 movw r31:r30,r17:r16 ; restore z register
182 mulsu r19,r23 ; (signed)Ah * (unsigned)Bh - multiply high bytes
183 add r4,r0 ; accumulate result
184 adc r5,r1
185 mul r18,r22 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
186 add r2,r0 ; accumulate result
187 adc r3,r1
188 adc r4,r14 ; r14 is cleared above
189 adc r5,r14
190 mulsu r19,r22 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
191 sbc r5,r14 ; r14 is cleared above - subtract sign bit
192 add r3,r0 ; accumulate result
193 adc r4,r1
194 adc r5,r14 ; r14 is cleared above
195 mul r23,r18 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
196 add r3,r0 ; accumulate result
197 adc r4,r1
198 adc r5,r14 ; r14 is cleared above
199
200 ;set lfo depth
201 movw r19:r18,r5:r4 ; move lfo signal to multiply register
202 mov r21,r29 ; move lfo depth to multiply register
203 mulsu r19,r21 ; (signed)ah * (unsigned)b
204 movw r5:r4,r1:r0
205 mul r21,r18 ; (unsigned)b * (unsigned)al
206 add r4,r1
207 adc r5,r14 ; r14 is cleared above
208
209 ;create first voice
210 ;add lfo to delay
211 mov r2,r4 ; make a backup copy of lfo value for second voice
212 mov r28,r5
213 movw r17:r16,r25:r24 ; move current location to read address
214 subi r16,low(delay1_000042) ; remove delay time
215 sbci r17,high(delay1_000042)
216 clr r21 ; prepare to add in lfo time
217 tst r5 ; check if lfo time is negative
218 brpl lfoadd_000042 ; add in lfo time if positive
219 ser r21 ; subtract lfo time if negative
220
221 lfoadd_000042: ; add in lfo time
222
223 add r16,r5 ; add in lfo time
224 adc r17,r21
225
226 ;get left channel sample 1 from sram
227 out portd,r16 ; set address
228 sts porth,r17
229 nop ; wait setup period of two cycles
230 nop
231 in r18,pina ; get data
232 in r19,pinc ; get data
233
234 ;multiply sample 1 by distance
235 mov r20,r4 ; get distance from sample 1
236 com r20 ; invert distance for sample weighting
237 mulsu r19,r20 ; (signed)ah * b
238 movw r5:r4,r1:r0
239 mul r18,r20 ; al * b
240 add r4,r1
241 adc r5,r14 ; r14 is cleared above
242 mov r3,r0
243
244 ;get left channel sample 2 from sram
245 subi r16,$ff ; set to next sample
246 sbci r17,$ff ; done this way because there is no addi or adci
247 out portd,r16 ; set address
248 sts porth,r17
249 nop ; wait setup period of two cycles
250 nop
251 in r18,pina ; get data
252 in r19,pinc ; get data
253
254 ;multiply sample 2 by distance
255 com r20 ; reset sample 2 distance
256 mulsu r19,r20 ; (signed)ah * b
257 add r4,r0 ; accumulate result
258 adc r5,r1
259 mul r18,r20 ; al * b
260 add r3,r0 ; accumulate result
261 adc r4,r1
262 adc r5,r14 ; r14 is cleared above
263 movw r7:r6,r5:r4 ; store voice 1
264
265 ;create second voice
266 ;add lfo to delay
267 mov r4,r2 ; restore lfo value for second voice
268 mov r5,r28
269 com r4 ; invert lfo for second voice
270 com r5
271 movw r17:r16,r25:r24 ; move current location to read address
272 subi r16,low(delay2_000042) ; remove delay time
273 sbci r17,high(delay2_000042)
274 clr r21 ; prepare to add in lfo time
275 tst r5 ; check if lfo time is negative
276 brpl lfoadd1_000042 ; add in lfo time if positive
277 ser r21 ; subtract lfo time if negative
278
279 lfoadd1_000042: ; add in lfo time
280
281 add r16,r5 ; add in lfo time
282 adc r17,r21
283
284 ;get left channel sample 1 from sram
285 out portd,r16 ; set address
286 sts porth,r17
287 nop ; wait setup period of two cycles
288 nop
289 in r18,pina ; get data
290 in r19,pinc ; get data
291
292 ;multiply sample 1 by distance
293 mov r20,r4 ; get distance from sample 1
294 com r20 ; invert distance for sample weighting
295 mulsu r19,r20 ; (signed)ah * b
296 movw r5:r4,r1:r0
297 mul r18,r20 ; al * b
298 add r4,r1
299 adc r5,r14 ; r14 is cleared above
300 mov r3,r0
301
302 ;get left channel sample 2 from sram
303 subi r16,$ff ; set to next sample
304 sbci r17,$ff ; done this way because there is no addi or adci
305 out portd,r16 ; set address
306 sts porth,r17
307 nop ; wait setup period of two cycles
308 nop
309 in r18,pina ; get data
310 in r19,pinc ; get data
311
312 ;multiply sample 2 by distance
313 com r20 ; reset sample 2 distance
314 mulsu r19,r20 ; (signed)ah * b
315 add r4,r0 ; accumulate result
316 adc r5,r1
317 mul r18,r20 ; al * b
318 add r3,r0 ; accumulate result
319 adc r4,r1
320 adc r5,r14 ; r14 is cleared above
321
322 ;divide both voices by 2 and add them
323 asr r7 ; divide voice 1 by 2
324 ror r6
325 asr r5 ; divide voice 2 by 2
326 ror r4
327 add r4,r6 ; add the two voices
328 adc r5,r7
329
330 ;check rotary encoder and adjust lfo depth
331 ; although rotary encoder is externally debounced, it is done here again.
332 ; pin1 is sampled on a transition from high to low on pin0. if pin1 is
333 ; high, a left turn occured, if pin1 is low, a right turn occured.
334 dec r11 ; check if time to sample rotary encoder
335 brne adcsample_000042 ; continue if not
336 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
337 mov r11,r17
338 lds r17,pinj ; get switch data
339 sbrs r17,$00 ; check if pin0 is low
340 rjmp edge_000042 ; check if pin0 was low on previous sample
341 clt ; clear state register if back high
342 rjmp adcsample_000042 ; finish off
343
344 edge_000042: ; check for falling edge
345
346 brts adcsample_000042 ; do nothing if edge was already detected
347 set ; set t register to indicate edge detected
348 sbrs r17,$01 ; check if pin1 is high
349 rjmp increment_000042 ; increment desired delay if right rotation
350 subi r29,$01 ; decrement lfo depth register
351 brcc adcsample_000042 ; check if underflow occured
352 clr r29 ; set lfo depth to min
353 rjmp adcsample_000042 ; finish off
354
355 increment_000042: ; increment desired delay register
356
357 ldi r16,$01 ; increment lfo depth register
358 add r29,r16
359 brcc adcsample_000042 ; check if overflow occured
360 ser r29 ; set lfo depth to max
361
362 adcsample_000042: ; sample adc for lfo rate
363
364 lds r17,adcsra ; get adc control register
365 sbrs r17,adif ; check if adc conversion is complete
366 rjmp done_000042 ; skip adc sampling
367 lds r16,adcl ; get low byte adc value
368 lds r17,adch ; get high byte adc value
369 add r8,r16 ; accumulate adc samples
370 adc r9,r17
371 adc r10,r14 ; r14 is cleared above
372 ldi r17,$f7
373 sts adcsra,r17 ; clear interrupt flag
374 dec r15 ; countdown adc sample clock
375 brne done_000042 ; get delay time if its been long enough
376
377 deadband_000042: ; set the low value of the delay
378
379 lsr r10 ; divide adc value by 16
380 ror r9
381 ror r8
382 lsr r10
383 ror r9
384 ror r8
385 lsr r9 ; r10 is now empty
386 ror r8
387 lsr r9
388 ror r8
389 movw r17:r16,r9:r8 ; move adc sample to temporary register
390 ldi r21,$80 ; add in offset of min lfo rate ($0080)
391 add r16,r21
392 adc r17,r14 ; r14 is cleared above
393 sub r16,r12 ; find difference between adc sample and current lfo rate
394 sbc r17,r13
395 brsh check_000042 ; check for deadband if positive
396 neg r16 ; invert if negative
397 adc r17,r14 ; r14 is cleared above
398 neg r17
399
400 check_000042: ; check if difference is greater than deadband
401
402 cpi r16,$10 ; check if difference is less than 1 adc lsb
403 cpc r17,r14 ; r14 cleared above
404 brlo empty_000042 ; do nothing if less than 1 adc lsb
405 movw r13:r12,r9:r8 ; move adc sample to lfo rate register
406 add r12,r21 ; add in offset of min lfo rate ($0080)
407 adc r13,r14 ; r14 is cleared above
408
409 empty_000042: ; empty accumulation registers and finish off
410
411 clr r8 ; empty accumulation registers
412 clr r9
413 clr r10
414
415 switchsample_000042: ; check rotary switch
416
417 lds r16,pinj ; get switch data
418 andi r16,$78 ; mask off rotary switch
419 lsr r16 ; adjust switch position to program memory location
420 lsr r16
421 ldi r17,$02
422 add r16,r17
423 cpse r16,r31 ; check if location has changed
424 clr r30 ; reset jump register to intial state
425 mov r31,r16
426
427 done_000042:
428
429 reti ; return to waiting
430