DelayPingPong

MICrODEC: Stock Functions

3s Stereo Ping-Pong Delay

This program performs a ping-pong delay routine. It stores the incoming data from the codec, and plays back a delayed sample, but reverses the orientation of the channels on playback. In this way, the audio coming in on the left channel comes out on the right after a delay. With a bit of feedback, the signals chase each other from left to right. The input data is stereo, and is taken from the left and right channels. The output is also stereo, and is presented on both the left and right channels.

The pot (MOD1) controls the delay time. It goes from 3ms to 3s. MOD2 does nothing for this program. Most analog delay pedals change the rate at which data is being clocked in and out, to change the delay time. This gives a smooth change in audio when the delay is varied, although it also changes the frequency resolution of the signal. To change the delay time in the MICrODEC, the easiest way is to just jump to the new delay time, although this gives an audible pop as the data abruptly switches. In this program, we increment the delay time (or decrement it) at a rate of one sample per sample, until it matches what you want it to be. This gives the effect of frequency doubling (or reversing) during delay transitions, which can be interesting sonically.

delay_ping_pong.asm


; program: ping_pong-18b-pot.asm
; UID = 000030 - unique id to eliminate conflicts between variables
; 18b address space (3s delay time)
; stereo data - left and right swap to create ping-pong effect
; pot (MOD1) controlled delay time (3ms - 3s)

; 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.  ADC0 (MOD1) is read
; and averaged over 256 samples to reduce jitter.  this value is subtracted
; from the write address to create the desired read address.  if the actual
; read address doesnt match the desired read address, it is either
; incremented or decremented by one sample each sample period until it
; matches.  this reduces noise during delay time transitions.  the left
; channel input is recorded to the right channel output buffer (and vice
; versa), so the sound bounces back and forth between the two channels.

; register usage - may be redefined in other sections
;
; r0  deisred delay time fractional byte
; r1  
; r2  left lsb out
; r3  left msb out
; r4  right lsb out
; r5  right msb out
; r6  left lsb in
; r7  left msb in
; r8  right lsb in
; r9  right msb in
; r10 adc accumulator lsb
; r11 adc accumulator msb
; r12 actual delay lsb
; r13 actual delay msb
; r14 adc sample counter
; r15 switch sample counter
; r16 temporary swap register
; r17 temporary swap register
; r18 null register
; r19 adc accumulator fractional byte
; r20 temporary swap register
; r21 actual delay fractional byte
; r22 write address third byte
; r23 read address third byte
; r24 write address lsb
; r25 write address msb
; r26 desired delay lsb
; r27 desired delay 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
ldi r30,$25 ; 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_000030: ; 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,$04 ; check if full memory space has been cleared
breq cleardone_000030 ; 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_000030 ; continue clearing

cleardone_000030: ; reset registers

clr r24 ; clear write register
clr r25
ldi r22,$00 ; setup write address high byte
clr r28 ; set read address to minimum delay
ldi r29,$ff
ldi r23,$07 ; setup read address high byte
clr r21 ; set actual delay time to minimum delay
ldi r16,$01
mov r12,r16
clr r13
clr r2 ; initialize data output registers
clr r3
clr r4
clr r5
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,r3 ; send out left channel msb
cbi portb,portb0

;increment sram addresses
adiw r25:r24,$01 ; increment write address
adc r22,r18 ; increment write third byte
andi r22,$03 ; mask off unsed bits
adiw r29:r28,$01 ; increment read address
adc r23,r18 ; increment read third byte
andi r23,$03 ; mask off unsed bits
ori r23,$04 ; set we bit for reading

wait1_000030: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait1_000030
in r7,spdr ; recieve in left channel msb
out spdr,r2 ; send out left channel lsb

