Reverse delay?

Software questions and issues with the FV-1

Moderator: frank

Digital Larry
Posts: 338
Joined: Mon Nov 12, 2012 1:12 pm
Contact:

Post by Digital Larry »

Few comments.

1) Best place to ask about SpinCAD is over at my forum.

2) Someone sent me the reverse delay code and I slapped it into SpinCAD without thinking about it very much. Off the top of my head I do not know how it works. It could be improved no doubt. Reverse engineering FV-1 code makes my brain hurt.

3) If you come up with an improved version and would like me to include it in SpinCAD, please let me know.

Thanks,

DL
the_frey
Posts: 13
Joined: Sat Jun 04, 2016 9:56 am

Post by the_frey »

Cool, was just curious. The crossfade is very dreamy, I was basically wanting to get something going with that. I'll reverse engineer it and see what I can do :)
knutolai
Posts: 65
Joined: Wed Nov 23, 2016 9:43 am
Location: Bergen, Norway

Post by knutolai »

Reviving an old thread!

I wrote this code that gets you 500 ms of reverse delay. The delay time, or rather the size of each reversed segment can be adjuster with POT0.

The program uses a counter to increment ADDR_PTR to read though the delay buffer at double the speed of the audio samples (creating reverse playback). I'm having trouble properly removing the "click" sound when the "read head" wraps around/resets. I need to add some kind of envelope that gradually mutes the signal when the "read head" approaches the end of the delay buffer, and gradually un-mutes it when it has reset.

I've tried a couple of things, all very crude. Any suggestions would be great!

Code: Select all

; Reverse Delay 
; Knut Helle
; June 2017
; 
; POT0 = Reverse segment length
;
equ	length	32767 	; Buffer length
equ	inc	512	; increment
;
mem	echo	length	; Delay Buffer 
;
equ	addr	reg0	; rmpa / addr_ptr addres
equ	size	reg1	; delay buffer size value
;
; Startup Initialization ################################
;
skp   RUN,   loop 
clr 
wrax	addr, 0		; Clear reg on startup
loop: 
;
; Reset address pointer ################################
;
sof	0, 0.1		; +0.1
rdax	POT0, 0.9	; Pot / 'size' range 0.1 to 1
wrax	size, 0		; write to reg, clear
;
clr	
or	length*256	; add max delay addres	( or 8388352 ) 
mulx	size		; * size variable
rdax	addr, -1		; subtract current addres
;
skp	GEZ, reset	; if NEG (addr > max addres)
clr
wrax	addr, 0		; Reset addr ( = 0 )
reset:
;
; Set address pointer 	USES '-1' instead og 1.0 for better accuracy ###################
;
clr			; acc = 0
or	inc		; add increment (+)
sof	-1, 0		; -> (-)
rdax	addr, -1		; add addr (-)
sof	-1, 0		; -> (+)
wrax	addr, -1		; write to addr, then (-)
sof	-1, 0		; -> (+)
wrax	ADDR_PTR, 0	; write to addr_ptr (+), clear acc
;
; Audio In/Out	###########################################
;
ldax	ADCL		; Get input 
wra	echo, 0		; Write it to the head of the delay buffer
rmpa	1		; Read from memory (set by ADDR_PTR)
wrax	DACL, 0		; ACC-> DAC 
Digital Larry
Posts: 338
Joined: Mon Nov 12, 2012 1:12 pm
Contact:

Post by Digital Larry »

Some high level thoughts about putting crossfades on the ends of knulotai's code.

The delay time goes from 0.1 to 1.0 times the pot setting. So the minimum would be 100 msec at 32 kHz if you allocated the whole memory for this.

A 50 msec fade is probably fine.

You can check whether addr_ptr is near either end of the buffer and whether a fade up or fade down is in order.

Big question here is whether you are just going to fade up and down at the ends to get rid of the glitch, or are you going to crossfade to something else? If your plan is to crossfade then you would need either another trailing pointer running at mid buffer, or maybe a separate buffer.

