MICrODEC: Stock Functions
Chromatic Pitch Shifter
This function implements a pitch shifter. It takes mono in on the left channel, and outputs a mono signal to both the left and the right channels. The rotary encoder (MOD2) adjusts the pitch shift amount, and the pot (MOD1) adjusts the buffer size (and resultant delay).
The easiest way to change the pitch of a signal, is to play back the samples at a faster or slower rate, although this introduces two problems. The first is that if you are sending out data at a rate different from the rate at which you are receiving data, you will eventually run out of data to send out. This is called a buffer over-run or under-run, depending upon whether you're going faster or slower, and hitting the top or bottom of the buffer (the data stored in SRAM). The second problem is that the data is sampled at discreet points in time, and if you want a playback speed that is not a multiple of this time, you will need data from somewhere between those sample periods.
There are a number of options for how to deal with buffer boundaries, but the main issue which we are trying to overcome, is the sharp transition as you go from one end of the buffer to the other, and the data is no longer consistent. This creates an audible click in the sample playback. In this case, we are using a 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 most common method of dealing with the second problem (fractional sample rates) is interpolation. Interpolation is a method of guessing what a value might have been if we actually had sampled at that point in time. For this pitch-shifter function, we use a linear interpolation. This means we draw a straight line between the two adjacent samples from where we want data, and assume our value is on that line. So if we're closer in time to one sample versus the other, than our output value is closer in value to that sample (the output is a sum of the two values, weighted by their distance to our sample point).
The pot (MOD1) varies the buffer size used for sample playback, from 12ms to 1.5s. Smaller buffer sizes give a more accurate pitch-shifting effect, as you are playing through only very small samples at a time, and do not hear 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 very large buffer sizes, it becomes a pitch shifted delay, which can be interesting when used with feedback, as each time the signal gets fed back in, its pitch is shifted again, causing an ever rising tone.
The rotary encoder (MOD2) varies the pitch shift amount, from -1 octave to +1 octave, in 12 chromatic steps each direction. In this way, the output can always be made to be "in tune" with the original signal. Rotations to the right increase the pitch, and vice versa. To maintain the correct pitch shift amount, a look-up table in program memory is used to store the precalculated values for each step.
1 ; program: pitch_shifter-16b-fading.asm 2 ; UID = 000039 - 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 out on both left and right 5 ; rotary encoder (MOD2) controlled playback speed 6 ; pot (MOD1) controlled buffer size 7 8 ; program overview 9 ; 10 ; data is sent out and taken in from the codec. data is taken in on the 11 ; left channel, and played out on both left and right. a buffer of the 12 ; past n seconds is kept and the output is the result of sampling this 13 ; buffer at varying playback speeds. the speed at which it plays through 14 ; the memory is controlled by the rotary encoder (MOD2). turning the 15 ; encoder to the right speeds playback up, and turning it the left slows 16 ; playback down. this playback speed is limited to chromatic steps by 17 ; using a lookup table in program memory to determine playback speed. the 18 ; audio is kept clean over fractional sample periods by interpolating 19 ; between the two closest samples. the output is a mix of the current 20 ; sample, and a sample from the opposite side of the buffer, with the 21 ; relative mix being determined by the distance to the buffer boundary. 22 ; in this way, the audio is faded down as it crosses the buffer boundary. 23 ; the pot (MOD1) controls the buffer size. the adc samples the pot 256 24 ; times and deadbands the signal to remove glitches. 25 26 ; constant definitions 27 ; 28 .equ buffer_min_000039 = $0200 ; minimum buffer size 29 .equ delay_mem_000039 = $0200 ; memory position for desired delay time 30 ; 31 ;.equ step-12_000039 = $0080 ; these are the playback speeds used 32 ;.equ step-11_000039 = $0088 ; they are stored in program memory 33 ;.equ step-10_000039 = $0090 ; and not used here 34 ;.equ step-9_000039 = $0098 35 ;.equ step-8_000039 = $00A1 36 ;.equ step-7_000039 = $00AB 37 ;.equ step-6_000039 = $00B5 38 ;.equ step-5_000039 = $00C0 39 ;.equ step-4_000039 = $00CB 40 ;.equ step-3_000039 = $00D7 41 ;.equ step-2_000039 = $00E4 42 ;.equ step-1_000039 = $00F2 43 ;.equ step00_000039 = $0100 44 ;.equ step01_000039 = $010F 45 ;.equ step02_000039 = $011F 46 ;.equ step03_000039 = $0130 47 ;.equ step04_000039 = $0143 48 ;.equ step05_000039 = $0156 49 ;.equ step06_000039 = $016A 50 ;.equ step07_000039 = $0180 51 ;.equ step08_000039 = $0196 52 ;.equ step09_000039 = $01AF 53 ;.equ step10_000039 = $01C8 54 ;.equ step11_000039 = $01E3 55 ;.equ step12_000039 = $0200 56 57 ; register usage - may be redefined in other sections 58 ; 59 ; r0 multiply result lsb 60 ; r1 multiply result msb 61 ; r2 sample 3/4 lsb 62 ; r3 sample 3/4 msb 63 ; r4 left/right lsb out 64 ; r5 left/right msb out 65 ; r6 left lsb in/temporary swap register 66 ; r7 left msb in/temporary swap register 67 ; r8 rotary encoder position counter 68 ; r9 adc msb accumulator 69 ; r10 adc fractional byte accumulator 70 ; r11 adc lsb accumulator 71 ; r12 playback speed increment lsb value ($0100 is normal speed) 72 ; r13 playback speed increment msb value 73 ; r14 rotary encoder counter 74 ; r15 switch\adc counter 75 ; r16 temporary swap register 76 ; r17 temporary swap register 77 ; r18 signed multiply register 78 ; r19 signed multiply register 79 ; r20 unsigned multiply register 80 ; r21 unsigned multiply register 81 ; r22 write address third byte/null register 82 ; r23 read address fractional byte 83 ; r24 write address lsb 84 ; r25 write address msb 85 ; r26 buffer length lsb 86 ; r27 buffer length msb 87 ; r28 read address lsb 88 ; r29 read address msb 89 ; r30 jump location for interrupt lsb 90 ; r31 jump location for interrupt msb 91 ; t rotary encoder edge indicator 92 93 ;program starts here first time 94 ; intialize registers 95 ldi r30,$29 ; set jump location to program start 96 clr r24 ; clear write register 97 clr r25 98 ldi r22,$00 ; setup write address high byte 99 clr r18 ; setup r18 as null register for carry addition and ddr setting 100 ldi r17,$ff ; setup r17 for ddr setting 101 102 clear_000039: ; clear delay buffer 103 ; eliminates static when first switching to the delay setting 104 105 adiw r25:r24,$01 ; increment write register 106 adc r22,r18 ; increment write third byte 107 cpi r22,$01 ; check if 16b memory space has been cleared 108 breq cleardone_000039 ; continue until end of buffer reached 109 out portd,r24 ; set address 110 sts porth,r25 111 out portg,r22 ; pull ce low,we low,and set high bits of address 112 out ddra,r17 ; set porta as output for data write 113 out ddrc,r17 ; set portc as output for data write 114 out porta,r18 ; set data 115 out portc,r18 ; r18 is cleared above 116 sbi portg,portg2 ; pull we high to write 117 out ddra,r18 ; set porta as input for data lines 118 out ddrc,r18 ; set portc as input for data lines 119 rjmp clear_000039 ; continue clearing 120 121 cleardone_000039: ; reset registers 122 123 ldi r24,$00 ; initialize write register 124 ldi r25,$00 125 clr r22 ; setup null register 126 ldi r28,$00 ; set read address to minimum delay 127 ldi r29,$fd 128 clr r4 ; initialize data output registers 129 clr r5 130 ldi r26,$00 ; initialize buffer size 131 ldi r27,$06 132 sts delay_mem_000039,r26 ; store desired buffer size 133 sts (delay_mem_000039 + 1),r27 ; i ran out of registers 134 clr r12 ; initialize playback speed 135 ldi r16,$01 136 mov r13,r16 137 ldi r16,$0c ; initialize playback speed pointer 138 mov r8,r16 139 reti ; return and wait for next interrupt 140 141 ;program begins here every time but first 142 ; initiate data transfer to codec 143 sbi portb,portb0 ; toggle slave select pin 144 out spdr,r5 ; send out left channel msb 145 cbi portb,portb0 146 147 ;increment write address 148 adiw r25:r24,$01 ; increment write address 149 cp r24,r26 ; check if at end of buffer 150 cpc r25,r27 151 brlo wait1_000039 ; do nothing if not at end of buffer 152 clr r24 ; reset buffer to bottom 153 clr r25 154 155 wait1_000039: ; check if byte has been sent 156 157 in r17,spsr 158 sbrs r17,spif 159 rjmp wait1_000039 160 in r7,spdr ; recieve in left channel msb 161 out spdr,r4 ; send out left channel lsb 162 163 ;increment read address 164 add r23,r12 ; increment read register 165 adc r28,r13 166 adc r29,r22 ; r22 is cleared above 167 cp r28,r26 ; check if at end of buffer 168 cpc r29,r27 169 brlo wait2_000039 ; do nothing if not at end of buffer 170 clr r28 ; reset buffer to bottom 171 clr r29 172 173 wait2_000039: ; check if byte has been sent 174 175 in r17,spsr 176 sbrs r17,spif 177 rjmp wait2_000039 178 in r6,spdr ; recieve in left channel lsb 179 out spdr,r5 ; send out right channel msb 180 181 ;write left channel data to sram 182 out portd,r24 ; set address 183 sts porth,r25 184 out portg,r22 ; pull ce low,we low,and set high bits of address 185 ldi r17,$ff 186 out ddra,r17 ; set porta as output for data write 187 out ddrc,r17 ; set portc as output for data write 188 out porta,r6 ; set data 189 out portc,r7 190 sbi portg,portg2 ; pull we high to write 191 out ddra,r22 ; set porta as input for data lines 192 out ddrc,r22 ; set portc as input for data lines 193 194 wait3_000039: ; check if byte has been sent 195 196 in r17,spsr 197 sbrs r17,spif 198 rjmp wait3_000039 199 in r17,spdr ; recieve in right channel msb 200 out spdr,r4 ; send out right channel lsb 201 202 ;get left channel sample 1 data from sram 203 movw r17:r16,r29:r28 ; move read address to temporary register 204 out portd,r16 ; set address 205 sts porth,r17 206 ldi r21,$01 ; increment read address 207 add r16,r21 ; placed here to use 2 cycle wait 208 adc r17,r22 ; r22 is cleared above 209 in r6,pina ; get data 210 in r18,pinc ; get data 211 cp r16,r26 ; check if at end of buffer 212 cpc r17,r27 213 brlo wait4_000039 ; do nothing if not at end of buffer 214 clr r16 ; reset buffer to bottom 215 clr r17 216 217 wait4_000039: ; check if byte has been sent 218 219 in r19,spsr 220 sbrs r19,spif 221 rjmp wait4_000039 222 in r19,spdr ; recieve in right channel lsb 223 224 ;get left channel sample 2 data from sram 225 out portd,r16 ; set address 226 sts porth,r17 227 nop ; wait 2 cycle setup time 228 nop 229 in r7,pina ; get data 230 in r19,pinc ; get data 231 232 ;multiply sample 1 by distance 233 mov r20,r23 ; get distance from sample 1 234 com r20 235 mulsu r18,r20 ; (signed)Ah * (unsigned)B 236 movw r5:r4,r1:r0 237 mul r6,r20 ; (unsigned)Al * (unsigned)B 238 add r4,r1 239 adc r5,r22 ; r22 is cleared above 240 mov r17,r0 241 242 ;multiply and accumulate sample 2 by distance 243 mulsu r19,r23 ; (signed)Ah * (unsigned)B 244 add r4,r0 ; accumulate result 245 adc r5,r1 246 mul r7,r23 ; (unsigned)Al * (unsigned)B 247 add r17,r0 ; accumulate result 248 adc r4,r1 249 adc r5,r22 ; r22 is cleared above 250 251 ;get sample from other side of buffer 252 movw r17:r16,r29:r28 ; move current position to temporary register 253 movw r7:r6,r27:r26 ; move buffer size to temporary register 254 lsr r7 ; divide buffer size by 2 255 ror r6 256 cp r16,r6 ; check if in lower or upper half of buffer 257 cpc r17,r7 258 brsh buffer_flip_000039 ; subtract half buffer if in upper half 259 add r16,r6 ; add half buffer size if in lower half 260 adc r17,r7 261 rjmp getsample3_000039 ; continue 262 263 buffer_flip_000039: ; adjust to opposite side of memory 264 265 sub r16,r6 ; subtract half buffer size if in upper half 266 sbc r17,r7 267 268 getsample3_000039: ;get left channel sample 3 data from sram 269 270 out portd,r16 ; set address 271 sts porth,r17 272 add r16,r21 ; increment read address - r21 set to $01 above 273 adc r17,r22 ; r22 is cleared above 274 in r6,pina ; get data 275 in r18,pinc ; get data 276 cp r16,r26 ; check if at end of buffer 277 cpc r17,r27 278 brlo getsample4_000039 ; do nothing if not at end of buffer 279 clr r16 ; reset buffer to bottom 280 clr r17 281 282 getsample4_000039: ;get left channel sample 4 data from sram 283 284 out portd,r16 ; set address 285 sts porth,r17 286 nop ; wait 2 cycle setup time 287 nop 288 in r7,pina ; get data 289 in r19,pinc ; get data 290 291 ;multiply sample 3 by distance 292 mulsu r18,r20 ; (signed)ah * b 293 movw r3:r2,r1:r0 294 mul r6,r20 ; al * b 295 add r2,r1 296 adc r3,r22 ; r22 is cleared above 297 mov r17,r0 298 299 ;multiply sample 4 by distance 300 mulsu r19,r23 ; (signed)ah * b 301 add r2,r0 ; accumulate result 302 adc r3,r1 303 mul r7,r23 ; al * b 304 add r17,r0 ; accumulate result 305 adc r2,r1 306 adc r3,r22 ; r22 is cleared above 307 308 ;get distance to boundary 309 movw r17:r16,r29:r28 ; move read address to temporary register 310 mov r18,r23 311 sub r16,r24 ; find distance to loop boundary 312 sbc r17,r25 313 brcc half_000039 ; check if result is negative 314 com r16 ; invert distance if negative 315 com r17 316 com r18 317 add r18,r21 ; r21 set to $01 above 318 adc r16,r22 ; r22 cleared above 319 adc r17,r22 320 321 half_000039: ; check if result is greater than half the buffer size 322 323 movw r7:r6,r27:r26 ; move buffer size to temporary register 324 lsr r7 ; divide buffer size by 2 325 ror r6 326 cp r16,r6 ; check if result is greater than half the buffer size 327 cpc r17,r7 328 brlo scale_000039 ; skip flip if not 329 sub r16,r26 ; flip result around boundary 330 sbc r17,r27 331 com r16 332 com r17 333 com r18 334 add r18,r21 ; r21 set to $01 above 335 adc r16,r22 336 adc r17,r22 337 338 scale_000039: ; scale distance to match buffer size - 50% accurate 339 340 movw r7:r6,r27:r26 ; move buffer size to temporary register 341 sbrc r7,$07 ; check if msb of buffer size is set 342 rjmp attenuate_000039 ; attenuate signal if 16b value 343 344 shift_000039: ; shift buffer size till it occupies full 16b 345 346 lsl r6 ; multiply buffer size by 2 347 rol r7 348 lsl r18 ; multiply distance by 2 349 rol r16 350 rol r17 351 sbrs r7,$07 ; check if msb of buffer size is set 352 rjmp shift_000039 ; keep checking if not set 353 354 attenuate_000039: ; multiply sample 1/2 by distance 355 356 lsl r18 ; multiply distance by 2 since max value is 1/2 buffer size 357 rol r16 358 rol r17 359 sub r6,r16 ; find complementary distance of sample 3/4 360 sbc r7,r17 ; only 1 bit error for not subtracting r18 as well 361 movw r21:r20,r7:r6 ; move distance to signed multiply register 362 movw r19:r18,r5:r4 ; move value to signed multiply register 363 mulsu r19,r17 ; (signed)ah * bh 364 movw r5:r4,r1:r0 365 mul r18,r16 ; al * bl 366 movw r7:r6,r1:r0 367 mulsu r19,r16 ; (signed)ah * bl 368 sbc r5,r22 ; r22 is cleared above 369 add r7,r0 370 adc r4,r1 371 adc r5,r22 372 mul r17,r18 ; bh * al 373 add r7,r0 374 adc r4,r1 375 adc r5,r22 376 377 ;multiply and accumulate sample 3/4 with result from above 378 movw r19:r18,r3:r2 ; move value to signed multiply register 379 mulsu r19,r21 ; (signed)ah * bh 380 add r4,r0 ; accumulate result 381 adc r5,r1 382 mul r18,r20 ; al * bl 383 add r6,r0 ; accumulate result 384 adc r7,r1 385 adc r4,r22 ; r22 is cleared above 386 adc r5,r22 387 mulsu r19,r20 ; (signed)ah * bl 388 sbc r5,r22 ; accumulate result 389 add r7,r0 390 adc r4,r1 391 adc r5,r22 392 mul r21,r18 ; bh * al 393 add r7,r0 394 adc r4,r1 395 adc r5,r22 396 397 rotary_000039: ; check rotary encoder and adjust playback rate 398 ; rotary encoder is externally debounced, so that is not done here. 399 ; pin1 is sampled on a transition from high to low on pin0. if pin1 is 400 ; high, a left turn occured, if pin1 is low, a right turn occured. 401 dec r14 ; reduce the sampling rate to help with debounce 402 brne check_000039 ; continue if not ready yet 403 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms) 404 mov r14,r17 405 lds r17,pinj ; get switch data 406 sbrs r17,$00 ; check if pin0 is low 407 rjmp edge_000039 ; check if pin0 was low on previous sample 408 clt ; clear state register if back high 409 rjmp check_000039 ; finish off 410 411 edge_000039: ; check for falling edge 412 413 brts check_000039 ; do nothing if the edge was already detected 414 set ; set state register to indicate a falling edge occured 415 sbrs r17,$01 ; check if pin1 is high 416 rjmp increment_000039 ; increment playback if right rotation 417 ldi r16,$01 ; check if pitch at min 418 cp r8,r16 419 brlo check_000039 ; do nothing it at bottom 420 dec r8 ; decrement rotary encoder position counter 421 movw r17:r16,z ; store z register 422 ldi zh,$4c ; setup z pointer to fetch tone from lookup table 423 mov zl,r8 424 lsl zl 425 lpm r12,z+ ; move tone to pitch register 426 lpm r13,z 427 movw z,r17:r16 ; restore z register 428 rjmp check_000039 ; finish off 429 430 increment_000039: ; increment playback speed 431 432 ldi r16,$18 ; check if pitch at max 433 cp r8,r16 434 brsh reset1_000039 ; do nothing if at max already 435 inc r8 ; increment rotary encoder position counter 436 movw r17:r16,z ; store z register 437 ldi zh,$4c ; setup z pointer to fetch tone from lookup table 438 mov zl,r8 439 lsl zl 440 lpm r12,z+ ; move tone to pitch register 441 lpm r13,z 442 movw z,r17:r16 ; restore z register 443 rjmp check_000039 ; finish off 444 445 reset1_000039: ; reset tone register in case it goes too high 446 447 mov r8,r16 ; set tone register to max 448 449 check_000039: ; check if buffer size is correct 450 451 lds r16,delay_mem_000039 ; fetch desired buffer size 452 lds r17,(delay_mem_000039 + 1) ; i ran out of registers 453 cp r26,r16 ; compare current delay to desired delay 454 cpc r27,r17 455 brlo upcount_000039 ; increment if smaller than 456 breq adcsample_000039 ; do nothing if they are same size 457 sbiw r27:r26,$02 ; decrement buffer size 458 rjmp adcsample_000039 ; finish off 459 460 upcount_000039: ; increment buffer size register 461 462 adiw r27:r26,$02 ; increment buffer size 463 464 adcsample_000039: ; get loop setting 465 466 lds r17,adcsra ; get adc control register 467 sbrs r17,adif ; check if adc conversion is complete 468 rjmp done_000039 ; skip adc sampling 469 lds r16,adcl ; get low byte adc value 470 lds r17,adch ; get high byte adc value 471 add r10,r16 ; accumulate adc samples 472 adc r11,r17 473 adc r9,r22 ; r22 is cleared above 474 ldi r17,$f7 475 sts adcsra,r17 ; clear interrupt flag 476 dec r15 ; countdown adc sample clock 477 brne done_000039 ; move adc value to loop setting after 256 samples 478 lsr r9 ; divide accumulated value by 4 to make a 16b value 479 ror r11 480 ror r10 481 lsr r9 482 ror r11 483 ror r10 484 ldi r16,low(buffer_min_000039) ; fetch min buffer size 485 ldi r17,high(buffer_min_000039) 486 cp r10,r16 ; compare adc value to min buffer size 487 cpc r11,r17 488 brsh compare_000039 ; skip if above minimum buffer size 489 movw r11:r10,r17:r16 ; else set to minimum buffer size 490 491 compare_000039: ; compare to previous value 492 493 lds r16,delay_mem_000039 ; fetch desired delay time 494 lds r17,(delay_mem_000039 + 1) ; i ran out of registers 495 sub r16,r10 ; find difference between adc value and desired buffer size 496 sbc r17,r11 497 brcc deadband_000039 ; check for magnitude of change if positive 498 neg r16 ; else invert difference if negative 499 adc r17,r22 ; r22 is cleared above 500 neg r17 501 502 deadband_000039: ; see if pot has moved or if its just noise 503 504 cpi r16,$40 ; see if difference is greater than 1 lsb 505 cpc r17,r22 ; r22 is cleared above 506 brlo nochange_000039 ; dont update loop time if difference is not large enough 507 ldi r16,$fe ; make sure buffer size is even 508 and r10,r16 509 sts delay_mem_000039,r10 ; store new desired buffer size 510 sts (delay_mem_000039 + 1),r11 ; i ran out of registers 511 512 nochange_000039: ; clear accumulation registers 513 514 clr r10 ; empty accumulation registers 515 clr r11 516 clr r9 517 518 switchsample_000039: ; check rotary switch state 519 520 lds r16,pinj ; get switch data 521 andi r16,$78 ; mask off rotary switch 522 lsr r16 ; adjust switch position to program memory location 523 lsr r16 524 ldi r17,$02 525 add r16,r17 526 cpse r16,r31 ; check if location has changed 527 clr r30 ; reset jump register to intial state 528 mov r31,r16 529 530 done_000039: 531 532 reti ; return to waiting 533