;get left channel data from sram
out portg,r23 ; pull ce low, we high, and set high bits of register
out portd,r28 ; set address
sts porth,r29
adiw r29:r28,$01 ; increment read address
adc r23,r18 ; placed here to use setup time efficiently
andi r23,$03 ; mask off unsed bits
ori r23,$04 ; set we bit for reading
in r2,pina ; get data
in r3,pinc ; get data

wait2_000030: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait2_000030
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,r18 ; set porta as input for data lines
out ddrc,r18 ; set portc as input for data lines

wait3_000030: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait3_000030
in r9,spdr ; recieve in right channel msb
out spdr,r4 ; send out right channel lsb

;get right channel data from sram
out portg,r23 ; pull ce low,we high,oe low, and set high bits of address
out portd,r28 ; set address
sts porth,r29
adiw r25:r24,$01 ; increment write address
adc r22,r18 ; placed here for efficient use of setup time
andi r22,$03 ; mask off unsed bits
in r4,pina ; get data
in r5,pinc ; get data

wait4_000030: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait4_000030
in r8,spdr ; recieve in left channel lsb

;write right 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,r8 ; set data
out portc,r9
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

; get delay settings
lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp shift_000030 ; skip adc sampling
lds r16,adcl ; get low byte adc value
lds r17,adch ; get high byte adc value
add r19,r16 ; accumulate adc samples
adc r10,r17
adc r11,r18 ; r18 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r14 ; countdown adc sample clock
brne shift_000030 ; get delay time if its been long enough
ldi r17,$01 ; check if adc value is less than $000100
cp r19,r18 ; r18 is cleared above
cpc r10,r17
cpc r11,r18
brsh deadband_000030 ; check if adc value changed enough to update delay
inc r10 ; set minimum delay to $000100 = 3ms
clr r19

deadband_000030: ; check for change in adc value

movw r17:r16,r11:r10 ; move adc sample to temporary register
mov r20,r19
sub r20,r0 ; find difference between adc sample and desired delay time
sbc r16,r26
sbc r17,r27
brsh check_000030 ; check for deadband if positive
com r20 ; invert if negative
com r16 ; using ones complement as it is faster, and only has 1 bit error
com r17

check_000030: ; check if difference is greater than deadband

cpi r16,$01 ; check if difference is less than 1 lsb
cpc r17,r18 ; r18 cleared above
brlo empty_000030 ; do nothing if less than 1 lsb
movw r27:r26,r11:r10 ; move adc sample to delay time if large enough change
andi r19,$fc ; make sure delay time is a multiple of 4
mov r0,r19

empty_000030: ; empty accumulation registers and finish off

clr r10 ; empty accumulation registers
clr r11
clr r19

shift_000030: ; check if delay time is correct

cp r0,r21 ; compare desired delay to actual delay
cpc r26,r12
cpc r27,r13
breq switchsample_000030 ; do nothing if the same
brlo indexdown_000030
ldi r17,$04 ; increment delay register
add r21,r17
adc r12,r18 ; r18 is cleared above
adc r13,r18
ldi r17,$03
and r13,r17 ; mask off unused bits
rjmp switchsample_000030

indexdown_000030:

subi r21,$02 ; decrement delay register
sbc r12,r18 ; r18 is cleared above
sbc r13,r18
ldi r17,$03
and r13,r17 ; mask off unused bits

switchsample_000030: ; check state of rotary switch

dec r15
brne done_000030
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 ; move to jump register
cp r16,r31 ; check if location has changed
breq done_000030 ; finish off if no change
clr r30 ; reset jump register to intial state
mov r31,r16

done_000030:

movw r29:r28,r25:r24 ; move write address to read destination register
mov r23,r22 ; move write third byte to read third byte
sub r28,r21 ; subtract delay time
sbc r29,r12
sbc r23,r13
andi r23,$03 ; mask off unsed bits
ori r23,$04 ; set we bit for reading
movw r17:r16,r3:r2 ; swap left and right channels
movw r3:r2,r5:r4
movw r5:r4,r17:r16
reti

Last edited 2010-08-21 by guest · 3 revisions