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