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,$27 ; 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 reti ; return and wait for next interrupt 138 139 ;program begins here every time but first 140 ; initiate data transfer to codec 141 sbi portb,portb0 ; toggle slave select pin 142 out spdr,r5 ; send out left channel msb 143 cbi portb,portb0 144 145 ;increment write address 146 adiw r25:r24,$01 ; increment write address 147 cp r24,r26 ; check if at end of buffer 148 cpc r25,r27 149 brlo wait1_000039 ; do nothing if not at end of buffer 150 clr r24 ; reset buffer to bottom 151 clr r25 152 153 wait1_000039: ; check if byte has been sent 154 155 in r17,spsr 156 sbrs r17,spif 157 rjmp wait1_000039 158 in r7,spdr ; recieve in left channel msb 159 out spdr,r4 ; send out left channel lsb 160 161 ;increment read address 162 add r23,r12 ; increment read register 163 adc r28,r13 164 adc r29,r22 ; r22 is cleared above 165 cp r28,r26 ; check if at end of buffer 166 cpc r29,r27 167 brlo wait2_000039 ; do nothing if not at end of buffer 168 clr r28 ; reset buffer to bottom 169 clr r29 170 171 wait2_000039: ; check if byte has been sent 172 173 in r17,spsr 174 sbrs r17,spif 175 rjmp wait2_000039 176 in r6,spdr ; recieve in left channel lsb 177 out spdr,r5 ; send out right channel msb 178 179 ;write left channel data to sram 180 out portd,r24 ; set address 181 sts porth,r25 182 out portg,r22 ; pull ce low,we low,and set high bits of address 183 ldi r17,$ff 184 out ddra,r17 ; set porta as output for data write 185 out ddrc,r17 ; set portc as output for data write 186 out porta,r6 ; set data 187 out portc,r7 188 sbi portg,portg2 ; pull we high to write 189 out ddra,r22 ; set porta as input for data lines 190 out ddrc,r22 ; set portc as input for data lines 191 192 wait3_000039: ; check if byte has been sent 193 194 in r17,spsr 195 sbrs r17,spif 196 rjmp wait3_000039 197 in r17,spdr ; recieve in right channel msb 198 out spdr,r4 ; send out right channel lsb 199 200 ;get left channel sample 1 data from sram 201 movw r17:r16,r29:r28 ; move read address to temporary register 202 out portd,r16 ; set address 203 sts porth,r17 204 ldi r21,$01 ; increment read address 205 add r16,r21 ; placed here to use 2 cycle wait 206 adc r17,r22 ; r22 is cleared above 207 in r6,pina ; get data 208 in r18,pinc ; get data 209 cp r16,r26 ; check if at end of buffer 210 cpc r17,r27 211 brlo wait4_000039 ; do nothing if not at end of buffer 212 clr r16 ; reset buffer to bottom 213 clr r17 214 215 wait4_000039: ; check if byte has been sent 216 217 in r19,spsr 218 sbrs r19,spif 219 rjmp wait4_000039 220 in r19,spdr ; recieve in right channel lsb 221 222 ;get left channel sample 2 data from sram 223 out portd,r16 ; set address 224 sts porth,r17 225 nop ; wait 2 cycle setup time 226 nop 227 in r7,pina ; get data 228 in r19,pinc ; get data 229 230 ;multiply sample 1 by distance 231 mov r20,r23 ; get distance from sample 1 232 com r20 233 mulsu r18,r20 ; (signed)Ah * (unsigned)B 234 movw r5:r4,r1:r0 235 mul r6,r20 ; (unsigned)Al * (unsigned)B 236 add r4,r1 237 adc r5,r22 ; r22 is cleared above 238 mov r17,r0 239 240 ;multiply and accumulate sample 2 by distance 241 mulsu r19,r23 ; (signed)Ah * (unsigned)B 242 add r4,r0 ; accumulate result 243 adc r5,r1 244 mul r7,r23 ; (unsigned)Al * (unsigned)B 245 add r17,r0 ; accumulate result 246 adc r4,r1 247 adc r5,r22 ; r22 is cleared above 248 249 ;get sample from other side of buffer 250 movw r17:r16,r29:r28 ; move current position to temporary register 251 movw r7:r6,r27:r26 ; move buffer size to temporary register 252 lsr r7 ; divide buffer size by 2 253 ror r6 254 cp r16,r6 ; check if in lower or upper half of buffer 255 cpc r17,r7 256 brsh buffer_flip_000039 ; subtract half buffer if in upper half 257 add r16,r6 ; add half buffer size if in lower half 258 adc r17,r7 259 rjmp getsample3_000039 ; continue 260 261 buffer_flip_000039: ; adjust to opposite side of memory 262 263 sub r16,r6 ; subtract half buffer size if in upper half 264 sbc r17,r7 265 266 getsample3_000039: ;get left channel sample 3 data from sram 267 268 out portd,r16 ; set address 269 sts porth,r17 270 add r16,r21 ; increment read address - r21 set to $01 above 271 adc r17,r22 ; r22 is cleared above 272 in r6,pina ; get data 273 in r18,pinc ; get data 274 cp r16,r26 ; check if at end of buffer 275 cpc r17,r27 276 brlo getsample4_000039 ; do nothing if not at end of buffer 277 clr r16 ; reset buffer to bottom 278 clr r17 279 280 getsample4_000039: ;get left channel sample 4 data from sram 281 282 out portd,r16 ; set address 283 sts porth,r17 284 nop ; wait 2 cycle setup time 285 nop 286 in r7,pina ; get data 287 in r19,pinc ; get data 288 289 ;multiply sample 3 by distance 290 mulsu r18,r20 ; (signed)ah * b 291 movw r3:r2,r1:r0 292 mul r6,r20 ; al * b 293 add r2,r1 294 adc r3,r22 ; r22 is cleared above 295 mov r17,r0 296 297 ;multiply sample 4 by distance 298 mulsu r19,r23 ; (signed)ah * b 299 add r2,r0 ; accumulate result 300 adc r3,r1 301 mul r7,r23 ; al * b 302 add r17,r0 ; accumulate result 303 adc r2,r1 304 adc r3,r22 ; r22 is cleared above 305 306 ;get distance to boundary 307 movw r17:r16,r29:r28 ; move read address to temporary register 308 mov r18,r23 309 sub r16,r24 ; find distance to loop boundary 310 sbc r17,r25 311 brcc half_000039 ; check if result is negative 312 com r16 ; invert distance if negative 313 com r17 314 com r18 315 add r18,r21 ; r21 set to $01 above 316 adc r16,r22 ; r22 cleared above 317 adc r17,r22 318 319 half_000039: ; check if result is greater than half the buffer size 320 321 movw r7:r6,r27:r26 ; move buffer size to temporary register 322 lsr r7 ; divide buffer size by 2 323 ror r6 324 cp r16,r6 ; check if result is greater than half the buffer size 325 cpc r17,r7 326 brlo scale_000039 ; skip flip if not 327 sub r16,r26 ; flip result around boundary 328 sbc r17,r27 329 com r16 330 com r17 331 com r18 332 add r18,r21 ; r21 set to $01 above 333 adc r16,r22 334 adc r17,r22 335 336 scale_000039: ; scale distance to match buffer size - 50% accurate 337 338 movw r7:r6,r27:r26 ; move buffer size to temporary register 339 sbrc r7,$07 ; check if msb of buffer size is set 340 rjmp attenuate_000039 ; attenuate signal if 16b value 341 342 shift_000039: ; shift buffer size till it occupies full 16b 343 344 lsl r6 ; multiply buffer size by 2 345 rol r7 346 lsl r18 ; multiply distance by 2 347 rol r16 348 rol r17 349 sbrs r7,$07 ; check if msb of buffer size is set 350 rjmp shift_000039 ; keep checking if not set 351 352 attenuate_000039: ; multiply sample 1/2 by distance 353 354 lsl r18 ; multiply distance by 2 since max value is 1/2 buffer size 355 rol r16 356 rol r17 357 sub r6,r16 ; find complementary distance of sample 3/4 358 sbc r7,r17 ; only 1 bit error for not subtracting r18 as well 359 movw r21:r20,r7:r6 ; move distance to signed multiply register 360 movw r19:r18,r5:r4 ; move value to signed multiply register 361 mulsu r19,r17 ; (signed)ah * bh 362 movw r5:r4,r1:r0 363 mul r18,r16 ; al * bl 364 movw r7:r6,r1:r0 365 mulsu r19,r16 ; (signed)ah * bl 366 sbc r5,r22 ; r22 is cleared above 367 add r7,r0 368 adc r4,r1 369 adc r5,r22 370 mul r17,r18 ; bh * al 371 add r7,r0 372 adc r4,r1 373 adc r5,r22 374 375 ;multiply and accumulate sample 3/4 with result from above 376 movw r19:r18,r3:r2 ; move value to signed multiply register 377 mulsu r19,r21 ; (signed)ah * bh 378 add r4,r0 ; accumulate result 379 adc r5,r1 380 mul r18,r20 ; al * bl 381 add r6,r0 ; accumulate result 382 adc r7,r1 383 adc r4,r22 ; r22 is cleared above 384 adc r5,r22 385 mulsu r19,r20 ; (signed)ah * bl 386 sbc r5,r22 ; accumulate result 387 add r7,r0 388 adc r4,r1 389 adc r5,r22 390 mul r21,r18 ; bh * al 391 add r7,r0 392 adc r4,r1 393 adc r5,r22 394 395 rotary_000039: ; check rotary encoder and adjust playback rate 396 ; rotary encoder is externally debounced, so that is not done here. 397 ; pin1 is sampled on a transition from high to low on pin0. if pin1 is 398 ; high, a left turn occured, if pin1 is low, a right turn occured. 399 dec r14 ; reduce the sampling rate to help with debounce 400 brne check_000039 ; continue if not ready yet 401 ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms) 402 mov r14,r17 403 lds r17,pinj ; get switch data 404 sbrs r17,$00 ; check if pin0 is low 405 rjmp edge_000039 ; check if pin0 was low on previous sample 406 clt ; clear state register if back high 407 rjmp check_000039 ; finish off 408 409 edge_000039: ; check for falling edge 410 411 brts check_000039 ; do nothing if the edge was already detected 412 set ; set state register to indicate a falling edge occured 413 sbrs r17,$01 ; check if pin1 is high 414 rjmp increment_000039 ; increment playback if right rotation 415 ldi r16,$01 ; check if pitch at min 416 cp r8,r16 417 brlo check_000039 ; do nothing it at bottom 418 dec r8 ; decrement rotary encoder position counter 419 movw r17:r16,z ; store z register 420 ldi zh,$4c ; setup z pointer to fetch tone from lookup table 421 mov zl,r8 422 lsl zl 423 lpm r12,z+ ; move tone to pitch register 424 lpm r13,z 425 movw z,r17:r16 ; restore z register 426 rjmp check_000039 ; finish off 427 428 increment_000039: ; increment playback speed 429 430 ldi r16,$18 ; check if pitch at max 431 cp r8,r16 432 brsh reset1_000039 ; do nothing if at max already 433 inc r8 ; increment rotary encoder position counter 434 movw r17:r16,z ; store z register 435 ldi zh,$4c ; setup z pointer to fetch tone from lookup table 436 mov zl,r8 437 lsl zl 438 lpm r12,z+ ; move tone to pitch register 439 lpm r13,z 440 movw z,r17:r16 ; restore z register 441 rjmp check_000039 ; finish off 442 443 reset1_000039: ; reset tone register in case it goes too high 444 445 ldi r16,$18 ; set tone register to max 446 mov r8,r16 447 448 check_000039: ; check if buffer size is correct 449 450 lds r16,delay_mem_000039 ; fetch desired buffer size 451 lds r17,(delay_mem_000039 + 1) ; i ran out of registers 452 cp r26,r16 ; compare current delay to desired delay 453 cpc r27,r17 454 brlo upcount_000039 ; increment if smaller than 455 breq adcsample_000039 ; do nothing if they are same size 456 sbiw r27:r26,$02 ; decrement buffer size 457 rjmp adcsample_000039 ; finish off 458 459 upcount_000039: ; increment buffer size register 460 461 adiw r27:r26,$02 ; increment buffer size 462 463 adcsample_000039: ; get loop setting 464 465 lds r17,adcsra ; get adc control register 466 sbrs r17,adif ; check if adc conversion is complete 467 rjmp done_000039 ; skip adc sampling 468 lds r16,adcl ; get low byte adc value 469 lds r17,adch ; get high byte adc value 470 add r10,r16 ; accumulate adc samples 471 adc r11,r17 472 adc r9,r22 ; r22 is cleared above 473 ldi r17,$f7 474 sts adcsra,r17 ; clear interrupt flag 475 dec r15 ; countdown adc sample clock 476 brne done_000039 ; move adc value to loop setting after 256 samples 477 lsr r9 ; divide accumulated value by 4 to make a 16b value 478 ror r11 479 ror r10 480 lsr r9 481 ror r11 482 ror r10 483 ldi r16,low(buffer_min_000039) ; fetch min buffer size 484 ldi r17,high(buffer_min_000039) 485 cp r10,r16 ; compare adc value to min buffer size 486 cpc r11,r17 487 brsh compare_000039 ; skip if above minimum buffer size 488 movw r11:r10,r17:r16 ; else set to minimum buffer size 489 490 compare_000039: ; compare to previous value 491 492 lds r16,delay_mem_000039 ; fetch desired delay time 493 lds r17,(delay_mem_000039 + 1) ; i ran out of registers 494 sub r16,r10 ; find difference between adc value and desired buffer size 495 sbc r17,r11 496 brcc deadband_000039 ; check for magnitude of change if positive 497 neg r16 ; else invert difference if negative 498 adc r17,r22 ; r22 is cleared above 499 neg r17 500 501 deadband_000039: ; see if pot has moved or if its just noise 502 503 cpi r16,$40 ; see if difference is greater than 1 lsb 504 cpc r17,r22 ; r22 is cleared above 505 brlo nochange_000039 ; dont update loop time if difference is not large enough 506 ldi r16,$fe ; make sure buffer size is even 507 and r10,r16 508 sts delay_mem_000039,r10 ; store new desired buffer size 509 sts (delay_mem_000039 + 1),r11 ; i ran out of registers 510 511 nochange_000039: ; clear accumulation registers 512 513 clr r10 ; empty accumulation registers 514 clr r11 515 clr r9 516 517 switchsample_000039: ; check rotary switch state 518 519 lds r16,pinj ; get switch data 520 andi r16,$78 ; mask off rotary switch 521 lsr r16 ; adjust switch position to program memory location 522 lsr r16 523 ldi r17,$02 524 add r16,r17 525 cpse r16,r31 ; check if location has changed 526 clr r30 ; reset jump register to intial state 527 mov r31,r16 528 529 done_000039: 530 531 reti ; return to waiting 532