; program: pitch_shifter-16b-fading.asm
; UID = 000039 - this is a unique id so variables dont conflict
; 16b address space (1.5s sample time)
; mono data in on left channel, mono out on both left and right
; rotary encoder (MOD2) controlled playback speed
; 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 at varying playback speeds. the speed at which it plays through
; the memory is controlled by the rotary encoder (MOD2).  turning the
; encoder to the right speeds playback up, and turning it the left slows
; playback down.  this playback speed is limited to chromatic steps by
; using a lookup table in program memory to determine playback speed.  the
; audio is kept clean over fractional sample periods by interpolating
; between the two closest samples.  the output is a mix of the current
; sample, and a sample from the opposite side of the buffer, with the
; relative mix being determined by the distance to the buffer boundary.
; in this way, the audio is faded down as it crosses the buffer boundary.
; the pot (MOD1) controls the buffer size.  the adc samples the pot 256
; times and deadbands the signal to remove glitches.

; constant definitions
;
.equ buffer_min_000039 = $0200 ; minimum buffer size
.equ delay_mem_000039 = $0200 ; memory position for desired delay time
;
;.equ step-12_000039 = $0080 ; these are the playback speeds used
;.equ step-11_000039 = $0088 ; they are stored in program memory
;.equ step-10_000039 = $0090 ; and not used here
;.equ step-9_000039 = $0098
;.equ step-8_000039 = $00A1
;.equ step-7_000039 = $00AB
;.equ step-6_000039 = $00B5
;.equ step-5_000039 = $00C0
;.equ step-4_000039 = $00CB
;.equ step-3_000039 = $00D7
;.equ step-2_000039 = $00E4
;.equ step-1_000039 = $00F2
;.equ step00_000039 = $0100
;.equ step01_000039 = $010F
;.equ step02_000039 = $011F
;.equ step03_000039 = $0130
;.equ step04_000039 = $0143
;.equ step05_000039 = $0156
;.equ step06_000039 = $016A
;.equ step07_000039 = $0180
;.equ step08_000039 = $0196
;.equ step09_000039 = $01AF
;.equ step10_000039 = $01C8
;.equ step11_000039 = $01E3
;.equ step12_000039 = $0200

; register usage - may be redefined in other sections
;
; r0  multiply result lsb
; r1  multiply result msb
; r2  sample 3/4 lsb
; r3  sample 3/4 msb
; r4  left/right lsb out
; r5  left/right msb out
; r6  left lsb in/temporary swap register
; r7  left msb in/temporary swap register
; r8  rotary encoder position counter
; r9  adc msb accumulator
; r10 adc fractional byte accumulator
; r11 adc lsb accumulator
; r12 playback speed increment lsb value ($0100 is normal speed)
; r13 playback speed increment msb value
; r14 rotary encoder counter
; r15 switch\adc counter
; r16 temporary swap register
; r17 temporary swap register
; r18 signed multiply register
; r19 signed multiply register
; r20 unsigned multiply register
; r21 unsigned multiply register
; r22 write address third byte/null register
; r23 read address fractional byte
; 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   rotary encoder edge indicator

;program starts here first time
; intialize registers
ldi r30,$29 ; 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_000039: ; 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_000039 ; 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_000039 ; continue clearing

cleardone_000039: ; reset registers

ldi r24,$00 ; initialize write register
ldi r25,$00
clr r22 ; 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
sts delay_mem_000039,r26 ; store desired buffer size
sts (delay_mem_000039 + 1),r27 ; i ran out of registers
clr r12 ; initialize playback speed
ldi r16,$01
mov r13,r16
ldi r16,$0c ; initialize playback speed pointer
mov r8,r16
reti ; return and wait for next interrupt

;program begins 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 end of buffer
cpc r25,r27
brlo wait1_000039 ; do nothing if not at end of buffer
clr r24 ; reset buffer to bottom
clr r25

wait1_000039: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait1_000039
in r7,spdr ; recieve in left channel msb
out spdr,r4 ; send out left channel lsb

;increment read address
add r23,r12 ; increment read register
adc r28,r13
adc r29,r22 ; r22 is cleared above
cp r28,r26 ; check if at end of buffer
cpc r29,r27
brlo wait2_000039 ; do nothing if not at end of buffer
clr r28 ; reset buffer to bottom
clr r29

wait2_000039: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait2_000039
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_000039: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait3_000039
in r17,spdr ; recieve in right channel msb
out spdr,r4 ; send out right channel lsb

