; 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)

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
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 interpolate1_000058 ; continue if large enough
ldi r24,low(minbuff_000058) ; else set buffer size to minbuff
mov r25,r17

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
