ReverserGatedDelay

MICrODEC: Stock Functions

Gated Reverser with Delay

This function implements a reverser which is triggered by sounds above a certain threshold (gating). It takes in stereo data and outputs stereo data. The pot (MOD1) controls the delay time, and subsequently 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 are using a cross-fading method. This is the same as cross-fading between records when DJing. As one sample gets close to the buffer boundary, its volume is faded down, and a sample from the other side of the boundary is faded up. This continues as the sample moves forward in the buffer, with the volume of the first sample being reduced to zero by the time it gets to the buffer boundary. At this point, the sample on the other side is playing full volume, and takes over. This gives a relatively smooth transition across the buffer boundary, with only a slight dipping noticeable, but also keeps the playback very true to the original signal during the majority of playback, which is not near the boundary. The crossfade time is preset at the begining of the code, which also determines the minimum sample size, as there has to be enough data for both fading up and down on either side of the boundary.

The pot (MOD1) varies the delay time the function waits after the input crosses the threshold, before it starts reversing (from 6ms to 3s). This lets you trigger the device, and have it play the following notes backwards. There is also a backtrack time, which is a fixed amount of time that the function will continue playing after it has reached the beginning of the buffer (the trigger point). This allows the sampler to catch the rising edge of notes, before they cross the treshold. After this backtrack time, the reverser then crossfades to the currently forward moving signal.

In this implementation, only input audio on the left channel (TRS Connector: Tip) can trigger the gate.

reverser_gated_delay.asm


; program: reverser-18b-gated-delay.asm
; UID = 000050 - unique id to eliminate conflicts between variables
; 18b address space (3s sample time)
; stereo data, in and out
; pot (MOD1) controlled delay

; program overview
;
; data is read in from memory and written out to the codec at the same time
; new data is written to the memory from the codec.  the left channel input
; data is compared to a predetermined threshold level, and if it crosses
; this level, the reverser function is initiated after a delay.  the delay
; is set via the adc reading from the pot (MOD1).  this value is sampled
; 256 times and deadbanded to reduce jitter.  the codec passes data
; through while in forward mode, and continues to do so for the delay
; period after the input has crossed the threshold.  after this time, the
; input data is no longer written to memory, in order to increase the
; available buffer size.  the reverse data is played out back to the
; beginning, plus a predetermined backtrack amount to catch the rising
; edge of notes.  at this point, the forward data saving is resumed, and
; the reverse data is faded while the current data is faded in.  this
; crossfade size is determined via a constant at the beginning of the code.
; there is also a holdoff time after the reverser function has finished,
; before it can be retriggered, in order to reduce constant triggering and
; glitches from not having enough forward data stored.

; constant definitions
;
.equ backtrack_000050 = $0800 ; past memory used as starting point
; (must be even)
.equ holdoff_000050 = $0f00 ; holdoff before retriggering
; must be larger than backtrack (must be even)
.equ stepsize_000050 = $0080 ; 65536/(stepsize * 44.1) = crossfade time (ms)
.equ threshold_000050 = $3900 ; threshold for turn on of reverser ($7fff max)
.equ minbuff_000050 = $02 ; minimum buffer time in multiples of 5.8ms
.equ maxbuff_000050 = ($03ff00 - (2 * ($010000 / stepsize_000050)) - holdoff_000050)
; maximum buffer time to keep from overlapping
.equ mem_000050 = $0200 ; memory location for storage in internal sram

; register usage - may be redefined in other sections
;
; r0  multiply result lsb
; r1  multiply result msb
; 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 accumulation lsb
; r11 adc accumulation msb
; r12 buffer bottom / crossfade lsb
; r13 buffer bottom / crossfade msb
; r14 adc accumulation fractional byte
; r15 switch/adc sample counter
; r16 temporary swap register
; r17 temporary swap register
; r18 null register
; r19 reverse mode indicator
; r20 temporary swap regiser
; r21 buffer bottom third byte
; r22 write address third byte
; r23 read address third byte
; r24 write address lsb
; r25 write address msb
; r26 desired buffer size lsb
; r27 desired buffer size msb
; r28 read address lsb
; r29 read address msb
; r30 jump location for interrupt lsb
; r31 jump location for interrupt msb
; t   foward/reverse indicator