- a parenthetical note about writing blocks for SpinCAD - you can't make simplifying assumptions such as being sure that the memory buffer always starts at zero. I wish! But you have to assume that the buffer will have an offset.
knutolai
Posts: 65
Joined: Wed Nov 23, 2016 9:43 am
Location: Bergen, Norway

Post by knutolai »

The delay time goes from 0.1 to 1.0 times the pot setting. So the minimum would be 100 msec at 32 kHz if you allocated the whole memory for this.

A 50 msec fade is probably fine.
The minimum "read window" would be 100 msec, but that translates to 50ms of reverse delay. The problem might be that I've made the fade out/in too sharp. I'll try to change the delay time pot to 0.2 to 1.0 and add 50ms fade in and 50ms fade out. A 50ms fade would be spread across ~1639 program cycles.
You can check whether addr_ptr is near either end of the buffer and whether a fade up or fade down is in order.
Good idea. Something like this would add a linear fade. Might be good enough:

Code: Select all

increment = +/- 1/(32768*0.050) = +/- 0.00061   ; will increment from 0 to 1 in 50ms

if ADDR_PTR < [pot-derived buffersize - 1639]   ; if less than fade out point
   increment = +(1/1639)  ; Positive increment, Fade In (1 + increment = 1 because of reg value limit) 
else if ADDR_PTR>= [pot-derived buffersize - 1639]   ; if at or beyond fade out point
   increment = -(1/1639)   ; Negative increment, Fade Out

volume = volume + increment   ; add increment
if volume < 0   ; if less than 0 : set to 0
   volume = 0

drolo
Posts: 10
Joined: Tue Feb 10, 2015 3:14 am
Location: BE
Contact:

Post by drolo »

knutolai wrote:Reviving an old thread!

I wrote this code that gets you 500 ms of reverse delay. The delay time, or rather the size of each reversed segment can be adjuster with POT0.

The program uses a counter to increment ADDR_PTR to read though the delay buffer at double the speed of the audio samples (creating reverse playback). I'm having trouble properly removing the "click" sound when the "read head" wraps around/resets. I need to add some kind of envelope that gradually mutes the signal when the "read head" approaches the end of the delay buffer, and gradually un-mutes it when it has reset.

I've tried a couple of things, all very crude. Any suggestions would be great!

Code: Select all

; Reverse Delay 
; Knut Helle
; June 2017
; 
; POT0 = Reverse segment length
;
equ	length	32767 	; Buffer length
equ	inc	512	; increment
;
mem	echo	length	; Delay Buffer 
;
equ	addr	reg0	; rmpa / addr_ptr addres
equ	size	reg1	; delay buffer size value
;
; Startup Initialization ################################
;
skp   RUN,   loop 
clr 
wrax	addr, 0		; Clear reg on startup
loop: 
;
; Reset address pointer ################################
;
sof	0, 0.1		; +0.1
rdax	POT0, 0.9	; Pot / 'size' range 0.1 to 1
wrax	size, 0		; write to reg, clear
;
clr	
or	length*256	; add max delay addres	( or 8388352 ) 
mulx	size		; * size variable
rdax	addr, -1		; subtract current addres
;
skp	GEZ, reset	; if NEG (addr > max addres)
clr
wrax	addr, 0		; Reset addr ( = 0 )
reset:
;
; Set address pointer 	USES '-1' instead og 1.0 for better accuracy ###################
;
clr			; acc = 0
or	inc		; add increment (+)
sof	-1, 0		; -> (-)
rdax	addr, -1		; add addr (-)
sof	-1, 0		; -> (+)
wrax	addr, -1		; write to addr, then (-)
sof	-1, 0		; -> (+)
wrax	ADDR_PTR, 0	; write to addr_ptr (+), clear acc
;
; Audio In/Out	###########################################
;
ldax	ADCL		; Get input 
wra	echo, 0		; Write it to the head of the delay buffer
rmpa	1		; Read from memory (set by ADDR_PTR)
wrax	DACL, 0		; ACC-> DAC 
Knut, this is great !
(still not getting notifications for post replies ...)
Between this and your advice on the other thread about playback speed, I was able to understand a lot better how the memory can be addressed in different ways. This has been really helpful. Thanks !! :)
potul
Posts: 76
Joined: Tue Sep 26, 2017 12:33 am

