1

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.

sampler_6s.asm


   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