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.
; program: sampler-18b-pot.asm
; UID = 000058 - unique id to eliminate conflicts between variables
; 18b address space (6s sample time)
; mono data in on left channel, mono data out on left and right
; pot (MOD1) controlled playback speed
; program overview
;
; data is read in from the codec and placed into memory. a memory address
; pointer is incrmented to find the next sample, and this is sent to the
; codec. the pointer is incremented at a variable rate, with fractional
; values less than one slowing down the playback speed, and fractional
; values above one increasing the playback speed. the output data is an
; interpolation of the two samples adjacent to the pointer. left channel
; data is taken in, and the result is placed on both left and right. ADC0
; is averaged over 256 samples and is used to create the pointer increment
; value. the pushbutton takes in data when depressed, and plays back when
; released. the volume is reduced around the sample boundary to reduce
; clicking sounds.
; constant definitions
;
.equ stepsize_000058 = ($0100 / fade_000058) ; crossfade counter decrement
.equ fade_000058 = $04 ; crossfade sample distance ($02 - $70 valid)
; crossfade time [ms] = ((fade x 256) / 44.1)
.equ minbuff_000058 = (($0240 * fade_000058 * 2) + $0200)
; minimum buffer size to ensure that there is enough time for fading
.equ mem_000058 = $0200 ; memory location for opposing buffer address
; i ran out of registers
; register usage - may be redefined in other sections
;
; r0 multiply result lsb
; r1 multiply result msb
; r2 left lsb in
; r3 left msb in
; r4 left/right lsb out
; r5 left/right msb out
; r6 temporary swap register
; r7 temporary swap register
; r8 playback speed fractional byte
; r9 adc accumulation fractional byte
; r10 adc accumulation lsb
; r11 adc accumulation msb
; r12 playback speed lsb
; r13 playback speed msb
; r14 null register
; r15 switch sample counter
; r16 temporary swap register
; r17 temporary swap register
; r18 temporary swap register
; r19 temporary swap register
; r20 fade state register
; r21 read address fractional byte
; r22 write address/buffer size high byte
; r23 read address high byte
; r24 write address/buffer size lsb
; r25 write address/buffer size msb
; r26 crossfade distance lsb
; r27 crossfade distance msb
; r28 read address lsb
; r29 read address msb
; r30 jump location for interrupt lsb
; r31 jump location for interrupt msb
; t sampler record indicator
;program starts here first time
; initialize registers
; memory is not blanked in case you want to sample a neighboring function
ldi r30,$11 ; set jump location to program start
clr r14 ; set up null register
clr r24 ; set initial buffer size to first 16b
clr r25
ldi r22,$01
ldi r16,$01
clr r12 ; initialize playback speed to normal
mov r13,r16
clr r9 ; clear accumulation registers
clr r10
clr r11
clr r20 ; initialize fading state register
clr r28 ; initialize read address
clr r29
clr r23
clt ; initialize sampler indicator
reti ; return and wait for next interrupt
;program starts here every time but first
; initiate data transfer to codec
sbi portb,portb0 ; toggle slave select pin
out spdr,r5 ; send out left channel msb
cbi portb,portb0
wait1_000058: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait1_000058
in r3,spdr ; recieve in left channel msb
out spdr,r4 ; send out left channel lsb
wait2_000058: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait2_000058
in r2,spdr ; recieve in left channel lsb
out spdr,r5 ; send out right channel msb
wait3_000058: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait3_000058
in r17,spdr ; recieve in right channel msb
out spdr,r4 ; send out right channel lsb
wait4_000058: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait4_000058
in r17,spdr ; recieve in left channel lsb
;check pushbutton
lds r16,pinj ; get pushbutton data
sbrc r16,$02 ; check if pushbutton depressed
rjmp interpolate_000058 ; playback if button is not depressed
brts write_000058 ; skip initialization if already done
set ; set the t register to indicate sampling
clr r24 ; initialize the write address
clr r25
clr r22
clr r19 ; initialize buffer overflow indicator
; (r19 not used elsewhere during sampling period)
ldi r17,high(minbuff_000058) ; check if buffer is too small
cpi r24,low(minbuff_000058)
cpc r25,r17
cpc r22,r14 ; r14 is cleared above
brsh write_000058 ; continue if large enough
ldi r24,low(minbuff_000058) ; else set buffer size to minbuff
mov r25,r17
write_000058: ; write left channel data to sram
movw r5:r4,r3:r2 ; pass data through while recording
sbrc r19,$00 ; check if overflow occured
rjmp adcsample_000058 ; finish off if overflow
out portd,r24 ; else set address
sts porth,r25
out portg,r22 ; pull ce low,we low,and set high bits of address
ldi r17,$ff
out ddra,r17 ; set porta as output for data write
out ddrc,r17 ; set portc as output for data write
out porta,r2 ; set data
out portc,r3
sbi portg,portg2 ; pull we high to write
out ddra,r14 ; set porta as input for data lines
out ddrc,r14 ; set portc as input for data lines
adiw r25:r24,$01 ; increment write address
adc r22,r14 ; r14 is cleared above
sbrc r22,$02 ; check for buffer overflow
ldi r19,$01 ; set overflow indicator if overflow
rjmp adcsample_000058 ; else finish off
interpolate_000058: ; interpolate data based upon speed setting
brtc interpolate1_000058 ; check if pushbutton just released
clr r28 ; initialize read address
clr r29
clr r23
clt ; clear the sampling indicator
interpolate1_000058: ; continue with interpolation
add r21,r12 ; increment read register
adc r28,r13
adc r29,r14 ; r14 is cleared above
adc r23,r14
read1_000058: ; get left channel sample 1 data from sram
ori r23,$04 ; set we\ bit in high byte register
out portg,r23 ; pull ce low, we high, and set high bits of register
out portd,r28 ; set address
sts porth,r29
nop ; wait input latch time of 2 clock cycles
nop
in r4,pina ; get data
in r5,pinc ; get data
;increment read address to next sample
movw r17:r16,r29:r28 ; move read address to temporary register
mov r18,r23
ldi r19,$01 ; increment read address
add r16,r19
adc r17,r14 ; r14 is cleared above
adc r18,r14
read2_000058: ; get left channel sample 2 data from sram
ori r18,$04 ; just to be sure we\ is high
out portg,r18 ; pull ce low, we high, and set high bits of register
out portd,r16 ; set address
sts porth,r17
nop ; wait input latch time of 2 clock cycles
nop
in r2,pina ; get data
in r3,pinc ; get data
;multiply sample 1 by distance
movw r17:r16,r5:r4 ; move sample to multiply register
mov r18,r21 ; get distance from sample 1
com r18
mulsu r17,r18 ; (signed)Ah * (unsigned)B
movw r5:r4,r1:r0
mul r16,r18 ; (unsigned)Al * (unsigned)B
add r4,r1 ; accumulate result
adc r5,r14 ; r14 is cleared above
mov r19,r0
;multiply sample 2 by distance
movw r17:r16,r3:r2 ; move sample to multiply register
mulsu r17,r21 ; (signed)Ah * (unsigned)B
add r4,r0 ; accumulate result
adc r5,r1
mul r16,r21 ; (unsigned)Al * (unsigned)B
add r19,r0 ; accumulate result
adc r4,r1
adc r5,r14 ; r14 is cleared above
sbrc r20,$00 ; check if fading
rjmp crossfade_000058 ; crossfade if appropriate
; else check if time to do so
;get distance to boundary
movw r17:r16,r25:r24 ; move buffer size to temporary register
clr r19
mov r18,r22
andi r23,$03 ; mask off unused bits in read high byte
sub r19,r21 ; find distance to buffer boundary
sbc r16,r28
sbc r17,r29
sbc r18,r23
;subi r16,$01 ; buffer boundary is 1 sample past last sample
;sbc r17,r14 ; uncomment this if glitches occur around buffer boundary
;sbc r18,r14 ; although its been fine so far
;check if within fade distance
ldi r19,fade_000058 ; fetch fade distance
;scale fade distance by playback speed (r13:r12)
mul r13,r19 ; (unsigned)Ah x (unsigned)B
movw r7:r6,r1:r0
mul r12,r19 ; (unsigned)Al x (unsigned)B
add r6,r1 ; accumulate result
adc r7,r14 ; r14 is cleared above
;compare current distance to fade distance
cp r0,r16 ; compare current distance to scaled fade distance
cpc r6,r17
cpc r7,r18
brsh initialize_000058 ; initialize counters if within fade distance
rjmp adcsample_000058 ; else finish off
initialize_000058: ; initialize crossfade registers
clr r26 ; initialize crossfade counter
clr r27
sts mem_000058,r26 ; initialize opposing buffer address
sts (mem_000058 + 1),r27
sts (mem_000058 + 2),r21
subi r26,stepsize_000058 ; prepare crossfade counter for next cycle
sbc r27,r14 ; r14 is cleared above
ldi r20,$01 ; set crossfade indicator
crossfade_000058: ; crossfade across sample boundary
lds r16,mem_000058 ; fetch opposing buffer address
lds r17,(mem_000058 + 1)
lds r18,(mem_000058 + 2)
add r18,r12 ; add in playback speed
adc r16,r13
adc r17,r14 ; r14 is cleared above
sts mem_000058,r16 ; re-store opposing buffer address
sts (mem_000058 + 1),r17
sts (mem_000058 + 2),r18
;get left channel sample 3 data from sram
ldi r19,$04
out portg,r19 ; pull ce low, we high, and set high bits of register
out portd,r16 ; set address
sts porth,r17
nop ; wait input latch time of 2 clock cycles
nop
in r2,pina ; get data
in r3,pinc ; get data
;increment read address to next sample
ldi r19,$01 ; increment read address to next sample
add r16,r19
adc r17,r14 ; r14 is cleared above
;get left channel sample 4 data from sram
;ldi r19,$04 ; portg already set above
;out portg,r19 ; pull ce low, we high, and set high bits of register
out portd,r16 ; set address
sts porth,r17
nop ; wait input latch time of 2 clock cycles
nop
in r16,pina ; get data
in r17,pinc ; get data
;multiply sample 4 by distance
mulsu r17,r18 ; (signed)Ah * (unsigned)B
movw r7:r6,r1:r0
mul r16,r18 ; (unsigned)Al * (unsigned)B
add r6,r1 ; accumulate result
adc r7,r14 ; r14 is cleared above
mov r19,r0
;multiply sample 3 by distance
com r18 ; get distance from sample 3
movw r17:r16,r3:r2 ; move sample to multiply register
mulsu r17,r18 ; (signed)Ah * (unsigned)B
add r6,r0 ; accumulate result
adc r7,r1
mul r16,r18 ; (unsigned)Al * (unsigned)B
add r19,r0 ; accumulate result
adc r6,r1
adc r7,r14 ; r14 is cleared above
;add samples 1/2 and 3/4 together
;multiply sample 1/2
movw r17:r16,r5:r4 ; move sample 1/2 to signed multiply register
movw r19:r18,r27:r26 ; move fade distance to multiply register
mulsu r17,r19 ; (signed)Ah * (unsigned)Bh - multiply high bytes
movw r5:r4,r1:r0 ; store high bytes result for later
mul r16,r18 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
movw r3:r2,r1:r0 ; store low byets for later
mulsu r17,r18 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc r5,r14 ; r14 is cleared above - subtract sign bit
add r3,r0 ; accumulate result
adc r4,r1
adc r5,r14 ; r14 is cleared above
mul r19,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
add r3,r0 ; accumulate result
adc r4,r1
adc r5,r14 ; r14 is cleared above
;multiply and accumulate sample 3/4
movw r17:r16,r7:r6 ; move data to signed multiply register
movw r19:r18,r27:r26 ; move fade distance to multiply register
com r18 ; invert distance for sample 2
com r19
mulsu r17,r19 ; (signed)Ah * (unsigned)Bh - multiply high bytes
add r4,r0 ; accumulate result
adc r5,r1
mul r16,r18 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
add r2,r0 ; accumulate result
adc r3,r1
adc r4,r14
adc r5,r14
mulsu r17,r18 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc r5,r14 ; r14 is cleared above - subtract sign bit
add r3,r0 ; accumulate result
adc r4,r1
adc r5,r14 ; r14 is cleared above
mul r19,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
add r3,r0 ; accumulate result
adc r4,r1
adc r5,r14 ; r14 is cleared above
;check if done crossfading
subi r26,stepsize_000058 ; reduce crossfade counter
sbc r27,r14 ; r14 is cleared above
breq fadedone_000058 ; reset if crossfade over
brcs fadedone_000058 ; reset if crossfade over
rjmp adcsample_000058 ; else finish off
fadedone_000058: ; turn off crossfade
lds r28,mem_000058 ; set new buffer read address
lds r29,(mem_000058 + 1)
lds r21,(mem_000058 + 2)
clr r23
clr r20 ; reset crossfade indicator
adcsample_000058: ; get speed settings
lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp done_000058 ; skip adc sampling
lds r16,adcl ; get low byte adc value
lds r17,adch ; get high byte adc value
add r9,r16 ; accumulate adc samples
adc r10,r17
adc r11,r14 ; r14 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r15 ; countdown adc sample clock
brne switchsample_000058 ; get adc value if its been long enough
lsr r11 ; divide accumulated value by 2
ror r10
ror r9
ldi r17,$40 ; place in offset
add r10,r17
adc r11,r14 ; r14 is cleared above
;check for deadband
movw r17:r16,r11:r10 ; move adc sample to temporary register
mov r18,r9
sub r18,r8
sbc r16,r12 ; find difference between adc sample and playback speed
sbc r17,r13
brsh check_000058 ; check for deadband if positive
com r18 ; invert if negative
com r16 ; only 1 lsb error with ones complement
com r17
check_000058: ; check if difference is greater than deadband
cpi r18,$80 ; check if difference is less than 1 lsb
cpc r16,r14
cpc r17,r14 ; r14 cleared above
brlo empty_000058 ; do nothing if less than $02
movw r13:r12,r11:r10 ; move adc sample to playback speed
mov r8,r9 ; if large enough change
empty_000058: ; empty accumulation registers and finish off
clr r9 ; empty accumulation registers
clr r10
clr r11
switchsample_000058: ;check switch
lds r16,pinj ; get switch data
andi r16,$78 ; mask off rotary switch
lsr r16 ; adjust switch position to program memory location
lsr r16
subi r16,$fe ; same as adding $02
cpse r16,r31 ; check if location has changed
clr r30 ; reset jump register to intial state
mov r31,r16
done_000058:
reti ; return to waiting
Last edited 2010-08-21 by guest · 5 revisions