Post by potul »

Hi

Were you able to add a crossfade to the reverse delay?
I like your code, but the cliking is quite annoying.

Mate
igorp
Posts: 65
Joined: Tue May 19, 2015 6:10 am
Location: RU

Post by igorp »

I am trying to made reverse delay too , wrote own dummy code, it's very close to knutolai one (2Hz ramp LFO from 0 to -1) and now working on crossfades.

My scope show what sometimes it's too much "constant voltage" in reversed loop , accumulator saturated and locks , so some experiments were started.
(first of all- it's hpf in the loop)

if we roll back 2 samples every tick, our backward pointer never play even or odd samples. They are always skipped.
So, it may be a good way to make delay loop odd, for exaple, 16383 samples, not 16384

This may avoid aliasing and "constant voltage", because HPF on forward loop is working with sequental signal, not interleaved.

Or , another idea is to use these unused steps in manner of 7 second delay.
Even lines will contain guitar input, odd will contain delay loop and both could be crossfaded
igorp
Posts: 65
Joined: Tue May 19, 2015 6:10 am
Location: RU

Post by igorp »

4 msec before and 4 msec after zero adress X-fade was enough.
longer falling eat attack

Image
alpignolo
Posts: 20
Joined: Tue May 27, 2014 8:40 am

Post by alpignolo »

igorp wrote:4 msec before and 4 msec after zero adress X-fade was enough.
longer falling eat attack

Image
Someone has solved the clicking problem?
igorp
Posts: 65
Joined: Tue May 19, 2015 6:10 am
Location: RU

Re: Reverse delay?

Post by igorp »

I did, but for commertial product . declicker diagramm you can see on the photo above.
igorp
Posts: 65
Joined: Tue May 19, 2015 6:10 am
Location: RU

Re: Reverse delay?

Post by igorp »

ok, after PM I am showing cuted off reverse part of pdx delay , as is
fade envelope is not too perfect, but it was no room for more accurate one, because patch was complex.
Hello to Fast Tracker 2.07 authors and thanks for happy childhood

Code: Select all

; reverse-delay by igor@shift-line.com 2018
; simplified part of A+ Paradox delay

	equ	size  32767
	mem	mem_dly size

	equ	FC0  0.98	; фильтр хвостов

	equ	out_fwd reg0	; выход прямого дилея
	equ	out_bwd reg1	; выход реверсного дилея

	EQU	f1	reg3	; LPF хвостов
	EQU	f4	reg4	; hpf петли
	EQU	f5	reg5	; lpf огибающей кроссфэйда

	equ	fbk	reg6	; фидбэк с хвоста на вход. у меня задержан на 1 тик.	

	EQU	cf1	reg10	; cross-fade
	EQU	th1	reg11   ; threshold1 кроссфэйд реверса часть до перескока
	EQU	th2	reg12	; threshold2 кроссфэйд реверса часть после перескока

	equ	ad_fbk	reg14	; current address (forward) fbk. Реверс подстраивается под него
	equ	ad_reg  reg15	; current address reverse

	EQU	temp	reg16

	equ	f2 		reg31

;	equ	pot_unzip 	pot0
	equ	pot_delay   	pot2 
	equ	pot_feedback  	pot1


	skp	run , start
	clr
	wrax	ad_reg , 0
start:

;{ controls


;}

; ####################################################
;{ DELAY


	;rdax	fbk , 1
	ldax	fbk
	rdax	adcl,   1.0/2	; Порцию сигнала фильтранули и прибавили к памяти. Просто писать к памяти чревато еще более громкими щелчками.
	wra	mem_dly , 0

;{{ dly
	or	size*256
	mulx	pot_delay	; |задержка|
	wrax	ad_fbk , 1
	wrax	addr_ptr,  0
	rmpa	1
;}}     

;filter HPF 
	rdfx	f4, 0.003202	; HPF remove highs to avoid constant voltage accumulation. could be WRHX
	wrax	f4 , -1
	rmpa	1		; экономия команды на сохранении и чтении
;	wrax	flt_in , 1

; LPF dummy for repeats
	RDFX	f1 ,	FC0	; LPF fbk 
	WRAX 	f1 , 	1
	wrax	out_fwd , 1	; output of forward delay 

     	mulx	pot_feedback	; feedback value 12 +/-
     	mulx	pot_feedback

	sof 	1.1 , 0
	wrax	fbk , 0
;} end delay


;{ REVERSE read
	or 	0xFFFE00
	rdax	ad_reg , 1
	skp 	gez , ok1
	ldax	ad_fbk
	wrax	ad_reg , 1
ok1:
	and	0x7FFFFF
;	wrax 	dacr , 1	; ***

	wrax	ad_reg ,  -1   	; +1 = орган, -1 = обратка (+1 = octave up , -1 = reverse read)
	wrax	addr_ptr,   0   ; посчитать кроссфэйд . Если адрес 32767-256 или 0..256 - убавлять громкость.
				; здесь отмасштабировать память 
	rmpa	1
	mulx	f5
	wrax	out_bwd , 0
;}



; *********************************
; подготовка огибающей для фэйда
; ********************************
; fade envelope
;{
	clr
	rdax	ad_reg , -1
	and	0x7FFFFF
;	wrax	dacl , 1	; ***
	wrax	temp , 1	; temp 2 == LFO
; в аккумуляторе LFO (A=LFO value)


; начало рампы (ramp begin)
	sof	1 , - 1/256 	; порог сравнения первая (последняя?) доля (compare)
	skp 	gez , gez1
	clr
	wrax 	th1 , 0
	skp 	run , gez2
gez1:
	sof 	0 , 0.998	
	wrax 	th1 , 0
gez2:	
; конец рампы (ramp end)


	ldax	temp
	sof	1 , - 255/256 	; порог сравнения первая (последняя?) доля (compare)
	skp 	gez , gez3
	sof 	0 , 0.998	
	wrax 	th2 , 0
	skp 	run , gez4
gez3:
	clr
	wrax 	th2 , 0
gez4:	
	ldax th2
	mulx th1		; аккум = общий триггер времени срабатывания кроссфэйда (sum of fades)
	
	rdfx	f5, 0.0006*64	; capacitor for declicking (smooth angles of square)
	wrax	f5 , 0
;}


;{ ========= OUT ==============
	rdax	out_bwd , -2
	wrax 	dacl , 0	; ***
;}	

	

eof:
potul
Posts: 76
Joined: Tue Sep 26, 2017 12:33 am

Re: Reverse delay?

Post by potul »

Wow, this one is really improved and very usable.
I love it

Thanks Igor!
ndf
Posts: 12
Joined: Tue Jun 20, 2017 5:43 am

Re: Reverse delay?

Post by ndf »

Another approach is to cut the reverse over a zero crossing and then fade-in from reset. You still get tempo artifacts when the delay time does not match your rhythm, but it sounds good for percussive sounds.

Code: Select all

; Assembler declarations
MEM     dline   $7fff   ; delay line - use full memory
EQU     mindel  $060000 ; minimum delay length: 2048-512 samples
EQU     potscl  0.9395  ; $080000 + potscl*max(POT0) >= $7fffff
EQU     fadein  0.0025  ; fade in to ~-1dBFS at ~1000 samples
EQU     stepr   $000200 ; reverse movement offset: +2 samples
EQU     ffb     REG0    ; forward feedback signal
EQU     rfb     REG1    ; reverse feedback signal
EQU     fptr    REG2    ; forward delay pointer
EQU     rptr    REG3    ; reverse delay pointer
EQU     prevr   REG4    ; previous reverse output, for zero cross
EQU     currr   REG5    ; current reverse output, for zero cross
EQU     fadev   REG6    ; fade-in value
EQU     oshot   REG7    ; zero cross 'overshoot' amount
EQU     fout    DACR    ; forward output channel
EQU     rout    DACL    ; reverse output channel
EQU     sigin   ADCL    ; signal input channel
EQU     delctl  POT0    ; delay length control POT
EQU     ffbctl  POT1    ; forward feedback control POT
EQU     rfbctl  POT2    ; reverse feedback control POT

; Update delay length, read from delay end, output next forward sample
start:  clr
        or      mindel          ; load minimum delay time and declick window
        rdax    delctl,potscl   ; add scaled delay control POT
        wrax    fptr,1.0        ; save to forward delay ptr
        wrax    addr_ptr,0.0    ; and prepare read pointer
        rmpa    1.0             ; read current forward delay output
        wrax    fout,1.0        ; output to forward channel
        mulx    ffbctl          ; scale by forward feedback control POT
        mulx    ffbctl
        wrax    ffb,0.0         ; save to forward fb reg and clear ACC

; Move reverse pointer and output next reverse sample
        or      stepr           ; load the address increment into ACC
        rdax    rptr,1.0        ; add to the current reverse ptr
        wrax    rptr,1.0        ; save updated position
        wrax    addr_ptr,0.0    ; and prepare read pointer
        rmpa    1.0             ; read current reverse delay sample
        wrax    currr,1.0       ; save current delay sample for ZRC
        mulx    fadev           ; fade in reverse delay
        wrax    rout,1.0        ; output to reverse channel
        mulx    rfbctl          ; scale by reverse feedback control POT
        mulx    rfbctl
        wrax    rfb,0.0         ; save to reverse fb reg and clear ACC
        or      $7fffff         ; load +1.0 into acc (0.99...)
        rdfx    fadev,fadein    ; update fade-in value
        wrax    fadev,0.0       ; save fade-in value

; Check if reverse pointer and fade-in need to be reset
        rdax    fptr,-1.0       ; subtract forward delay length
        rdax    rptr,1.0        ; add current reverse ptr
        wrax    oshot,1.0       ; save delay time overshoot for reset
        skp     neg,norst       ; if not yet in declick window, move on
        ldax    prevr           ; load last reverse sample
        ldax    currr           ; load current reverse sample
        skp     zrc,dozrc       ; perform reset on zero crossing
        skp     0,norst         ; else skip reset
dozrc:  ldax    oshot           ; re-load overshoot amount
        wrax    rptr,0.0        ; reset reverse pointer
        wrax    fadev,0.0       ; clear fade-in value
        wrax    rout,0.0        ; mute output channel
        wrax    rfb,0.0         ; mute reverse feedback reg
norst:  ldax    currr           ; load current reverse sample to ACC
        wrax    prevr,0.0       ; save and clear ACC

; Read inputs and feed combined signal into delay line
        ldax    ffb             ; read forward delay signal
        rdax    rfb,1.0         ; add reverse delay signal
        rdax    sigin,1.0       ; add left input channel
        wra     dline,0.0       ; write input to delay line and clear ACC

alpignolo
Posts: 20
Joined: Tue May 27, 2014 8:40 am

Re: Reverse delay?

Post by alpignolo »

Hi Igor,
Is there a way to eliminate the delay with the first repetition?

igorp wrote: Mon Jul 23, 2018 5:28 am ok, after PM I am showing cuted off reverse part of pdx delay , as is
fade envelope is not too perfect, but it was no room for more accurate one, because patch was complex.
Hello to Fast Tracker 2.07 authors and thanks for happy childhood

Code: Select all

; reverse-delay by igor@shift-line.com 2018
; simplified part of A+ Paradox delay

	equ	size  32767
	mem	mem_dly size

	equ	FC0  0.98	; фильтр хвостов

	equ	out_fwd reg0	; выход прямого дилея
	equ	out_bwd reg1	; выход реверсного дилея

	EQU	f1	reg3	; LPF хвостов
	EQU	f4	reg4	; hpf петли
	EQU	f5	reg5	; lpf огибающей кроссфэйда

	equ	fbk	reg6	; фидбэк с хвоста на вход. у меня задержан на 1 тик.	

	EQU	cf1	reg10	; cross-fade
	EQU	th1	reg11   ; threshold1 кроссфэйд реверса часть до перескока
	EQU	th2	reg12	; threshold2 кроссфэйд реверса часть после перескока

	equ	ad_fbk	reg14	; current address (forward) fbk. Реверс подстраивается под него
	equ	ad_reg  reg15	; current address reverse

	EQU	temp	reg16

	equ	f2 		reg31

;	equ	pot_unzip 	pot0
	equ	pot_delay   	pot2 
	equ	pot_feedback  	pot1


	skp	run , start
	clr
	wrax	ad_reg , 0
start:

;{ controls


;}

; ####################################################
;{ DELAY


	;rdax	fbk , 1
	ldax	fbk
	rdax	adcl,   1.0/2	; Порцию сигнала фильтранули и прибавили к памяти. Просто писать к памяти чревато еще более громкими щелчками.
	wra	mem_dly , 0

;{{ dly
	or	size*256
	mulx	pot_delay	; |задержка|
	wrax	ad_fbk , 1
	wrax	addr_ptr,  0
	rmpa	1
;}}     

;filter HPF 
	rdfx	f4, 0.003202	; HPF remove highs to avoid constant voltage accumulation. could be WRHX
	wrax	f4 , -1
	rmpa	1		; экономия команды на сохранении и чтении
;	wrax	flt_in , 1

; LPF dummy for repeats
	RDFX	f1 ,	FC0	; LPF fbk 
	WRAX 	f1 , 	1
	wrax	out_fwd , 1	; output of forward delay 

     	mulx	pot_feedback	; feedback value 12 +/-
     	mulx	pot_feedback

	sof 	1.1 , 0
	wrax	fbk , 0
;} end delay


;{ REVERSE read
	or 	0xFFFE00
	rdax	ad_reg , 1
	skp 	gez , ok1
	ldax	ad_fbk
	wrax	ad_reg , 1
ok1:
	and	0x7FFFFF
;	wrax 	dacr , 1	; ***

	wrax	ad_reg ,  -1   	; +1 = орган, -1 = обратка (+1 = octave up , -1 = reverse read)
	wrax	addr_ptr,   0   ; посчитать кроссфэйд . Если адрес 32767-256 или 0..256 - убавлять громкость.
				; здесь отмасштабировать память 
	rmpa	1
	mulx	f5
	wrax	out_bwd , 0
;}



; *********************************
; подготовка огибающей для фэйда
; ********************************
; fade envelope
;{
	clr
	rdax	ad_reg , -1
	and	0x7FFFFF
;	wrax	dacl , 1	; ***
	wrax	temp , 1	; temp 2 == LFO
; в аккумуляторе LFO (A=LFO value)


; начало рампы (ramp begin)
	sof	1 , - 1/256 	; порог сравнения первая (последняя?) доля (compare)
	skp 	gez , gez1
	clr
	wrax 	th1 , 0
	skp 	run , gez2
gez1:
	sof 	0 , 0.998	
	wrax 	th1 , 0
gez2:	
; конец рампы (ramp end)


	ldax	temp
	sof	1 , - 255/256 	; порог сравнения первая (последняя?) доля (compare)
	skp 	gez , gez3
	sof 	0 , 0.998	
	wrax 	th2 , 0
	skp 	run , gez4
gez3:
	clr
	wrax 	th2 , 0
gez4:	
	ldax th2
	mulx th1		; аккум = общий триггер времени срабатывания кроссфэйда (sum of fades)
	
	rdfx	f5, 0.0006*64	; capacitor for declicking (smooth angles of square)
	wrax	f5 , 0
;}


;{ ========= OUT ==============
	rdax	out_bwd , -2
	wrax 	dacl , 0	; ***
;}	

	

eof:
Post Reply