;get left channel sample 1 data from sram
movw r17:r16,r29:r28 ; move read address to temporary register
out portd,r16 ; set address
sts porth,r17
ldi r21,$01 ; increment read address
add r16,r21 ; placed here to use 2 cycle wait
adc r17,r22 ; r22 is cleared above
in r6,pina ; get data
in r18,pinc ; get data
cp r16,r26 ; check if at end of buffer
cpc r17,r27
brlo wait4_000039 ; do nothing if not at end of buffer
clr r16 ; reset buffer to bottom
clr r17

wait4_000039: ; check if byte has been sent

in r19,spsr
sbrs r19,spif
rjmp wait4_000039
in r19,spdr ; recieve in right channel lsb

;get left channel sample 2 data from sram
out portd,r16 ; set address
sts porth,r17
nop ; wait 2 cycle setup time
nop
in r7,pina ; get data
in r19,pinc ; get data

;multiply sample 1 by distance
mov r20,r23 ; get distance from sample 1
com r20
mulsu r18,r20 ; (signed)Ah * (unsigned)B
movw r5:r4,r1:r0
mul	r6,r20	; (unsigned)Al * (unsigned)B
add	r4,r1
adc	r5,r22 ; r22 is cleared above
mov r17,r0

;multiply and accumulate sample 2 by distance
mulsu r19,r23 ; (signed)Ah * (unsigned)B
add r4,r0 ; accumulate result
adc r5,r1
mul	r7,r23	; (unsigned)Al * (unsigned)B
add r17,r0 ; accumulate result
adc	r4,r1
adc	r5,r22 ; r22 is cleared above

;get sample 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_000039 ; subtract half buffer if in upper half
add r16,r6 ; add half buffer size if in lower half
adc r17,r7
rjmp getsample3_000039 ; continue

buffer_flip_000039: ; adjust to opposite side of memory

sub r16,r6 ; subtract half buffer size if in upper half
sbc r17,r7

getsample3_000039: ;get left channel sample 3 data from sram

out portd,r16 ; set address
sts porth,r17
add r16,r21 ; increment read address - r21 set to $01 above
adc r17,r22 ; r22 is cleared above
in r6,pina ; get data
in r18,pinc ; get data
cp r16,r26 ; check if at end of buffer
cpc r17,r27
brlo getsample4_000039 ; do nothing if not at end of buffer
clr r16 ; reset buffer to bottom
clr r17

getsample4_000039: ;get left channel sample 4 data from sram

out portd,r16 ; set address
sts porth,r17
nop ; wait 2 cycle setup time
nop
in r7,pina ; get data
in r19,pinc ; get data

;multiply sample 3 by distance
mulsu r18,r20 ; (signed)ah * b
movw r3:r2,r1:r0
mul	r6,r20 ; al * b
add	r2,r1
adc	r3,r22 ; r22 is cleared above
mov r17,r0

;multiply sample 4 by distance
mulsu r19,r23 ; (signed)ah * b
add r2,r0 ; accumulate result
adc r3,r1
mul	r7,r23	; al * b
add r17,r0 ; accumulate result
adc	r2,r1
adc	r3,r22 ; r22 is cleared above

;get distance to boundary
movw r17:r16,r29:r28 ; move read address to temporary register
mov r18,r23
sub r16,r24 ; find distance to loop boundary
sbc r17,r25
brcc half_000039 ; check if result is negative
com r16 ; invert distance if negative
com r17
com r18
add r18,r21 ; r21 set to $01 above
adc r16,r22 ; r22 cleared above
adc r17,r22

half_000039: ; 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_000039 ; skip flip if not
sub r16,r26 ; flip result around boundary
sbc r17,r27
com r16
com r17
com r18
add r18,r21 ; r21 set to $01 above
adc r16,r22
adc r17,r22

scale_000039: ; 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_000039 ; attenuate signal if 16b value

shift_000039: ; shift buffer size till it occupies full 16b

lsl r6 ; multiply buffer size by 2
rol r7
lsl r18 ; multiply distance by 2
rol r16
rol r17
sbrs r7,$07 ; check if msb of buffer size is set
rjmp shift_000039 ; keep checking if not set

attenuate_000039: ; multiply sample 1/2 by distance

lsl r18 ; multiply distance by 2 since max value is 1/2 buffer size
rol r16
rol r17
sub r6,r16 ; find complementary distance of sample 3/4
sbc r7,r17 ; only 1 bit error for not subtracting r18 as well
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 sample 3/4 with result from above
movw r19:r18,r3:r2 ; move value to signed multiply register
mulsu r19,r21 ; (signed)ah * bh
add	r4,r0 ; accumulate result
adc	r5,r1
mul	r18,r20 ; al * bl
add	r6,r0 ; accumulate result
adc	r7,r1
adc	r4,r22 ; r22 is cleared above
adc	r5,r22
mulsu r19,r20 ; (signed)ah * bl
sbc	r5,r22 ; accumulate result
add	r7,r0
adc	r4,r1
adc	r5,r22
mul r21,r18 ; bh * al
add	r7,r0
adc	r4,r1
adc	r5,r22

