Spin FV-1 Dual Mono Delay?

Started by olrs, March 31, 2022, 05:23:25 PM

Previous topic - Next topic

olrs

Hi all, wondering if someone could help me figure out how to properly adapt one of the OEM delay programs for the FV-1 into a dual mono delay (ie: independent delays on right and left with their own time controls).

The adapted code is below. This is working as expected on the left channel but I get no audio on the right channel. I'm very new to DSP programming and my gut tells me the problem is something to do with addr_ptr. Any guidance would be very much appreciated!

;Operational SSR DSP Program 0

;pot0 = Left Delay
;pot1 = Right Delay


; LEFT
;The delay is variable from 0 to 240mS

mem ldelay 16383

equ lpfil1 reg0
equ lpfil2 reg1
equ lnxtadd reg2
equ linterp reg3
equ ldout1 reg4
equ ldout2 reg5
equ lfbk reg6

;read inputs, add to feedback and write to delay:

rdax adcl,1 ;read and add inputs
wra ldelay,0 ;write result to delay

;prepare read pointer based on pot0 setting:

rdax pot0,1
and %01111100_00000000_00000000
sof 0.24,0
rdfx lpfil1,0.001 ;filter pot value
wrax lpfil1,1 ;write filter register, keep in ACC
rdfx lpfil2,0.001
wrax lpfil2,1
wrax addr_ptr,0 ;load address pointer with first read position
rmpa 1 ;read memory from pointer position
wrax ldout1,0 ;store first value, clear ACC

;now get second  value:

or %00000000_00000001_00000000
rdax lpfil2,1 ;get pointer back and add
wrax addr_ptr,0 ;load pointer again
rmpa 1
wrax ldout2,0 ;store second value, clear accumulator

;now get an interpolation value:

rdax lpfil2,1
and %00000000_00000000_11111111
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof 1.999,0 ;shift 15 paces
wrax linterp,0 ;save in register

;now crossfade between delay read values using interp value:

rdax ldout2,1
rdax ldout1,-1
mulx linterp ;crossfade with interp
rdax ldout1,1

;now take this as the output, filter and arrange feedback:

wrax dacl,1 ;write result to left output, full scale
wrax lfbk,0 ;write to feedback register
clr


;------------------------------------------------------------------------

; RIGHT
;The delay is variable from 0 to 240mS
rdax adcr,1 ;read and add inputs

mem rdelay 16383

equ rpfil1 reg7
equ rpfil2 reg8
equ rnxtadd reg9
equ rinterp reg10
equ rdout1 reg11
equ rdout2 reg12
equ rfbk reg13

;read inputs, add to feedback and write to delay:

rdax adcr,1 ;read and add inputs
wra rdelay,0 ;write result to delay

;prepare read pointer based on pot0 setting:

rdax pot1,1
and %01111100_00000000_00000000
sof 0.24,0
rdfx rpfil1,0.001 ;filter pot value
wrax rpfil1,1 ;write filter register, keep in ACC
rdfx rpfil2,0.001
wrax rpfil2,1
wrax addr_ptr,0 ;load address pointer with first read position
rmpa 1 ;read memory from pointer position
wrax rdout1,0 ;store first value, clear ACC

;now get second  value:

or %00000000_00000001_00000000
rdax rpfil2,1 ;get pointer back and add
wrax addr_ptr,0 ;load pointer again
rmpa 1
wrax rdout2,0 ;store second value, clear accumulator

;now get an interpolation value:

rdax rpfil2,1
and %00000000_00000000_11111111
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof -2,0
sof 1.999,0 ;shift 15 paces
wrax rinterp,0 ;save in register

;now crossfade between delay read values using interp value:

rdax rdout2,1
rdax rdout1,-1
mulx rinterp ;crossfade with interp
rdax rdout1,1

;now take this as the output, filter and arrange feedback:

wrax dacr,1 ;write result to left output, full scale
wrax rfbk,0 ;write to feedback register
clr


Vivek

This can be done very easily with SpinCAD.

Vivek

#2
Do you have 2 of this instruction?

rdax   adcr,1      ;read and add inputs   



Are you really using rfbk and lfbk ?

olrs

Quote from: Vivek on March 31, 2022, 05:57:17 PM
Do you have 2 of this instruction?

