MICrODEC: Stock Functions
Fullwave Distortion with Delay and Lowpass
This function implements a fullwave rectifier, with an optional delay and variable cutoff lowpass filter. The input is mono on the left channel, with the direct fullwave signal presented on the right output, and a delayed copy on the left output. The delay is set via the pot (MOD1), and the cutoff frequency is set with the rotary encoder (MOD2).
A fullwave rectifier is a simple function which takes all negative input values, and makes them positive. The analog version is often used on the input to powersupplies, to turn the incoming AC voltages into DC. This has the effect of doubling all of the frequencies, as a signal which normally would go from positive to negative, now goes from positive down to zero, and then back up to positive, adding an extra oscillation where there wasn't one before. It also adds a lot of high frequency noise, as the transition where it flips the negatives to positives is very sharp.
The lowpass filter inside this function allows you to adjust how much of this high frequency noise you want to let through. There are 7 different cut off frequencies, with rotations to the right of MOD2 increasing the cutoff frequency. The lowpass is implemented as a moving average filter. This takes an average of the past few samples, and presents it at the output. In this manner, if a few samples are really different from their neighbors, they get smoothed out in the averaging process. By increasing the number of samples that are averaged, you increase the smoothing effect. In order for this sort of filter to work, the average of the past samples must be very accurate, so each time the cutoff frequency is changed, the sample buffer is cleared, causing a click at the output.
The pot (MOD1) adjust the delay time, with a range of 0s to 1.5s. The signal is delayed after its been fullwave rectified and lowpassed. Both the direct and delayed signals are presented on the right and left channels, respectively, so a stereo out really helps to get the most out of this effect. If the delay is turned to maximum, the location in memory it will fetch from will be the lowpass sample buffer, so the delay will be very short, and a slightly harsher sound is produced.
1 ; program: fullwave-delay-lowpass.asm
2 ; UID = 000055 - unique id to eliminate conflicts between variables
3 ; mono data (left in only, delayed output on left, direct out on right)
4 ; pot (MOD1) controls the delay time (0s - 1.5s)
5 ; rotary encoder (MOD2) controls the cutoff frequency
6
7 ; program overview
8 ;
9 ; data is read in from the codec, and negative values are inverted to
10 ; positive values. all values are then shifted down to mid-rail and
11 ; multiplied by 2 to normalize the output. these values are then written
12 ; to memory, and read back out and accumulated. this creates a simple
13 ; moving average low pass filter. the delay time (and corresponding cutoff
14 ; frequency) is set with the rotary encoder (MOD2). there are 7 different
15 ; cutoff frequencies, with rotations to the right increasing the cutoff
16 ; frequnecy. changing the cutoff frequency restarts the program to blank
17 ; the accumulation buffer. the delay time is set with the pot (MOD1). the
18 ; adc is oversampled 256 times and deadbanded to reduce jitter. turning the
19 ; pot all the way to the right overlaps the delay and lowpass buffers,
20 ; causing some distortions.
21
22 ; register usage - may be redefined in other sections
23 ;
24 ; r0 adc accumulation fractional byte
25 ; r1 adc accumulation lsb
26 ; r2 adc accumulation msb
27 ; r3
28 ; r4 left lsb out
29 ; r5 left msb out
30 ; r6 left lsb in
31 ; r7 left msb in
32 ; r8 right output lsb
33 ; r9 right output msb
34 ; r10 accumulation lsb
35 ; r11 accumulation mlb
36 ; r12 accumulation mhb
37 ; r13 accumulation msb
38 ; r14 rotary encoder counter
39 ; r15 adc/switch sample counter
40 ; r16 temporary swap register
41 ; r17 temporary swap register
42 ; r18 null register
43 ; r19 cutoff frequency
44 ; r20 temporary register
45 ; r21
46 ; r22 actual delay lsb
47 ; r23 actual delay msb
48 ; r24 write address lsb
49 ; r25 write address msb
50 ; r26 desired delay lsb
51 ; r27 desired delay msb
52 ; r28 read address lsb
53 ; r29 read address msb
54 ; r30 jump location for interrupt lsb
55 ; r31 jump location for interrupt msb
56 ; t rotary encoder state bit
57
58 ;program starts here first time and after buffer changes
59 ldi r30,$1c ; set jump location to program start
60 ldi r16,$08 ; set lowpass buffer size to mid range
61 ldi r19,$03 ; initialize cutoff frequency to midrange
62
63 restart_000055: ; restart location for clearing memory
64
65 clr r24 ; clear write register
66 clr r25
67 clr r18 ; setup r18 as null register for carry addition and ddr setting
68 ldi r17,$ff ; setup r17 for ddr setting
69
70 clear_000055: ; clear lowpass buffer
71 ; required to ensure an accurate accumulation
72 out portd,r24 ; set address
73 sts porth,r25
74 out portg,r18 ; pull ce low,we low,and set high bits of address
75 out ddra,r17 ; set porta as output for data write
76 out ddrc,r17 ; set portc as output for data write
77 out porta,r18 ; set data
78 out portc,r18 ; r18 is cleared above
79 sbi portg,portg2 ; pull we high to write
80 out ddra,r18 ; set porta as input for data lines
81 out ddrc,r18 ; set portc as input for data lines
82 inc r24 ; increment write register - only clears first 256 bytes
83 brne clear_000055 ; continue until end of buffer reached
84
85 cleardone_000055: ; reset registers
86
87 mov r24,r16 ; set buffer size for lowpass
88 clr r28 ; set read address
89 clr r29
90 clr r10 ; initialize accumulation registers
91 clr r11
92 clr r12
93 clr r13
94 reti ; finish with initialization and wait for next interrupt
95
96 ; program starts here every time but first
97 ; initiate data transfer to codec
98 sbi portb,portb0 ; toggle slave select pin
99 out spdr,r5 ; send out left channel msb
100 cbi portb,portb0
101
102 ;increment sram addreses
103 adiw r25:r24,$01 ; increment write address
104 adiw r29:r28,$01 ; increment read address
105
106 wait1_000055: ; check if byte has been sent
107
108 in r17,spsr
109 sbrs r17,spif
110 rjmp wait1_000055
111 in r7,spdr ; recieve in left channel msb
112 out spdr,r4 ; send out left channel lsb
113
114 wait2_000055: ; check if byte has been sent
115
116 in r17,spsr
117 sbrs r17,spif
118 rjmp wait2_000055
119 in r6,spdr ; recieve in left channel lsb
120 out spdr,r9 ; send out right channel msb
121
122 ;fullwave rectify left data
123 sbrs r7,$07 ; check if negative
124 rjmp normalize_000055
125 com r6 ; invert data if negative (using ones complement to avoid problem at $8000)
126 com r7
127
128 normalize_000055: ; normalize data since its all positive values now
129
130 lsl r6 ; multiply data by two
131 rol r7 ; data is unsigned integer at this point
132 ldi r16,$80 ; convert to signed integer
133 add r7,r16
134
135 wait3_000055: ; check if byte has been sent
136
137 in r17,spsr
138 sbrs r17,spif
139 rjmp wait3_000055
140 in r17,spdr ; recieve in right channel msb
141 out spdr,r8 ; send out right channel lsb
142
143 ;write rectified left channel data to sram
144 out portd,r24 ; set address
145 sts porth,r25
146 out portg,r18 ; pull ce low,we low,and set high bits of address
147 ldi r17,$ff
148 out ddra,r17 ; set porta as output for data write
149 out ddrc,r17 ; set portc as output for data write
150 out porta,r6 ; set data
151 out portc,r7
152 sbi portg,portg2 ; pull we high to write
153 out ddra,r18 ; set porta as input for data lines
154 out ddrc,r18 ; set portc as input for data lines
155
156 wait4_000055: ; check if byte has been sent
157
158 in r17,spsr
159 sbrs r17,spif
160 rjmp wait4_000055
161 in r17,spdr ; recieve in left channel lsb
162
163 ;get left channel data from sram
164 out portd,r28 ; set address
165 sts porth,r29
166 nop ; wait input latch time of 2 clock cycles
167 nop
168 in r4,pina ; get data
169 in r5,pinc ; get data
170
171 ;accumulate samples for lowpass
172 add r10,r6 ; add in current sample
173 adc r11,r7
174 sbrc r7,$07 ; check if data is negative
175 ldi r18,$ff ; set high bits if it is
176 adc r12,r18 ; r18 is cleared above
177 adc r13,r18
178 clr r18 ; reset null register
179 sub r10,r4 ; remove last sample in buffer
180 sbc r11,r5
181 sbrc r5,$07 ; check if data is negative
182 ldi r18,$ff ; set high bits if it is
183 sbc r12,r18 ; r18 is cleared above
184 sbc r13,r18
185 clr r18 ; reset null register
186
187 mov r4,r10 ; divide by 256 and move to ouptput register
188 mov r5,r11
189 mov r17,r12
190 tst r19 ; check if no dividing necessary
191 breq store_000055 ; keep dividing till the right size
192 mov r16,r19 ; move cutoff to temporary register
193
194 divide_000055: ; divide accumulation for proper scaling
195
196 asr r17 ; divide accumulation
197 ror r5
198 ror r4
199 dec r16 ; check if done
200 brne divide_000055 ; keep dividing till the right size
201
202 store_000055: ; store lowpassed data to memory
203
204 movw r9:r8,r5:r4 ; move immediate data to right output
205 movw r17:r16,r25:r24 ; move write address to temporary register
206 subi r17,$01 ; move to delay buffer location
207 out portd,r16 ; set address
208 sts porth,r17
209 out portg,r18 ; pull ce low,we low,and set high bits of address
210 ldi r20,$ff
211 out ddra,r20 ; set porta as output for data write
212 out ddrc,r20 ; set portc as output for data write
213 out porta,r4 ; set data
214 out portc,r5
215 sbi portg,portg2 ; pull we high to write
216 out ddra,r18 ; set porta as input for data lines
217 out ddrc,r18 ; set portc as input for data lines
218
219 ;fetch delayed data from memory
220 sub r16,r22 ; subtract delay time
221 sbc r17,r23
222 out portd,r16 ; set address
223 sts porth,r17
224 nop ; wait input latch time of 2 clock cycles
225 nop
226 in r4,pina ; get data
227 in r5,pinc ; put delayed data to left output
228
229 rotary_000055: ; check rotary encoder and adjust cutoff frequency
230 ; although rotary encoder is externally debounced, it is done here again.
231 ; pin1 is sampled on a transition from high to low on pin0. if pin1 is
232 ; high, a left turn occured, if pin1 is low, a right turn occured.
233 dec r14 ; check if time to sample rotary encoder
234 brne shift_000055 ; continue if not
235 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
236 mov r14,r17
237 lds r17,pinj ; get switch data
238 sbrs r17,$00 ; check if pin0 is low
239 rjmp edge_000055 ; check if pin0 was low on previous sample
240 clt ; clear state register if back high
241 rjmp shift_000055 ; finish off
242
243 edge_000055: ; check for falling edge
244
245 brts shift_000055 ; do nothing if edge was already detected
246 set ; set t register to indicate edge detected
247 sbrs r17,$01 ; check if pin1 is high
248 rjmp increment_000055 ; increase cutoff frequency if right rotation
249 cpi r19,$06 ; else check if at max value
250 brsh shift_000055 ; finish off if at max
251 inc r19 ; incrementing cutoff value decreases cutoff frequency
252 rjmp buffer_000055 ; reset accumulation buffer
253
254 increment_000055: ; increase cutoff frequency
255
256 cpi r19,$01 ; check if cutoff at min value
257 brlo shift_000055 ; finish off if at min
258 dec r19 ; decrementing cutoff value increases cutoff frequency
259
260 buffer_000055: ; adjust buffer size
261
262 movw r29:r28,r25:r24 ; move write address to read address
263 ldi r16,$01 ; initialize the offset register
264 tst r19 ; check if any shifting is required
265 breq bufferload_000055
266 mov r17,r19 ; move cutoff to temporary register
267
268 shift1_000055: ; shift in zeros to make correct buffer size
269
270 lsl r16 ; increment buffer size
271 dec r17
272 brne shift1_000055 ; keep shifting until done
273
274 bufferload_000055: ; load buffer size
275
276 rjmp restart_000055 ; clear accumulation buffer
277
278 shift_000055: ; check if delay time is correct
279
280 cp r26,r22 ; compare desired delay to actual delay
281 cpc r27,r23
282 breq adcsample_000055 ; do nothing if the same
283 brlo indexdown_000055
284 ldi r17,$02 ; increment delay register
285 add r22,r17
286 adc r23,r18 ; r18 is cleared above
287 rjmp adcsample_000055
288
289 indexdown_000055:
290
291 ldi r17,$01 ; decrement delay register
292 sub r22,r17
293 sbc r23,r18 ; r18 is cleared above
294
295 adcsample_000055: ; get delay settings
296
297 lds r17,adcsra ; get adc control register
298 sbrs r17,adif ; check if adc conversion is complete
299 rjmp done_000055 ; skip adc sampling
300 lds r16,adcl ; get low byte adc value
301 lds r17,adch ; get high byte adc value
302 add r0,r16 ; accumulate adc samples
303 adc r1,r17
304 adc r2,r18 ; r18 is cleared above
305 ldi r17,$f7
306 sts adcsra,r17 ; clear interrupt flag
307 dec r15 ; countdown adc sample clock
308 brne done_000055 ; get delay time if its been long enough
309 lsr r2 ; divide adc sample by 4 to make 16b value
310 ror r1
311 ror r0
312 lsr r2
313 ror r1
314 ror r0
315
316 deadband_000055: ; check if adc has changed enough to warrant update
317
318 movw r17:r16,r1:r0 ; move adc sample to temporary register
319 sub r16,r26 ; find difference between adc sample and desired delay time
320 sbc r17,r27
321 brsh check_000055 ; check for deadband if positive
322 neg r16 ; invert if negative
323 adc r17,r18 ; r18 is cleared above
324 neg r17
325
326 check_000055: ; check if difference is greater than deadband
327
328 cpi r16,$40 ; check if difference is less than 1 lsb
329 cpc r17,r18 ; r18 cleared above
330 brlo empty_000055 ; do nothing if less than 1 lsb
331 movw r27:r26,r1:r0 ; move adc sample to delay time if large enough change
332 andi r26,$fe ; make sure delay time is a multiple of 2
333
334 empty_000055: ; empty accumulation registers and finish off
335
336 clr r0 ; empty adc accumulation registers
337 clr r1
338 clr r2
339
340 switchsample_000055: ; check rotary switch
341
342 lds r16,pinj ; get switch data
343 andi r16,$78 ; mask off rotary switch
344 lsr r16 ; adjust switch position to program memory location
345 lsr r16
346 ldi r17,$02
347 add r16,r17
348 cp r16,r31 ; check if location has changed
349 breq done_000055 ; finish off if no change
350 clr r30 ; reset jump register to new location
351 mov r31,r16
352
353 done_000055: ; normalize data and move to read buffer
354
355 reti ; return to waiting
356