MICrODEC: Stock Functions
Reverser with Fading
This function implements a reverser, so all input sounds are played backwards. It takes mono data in on the left channel, and presents mono data out on both the left and right channels. The pot (MOD1) controls the buffer size.
A reverser is created by playing all the samples in reverse order. Unfortunately, you end up hitting the beginning of your buffer at some point, and need to start over again. This creates an audible click in the sample playback. To eliminate this click, we use the fading method. This involves having two samples playing back simultaneously, each from a different point in the buffer (spaced a half-buffer's distance from each other). As one sample gets closer to the boundary, its volume is faded down, and the other is faded up. This continues as each sample moves forward in the buffer, with the volume of the sample being determined by its distance from the buffer boundary. This gives very smooth transitions across the buffer boundary, but also has a slight reverb effect, as multiple delayed signals are being mixed together.
The pot (MOD1) varies the buffer size used for sample playback, from 6ms to 1.5s. Smaller buffer sizes give a more subtle effect, as you are playing through only very small samples at a time, but has the advantage of not having much of a delay. If the buffer is too small, you begin to hear the rate at which you are moving through the buffer, almost like a slight tremolo. For large buffer sizes, the samples sound more like reversed audio, but the delay becomes larger as well.
1 ; program: reverser-16b-pot-fading.asm 2 ; UID = 000036 - this is a unique id so variables dont conflict 3 ; 16b address space (1.5s sample time) 4 ; mono data in on left channel, mono data out on left and right 5 ; pot (MOD1) controlled buffer size 6 7 ; program overview 8 ; 9 ; data is sent out and taken in from the codec. data is taken in on the 10 ; left channel, and played out on both left and right. a buffer of the 11 ; past n seconds is kept and the output is the result of sampling this 12 ; buffer in reverse. this buffer size is determined by the pot (MOD1) value, 13 ; which is multiplied up to a 16b value. the buffer size is adjusted at a 14 ; rate of 2 lsb per sample period, until it matches to what the pot says 15 ; it should be. two samples are taken and averaged together by the 16 ; ratio of their distances to the buffer boundary. they are 180 degrees 17 ; out of phase, so as one sample is crossing the buffer boundary, it is 18 ; silent, and the other plays full volume. this helps reduce the clicks 19 ; at buffer boundary transitions. it also creates a reverb sound. 20 21 ; constant definitions 22 ; 23 .equ buffer_min_000036 = $0200 ; minimum sample buffer size 24 25 ; register usage - may be redefined in other sections 26 ; 27 ; r0 multiply result lsb 28 ; r1 multiply result msb 29 ; r2 sample 2 lsb 30 ; r3 sample 2 msb 31 ; r4 left/right lsb out / sample 1 lsb 32 ; r5 left/right msb out / sample 1 msb 33 ; r6 left lsb in 34 ; r7 left msb in 35 ; r8 36 ; r9 adc msb accumulator 37 ; r10 adc fractional byte accumulator 38 ; r11 adc lsb accumulator 39 ; r12 desired buffer size lsb 40 ; r13 desired buffer size msb 41 ; r14 42 ; r15 switch/adc counter 43 ; r16 temporary swap register 44 ; r17 temporary swap register 45 ; r18 multiplicand lsb 46 ; r19 multiplicand msb 47 ; r20 multiplicand lsb 48 ; r21 multiplicand msb 49 ; r22 null register 50 ; r23 51 ; r24 write address lsb 52 ; r25 write address msb 53 ; r26 buffer length lsb 54 ; r27 buffer length msb 55 ; r28 read address lsb 56 ; r29 read address msb 57 ; r30 jump location for interrupt lsb 58 ; r31 jump location for interrupt msb 59 ; t 60 61 ; program starts here first time 62 ; initialze z pointer for correct jump 63 ; this assumes a less than 256 word jump 64 ldi r30,$21 ; set jump location to program start 65 clr r24 ; clear write register 66 clr r25 67 ldi r22,$00 ; setup write address high byte 68 clr r18 ; setup r18 as null register for carry addition and ddr setting 69 ldi r17,$ff ; setup r17 for ddr setting 70 71 clear_000036: ; clear delay buffer 72 ; eliminates static when first switching to the delay setting 73 74 adiw r25:r24,$01 ; increment write register 75 adc r22,r18 ; increment write third byte 76 cpi r22,$01 ; check if 16b memory space has been cleared 77 breq cleardone_000036 ; continue until end of buffer reached 78 out portd,r24 ; set address 79 sts porth,r25 80 out portg,r22 ; pull ce low,we low,and set high bits of address 81 out ddra,r17 ; set porta as output for data write 82 out ddrc,r17 ; set portc as output for data write 83 out porta,r18 ; set data 84 out portc,r18 ; r18 is cleared above 85 sbi portg,portg2 ; pull we high to write 86 out ddra,r18 ; set porta as input for data lines 87 out ddrc,r18 ; set portc as input for data lines 88 rjmp clear_000036 ; continue clearing 89 90 cleardone_000036: ; reset registers 91 92 ldi r24,$00 ; initialize write register 93 ldi r25,$00 94 ldi r22,$00 ; setup write address high byte 95 ldi r28,$00 ; set read address to minimum delay 96 ldi r29,$fd 97 clr r4 ; initialize data output registers 98 clr r5 99 ldi r26,$00 ; initialize buffer size 100 ldi r27,$06 101 movw r13:r12,r27:r26 102 reti ; finish with initialization and wait for next interrupt 103 104 ; program begins here 105 ; initiate data transfer to codec 106 sbi portb,portb0 ; toggle slave select pin 107 out spdr,r5 ; send out left channel msb 108 cbi portb,portb0 109 110 ;increment write address 111 adiw r25:r24,$01 ; increment write address 112 cp r24,r26 ; check if at end of buffer 113 cpc r25,r27 114 brlo wait1_000036 ; do nothing if not at end of buffer 115 clr r24 ; reset buffer to bottom 116 clr r25 117 118 wait1_000036: ; check if byte has been sent 119 120 in r17,spsr 121 sbrs r17,spif 122 rjmp wait1_000036 123 in r7,spdr ; recieve in left channel msb 124 out spdr,r4 ; send out left channel lsb 125 126 ;decrement read address (for going in reverse) 127 sbiw r29:r28,$01 128 brcc wait2_000036 ; do nothing if not at end of buffer 129 movw r29:r28,r27:r26 ; reset to top of buffer 130 sbiw r29:r28,$01 ; reset to top of buffer 131 132 wait2_000036: ; check if byte has been sent 133 134 in r17,spsr 135 sbrs r17,spif 136 rjmp wait2_000036 137 in r6,spdr ; recieve in left channel lsb 138 out spdr,r5 ; send out right channel msb 139 140 ;write left channel data to sram 141 out portd,r24 ; set address 142 sts porth,r25 143 out portg,r22 ; pull ce low,we low,and set high bits of address 144 ldi r17,$ff 145 out ddra,r17 ; set porta as output for data write 146 out ddrc,r17 ; set portc as output for data write 147 out porta,r6 ; set data 148 out portc,r7 149 sbi portg,portg2 ; pull we high to write 150 out ddra,r22 ; set porta as input for data lines 151 out ddrc,r22 ; set portc as input for data lines 152 153 wait3_000036: ; check if byte has been sent 154 155 in r17,spsr 156 sbrs r17,spif 157 rjmp wait3_000036 158 in r17,spdr ; recieve in right channel msb 159 out spdr,r4 ; send out right channel lsb 160 161 ;get sample 1 from sram 162 out portd,r28 ; set address 163 sts porth,r29 164 nop ; wait required 2 cycle setup time 165 nop 166 in r4,pina ; get data 167 in r5,pinc ; get data 168 169 wait4_000036: ; check if byte has been sent 170 171 in r17,spsr 172 sbrs r17,spif 173 rjmp wait4_000036 174 in r17,spdr ; recieve in right channel lsb 175 176 ;get sample 2 from other side of buffer 177 movw r17:r16,r29:r28 ; move current position to temporary register 178 movw r7:r6,r27:r26 ; move buffer size to temporary register 179 lsr r7 ; divide buffer size by 2 180 ror r6 181 cp r16,r6 ; check if in lower or upper half of buffer 182 cpc r17,r7 183 brsh buffer_flip_000036 ; subtract half buffer if in upper half 184 add r16,r6 ; add half buffer size if in lower half 185 adc r17,r7 186 rjmp getsample2_000036 ; continue 187 188 buffer_flip_000036: ; adjust to opposite side of memory 189 190 sub r16,r6 ; subtract half buffer size if in upper half 191 sbc r17,r7 192 193 getsample2_000036: ;get left channel sample 3 data from sram 194 195 out portd,r16 ; set address 196 sts porth,r17 197 nop ; wait 2 cycle setup time 198 nop 199 in r2,pina ; get data 200 in r3,pinc ; get data 201 202 ;get distance to boundary 203 movw r17:r16,r29:r28 ; move read address to temporary register 204 sub r16,r24 ; find distance to loop boundary 205 sbc r17,r25 206 brcc half_000036 ; check if result is negative 207 neg r16 ; invert distance if negative 208 adc r17,r22 ; r22 is cleared above 209 neg r17 210 211 half_000036: ; check if result is greater than half the buffer size 212 213 movw r7:r6,r27:r26 ; move buffer size to temporary register 214 lsr r7 ; divide buffer size by 2 215 ror r6 216 cp r16,r6 ; check if result is greater than half the buffer size 217 cpc r17,r7 218 brlo scale_000036 ; skip flip if not 219 sub r16,r26 ; flip result around boundary 220 sbc r17,r27 221 neg r16 ; invert distance 222 adc r17,r22 ; r22 is cleared above 223 neg r17 224 225 scale_000036: ; scale distance to match buffer size - 50% accurate 226 227 movw r7:r6,r27:r26 ; move buffer size to temporary register 228 sbrc r7,$07 ; check if msb of buffer size is set 229 rjmp attenuate_000036 ; attenuate signal if 16b value 230 231 shift_000036: ; shift buffer size till it occupies full 16b 232 233 lsl r6 ; multiply buffer size by 2 234 rol r7 235 lsl r16 ; multiply distance by 2 236 rol r17 237 sbrs r7,$07 ; check if msb of buffer size is set 238 rjmp shift_000036 ; keep checking if not set 239 240 attenuate_000036: ; multiply sample 1 by distance 241 242 lsl r16 ; multiply distance by 2 since max value is 1/2 buffer size 243 rol r17 244 sub r6,r16 ; find complementary distance of sample 2 245 sbc r7,r17 246 movw r21:r20,r7:r6 ; move distance to signed multiply register 247 movw r19:r18,r5:r4 ; move value to signed multiply register 248 mulsu r19,r17 ; (signed)ah * bh 249 movw r5:r4,r1:r0 250 mul r18,r16 ; al * bl 251 movw r7:r6,r1:r0 252 mulsu r19,r16 ; (signed)ah * bl 253 sbc r5,r22 ; r22 is cleared above 254 add r7,r0 255 adc r4,r1 256 adc r5,r22 257 mul r17,r18 ; bh * al 258 add r7,r0 259 adc r4,r1 260 adc r5,r22 261 262 ;multiply and accumulate opposing sample with result from above 263 movw r19:r18,r3:r2 ; move value to signed multiply register 264 mulsu r19,r21 ; (signed)ah * bh 265 add r4,r0 266 adc r5,r1 267 mul r18,r20 ; al * bl 268 add r6,r0 269 adc r7,r1 270 adc r4,r22 271 adc r5,r22 272 mulsu r19,r20 ; (signed)ah * bl 273 sbc r5,r22 274 add r7,r0 275 adc r4,r1 276 adc r5,r22 277 mul r21,r18 ; bh * al 278 add r7,r0 279 adc r4,r1 280 adc r5,r22 281 282 ;check if buffer size is correct 283 cp r26,r12 ; compare current delay to desired delay 284 cpc r27,r13 285 brlo upcount_000036 ; increment if smaller than 286 breq adcsample_000036 ; do nothing if they are same size 287 sbiw r27:r26,$02 ; decrement buffer size 288 rjmp adcsample_000036 ; finish off 289 290 upcount_000036: ; increment buffer size register 291 292 adiw r27:r26,$02 ; increment buffer size 293 294 adcsample_000036: ; get loop setting 295 296 lds r17,adcsra ; get adc control register 297 sbrs r17,adif ; check if adc conversion is complete 298 rjmp done_000036 ; skip adc sampling 299 lds r16,adcl ; get low byte adc value 300 lds r17,adch ; get high byte adc value 301 add r10,r16 302 adc r11,r17 ; accumulate adc samples 303 adc r9,r22 ; accumulate adc samples - r22 is cleared above 304 ldi r17,$f7 305 sts adcsra,r17 ; clear interrupt flag 306 dec r15 ; countdown adc sample clock 307 brne done_000036 ; move adc value to loop setting after 256 samples 308 lsr r9 ; divide accumulated value by 4 309 ror r11 310 ror r10 311 lsr r9 312 ror r11 313 ror r10 314 ldi r16,low(buffer_min_000036) ; load minimum buffer size 315 ldi r17,high(buffer_min_000036) 316 cp r10,r16 ; check if less than minimum 317 cpc r11,r17 318 brsh compare_000036 ; compare to previous value if above min 319 movw r11:r10,r17:r16 ; set buffer size to minimum 320 321 compare_000036: ; compare to previous value 322 323 movw r17:r16,r13:r12 ; make a copy of current loop time for comparison 324 sub r16,r10 ; find difference between current loop time and last loop time 325 sbc r17,r11 326 brcc deadband_000036 ; see if difference is large enough to indicate a change 327 neg r16 ; invert difference if negative 328 adc r17,r22 ; r22 is cleared above 329 neg r17 330 331 deadband_000036: ; see if pot has moved or if its just noise 332 333 cpi r16,$40 ; see if difference is greater than 1 lsb 334 cpc r17,r22 ; r22 is cleared above 335 brlo nochange_000036 ; dont update loop time if difference is not large enough 336 ldi r16,$fe ; make sure buffer size is even 337 and r10,r16 338 movw r13:r12,r11:r10 ; move adc value to loop time register 339 340 nochange_000036: ; clear accumulation registers 341 342 clr r10 ; empty accumulation registers 343 clr r11 344 clr r9 345 346 ;check rotary switch state 347 lds r16,pinj ; get switch data 348 andi r16,$78 ; mask off rotary switch 349 lsr r16 ; adjust switch position to program memory location 350 lsr r16 351 ldi r17,$02 352 add r16,r17 353 cpse r16,r31 ; check if location has changed 354 clr r30 ; reset jump register to intial state 355 mov r31,r16 356 357 done_000036: 358 359 reti ; return to waiting 360