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 use the 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 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-fading.asm
; UID = 000036 - this is a unique id so variables dont conflict
; 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 sent out and taken in from the codec. data is taken in on the
; left channel, and played out on both left and right. a buffer of the
; past n seconds is kept and the output is the result of sampling this
; buffer in reverse. this buffer size 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. two samples are taken and averaged together by the
; ratio of their distances to the buffer boundary. they are 180 degrees
; out of phase, so as one sample is crossing the buffer boundary, it is
; silent, and the other plays full volume. this helps reduce the clicks
; at buffer boundary transitions. it also creates a reverb sound.
; constant definitions
;
.equ buffer_min_000036 = $0200 ; minimum sample buffer size
; register usage - may be redefined in other sections
;
; r0 multiply result lsb
; r1 multiply result msb
; r2 sample 2 lsb
; r3 sample 2 msb
; r4 left/right lsb out / sample 1 lsb
; r5 left/right msb out / sample 1 msb
; r6 left lsb in
; r7 left msb in
; 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
; r15 switch/adc counter
; r16 temporary swap register
; r17 temporary swap register
; r18 multiplicand lsb
; r19 multiplicand msb
; r20 multiplicand lsb
; r21 multiplicand msb
; r22 null register
; r23
; r24 write address lsb
; r25 write address msb
; r26 buffer length lsb
; r27 buffer length msb
; r28 read address lsb
; r29 read address msb
; r30 jump location for interrupt lsb
; r31 jump location for interrupt msb
; t
; program starts here first time
; initialze z pointer for correct jump
; this assumes a less than 256 word jump
ldi r30,$21 ; 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_000036: ; 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_000036 ; 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_000036 ; continue clearing
cleardone_000036: ; reset registers
ldi r24,$00 ; initialize write register
ldi r25,$00
ldi r22,$00 ; setup write address high byte
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
movw r13:r12,r27:r26
reti ; finish with initialization and wait for next interrupt
; program begins here
; 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 end of buffer
cpc r25,r27
brlo wait1_000036 ; do nothing if not at end of buffer
clr r24 ; reset buffer to bottom
clr r25
wait1_000036: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait1_000036
in r7,spdr ; recieve in left channel msb
out spdr,r4 ; send out left channel lsb
;decrement read address (for going in reverse)
sbiw r29:r28,$01
brcc wait2_000036 ; do nothing if not at end of buffer
movw r29:r28,r27:r26 ; reset to top of buffer
sbiw r29:r28,$01 ; reset to top of buffer
wait2_000036: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait2_000036
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,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,r6 ; set data
out portc,r7
sbi portg,portg2 ; pull we high to write
out ddra,r22 ; set porta as input for data lines
out ddrc,r22 ; set portc as input for data lines
wait3_000036: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait3_000036
in r17,spdr ; recieve in right channel msb
out spdr,r4 ; send out right channel lsb
;get sample 1 from sram
out portd,r28 ; set address
sts porth,r29
nop ; wait required 2 cycle setup time
nop
in r4,pina ; get data
in r5,pinc ; get data
wait4_000036: ; check if byte has been sent
in r17,spsr
sbrs r17,spif
rjmp wait4_000036
in r17,spdr ; recieve in right channel lsb
;get sample 2 from other side of buffer
movw r17:r16,r29:r28 ; move current position to temporary register
movw r7:r6,r27:r26 ; move buffer size to temporary register
lsr r7 ; divide buffer size by 2
ror r6
cp r16,r6 ; check if in lower or upper half of buffer
cpc r17,r7
brsh buffer_flip_000036 ; subtract half buffer if in upper half
add r16,r6 ; add half buffer size if in lower half
adc r17,r7
rjmp getsample2_000036 ; continue
buffer_flip_000036: ; adjust to opposite side of memory
sub r16,r6 ; subtract half buffer size if in upper half
sbc r17,r7
getsample2_000036: ;get left channel sample 3 data from sram
out portd,r16 ; set address
sts porth,r17
nop ; wait 2 cycle setup time
nop
in r2,pina ; get data
in r3,pinc ; get data
;get distance to boundary
movw r17:r16,r29:r28 ; move read address to temporary register
sub r16,r24 ; find distance to loop boundary
sbc r17,r25
brcc half_000036 ; check if result is negative
neg r16 ; invert distance if negative
adc r17,r22 ; r22 is cleared above
neg r17
half_000036: ; check if result is greater than half the buffer size
movw r7:r6,r27:r26 ; move buffer size to temporary register
lsr r7 ; divide buffer size by 2
ror r6
cp r16,r6 ; check if result is greater than half the buffer size
cpc r17,r7
brlo scale_000036 ; skip flip if not
sub r16,r26 ; flip result around boundary
sbc r17,r27
neg r16 ; invert distance
adc r17,r22 ; r22 is cleared above
neg r17
scale_000036: ; scale distance to match buffer size - 50% accurate
movw r7:r6,r27:r26 ; move buffer size to temporary register
sbrc r7,$07 ; check if msb of buffer size is set
rjmp attenuate_000036 ; attenuate signal if 16b value
shift_000036: ; shift buffer size till it occupies full 16b
lsl r6 ; multiply buffer size by 2
rol r7
lsl r16 ; multiply distance by 2
rol r17
sbrs r7,$07 ; check if msb of buffer size is set
rjmp shift_000036 ; keep checking if not set
attenuate_000036: ; multiply sample 1 by distance
lsl r16 ; multiply distance by 2 since max value is 1/2 buffer size
rol r17
sub r6,r16 ; find complementary distance of sample 2
sbc r7,r17
movw r21:r20,r7:r6 ; move distance to signed multiply register
movw r19:r18,r5:r4 ; move value to signed multiply register
mulsu r19,r17 ; (signed)ah * bh
movw r5:r4,r1:r0
mul r18,r16 ; al * bl
movw r7:r6,r1:r0
mulsu r19,r16 ; (signed)ah * bl
sbc r5,r22 ; r22 is cleared above
add r7,r0
adc r4,r1
adc r5,r22
mul r17,r18 ; bh * al
add r7,r0
adc r4,r1
adc r5,r22
;multiply and accumulate opposing sample with result from above
movw r19:r18,r3:r2 ; move value to signed multiply register
mulsu r19,r21 ; (signed)ah * bh
add r4,r0
adc r5,r1
mul r18,r20 ; al * bl
add r6,r0
adc r7,r1
adc r4,r22
adc r5,r22
mulsu r19,r20 ; (signed)ah * bl
sbc r5,r22
add r7,r0
adc r4,r1
adc r5,r22
mul r21,r18 ; bh * al
add r7,r0
adc r4,r1
adc r5,r22
;check if buffer size is correct
cp r26,r12 ; compare current delay to desired delay
cpc r27,r13
brlo upcount_000036 ; increment if smaller than
breq adcsample_000036 ; do nothing if they are same size
sbiw r27:r26,$02 ; decrement buffer size
rjmp adcsample_000036 ; finish off
upcount_000036: ; increment buffer size register
adiw r27:r26,$02 ; increment buffer size
adcsample_000036: ; get loop setting
lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp done_000036 ; 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,r22 ; accumulate adc samples - r22 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r15 ; countdown adc sample clock
brne done_000036 ; move adc value to loop setting after 256 samples
lsr r9 ; divide accumulated value by 4
ror r11
ror r10
lsr r9
ror r11
ror r10
ldi r16,low(buffer_min_000036) ; load minimum buffer size
ldi r17,high(buffer_min_000036)
cp r10,r16 ; check if less than minimum
cpc r11,r17
brsh compare_000036 ; compare to previous value if above min
movw r11:r10,r17:r16 ; set buffer size to minimum
compare_000036: ; 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_000036 ; see if difference is large enough to indicate a change
neg r16 ; invert difference if negative
adc r17,r22 ; r22 is cleared above
neg r17
deadband_000036: ; see if pot has moved or if its just noise
cpi r16,$40 ; see if difference is greater than 1 lsb
cpc r17,r22 ; r22 is cleared above
brlo nochange_000036 ; 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_000036: ; 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_000036:
reti ; return to waiting
Last edited 2010-08-21 by guest · 6 revisions