1

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.

chorus_sine.asm


   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