; program starts here first time
; initialze z pointer for correct jump
; this assumes a less than 256 word jump
ldi r30,$23 ; 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_000050: ; 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_000050 ; 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_000050 ; continue clearing

cleardone_000050: ; reset registers

ldi r24,$01 ; initialize write register
ldi r25,$00
ldi r22,$00 ; setup write address high byte
ldi r28,$01 ; set read address to minimum delay
ldi r29,$ff
ldi r23,$07 ; setup read address high byte
clr r19 ; initialize reverse mode register
clr r2 ; initialize data output registers
clr r3
clr r4
clr r5
clt ; clear t register to start with forward play
reti ; finish with initialization and wait for next interrupt

; initiate data transfer to codec
; this is the point the z-pointer is incremented to
; program starts here all but first time
sbi portb,portb0 ; toggle slave select pin
out spdr,r3 ; send out left channel msb
cbi portb,portb0

;increment write addresses
brts wait1_000050 ; dont write data to memory if reversing
adiw r25:r24,$01 ; increment write address
adc r22,r18 ; increment write third byte
andi r22,$03 ; mask off unsed bits

wait1_000050: ; check if byte has been sent

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

wait2_000050: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait2_000050
in r6,spdr ; recieve in left channel lsb
out spdr,r5 ; send out right channel msb

;write left channel data to sram
brts wait3_000050 ; dont write data to memory if reversing
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_000050: ; check if byte has been sent

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

;increment write address
brts wait4_000050 ; dont write data to memory if reversing
adiw r25:r24,$01 ; increment write address
adc r22,r18 ; increment write third byte
andi r22,$03 ; mask off unsed bits

wait4_000050: ; check if byte has been sent

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

;write right channel data to sram
brts dataload_000050 ; dont write data to memory if reversing
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

;check if input signal is above threshold
movw r3:r2,r7:r6 ; backup input values as they get modified
movw r5:r4,r9:r8 ; move input data to output
sbrc r19,$04 ; check if holding off after last trigger
rjmp holdtime_000050 ; countdown holdoff timer if waiting
sbrc r19,$07 ; check if already in reverse mode
rjmp reverse_000050 ; go to reverse function - else check threshold
ldi r16,low(threshold_000050) ; get threshold value
ldi r17,high(threshold_000050)
tst r7 ; check if negative
brpl compare_000050 ;  compare if positive
com r6 ; invert if negative
com r7 ; ones complement used for simplicity

compare_000050: ; compare input value to threshold

cp r6,r16 ; compare to left channel input
cpc r7,r17
brsh start_000050 ; start reversing if above threshold
rjmp adcsample_000050 ; else finish off

holdtime_000050: ; decrement holdoff timer

ldi r17,$02 ; subtract 2 from holdoff timer
sub r12,r17 ; 2 is used to have holdoff and backup match scaling
sbc r13,r18 ; r18 is cleared above
brne finishoff_000050 ; finish off if it not done holding off
clr r19 ; set mode to forward and clear hold off bit

finishoff_000050: ; finish off if not done holding off

rjmp adcsample_000050 ; finish off - too long for a branch instruction

start_000050: ; intialize reversing

ldi r19,$80 ; indicate that reverser is active, waiting for delay
movw r17:r16,r25:r24 ; move write address to temporary register
mov r21,r22
subi r16,low(backtrack_000050) ; remove initial buffer size
sbci r17,high(backtrack_000050)
sbc r21,r18 ; r18 cleared above
andi r21,$03 ; mask off low bits
movw r13:r12,r17:r16 ; move buffer bottom to its register
rjmp adcsample_000050 ; finish off

reverse_000050: ; check for reverse function

