MICrODEC: Stock Functions
Sampler with Pitch Shifter
This function implements a sampler with a 6s sample time. The input is mono on the left channel, and the output is mono on both left and right channels. The pushbutton on the rotary encoder (MOD2) stores data to memory when pressed, and plays back data when released. The pot (MOD1) varies the playback speed, from -2 octaves to +1 octave.
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 cross-fading method. This is the same as cross-fading between records when DJing. As one sample gets close to the buffer boundary, its volume is faded down, and a sample from the other side of the boundary is faded up. This continues as the sample moves forward in the buffer, with the volume of the first sample being reduced to zero by the time it gets to the buffer boundary. At this point, the sample on the other side is playing full volume, and takes over. This gives a relatively smooth transition across the buffer boundary, with only a slight dipping noticeable, but also keeps the playback very true to the original signal during the majority of playback, which is not near the boundary. The crossfade time is preset at the begining of the code, which also determines the minimum sample size, as there has to be enough data for both fading up and down on either side of the boundary.
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 pushbutton on MOD2 controls the sample size. It begins recording when the button is pressed, and stops when released. If the button is held down for a period less than the minimum buffer size, the sample is increased to this minimum size (although its next to impossible to hit the button that quickly). If the button is held down longer than the buffer size (6s), then only the first 6s of audio will be stored and played back when the button is released.
1 ; program: sampler-18b-pot.asm 2 ; UID = 000058 - unique id to eliminate conflicts between variables 3 ; 18b address space (6s sample time) 4 ; mono data in on left channel, mono data out on left and right 5 ; pot (MOD1) controlled playback speed 6 7 ; program overview 8 ; 9 ; data is read in from the codec and placed into memory. a memory address 10 ; pointer is incrmented to find the next sample, and this is sent to the 11 ; codec. the pointer is incremented at a variable rate, with fractional 12 ; values less than one slowing down the playback speed, and fractional 13 ; values above one increasing the playback speed. the output data is an 14 ; interpolation of the two samples adjacent to the pointer. left channel 15 ; data is taken in, and the result is placed on both left and right. ADC0 16 ; is averaged over 256 samples and is used to create the pointer increment 17 ; value. the pushbutton takes in data when depressed, and plays back when 18 ; released. the volume is reduced around the sample boundary to reduce 19 ; clicking sounds. 20 21 ; constant definitions 22 ; 23 .equ stepsize_000058 = ($0100 / fade_000058) ; crossfade counter decrement 24 .equ fade_000058 = $04 ; crossfade sample distance ($02 - $70 valid) 25 ; crossfade time [ms] = ((fade x 256) / 44.1) 26 .equ minbuff_000058 = (($0240 * fade_000058 * 2) + $0200) 27 ; minimum buffer size to ensure that there is enough time for fading 28 .equ mem_000058 = $0200 ; memory location for opposing buffer address 29 ; i ran out of registers 30 31 ; register usage - may be redefined in other sections 32 ; 33 ; r0 multiply result lsb 34 ; r1 multiply result msb 35 ; r2 left lsb in 36 ; r3 left msb in 37 ; r4 left/right lsb out 38 ; r5 left/right msb out 39 ; r6 temporary swap register 40 ; r7 temporary swap register 41 ; r8 playback speed fractional byte 42 ; r9 adc accumulation fractional byte 43 ; r10 adc accumulation lsb 44 ; r11 adc accumulation msb 45 ; r12 playback speed lsb 46 ; r13 playback speed msb 47 ; r14 null register 48 ; r15 switch sample counter 49 ; r16 temporary swap register 50 ; r17 temporary swap register 51 ; r18 temporary swap register 52 ; r19 temporary swap register 53 ; r20 fade state register 54 ; r21 read address fractional byte 55 ; r22 write address/buffer size high byte 56 ; r23 read address high byte 57 ; r24 write address/buffer size lsb 58 ; r25 write address/buffer size msb 59 ; r26 crossfade distance lsb 60 ; r27 crossfade distance msb 61 ; r28 read address lsb 62 ; r29 read address msb 63 ; r30 jump location for interrupt lsb 64 ; r31 jump location for interrupt msb 65 ; t sampler record indicator 66 67 ;program starts here first time 68 ; initialize registers 69 ; memory is not blanked in case you want to sample a neighboring function 70 ldi r30,$11 ; set jump location to program start 71 clr r14 ; set up null register 72 clr r24 ; set initial buffer size to first 16b 73 clr r25 74 ldi r22,$01 75 ldi r16,$01 76 clr r12 ; initialize playback speed to normal 77 mov r13,r16 78 clr r9 ; clear accumulation registers 79 clr r10 80 clr r11 81 clr r20 ; initialize fading state register 82 clr r28 ; initialize read address 83 clr r29 84 clr r23 85 clt ; initialize sampler indicator 86 reti ; return and wait for next interrupt 87 88 ;program starts here every time but first 89 ; initiate data transfer to codec 90 sbi portb,portb0 ; toggle slave select pin 91 out spdr,r5 ; send out left channel msb 92 cbi portb,portb0 93 94 wait1_000058: ; check if byte has been sent 95 96 in r17,spsr 97 sbrs r17,spif 98 rjmp wait1_000058 99 in r3,spdr ; recieve in left channel msb 100 out spdr,r4 ; send out left channel lsb 101 102 wait2_000058: ; check if byte has been sent 103 104 in r17,spsr 105 sbrs r17,spif 106 rjmp wait2_000058 107 in r2,spdr ; recieve in left channel lsb 108 out spdr,r5 ; send out right channel msb 109 110 wait3_000058: ; check if byte has been sent 111 112 in r17,spsr 113 sbrs r17,spif 114 rjmp wait3_000058 115 in r17,spdr ; recieve in right channel msb 116 out spdr,r4 ; send out right channel lsb 117 118 wait4_000058: ; check if byte has been sent 119 120 in r17,spsr 121 sbrs r17,spif 122 rjmp wait4_000058 123 in r17,spdr ; recieve in left channel lsb 124 125 ;check pushbutton 126 lds r16,pinj ; get pushbutton data 127 sbrc r16,$02 ; check if pushbutton depressed 128 rjmp interpolate_000058 ; playback if button is not depressed 129 brts write_000058 ; skip initialization if already done 130 set ; set the t register to indicate sampling 131 clr r24 ; initialize the write address 132 clr r25 133 clr r22 134 clr r19 ; initialize buffer overflow indicator 135 ; (r19 not used elsewhere during sampling period) 136 ldi r17,high(minbuff_000058) ; check if buffer is too small 137 cpi r24,low(minbuff_000058) 138 cpc r25,r17 139 cpc r22,r14 ; r14 is cleared above 140 brsh write_000058 ; continue if large enough 141 ldi r24,low(minbuff_000058) ; else set buffer size to minbuff 142 mov r25,r17 143 144 write_000058: ; write left channel data to sram 145 146 movw r5:r4,r3:r2 ; pass data through while recording 147 sbrc r19,$00 ; check if overflow occured 148 rjmp adcsample_000058 ; finish off if overflow 149 out portd,r24 ; else set address 150 sts porth,r25 151 out portg,r22 ; pull ce low,we low,and set high bits of address 152 ldi r17,$ff 153 out ddra,r17 ; set porta as output for data write 154 out ddrc,r17 ; set portc as output for data write 155 out porta,r2 ; set data 156 out portc,r3 157 sbi portg,portg2 ; pull we high to write 158 out ddra,r14 ; set porta as input for data lines 159 out ddrc,r14 ; set portc as input for data lines 160 adiw r25:r24,$01 ; increment write address 161 adc r22,r14 ; r14 is cleared above 162 sbrc r22,$02 ; check for buffer overflow 163 ldi r19,$01 ; set overflow indicator if overflow 164 rjmp adcsample_000058 ; else finish off 165 166 interpolate_000058: ; interpolate data based upon speed setting 167 168 brtc interpolate1_000058 ; check if pushbutton just released 169 clr r28 ; initialize read address 170 clr r29 171 clr r23 172 clt ; clear the sampling indicator 173 174 interpolate1_000058: ; continue with interpolation 175 176 add r21,r12 ; increment read register 177 adc r28,r13 178 adc r29,r14 ; r14 is cleared above 179 adc r23,r14 180 181 read1_000058: ; get left channel sample 1 data from sram 182 183 ori r23,$04 ; set we\ bit in high byte register 184 out portg,r23 ; pull ce low, we high, and set high bits of register 185 out portd,r28 ; set address 186 sts porth,r29 187 nop ; wait input latch time of 2 clock cycles 188 nop 189 in r4,pina ; get data 190 in r5,pinc ; get data 191 192 ;increment read address to next sample 193 movw r17:r16,r29:r28 ; move read address to temporary register 194 mov r18,r23 195 ldi r19,$01 ; increment read address 196 add r16,r19 197 adc r17,r14 ; r14 is cleared above 198 adc r18,r14 199 200 read2_000058: ; get left channel sample 2 data from sram 201 202 ori r18,$04 ; just to be sure we\ is high 203 out portg,r18 ; pull ce low, we high, and set high bits of register 204 out portd,r16 ; set address 205 sts porth,r17 206 nop ; wait input latch time of 2 clock cycles 207 nop 208 in r2,pina ; get data 209 in r3,pinc ; get data 210 211 ;multiply sample 1 by distance 212 movw r17:r16,r5:r4 ; move sample to multiply register 213 mov r18,r21 ; get distance from sample 1 214 com r18 215 mulsu r17,r18 ; (signed)Ah * (unsigned)B 216 movw r5:r4,r1:r0 217 mul r16,r18 ; (unsigned)Al * (unsigned)B 218 add r4,r1 ; accumulate result 219 adc r5,r14 ; r14 is cleared above 220 mov r19,r0 221 222 ;multiply sample 2 by distance 223 movw r17:r16,r3:r2 ; move sample to multiply register 224 mulsu r17,r21 ; (signed)Ah * (unsigned)B 225 add r4,r0 ; accumulate result 226 adc r5,r1 227 mul r16,r21 ; (unsigned)Al * (unsigned)B 228 add r19,r0 ; accumulate result 229 adc r4,r1 230 adc r5,r14 ; r14 is cleared above 231 232 sbrc r20,$00 ; check if fading 233 rjmp crossfade_000058 ; crossfade if appropriate 234 ; else check if time to do so 235 236 ;get distance to boundary 237 movw r17:r16,r25:r24 ; move buffer size to temporary register 238 clr r19 239 mov r18,r22 240 andi r23,$03 ; mask off unused bits in read high byte 241 sub r19,r21 ; find distance to buffer boundary 242 sbc r16,r28 243 sbc r17,r29 244 sbc r18,r23 245 ;subi r16,$01 ; buffer boundary is 1 sample past last sample 246 ;sbc r17,r14 ; uncomment this if glitches occur around buffer boundary 247 ;sbc r18,r14 ; although its been fine so far 248 249 ;check if within fade distance 250 ldi r19,fade_000058 ; fetch fade distance 251 252 ;scale fade distance by playback speed (r13:r12) 253 mul r13,r19 ; (unsigned)Ah x (unsigned)B 254 movw r7:r6,r1:r0 255 mul r12,r19 ; (unsigned)Al x (unsigned)B 256 add r6,r1 ; accumulate result 257 adc r7,r14 ; r14 is cleared above 258 259 ;compare current distance to fade distance 260 cp r0,r16 ; compare current distance to scaled fade distance 261 cpc r6,r17 262 cpc r7,r18 263 brsh initialize_000058 ; initialize counters if within fade distance 264 rjmp adcsample_000058 ; else finish off 265 266 initialize_000058: ; initialize crossfade registers 267 268 clr r26 ; initialize crossfade counter 269 clr r27 270 sts mem_000058,r26 ; initialize opposing buffer address 271 sts (mem_000058 + 1),r27 272 sts (mem_000058 + 2),r21 273 subi r26,stepsize_000058 ; prepare crossfade counter for next cycle 274 sbc r27,r14 ; r14 is cleared above 275 ldi r20,$01 ; set crossfade indicator 276 277 crossfade_000058: ; crossfade across sample boundary 278 279 lds r16,mem_000058 ; fetch opposing buffer address 280 lds r17,(mem_000058 + 1) 281 lds r18,(mem_000058 + 2) 282 add r18,r12 ; add in playback speed 283 adc r16,r13 284 adc r17,r14 ; r14 is cleared above 285 sts mem_000058,r16 ; re-store opposing buffer address 286 sts (mem_000058 + 1),r17 287 sts (mem_000058 + 2),r18 288 289 ;get left channel sample 3 data from sram 290 ldi r19,$04 291 out portg,r19 ; pull ce low, we high, and set high bits of register 292 out portd,r16 ; set address 293 sts porth,r17 294 nop ; wait input latch time of 2 clock cycles 295 nop 296 in r2,pina ; get data 297 in r3,pinc ; get data 298 299 ;increment read address to next sample 300 ldi r19,$01 ; increment read address to next sample 301 add r16,r19 302 adc r17,r14 ; r14 is cleared above 303 304 ;get left channel sample 4 data from sram 305 ;ldi r19,$04 ; portg already set above 306 ;out portg,r19 ; pull ce low, we high, and set high bits of register 307 out portd,r16 ; set address 308 sts porth,r17 309 nop ; wait input latch time of 2 clock cycles 310 nop 311 in r16,pina ; get data 312 in r17,pinc ; get data 313 314 ;multiply sample 4 by distance 315 mulsu r17,r18 ; (signed)Ah * (unsigned)B 316 movw r7:r6,r1:r0 317 mul r16,r18 ; (unsigned)Al * (unsigned)B 318 add r6,r1 ; accumulate result 319 adc r7,r14 ; r14 is cleared above 320 mov r19,r0 321 322 ;multiply sample 3 by distance 323 com r18 ; get distance from sample 3 324 movw r17:r16,r3:r2 ; move sample to multiply register 325 mulsu r17,r18 ; (signed)Ah * (unsigned)B 326 add r6,r0 ; accumulate result 327 adc r7,r1 328 mul r16,r18 ; (unsigned)Al * (unsigned)B 329 add r19,r0 ; accumulate result 330 adc r6,r1 331 adc r7,r14 ; r14 is cleared above 332 333 ;add samples 1/2 and 3/4 together 334 ;multiply sample 1/2 335 movw r17:r16,r5:r4 ; move sample 1/2 to signed multiply register 336 movw r19:r18,r27:r26 ; move fade distance to multiply register 337 mulsu r17,r19 ; (signed)Ah * (unsigned)Bh - multiply high bytes 338 movw r5:r4,r1:r0 ; store high bytes result for later 339 mul r16,r18 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes 340 movw r3:r2,r1:r0 ; store low byets for later 341 mulsu r17,r18 ; (signed)Ah * (unsigned)Bl - multiply middle bytes 342 sbc r5,r14 ; r14 is cleared above - subtract sign bit 343 add r3,r0 ; accumulate result 344 adc r4,r1 345 adc r5,r14 ; r14 is cleared above 346 mul r19,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes 347 add r3,r0 ; accumulate result 348 adc r4,r1 349 adc r5,r14 ; r14 is cleared above 350 351 ;multiply and accumulate sample 3/4 352 movw r17:r16,r7:r6 ; move data to signed multiply register 353 movw r19:r18,r27:r26 ; move fade distance to multiply register 354 com r18 ; invert distance for sample 2 355 com r19 356 mulsu r17,r19 ; (signed)Ah * (unsigned)Bh - multiply high bytes 357 add r4,r0 ; accumulate result 358 adc r5,r1 359 mul r16,r18 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes 360 add r2,r0 ; accumulate result 361 adc r3,r1 362 adc r4,r14 363 adc r5,r14 364 mulsu r17,r18 ; (signed)Ah * (unsigned)Bl - multiply middle bytes 365 sbc r5,r14 ; r14 is cleared above - subtract sign bit 366 add r3,r0 ; accumulate result 367 adc r4,r1 368 adc r5,r14 ; r14 is cleared above 369 mul r19,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes 370 add r3,r0 ; accumulate result 371 adc r4,r1 372 adc r5,r14 ; r14 is cleared above 373 374 ;check if done crossfading 375 subi r26,stepsize_000058 ; reduce crossfade counter 376 sbc r27,r14 ; r14 is cleared above 377 breq fadedone_000058 ; reset if crossfade over 378 brcs fadedone_000058 ; reset if crossfade over 379 rjmp adcsample_000058 ; else finish off 380 381 fadedone_000058: ; turn off crossfade 382 383 lds r28,mem_000058 ; set new buffer read address 384 lds r29,(mem_000058 + 1) 385 lds r21,(mem_000058 + 2) 386 clr r23 387 clr r20 ; reset crossfade indicator 388 389 adcsample_000058: ; get speed settings 390 391 lds r17,adcsra ; get adc control register 392 sbrs r17,adif ; check if adc conversion is complete 393 rjmp done_000058 ; skip adc sampling 394 lds r16,adcl ; get low byte adc value 395 lds r17,adch ; get high byte adc value 396 add r9,r16 ; accumulate adc samples 397 adc r10,r17 398 adc r11,r14 ; r14 is cleared above 399 ldi r17,$f7 400 sts adcsra,r17 ; clear interrupt flag 401 dec r15 ; countdown adc sample clock 402 brne switchsample_000058 ; get adc value if its been long enough 403 lsr r11 ; divide accumulated value by 2 404 ror r10 405 ror r9 406 ldi r17,$40 ; place in offset 407 add r10,r17 408 adc r11,r14 ; r14 is cleared above 409 410 ;check for deadband 411 movw r17:r16,r11:r10 ; move adc sample to temporary register 412 mov r18,r9 413 sub r18,r8 414 sbc r16,r12 ; find difference between adc sample and playback speed 415 sbc r17,r13 416 brsh check_000058 ; check for deadband if positive 417 com r18 ; invert if negative 418 com r16 ; only 1 lsb error with ones complement 419 com r17 420 421 check_000058: ; check if difference is greater than deadband 422 423 cpi r18,$80 ; check if difference is less than 1 lsb 424 cpc r16,r14 425 cpc r17,r14 ; r14 cleared above 426 brlo empty_000058 ; do nothing if less than $02 427 movw r13:r12,r11:r10 ; move adc sample to playback speed 428 mov r8,r9 ; if large enough change 429 430 empty_000058: ; empty accumulation registers and finish off 431 432 clr r9 ; empty accumulation registers 433 clr r10 434 clr r11 435 436 switchsample_000058: ;check switch 437 438 lds r16,pinj ; get switch data 439 andi r16,$78 ; mask off rotary switch 440 lsr r16 ; adjust switch position to program memory location 441 lsr r16 442 subi r16,$fe ; same as adding $02 443 cpse r16,r31 ; check if location has changed 444 clr r30 ; reset jump register to intial state 445 mov r31,r16 446 447 done_000058: 448 449 reti ; return to waiting 450