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