; 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