rotary_000039: ; check rotary encoder and adjust playback rate
; rotary encoder is externally debounced, so that is not done here.
; pin1 is sampled on a transition from high to low on pin0.  if pin1 is
; high, a left turn occured, if pin1 is low, a right turn occured.
dec r14 ; reduce the sampling rate to help with debounce
brne check_000039 ; continue if not ready yet
ldi r17,$40 ; adjust sample frequency to catch all rising edges (1.5ms)
mov r14,r17
lds r17,pinj ; get switch data
sbrs r17,$00 ; check if pin0 is low
rjmp edge_000039 ; check if pin0 was low on previous sample
clt ;  clear state register if back high
rjmp check_000039 ; finish off

edge_000039: ; check for falling edge

brts check_000039 ; do nothing if the edge was already detected
set ; set state register to indicate a falling edge occured
sbrs r17,$01 ; check if pin1 is high
rjmp increment_000039 ; increment playback if right rotation
ldi r16,$01 ; check if pitch at min
cp r8,r16
brlo check_000039 ; do nothing it at bottom
dec r8 ; decrement rotary encoder position counter
movw r17:r16,z ; store z register
ldi zh,$4c ; setup z pointer to fetch tone from lookup table
mov zl,r8
lsl zl
lpm r12,z+ ; move tone to pitch register
lpm r13,z
movw z,r17:r16 ; restore z register
rjmp check_000039 ; finish off

increment_000039: ; increment playback speed

ldi r16,$18 ; check if pitch at max
cp r8,r16
brsh reset1_000039 ; do nothing if at max already
inc r8 ; increment rotary encoder position counter
movw r17:r16,z ; store z register
ldi zh,$4c ; setup z pointer to fetch tone from lookup table
mov zl,r8
lsl zl
lpm r12,z+ ; move tone to pitch register
lpm r13,z
movw z,r17:r16 ; restore z register
rjmp check_000039 ; finish off

reset1_000039: ; reset tone register in case it goes too high

mov r8,r16 ; set tone register to max

check_000039: ; check if buffer size is correct

lds r16,delay_mem_000039 ; fetch desired buffer size
lds r17,(delay_mem_000039 + 1) ; i ran out of registers
cp r26,r16 ; compare current delay to desired delay
cpc r27,r17
brlo upcount_000039 ; increment if smaller than
breq adcsample_000039 ; do nothing if they are same size
sbiw r27:r26,$02 ; decrement buffer size
rjmp adcsample_000039 ; finish off

upcount_000039: ; increment buffer size register

adiw r27:r26,$02 ; increment buffer size

adcsample_000039: ; get loop setting

lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp done_000039 ; skip adc sampling
lds r16,adcl ; get low byte adc value
lds r17,adch ; get high byte adc value
add r10,r16 ; accumulate adc samples
adc r11,r17
adc r9,r22 ; r22 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r15 ; countdown adc sample clock
brne done_000039 ; move adc value to loop setting after 256 samples
lsr r9 ; divide accumulated value by 4 to make a 16b value
ror r11
ror r10
lsr r9
ror r11
ror r10
ldi r16,low(buffer_min_000039) ; fetch min buffer size
ldi r17,high(buffer_min_000039)
cp r10,r16 ; compare adc value to min buffer size
cpc r11,r17
brsh compare_000039 ; skip if above minimum buffer size
movw r11:r10,r17:r16 ; else set to minimum buffer size

compare_000039: ; compare to previous value

lds r16,delay_mem_000039 ; fetch desired delay time
lds r17,(delay_mem_000039 + 1) ; i ran out of registers
sub r16,r10 ; find difference between adc value and desired buffer size
sbc r17,r11
brcc deadband_000039 ; check for magnitude of change if positive
neg r16 ; else invert difference if negative
adc r17,r22 ; r22 is cleared above
neg r17

deadband_000039: ; 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_000039 ; dont update loop time if difference is not large enough
ldi r16,$fe ; make sure buffer size is even
and r10,r16
sts delay_mem_000039,r10 ; store new desired buffer size
sts (delay_mem_000039 + 1),r11 ; i ran out of registers

nochange_000039: ; clear accumulation registers

clr r10 ; empty accumulation registers
clr r11
clr r9

switchsample_000039: ; 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_000039:

reti ; return to waiting

