==== 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. [[attachment:fullwave_delay_lowpass.asm|fullwave_delay_lowpass.asm]] ---- {{{#!highlight nasm ; 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 }}}