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