This function implements a reverser, so all input sounds are played backwards. It takes mono data in on the left channel, and presents mono data out on both the left and right channels. The pot (MOD1) controls the buffer size.
A reverser is created by playing all the samples in reverse order. Unfortunately, you end up hitting the beginning of your buffer at some point, and need to start over again. This creates an audible click in the sample playback. To eliminate this click, 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 pot (MOD1) varies the buffer size used for sample playback, from 6ms to 1.5s. Smaller buffer sizes give a more subtle effect, as you are playing through only very small samples at a time, but has the advantage of not having 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 large buffer sizes, the samples sound more like reversed audio, but the delay becomes larger as well.
; program: reverser-16b-pot-crossfade.asm
; UID = 000044 - unique id to eliminate conflicts between variables
; 16b address space (1.5s sample time)
; mono data in on left channel, mono data out on left and right
; pot (MOD1) controlled buffer size
; program overview
;
; data is read in from memory and written out the codec at the same time
; new data is written to the memory from the codec. left channel data
; is read in, and presented on both right and left out. the write address
; increments until it reaches the top of the buffer and then starts at
; zero. the read address does the same thing, but in reverse. the adc
; value sets the buffer size, and is determined by the pot (MOD1) value,
; which is multiplied up to a 16b value. the buffer size is adjusted at a
; rate of 2 lsb per sample period, until it matches to what the pot says
; it should be. the samples are played normally, until within a fixed
; distance of the buffer boundary. at this point, the samples are
; crossfaded with the first samples on the other side of the boundary. this
; reduces the appearance of clicks at buffer transistions, without giving
; the reverb effect of the fading method, and with a little less tremolo
; than the ducking method.
; constant definitions
;
.equ stepsize_000044 = $0080 ; 65536/(stepsize * 44.1) = crossfade time (ms)
.equ buffer_min_000044 = (4 * ($10000 / stepsize_000044))
; minimum sample buffer size to accomodate crossfade time
; register usage - may be redefined in other sections
;
; r0 multiply result lsb
; r1 multiply result msb
; r2 multiply accumulate lsb
; r3 multiply accumulate mlb
; r4 left/right lsb out / sample 1 lsb / multiply accumulate mhb
; r5 left/right msb out / smaple 2 msb / multiply accumulate msb
; r6 left lsb in / sample 2 lsb
; r7 left msb in / sample 2 msb
; r8
; r9 adc msb accumulator
; r10 adc fractional byte accumulator
; r11 adc lsb accumulator
; r12 desired buffer size lsb
; r13 desired buffer size msb
; r14 null register
; r15 switch/adc counter
; r16 temporary swap register
; r17 temporary swap register
; r18 crossfade address temporary lsb
; r19 crossfade address temporary msb
; r20 crossfade distance lsb
; r21 crossfade distance msb
; r22 multiplicand lsb
; r23 multiplicand msb
; r24 write address lsb
; r25 write address msb
; r26 buffer size lsb
; r27 buffer size msb
; r28 read address lsb
; r29 read address msb
; r30 jump location for interrupt lsb
; r31 jump location for interrupt msb
; t crossfading indicator
; program starts here first time
; initialze z pointer for correct jump
; this assumes a less than 256 word jump
ldi r30,$20 ; set jump location to program start
clr r24 ; clear write register
clr r25
ldi r22,$00 ; setup write address high byte
clr r18 ; setup r18 as null register for carry addition and ddr setting
ldi r17,$ff ; setup r17 for ddr setting
clear_000044: ; clear delay buffer
; eliminates static when first switching to the delay setting
adiw r25:r24,$01 ; increment write register
adc r22,r18 ; increment write third byte
cpi r22,$01 ; check if 16b memory space has been cleared
breq cleardone_000044 ; continue until end of buffer reached
out portd,r24 ; set address
sts porth,r25
out portg,r22 ; pull ce low,we low,and set high bits of address
out ddra,r17 ; set porta as output for data write
out ddrc,r17 ; set portc as output for data write
out porta,r18 ; set data
out portc,r18 ; r18 is cleared above
sbi portg,portg2 ; pull we high to write
out ddra,r18 ; set porta as input for data lines
out ddrc,r18 ; set portc as input for data lines
rjmp clear_000044 ; continue clearing
cleardone_000044: ; reset registers
ldi r24,$00 ; initialize write register
ldi r25,$00
clr r14 ; setup null register
ldi r28,$00 ; set read address to minimum delay
ldi r29,$fd
clr r4 ; initialize data output registers
clr r5
ldi r26,$00 ; initialize buffer size
ldi r27,$06
reti ; finish with initialization 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
;increment write address
adiw r25:r24,$01 ; increment write address
cp r24,r26 ; check if at buffer boundary
cpc r25,r27
brlo wait1_000044 ; continue if not
clr r24 ; set write address to bottom
clr r25
wait1_000044: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait1_000044
in r7,spdr ; recieve in left channel msb
out spdr,r4 ; send out left channel lsb
;decrement read address
sbiw r29:r28,$01 ; decrement read address
brsh wait2_000044 ; check if at bottom of buffer
movw r29:r28,r27:r26 ; set counter to top
sbiw r29:r28,$01 ; decrement read address
wait2_000044: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait2_000044
in r6,spdr ; recieve in left channel lsb
out spdr,r5 ; send out right channel msb
;write left channel data to sram
out portd,r24 ; set address
sts porth,r25
out portg,r14 ; 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,r6 ; set data
out portc,r7
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
wait3_000044: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait3_000044
in r17,spdr ; recieve in right channel msb
out spdr,r4 ; send out right channel lsb
;get left/right channel data from sram
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
wait4_000044: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait4_000044
in r17,spdr ; recieve in left channel lsb
;get crossfade distance
movw r17:r16,r29:r28 ; move read address to temporary register
sub r16,r24 ; find distance to loop boundary
sbc r17,r25
brcc attenuate_000044 ; check if within crossfade distance if positive
add r16,r26 ; flip result around boundary if negative
adc r17,r27
attenuate_000044: ; multiply signal by distance to boundary
brts crossfade1_000044 ; skip if already crossfading
ldi r18,low(2 * ($10000 / stepsize_000044)) ; get crossfade distance
ldi r19,high(2 * ($10000 / stepsize_000044))
cp r16,r18 ; check if less than crossfade distance
cpc r17,r19
brsh check_000044 ; do nothing if not
set ; set t register to indicate crossfade and downcounting
clr r20 ; set crossfade counter to top
clr r21
subi r20,low(stepsize_000044) ; decrement for first sample
sbci r21,high(stepsize_000044)
crossfade1_000044: ; crossfade the signal
;setup sample 2 read address
movw r19:r18,r29:r28 ; move read address to temporary register
subi r18,low(2 * ($10000 / stepsize_000044)) ; get sample from other side of boundary
sbci r19,high(2 * ($10000 / stepsize_000044))
brcc crossfade2_000044 ; continue if no buffer underflow
add r18,r26 ; wrap read address around buffer
adc r19,r27
crossfade2_000044: ; continue crossfading signal
;get sample 2 from sram
out portd,r18 ; set address
sts porth,r19
nop ; wait input latch time of 2 clock cycles
nop
in r6,pina ; get data
in r7,pinc ; get data
;multiply sample 1
movw r17:r16,r5:r4 ; move data to signed multiply register
mulsu r17,r21 ; (signed)Ah * (unsigned)Bh - multiply high bytes
movw r5:r4,r1:r0 ; store high bytes result for later
mul r16,r20 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
movw r3:r2,r1:r0 ; store low byets for later
mulsu r17,r20 ; (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 r21,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 2
movw r17:r16,r7:r6 ; move data to signed multiply register
movw r23:r22,r21:r20 ; move distance counter to temporary register
com r22 ; invert distance for sample 2
com r23
mulsu r17,r23 ; (signed)Ah * (unsigned)Bh - multiply high bytes
add r4,r0 ; accumulate result
adc r5,r1
mul r16,r22 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
add r2,r0 ; accumulate result
adc r3,r1
adc r4,r14
adc r5,r14
mulsu r17,r22 ; (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 r23,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 r20,low(stepsize_000044) ; decrement crossfade
sbci r21,high(stepsize_000044)
brcc check1_000044 ; check if crossfade time is negative
movw r29:r28,r19:r18 ; move crossfade address to current address
clt ; clear the t register to indicate done
rjmp check_000044 ; finish off
check1_000044: ; continue checking crossfade time
brne check_000044 ; check if crossfade time is zero
movw r29:r28,r19:r18 ; move crossfade address to current address
clt ; clear the t register to indicate done
check_000044: ; check if buffer size is correct
cp r26,r12 ; compare current delay to desired delay
cpc r27,r13
brlo upcount2_000044 ; increment if smaller than
breq adcsample_000044 ; do nothing if they are same size
sbiw r27:r26,$02 ; decrement buffer size
rjmp adcsample_000044 ; finish off
upcount2_000044: ; increment buffer size register
adiw r27:r26,$02 ; increment buffer size
adcsample_000044: ; get loop setting
lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp done_000044 ; skip adc sampling
lds r16,adcl ; get low byte adc value
lds r17,adch ; get high byte adc value
add r10,r16
adc r11,r17 ; accumulate adc samples
adc r9,r14 ; accumulate adc samples - r14 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r15 ; countdown adc sample clock
brne done_000044 ; move adc value to loop setting after 256 samples
lsr r9 ; divide accumulated value by 4 to get 16b value
ror r11
ror r10
lsr r9
ror r11
ror r10
ldi r16,low(buffer_min_000044) ; load minimum buffer size
ldi r17,high(buffer_min_000044)
cp r10,r16 ; check if less than minimum
cpc r11,r17
brsh compare_000044 ; compare to previous value if above min
movw r11:r10,r17:r16 ; set buffer size to minimum
compare_000044: ; compare to previous value
movw r17:r16,r13:r12 ; make a copy of current loop time for comparison
sub r16,r10 ; find difference between current loop time and last loop time
sbc r17,r11
brcc deadband_000044 ; see if difference is large enough to indicate a change
neg r16 ; invert difference if negative
adc r17,r14 ; r14 is cleared above
neg r17
deadband_000044: ; see if pot has moved or if its just noise
cpi r16,$40 ; see if difference is greater than 1 lsb
cpc r17,r14 ; r14 is cleared above
brlo nochange_000044 ; dont update loop time if difference is not large enough
ldi r16,$fe ; make sure buffer size is even
and r10,r16
movw r13:r12,r11:r10 ; move adc value to loop time register
nochange_000044: ; clear accumulation registers
clr r10 ; empty accumulation registers
clr r11
clr r9
;check rotary switch state
lds r16,pinj ; get switch data
andi r16,$78 ; mask off rotary switch
lsr r16 ; adjust switch position to program memory location
lsr r16
ldi r17,$02
add r16,r17
cpse r16,r31 ; check if location has changed
clr r30 ; reset jump register to intial state
mov r31,r16
done_000044:
reti ; return to waiting
Last edited 2010-08-21 by guest · 6 revisions