rdax   adcr,1      ;read and add inputs   



Are you really using rfbk and lfbk ?

Ah, yes, but that's a mistake - I was debugging and left an extra one in in the version I uploaded. There should only be one read for each input.

I'm not sure about rfbk and lfbk actually. I'm only using one repeat (this is a pre-delay for an analog spring reverb). When I tried commenting the

wrax   lfbk,0      ;write to feedback register

line out it stopped working :icon_confused:

I guess I'm just looking for a very simple way to delay the incoming signal on both inputs with a different delay time. I also want to scale the delay adjustment to a range that's useful for a reverb pre-delay - say, 0-240ms (or something like that).

olrs

Quote from: Vivek on March 31, 2022, 05:54:50 PM
This can be done very easily with SpinCAD.

I tried that too actually. I found the code it output a little too confusing (as a noob) to modify for things like pot scaling. Maybe I should take another stab at it.

Vivek

Olrs, I sent you a PM. Please check.

Vivek

The address pointer of second delay has not been calculated properly

You need to add the delay memory base address start point to the pointer to get the correct address pointer

So right now, both left and right channels are reading from the first defined memory


See example on how to calculate correct pointer to second declared memory by adding the base address here


http://www.spinsemi.com/forum/viewtopic.php?f=3&t=104

potul

As Vivek pointed out, you are not accessing the right memory, different per delay line.

The ping pong delay vivek pointed to is almost 90% of what you need. You just need to have separate pots per delay line, and eliminiate the cross feedback loop, and you will have it.

Vivek

Potul brother,

Why does the original code posted by OP have interpolation of delay samples, when the delay is not being set by an LFO and only being read based on a pot ?

Is it very important to make smooth sounds for that short period when user is adjusting delay length pot ?

http://www.spinsemi.com/knowledge_base/effects.html#Continuously_varying_delays

potul

Quote from: Vivek on April 01, 2022, 06:46:37 AM
Potul brother,

Why does the original code posted by OP have interpolation of delay samples, when the delay is not being set by an LFO and only being read based on a pot ?

Is it very important to make smooth sounds for that short period when user is adjusting delay length pot ?

http://www.spinsemi.com/knowledge_base/effects.html#Continuously_varying_delays

I don't have a clue why the OP chose to use interpolation. It looks like an overkill when using a pot to control delay time.

ElectricDruid

I haven't tried it on the FV-1, and the pot values are *specifically filtered* to only change very slowly on the Fv-1, but doing a similar thing on the dsPIC, changing the delay time by jumping from one read location to another gives a unpleasant "zippering" noise.

In the end, I implemented a crossfade routine to smoothly change volume from one read pointer ot the other instead of jumping abruptly. It still makes a somewhat-strange artifact when you twiddle the delay time knob rapidly, but it's not an unpleasant sound - sounds more like a DJ rewinding a record rather than a nasty digital effect.

On the FV-1, it may well be overkill. Still, it uses 128 cycles whatever we do, so in a way, we might as well!

potul

Quote from: ElectricDruid on April 03, 2022, 04:38:16 PM
I haven't tried it on the FV-1, and the pot values are *specifically filtered* to only change very slowly on the Fv-1, but doing a similar thing on the dsPIC, changing the delay time by jumping from one read location to another gives a unpleasant "zippering" noise.

In the end, I implemented a crossfade routine to smoothly change volume from one read pointer ot the other instead of jumping abruptly. It still makes a somewhat-strange artifact when you twiddle the delay time knob rapidly, but it's not an unpleasant sound - sounds more like a DJ rewinding a record rather than a nasty digital effect.

On the FV-1, it may well be overkill. Still, it uses 128 cycles whatever we do, so in a way, we might as well!

In FV-1 what I have always done is adding a low pass filter to the pot, to reduce zipper noise when moving the delay pot. It's usually good enough and just 3 instructions.

ElectricDruid

Thanks Potul, interesting to hear that even more filtering (beyond the 100msecs time constant in the hardware) is needed on the pots to reduce the zippering.

potul

Quote from: ElectricDruid on April 04, 2022, 07:30:41 AM
Thanks Potul, interesting to hear that even more filtering (beyond the 100msecs time constant in the hardware) is needed on the pots to reduce the zippering.

