StereoFlangerPan

MICrODEC: Stock Functions

Stereo Panning Flanger

This function implements a flanger which varies the delay between the left and right outputs. It takes in mono audio on the left channel, and outputs a stereo signal on both left and right channels. the pot (MOD1) adjusts the LFO frequency, and the rotary encoder (MOD2) adjusts the LFO depth.

Flanging is based on analog tape and reel recording techniques, where slight pressure is applied to the flange of the reel, slowing down the playback. This creates a time-varying delayed signal, which, when mixed with the original signal, has a swept filter effect. 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 (ramp waves are also common). 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) can be modified by the rotary encoder (MOD2). Rotations to the right increase its amplitude (from 0ms to 6ms). The rotary encoder sets an 8b value, so it takes quite a few turns to go from one end to the other, although this also gives finer resolution. The flanger offset delay is 0ms on this function.

The left and right channels have out-of-phase delays, such that while one channel is moving forward in time, the other is moving backwards. This gives the effect of a single sound source moving its position, as our mind infers position from the relative arrival time between left and right ears (amongst other things). This effect is particularly pronounced when listening on headphones.

flanger_stereo_pan.asm


   1 ; program: stereo_flanger-16b.asm
   2 ; UID = 000053 - unique id to eliminate conflicts between variables
   3 ; 16b address space
   4 ; mono data in on left channel, stereo data out (opposite phasing)
   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.  data is read out of
  11 ; sram at a variable delay set by an lfo.  the lfo is generated from a
  12 ; 16b/512s half sinewave lookup table.  this is incremented with a 24b
  13 ; number to get very low frequencies.  the lfo rate is set via the adc,
  14 ; which is oversampled 256 times and deadbanded to get rid of glitches.
  15 ; the lfo depth is created by multiplying the lfo signal with an 8b depth
  16 ; value, which is set via the rotary encoder.
  17 
  18 ; register usage - may be redefined in other sections
  19 ;
  20 ; r0  multiply result lsb
  21 ; r1  multiply result msb
  22 ; r2  accumulation lsb
  23 ; r3  accumulation mlb
  24 ; r4  left lsb out / accumulation mhb
  25 ; r5  left msb out / accumulation msb
  26 ; r6  right out lsb
  27 ; r7  right out msb
  28 ; r8  adc accumulator fractional byte
  29 ; r9  adc accumulator lsb
  30 ; r10 adc accumulator msb
  31 ; r11 rotary encoder counter
  32 ; r12 lfo rate lsb
  33 ; r13 lfo rate msb
  34 ; r14 null register
  35 ; r15 switch sample counter
  36 ; r16 temporary swap register
  37 ; r17 temporary swap register
  38 ; r18 sine wave buffer/multiply msb
  39 ; r19 sine wave buffer/multiply msb
  40 ; r20 multiply swap register
  41 ; r21 multiply swap register
  42 ; r22 sinetable lookup address lsb
  43 ; r23 sinetable lookup address mlb
  44 ; r24 write address lsb
  45 ; r25 write address msb
  46 ; r26 sinetable lookup address mhb
  47 ; r27 sinetable lookup address msb
  48 ; r28 temporary swap register
  49 ; r29 lfo depth
  50 ; r30 jump location for interrupt lsb
  51 ; r31 jump location for interrupt msb
  52 ; t   rotary encoder edge detect indicator
  53 
  54 ; program starts here first time
  55 ; intialize registers
  56 ldi r30,$04 ; increment z pointer to new jump location
  57 clr r14 ; clear null register
  58 ldi r29,$0d ; intiialize lfo depth
  59 reti ; finish with initialization and wait for next interrupt
  60 
  61 ; program starts here every time but first
  62 ; initiate data transfer to codec
  63 sbi portb,portb0 ; toggle slave select pin
  64 out spdr,r5 ; send out left channel msb
  65 cbi portb,portb0
  66 
  67 adiw r25:r24,$01 ; increment write address
  68 
  69 wait1_000053: ; check if byte has been sent
  70 
  71 in r17,spsr
  72 sbrs r17,spif
  73 rjmp wait1_000053
  74 in r19,spdr ; recieve in left channel msb
  75 out spdr,r4 ; send out left channel lsb
  76 
  77 wait2_000053: ; check if byte has been sent
  78 
  79 in r17,spsr
  80 sbrs r17,spif
  81 rjmp wait2_000053
  82 in r18,spdr ; recieve in left channel lsb
  83 out spdr,r7 ; send out right channel msb
  84 
  85 ;write left channel to sram
  86 out portd,r24 ; set address
  87 sts porth,r25
  88 out portg,r14 ; pull ce low,we low,and set high bits of address
  89 ldi r17,$ff
  90 out ddra,r17 ; set porta as output for data write
  91 out ddrc,r17 ; set portc as output for data write
  92 out porta,r18 ; set data
  93 out portc,r19
  94 sbi portg,portg2 ; pull we high to write
  95 out ddra,r14 ; set porta as input for data lines
  96 out ddrc,r14 ; set portc as input for data lines
  97 
  98 wait3_000053: ; check if byte has been sent
  99 
 100 in r17,spsr
 101 sbrs r17,spif
 102 rjmp wait3_000053
 103 in r17,spdr ; recieve in right channel msb
 104 out spdr,r6 ; send out right channel lsb
 105 
 106 wait4_000053: ; check if byte has been sent
 107 
 108 in r17,spsr
 109 sbrs r17,spif
 110 rjmp wait4_000053
 111 in r17,spdr ; recieve in right channel lsb
 112 
 113 ; vco generation
 114 movw r17:r16,r31:r30 ; store z register
 115 ;get sample 1
 116 add r22,r12 ; increment sinetable address
 117 adc r23,r13
 118 adc r26,r14 ; r14 is cleared above
 119 adc r27,r14
 120 movw r31:r30,r27:r26 ; move to z register for data fetch
 121 lsl r30 ; adjust pointer for 16b fetch
 122 rol r31
 123 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
 124 ori r31,$48 ; set to memory address location where table is stored
 125 lpm r18,z+ ; get sine value lsb, increment z register
 126 lpm r19,z ; get sine value msb
 127 sbrc r27,$01 ; flip sign for half of the values
 128 rjmp interpolate_000053
 129 neg r18
 130 adc r19,r14 ; r14 is cleared above
 131 neg r19
 132 
 133 interpolate_000053: ; multiply sample 1 by distance
 134 
 135 movw r21:r20,r23:r22 ; get distance from sample 1
 136 com r20 ; invert distance
 137 com r21
 138 mulsu r19,r21 ; (signed)Ah * (unsigned)Bh - multiply high bytes
 139 movw r5:r4,r1:r0 ; store high bytes result for later
 140 mul     r18,r20 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
 141 movw r3:r2,r1:r0 ; store low byets for later
 142 mulsu r19,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
 143 sbc     r5,r14 ; r14 is cleared above - subtract sign bit
 144 add     r3,r0 ; accumulate result
 145 adc     r4,r1
 146 adc     r5,r14 ; r14 is cleared above
 147 mul r21,r18 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
 148 add     r3,r0 ; accumulate result
 149 adc     r4,r1
 150 adc     r5,r14 ; r14 is cleared above
 151 
 152 ;get sample 2
 153 adiw r27:r26,$01 ; set to next sample
 154 movw r31:r30,r27:r26 ; move to z register for data fetch
 155 lsl r30 ; adjust pointer for 16b fetch
 156 rol r31
 157 andi r31,$03 ; limit value to 10b (512 samples x 2 bytes)
 158 ori r31,$48 ; set to memory address location where table is stored
 159 lpm r18,z+ ; get sine value lsb, increment z register
 160 lpm r19,z ; get sine value msb
 161 sbrc r27,$01 ; flip sign for half of the values
 162 rjmp interpolate1_000053
 163 neg r18
 164 adc r19,r14 ; r14 is cleared above
 165 neg r19
 166 
 167 interpolate1_000053: ; multiply sample 2 by distance
 168 
 169 sbiw r27:r26,$01 ; reset address
 170 movw r31:r30,r17:r16 ; restore z register
 171 mulsu r19,r23 ; (signed)Ah * (unsigned)Bh - multiply high bytes
 172 add r4,r0 ; accumulate result
 173 adc r5,r1
 174 mul     r18,r22 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
 175 add r2,r0 ; accumulate result
 176 adc r3,r1
 177 adc r4,r14 ; r14 is cleared above
 178 adc r5,r14
 179 mulsu r19,r22 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
 180 sbc     r5,r14 ; r14 is cleared above - subtract sign bit
 181 add     r3,r0 ; accumulate result
 182 adc     r4,r1
 183 adc     r5,r14 ; r14 is cleared above
 184 mul r23,r18 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
 185 add     r3,r0 ; accumulate result
 186 adc     r4,r1
 187 adc     r5,r14 ; r14 is cleared above
 188 
 189 ;set lfo depth - 8b value
 190 ldi r16,$80 ; convert lfo to unsigned number
 191 add r5,r16
 192 movw r19:r18,r5:r4 ; move lfo signal to multiply register
 193 mov r21,r29 ; move lfo depth to multiply register
 194 mul r19,r21 ; (unsigned)ah * (unsigned)b
 195 movw r5:r4,r1:r0
 196 mul r21,r18 ; (unsigned)b * (unsigned)al
 197 add     r4,r1
 198 adc     r5,r14 ; r14 is cleared above
 199 mov r28,r5 ; store lfo for later
 200 mov r21,r4
 201 
 202 ;right channel data preperation
 203 ;add lfo to delay
 204 movw r17:r16,r25:r24 ; move current location to read address
 205 sec ; set the carry bit so all values are reduced by 1 lsb for fractional byte
 206 sbc r16,r5 ; remove lfo time
 207 sbc r17,r14 ; r14 is cleared above
 208 
 209 ;get right channel sample 1 from sram
 210 out portd,r16 ; set address
 211 sts porth,r17
 212 nop ; wait setup period of two cycles
 213 nop
 214 in r18,pina ; get data
 215 in r19,pinc ; get data
 216 
 217 ;multiply sample 1 by distance
 218 mov r20,r4 ; get distance from sample 1
 219 mulsu r19,r20 ; (signed)ah * b
 220 movw r5:r4,r1:r0
 221 mul r18,r20 ; al * b
 222 add r4,r1
 223 adc r5,r14 ; r14 is cleared above
 224 mov r3,r0
 225 
 226 ;get right channel sample 2 from sram
 227 subi r16,$ff ; set to next sample
 228 sbci r17,$ff ; done this way because there is no addi or adci
 229 out portd,r16 ; set address
 230 sts porth,r17
 231 nop ; wait setup period of two cycles
 232 nop
 233 in r18,pina ; get data
 234 in r19,pinc ; get data
 235 
 236 ;multiply sample 2 by distance
 237 com r20 ; get distance to sample 2
 238 mulsu r19,r20 ; (signed)ah * b
 239 add r4,r0 ; accumulate result
 240 adc r5,r1
 241 mul r18,r20 ; al * b
 242 add r3,r0 ; accumulate result
 243 add r4,r1
 244 adc r5,r14 ; r14 is cleared above
 245 movw r7:r6,r5:r4 ; move right channel to output register
 246 
 247 ;left channel data preperation
 248 ;add lfo to delay
 249 clr r4 ; get max depth value
 250 mov r5,r29
 251 sub r4,r21 ; subtract lfo to make inverse
 252 sbc r5,r28
 253 movw r17:r16,r25:r24 ; move current location to read address
 254 sec ; set the carry bit so all values are reduced by 1 lsb for fractional byte
 255 sbc r16,r5 ; remove lfo time
 256 sbc r17,r14 ; r14 is cleared above
 257 
 258 ;get left channel sample 1 from sram
 259 out portd,r16 ; set address
 260 sts porth,r17
 261 nop ; wait setup period of two cycles
 262 nop
 263 in r18,pina ; get data
 264 in r19,pinc ; get data
 265 
 266 ;multiply sample 1 by distance
 267 mov r20,r4 ; get distance from sample 1
 268 mulsu r19,r20 ; (signed)ah * b
 269 movw r5:r4,r1:r0
 270 mul r18,r20 ; al * b
 271 add r4,r1
 272 adc r5,r14 ; r14 is cleared above
 273 mov r3,r0
 274 
 275 ;get left channel sample 2 from sram
 276 subi r16,$ff ; set to next sample
 277 sbci r17,$ff ; done this way because there is no addi or adci
 278 out portd,r16 ; set address
 279 sts porth,r17
 280 nop ; wait setup period of two cycles
 281 nop
 282 in r18,pina ; get data
 283 in r19,pinc ; get data
 284 
 285 ;multiply sample 2 by distance
 286 com r20 ; get distance to sample 2
 287 mulsu r19,r20 ; (signed)ah * b
 288 add r4,r0 ; accumulate result
 289 adc r5,r1
 290 mul r18,r20 ; al * b
 291 add r3,r0 ; accumulate result
 292 add r4,r1
 293 adc r5,r14 ; r14 is cleared above
 294 
 295 ;check rotary encoder and adjust lfo depth
 296 ; although rotary encoder is externally debounced, it is done here again.
 297 ; pin1 is sampled on a transition from high to low on pin0.  if pin1 is
 298 ; high, a left turn occured, if pin1 is low, a right turn occured.
 299 dec r11 ; count down sample clock
 300 brne adcsample_000053 ; continue if not
 301 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
 302 mov r11,r17 ; reload sample clock
 303 lds r17,pinj ; get switch data
 304 sbrs r17,$00 ; check if pin0 is low
 305 rjmp edge_000053 ; check if pin0 was low on previous sample
 306 clt ;  clear state register if back high
 307 rjmp adcsample_000053 ; finish off
 308 
 309 edge_000053: ; check for falling edge
 310 
 311 brts adcsample_000053 ; do nothing if edge was already detected
 312 set ; set t register to indicate edge detected
 313 ldi r21,$01 ; prepare for addition or subtraction
 314 sbrs r17,$01 ; check if pin1 is high
 315 rjmp increment_000053 ; increment desired delay if right rotation
 316 sub r29,r21 ; decrement lfo depth register else
 317 brcc adcsample_000053 ; check if underflow
 318 clr r29 ; set depth to min
 319 rjmp adcsample_000053 ; finish off
 320 
 321 increment_000053: ; increment desired delay register
 322 
 323 add r29,r21 ; increment lfo depth register
 324 brcc adcsample_000053 ; check if overflow occured
 325 ser r29 ; set depth to max
 326 
 327 adcsample_000053: ; sample adc for lfo rate
 328 
 329 lds r17,adcsra ; get adc control register
 330 sbrs r17,adif ; check if adc conversion is complete
 331 rjmp done_000053 ; skip adc sampling
 332 lds r16,adcl ; get low byte adc value
 333 lds r17,adch ; get high byte adc value
 334 add r8,r16 ; accumulate adc samples
 335 adc r9,r17
 336 adc r10,r14 ; r14 is cleared above
 337 ldi r17,$f7
 338 sts adcsra,r17 ; clear interrupt flag
 339 dec r15 ; countdown adc sample clock
 340 brne done_000053 ; get delay time if its been long enough
 341 
 342 deadband_000053: ; set the low value of the delay
 343 
 344 lsr r10 ; divide adc value by 16
 345 ror r9
 346 ror r8
 347 lsr r10
 348 ror r9
 349 ror r8
 350 lsr r9 ; r10 now empty
 351 ror r8
 352 lsr r9
 353 ror r8
 354 movw r17:r16,r9:r8 ; move adc sample to temporary register
 355 ldi r21,$80 ; add in offset of min lfo rate ($0080)
 356 add r16,r21
 357 adc r17,r14 ; r14 is cleared above
 358 sub r16,r12 ; find difference between adc sample and current lfo rate
 359 sbc r17,r13
 360 brsh check_000053 ; check for deadband if positive
 361 neg r16 ; invert if negative
 362 adc r17,r14 ; r14 is cleared above
 363 neg r17
 364 
 365 check_000053: ; check if difference is greater than deadband
 366 
 367 cpi r16,$10 ; check if difference is less than 1 adc lsb
 368 cpc r17,r14 ; r14 cleared above
 369 brlo empty_000053 ; do nothing if less than 1 adc lsb
 370 movw r13:r12,r9:r8 ; move adc sample to lfo rate register
 371 add r12,r21 ; add in offset of min lfo rate ($0080)
 372 adc r13,r14 ; r14 is cleared above
 373 
 374 empty_000053: ; empty accumulation registers and finish off
 375 
 376 clr r8 ; empty accumulation registers
 377 clr r9
 378 clr r10
 379 
 380 switchsample_000053: ; check rotary switch
 381 
 382 lds r16,pinj ; get switch data
 383 andi r16,$78 ; mask off rotary switch
 384 lsr r16 ; adjust switch position to program memory location
 385 lsr r16
 386 ldi r17,$02
 387 add r16,r17
 388 cpse r16,r31 ; check if location has changed
 389 clr r30 ; reset jump register to intial state
 390 mov r31,r16
 391 
 392 done_000053:
 393 
 394 reti ; return to waiting
 395 

StereoFlangerPan (last edited 2010-08-21 02:08:23 by guest)