sbrc r19,$06 ; check if delay time has been met
rjmp dataload_000050 ; get data from memory if delay time has been met
; else check if delay time is up yet
movw r17:r16,r13:r12 ; move bottom of buffer to temporary register
mov r20,r21
add r17,r26 ; add in buffer size
adc r20,r27
andi r20,$03 ; mask off unused bits
cp r24,r16 ; check if write address is at top of buffer
cpc r25,r17
cpc r22,r20
breq bufferset_000050 ; set the read address to the top of the buffer
rjmp adcsample_000050 ; else finish off

bufferset_000050: ; set the read address to the top of the buffer

movw r29:r28,r25:r24 ; else set read address equal to write address
mov r23,r22
ori r19,$40 ; set delay time bit in function register
set ; set the t register to indicate reversing
rjmp dataload1_000050 ; get data from sram

dataload_000050: ; get data from sram

sbrc r19,$05 ; check if crossfading
rjmp dataload1_000050 ; skip buffer check if crossfading
andi r23,$03 ; mask off unused bits
cp r28,r12 ; check if at bottom of buffer
cpc r29,r13
cpc r23,r21
brne dataload1_000050 ; continue if not at bottom
ori r19,$20 ; set crossfade bit
ldi r16,$ff ; set crossfade distance to max value
mov r12,r16 ; buffer bottom value no longer needed
mov r13,r16
clt ; clear the t register to start recording forward again

dataload1_000050: ; continue getting data from sram

sbiw r29:r28,$03 ; decrement read address
sbc r23,r18 ;  r18 is cleared above
andi r23,$03 ; mask off unused bits
ori r23,$04 ; set we/ bit in high byte register

;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 ; increment write third byte
andi r23,$03 ; mask off unsed bits
ori r23,$04 ; set we/ bit in high byte register
in r2,pina ; get data
in r3,pinc ; get data

;get right 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
nop ; wait 2 cycle setup time
nop
in r4,pina ; get data
in r5,pinc ; get data

;check if crossfading
sbrs r19,$05 ; check if crossfading
rjmp adcsample_000050 ; finish off if not crossfading
ldi r16,low(stepsize_000050) ; get crossfade stepsize
ldi r17,high(stepsize_000050)
sub r12,r16 ; decrement crossfade distance counter
sbc r13,r17
breq crossfade_done_000050 ; stop crossfading if done
brcs crossfade_done_000050 ; stop crossfading if done
rjmp crossfade_000050

crossfade_done_000050: ; stop crossfading

movw r3:r2,r7:r6 ; move forward data to output
movw r5:r4,r9:r8
ldi r16,low(holdoff_000050) ; setup holdoff counter
ldi r17,high(holdoff_000050)
movw r13:r12,r17:r16 ; r13:r12 no longer needed for crossfading
ori r19,$10 ; set reverse mode to holdoff
rjmp adcsample_000050 ; finish off

crossfade_000050: ; crossfade between forward and reverse signals

sts mem_000050,r6 ; backup data to free a register
sts (mem_000050 + 1),r7

;multiply left reverse sample by crossfade distance
movw r17:r16,r3:r2 ; move left reverse data to multiply register
movw r21:r20,r13:r12 ; move crossfade distance to multiply register
mulsu r17,r21 ; (signed)Ah * (unsigned)Bh - multiply high bytes
movw r3:r2,r1:r0 ; store high bytes result for later
mul r16,r20 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
movw r7:r6,r1:r0 ; store low byets for later
mulsu r17,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc r3,r18 ; r18 is cleared above - subtract sign bit
add r7,r0 ; accumulate result
adc r2,r1
adc r3,r18 ; r18 is cleared above
mul r21,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
add r7,r0 ; accumulate result
adc r2,r1
adc r3,r18 ; r18 is cleared above

;multiply and accumulate left forward channel by crossfade distance
lds r16,mem_000050 ; fetch left forward channel from memory
lds r17,(mem_000050 + 1)
com r20 ; invert crossfade distance for forward channel
com r21
mulsu r17,r21 ; (signed)Ah * (unsigned)Bh - multiply high bytes
add r2,r0 ; accumulate result
adc r3,r1
mul r16,r20 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
add r6,r0 ; accumulate result
adc r7,r1
adc r2,r18 ; r18 cleared above
adc r3,r18
mulsu r17,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc r3,r18 ; r18 is cleared above - subtract sign bit
add r7,r0 ; accumulate result
adc r2,r1
adc r3,r18 ; r18 is cleared above
mul r21,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
add r7,r0 ; accumulate result
adc r2,r1
adc r3,r18 ; r18 is cleared above

;multiply right reverse sample by crossfade distance
movw r17:r16,r5:r4 ; move right reverse data to multiply register
movw r21:r20,r13:r12 ; move crossfade distance to 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 r7:r6,r1:r0 ; store low byets for later
mulsu r17,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc r5,r18 ; r18 is cleared above - subtract sign bit
add r7,r0 ; accumulate result
adc r4,r1
adc r5,r18 ; r18 is cleared above
mul r21,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
add r7,r0 ; accumulate result
adc r4,r1
adc r5,r18 ; r18 is cleared above

;multiply and accumulate right forward channel by crossfade distance
movw r17:r16,r9:r8 ; move left forward data to multiply register
com r20 ; invert crossfade distance for forward channel
com r21
mulsu r17,r21 ; (signed)Ah * (unsigned)Bh - multiply high bytes
add r4,r0 ; accumulate result
adc r5,r1
mul r16,r20 ; (unsigned)Al * (unsigned)Bl ; multiply low bytes
add r6,r0 ; accumulate result
adc r7,r1
adc r4,r18 ; r18 cleared above
adc r5,r18
mulsu r17,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc r5,r18 ; r18 is cleared above - subtract sign bit
add r7,r0 ; accumulate result
adc r4,r1
adc r5,r18 ; r18 is cleared above
mul r21,r16 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
add r7,r0 ; accumulate result
adc r4,r1
adc r5,r18 ; r18 is cleared above

adcsample_000050: ; get buffer size from adc

lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp done_000050 ; skip adc sampling
lds r16,adcl ; get low byte adc value
lds r17,adch ; get high byte adc value
add r14,r16 ; accumulate adc samples
adc r10,r17
adc r11,r18 ; r18 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r15 ; countdown adc sample clock
brne done_000050 ; dont get buffer size till its been long enough
ldi r17,minbuff_000050 ; fetch minimum buffer size
cp r10,r17 ; check if adc value is less than minimum buffer size
cpc r11,r18 ; r18 is cleared above
brsh high_000050 ; check if above max buffer size if not below min
mov r10,r17 ; set to minimum buffer size
rjmp deadband_000050 ; check if adc value changed enough

high_000050: ; check max value of adc

ldi r16,low(maxbuff_000050) ; fetch max buffer size
ldi r17,high(maxbuff_000050)
cp r10,r16 ; check if adc value larger than max buffer size
cpc r11,r17
brlo deadband_000050 ; check for value chnage if lower than max size
mov r10,r16 ; set buffer size to max value
mov r11,r17

deadband_000050: ; check if adc value changed enough to warrant update

movw r17:r16,r11:r10 ; move adc sample to temporary register
sub r16,r26 ; find difference between adc sample and desired delay time
sbc r17,r27
brsh check_000050 ; check for deadband if positive
neg r16 ; invert if negative
adc r17,r18 ; r18 is cleared above
neg r17

check_000050: ; check if difference is greater than deadband

cpi r16,$02 ; check if difference is less than 2 lsb
cpc r17,r18 ; r18 cleared above
brlo empty_000050 ; do nothing if less than $02
movw r27:r26,r11:r10 ; move adc sample to delay time if large enough change

empty_000050: ; empty accumulation registers and finish off

clr r10 ; empty accumulation registers
clr r11
clr r14

switchsample_000050: ; check if at same function

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 new value
mov r31,r16

done_000050:

reti ; return to waiting

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