I found this old post from Frank inthe Spinsemi forum with some guidance on the filter coeff to use for zipper noise reduction:


;Mono delay in left chan
;Frank Thomson
;OCT Distribution
;
;pot0 adjusts delay time

equ length 32767
equ smooth 0.000125 ; Adjust to your liking of speed/noise tradeoff, can be adjusted over a LARGE range
; 0.125 Fast but zipper noise
; 0.000125 Little zipper but more frequency shifting when changing, like an old tape delay

mem echo length ;echo delay

equ del_read reg0

; Clear register on start
skp RUN, loop
clr
wrax del_read,0

loop:

; Select tap from delay based on pot0, should range 0 to length
; Since pot only has 512 states, want to filter pot to avoid jumping

; Smooth POT0
clr ; Clear the ACC
or length*256 ; Put delay length into ACC alligned to ACC[22:8];
mulx pot0 ; Multiply by POT0, new target value
rdfx del_read, smooth ; Smooth it : (target - current) * C + current
wrax del_read, 0 ; Save it
;
rdax adcl, 0.5 ; Get input
rdax adcr, 0.5
wra echo, 1.0 ; Write it to the head of the delay
wrax dacr, 0 ; Write non-delayed to right DAC
;
rdax del_read, 1.0 ; Get the delay tap to read
wrax addr_ptr, 0 ; Write it to the address pointer register
rmpa 1 ; Read from memory
wrax dacl, 0 ; ACC-> DAC

Digital Larry

Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

Sweetalk

To control two (or more) memory blocks separately you have to do something like this:


EQU DelayLength 8000                  ; Length of the first delay block
EQU DelayLength2 20000                ; Length of the second delay block

MEM DelayMem DelayLength                ; First delay block
MEM DelayMem2 DelayLength2              ; Second delay block

EQU DelayBase         REG0          ; Base address of the first delay
EQU DelayBase2         REG1          ; Base address of the second delay
EQU Delay1 REG2 ; First delay signal
EQU Delay2 REG3 ; Second delay signal

; DELAY1
CLR ; Clear ACC
OR DelayMem*256 ; Align base address with acc [23:8]
WRAX DelayBase, 0 ; Save

OR DelayLength*256 ; Load delay length on the ACC aligned
MULX POT0         ; Multiply by POT0 to have adjustable delay
RDAX DelayBase, 1 ; Add base address

WRAX ADDR_PTR, 0 ; Write to address pointer

LDAX ADCL ; Read ADCL
WRA DelayMem, 0 ; Write into Delay 1 block
RMPA 1 ; Read from the block on the pointed address
WRAX Delay1, 0 ; Save the signal into the signal register

; REPEAT THE SAME ON DELAY BLOCK 2
; DELAY2
CLR
OR DelayMem2*256
WRAX DelayBase2, 0

OR DelayLength2*256
MULX POT1                  ; Multiply by POT1 to have adjustable delay
RDAX DelayBase2, 1
WRAX ADDR_PTR, 0

LDAX ADCR ; Read ADCR
WRA DelayMem2, 0
RMPA 1
WRAX Delay2, 0


Then you have the signal of both blocks in Delay1 and Delay2, you can mix them togheter or write them to left and right DACs separately. Delay blocks can have different lengths, can be adjusted with separate delay controls or with the same pot. Hope it helps.

Vivek

Please help me to understand the need to align

OR   DelayMem*256      ; Align base address with acc [23:8]

Thanks

Digital Larry

#17
As far as I know, delay addresses (represented in terms of samples) need to be shifted 8 bits to the left.  There's probably a number of architectural reasons for this but one side effect is that the fractional representation of 1.0 (or as close to 1.0 as you can get) represents the entire 32768 RAM addresses.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

octfrank

Quote from: Vivek on April 08, 2022, 05:27:49 PM
Please help me to understand the need to align

OR   DelayMem*256      ; Align base address with acc [23:8]

Thanks

Simply so the lower 8 bits could be used as an interpolation coefficient between adjacent samples. When you calculate a pointer you rarely land on a whole address so interpolating between samples is useful, not always necessary but we wanted to allow for it.
Frank Thomson
Experimental Noize

Vivek

Dear Larry, do the patches generated by SpinCAD follow this coding trick ?