welcome: please sign in
location: FullwaveDelayLowpass

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.

fullwave_delay_lowpass.asm


   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 

FullwaveDelayLowpass (last edited 2010-08-21 02:07:46 by guest)