Reverse delay?
Moderator: frank
-
- Posts: 338
- Joined: Mon Nov 12, 2012 1:12 pm
- Contact:
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
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
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!
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
-
- Posts: 338
- Joined: Mon Nov 12, 2012 1:12 pm
- Contact:
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.
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.
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.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.
Good idea. Something like this would add a linear fade. Might be good enough:You can check whether addr_ptr is near either end of the buffer and whether a fade up or fade down is in order.
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
Knut, this is great !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
(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 !!
![Smile :)](./images/smilies/icon_smile.gif)
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
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
Re: Reverse delay?
I did, but for commertial product . declicker diagramm you can see on the photo above.
Re: Reverse delay?
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
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:
Re: Reverse delay?
Wow, this one is really improved and very usable.
I love it
Thanks Igor!
I love it
Thanks Igor!
Re: Reverse delay?
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
Re: Reverse delay?
Hi Igor,
Is there a way to eliminate the delay with the first repetition?
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: