FullwaveDelayLowpass

MICrODEC: Stock Functions

Fullwave Distortion with Delay and Lowpass

This function implements a fullwave rectifier, with an optional delay and variable cutoff lowpass filter. The input is mono on the left channel, with the direct fullwave signal presented on the right output, and a delayed copy on the left output. The delay is set via the pot (MOD1), and the cutoff frequency is set with the rotary encoder (MOD2).

A fullwave rectifier is a simple function which takes all negative input values, and makes them positive. The analog version is often used on the input to powersupplies, to turn the incoming AC voltages into DC. This has the effect of doubling all of the frequencies, as a signal which normally would go from positive to negative, now goes from positive down to zero, and then back up to positive, adding an extra oscillation where there wasn't one before. It also adds a lot of high frequency noise, as the transition where it flips the negatives to positives is very sharp.

The lowpass filter inside this function allows you to adjust how much of this high frequency noise you want to let through. There are 7 different cut off frequencies, with rotations to the right of MOD2 increasing the cutoff frequency. The lowpass is implemented as a moving average filter. This takes an average of the past few samples, and presents it at the output. In this manner, if a few samples are really different from their neighbors, they get smoothed out in the averaging process. By increasing the number of samples that are averaged, you increase the smoothing effect. In order for this sort of filter to work, the average of the past samples must be very accurate, so each time the cutoff frequency is changed, the sample buffer is cleared, causing a click at the output.

The pot (MOD1) adjust the delay time, with a range of 0s to 1.5s. The signal is delayed after its been fullwave rectified and lowpassed. Both the direct and delayed signals are presented on the right and left channels, respectively, so a stereo out really helps to get the most out of this effect. If the delay is turned to maximum, the location in memory it will fetch from will be the lowpass sample buffer, so the delay will be very short, and a slightly harsher sound is produced.

fullwave_delay_lowpass.asm


; program: fullwave-delay-lowpass.asm
; UID = 000055 - unique id to eliminate conflicts between variables
; mono data (left in only, delayed output on left, direct out on right)
; pot (MOD1) controls the delay time (0s - 1.5s)
; rotary encoder (MOD2) controls the cutoff frequency

; program overview
;
; data is read in from the codec, and negative values are inverted to
; positive values.  all values are then shifted down to mid-rail and
; multiplied by 2 to normalize the output.  these values are then written
; to memory, and read back out and accumulated.  this creates a simple
; moving average low pass filter.  the delay time (and corresponding cutoff
; frequency) is set with the rotary encoder (MOD2).  there are 7 different
; cutoff frequencies, with rotations to the right increasing the cutoff
; frequnecy.  changing the cutoff frequency restarts the program to blank
; the accumulation buffer.  the delay time is set with the pot (MOD1).  the
; adc is oversampled 256 times and deadbanded to reduce jitter.  turning the
; pot all the way to the right overlaps the delay and lowpass buffers,
; causing some distortions.

; register usage - may be redefined in other sections
;
; r0  adc accumulation fractional byte
; r1  adc accumulation lsb
; r2  adc accumulation msb
; r3  
; r4  left lsb out
; r5  left msb out
; r6  left lsb in
; r7  left msb in
; r8  right output lsb
; r9  right output msb
; r10 accumulation lsb
; r11 accumulation mlb
; r12 accumulation mhb
; r13 accumulation msb
; r14 rotary encoder counter
; r15 adc/switch sample counter
; r16 temporary swap register
; r17 temporary swap register
; r18 null register 
; r19 cutoff frequency
; r20 temporary register
; r21 
; r22 actual delay lsb
; r23 actual delay msb
; 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   rotary encoder state bit

;program starts here first time and after buffer changes
ldi r30,$1c ; set jump location to program start
ldi r16,$08 ; set lowpass buffer size to mid range
ldi r19,$03 ; initialize cutoff frequency to midrange

restart_000055: ; restart location for clearing memory

clr r24 ; clear write register
clr r25
clr r18 ; setup r18 as null register for carry addition and ddr setting
ldi r17,$ff ; setup r17 for ddr setting

clear_000055: ; clear lowpass buffer
; required to ensure an accurate accumulation
out portd,r24 ; set address
sts porth,r25
out portg,r18 ; 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
inc r24 ; increment write register - only clears first 256 bytes
brne clear_000055 ; continue until end of buffer reached

cleardone_000055: ; reset registers

mov r24,r16 ; set buffer size for lowpass
clr r28 ; set read address
clr r29
clr r10 ; initialize accumulation registers
clr r11
clr r12
clr r13
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 sram addreses
adiw r25:r24,$01 ; increment write address
adiw r29:r28,$01 ; increment read address

wait1_000055: ; check if byte has been sent

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

wait2_000055: ; check if byte has been sent

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

;fullwave rectify left data
sbrs r7,$07 ; check if negative
rjmp normalize_000055
com r6 ; invert data if negative (using ones complement to avoid problem at $8000)
com r7

normalize_000055: ; normalize data since its all positive values now

lsl r6 ; multiply data by two
rol r7 ; data is unsigned integer at this point
ldi r16,$80 ; convert to signed integer
add r7,r16

wait3_000055: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait3_000055
in r17,spdr ; recieve in right channel msb
out spdr,r8 ; send out right channel lsb

;write rectified left channel data to sram
out portd,r24 ; set address
sts porth,r25
out portg,r18 ; 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

wait4_000055: ; check if byte has been sent

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

;get left 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

;accumulate samples for lowpass
add r10,r6 ; add in current sample
adc r11,r7
sbrc r7,$07 ; check if data is negative
ldi r18,$ff ; set high bits if it is
adc r12,r18 ; r18 is cleared above
adc r13,r18
clr r18 ; reset null register
sub r10,r4 ; remove last sample in buffer
sbc r11,r5
sbrc r5,$07 ; check if data is negative
ldi r18,$ff ; set high bits if it is
sbc r12,r18 ; r18 is cleared above
sbc r13,r18
clr r18 ; reset null register

mov r4,r10 ; divide by 256 and move to ouptput register
mov r5,r11
mov r17,r12
tst r19 ; check if no dividing necessary
breq store_000055 ; keep dividing till the right size
mov r16,r19 ; move cutoff to temporary register

divide_000055: ; divide accumulation for proper scaling

asr r17 ; divide accumulation
ror r5
ror r4
dec r16 ; check if done
brne divide_000055 ; keep dividing till the right size

store_000055: ; store lowpassed data to memory

movw r9:r8,r5:r4 ; move immediate data to right output
movw r17:r16,r25:r24 ; move write address to temporary register
subi r17,$01 ; move to delay buffer location
out portd,r16 ; set address
sts porth,r17
out portg,r18 ; pull ce low,we low,and set high bits of address
ldi r20,$ff
out ddra,r20 ; set porta as output for data write
out ddrc,r20 ; set portc as output for data write
out porta,r4 ; set data
out portc,r5
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

;fetch delayed data from memory
sub r16,r22 ; subtract delay time
sbc r17,r23
out portd,r16 ; set address
sts porth,r17
nop ; wait input latch time of 2 clock cycles
nop
in r4,pina ; get data
in r5,pinc ; put delayed data to left output

rotary_000055: ; check rotary encoder and adjust cutoff frequency
; although rotary encoder is externally debounced, it is done here again.
; 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 ; check if time to sample rotary encoder
brne shift_000055 ; continue if not
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_000055 ; check if pin0 was low on previous sample
clt ;  clear state register if back high
rjmp shift_000055 ; finish off

edge_000055: ; check for falling edge

brts shift_000055 ; do nothing if edge was already detected
set ; set t register to indicate edge detected
sbrs r17,$01 ; check if pin1 is high
rjmp increment_000055 ; increase cutoff frequency if right rotation
cpi r19,$06 ; else check if at max value
brsh shift_000055 ; finish off if at max
inc r19 ; incrementing cutoff value decreases cutoff frequency
rjmp buffer_000055 ; reset accumulation buffer

increment_000055: ; increase cutoff frequency

cpi r19,$01 ; check if cutoff at min value
brlo shift_000055 ; finish off if at min
dec r19 ; decrementing cutoff value increases cutoff frequency

buffer_000055: ; adjust buffer size

movw r29:r28,r25:r24 ; move write address to read address
ldi r16,$01 ; initialize the offset register
tst r19 ; check if any shifting is required
breq bufferload_000055
mov r17,r19 ; move cutoff to temporary register

shift1_000055: ; shift in zeros to make correct buffer size

lsl r16 ; increment buffer size
dec r17
brne shift1_000055 ; keep shifting until done

bufferload_000055: ; load buffer size

rjmp restart_000055 ; clear accumulation buffer

shift_000055: ; check if delay time is correct

cp r26,r22 ; compare desired delay to actual delay
cpc r27,r23
breq adcsample_000055 ; do nothing if the same
brlo indexdown_000055
ldi r17,$02 ; increment delay register
add r22,r17
adc r23,r18 ; r18 is cleared above
rjmp adcsample_000055

indexdown_000055:

ldi r17,$01 ; decrement delay register
sub r22,r17
sbc r23,r18 ; r18 is cleared above

adcsample_000055: ; get delay settings

lds r17,adcsra ; get adc control register
sbrs r17,adif ; check if adc conversion is complete
rjmp done_000055 ; skip adc sampling
lds r16,adcl ; get low byte adc value
lds r17,adch ; get high byte adc value
add r0,r16 ; accumulate adc samples
adc r1,r17
adc r2,r18 ; r18 is cleared above
ldi r17,$f7
sts adcsra,r17 ; clear interrupt flag
dec r15 ; countdown adc sample clock
brne done_000055 ; get delay time if its been long enough
lsr r2 ; divide adc sample by 4 to make 16b value
ror r1
ror r0
lsr r2
ror r1
ror r0

deadband_000055: ; check if adc has changed enough to warrant update

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

check_000055: ; check if difference is greater than deadband

cpi r16,$40 ; check if difference is less than 1 lsb
cpc r17,r18 ; r18 cleared above
brlo empty_000055 ; do nothing if less than 1 lsb
movw r27:r26,r1:r0 ; move adc sample to delay time if large enough change
andi r26,$fe ; make sure delay time is a multiple of 2

empty_000055: ; empty accumulation registers and finish off

clr r0 ; empty adc accumulation registers
clr r1
clr r2

switchsample_000055: ; check rotary 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
ldi r17,$02
add r16,r17
cp r16,r31 ; check if location has changed
breq done_000055 ; finish off if no change
clr r30 ; reset jump register to new location
mov r31,r16

done_000055: ; normalize data and move to read buffer

reti ; return to waiting

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