Emulating a Rotary Effect in Faust (and Teensy)

Started by niektb, September 09, 2021, 11:53:57 AM

Previous topic - Next topic

niektb

I've been inspired by Potul to try and create a Guitar Pedal Implementation, just for fun. I've been diving a lot into the world of DSP lately but didn't have a proper target to pursue until now!

Quote from: potul on September 02, 2021, 05:32:37 AM
I went through this journey some time ago (not with SpinCAD), and I found a couple of documents useful to understand the rotary speaker and how to simulate:

https://ses.library.usyd.edu.au/bitstream/handle/2123/8431/MCroteau_310303923_InitialTechReview.pdf

http://www.willpirkle.com/Downloads/Rotary%20Speaker%20Sim%20App%20Note.pdf

Enjoy the reading.

First the code I have so far:

import("stdfaust.lib");
ms_sample = ba.sec2samp(1) / 1000;
max_delay_ms = 30;

//UI Elements
bpc = checkbox("Bypass");
high_lfo_freq = hslider("Rate",5,0.1,10,0.1);
gain = hslider("Gain", 1.5, 0.1, 10, 0.1);
volume_slider = hslider("Volume", 1.5, 0.001, 2, 0.01);
dw = hslider("Mix", 0.5, 0.0, 1, 0.01);

// LFOs
high_lfo = os.lf_trianglepos(high_lfo_freq);
low_lfo = os.lf_trianglepos(high_lfo_freq/3);

// Dynamics Parameters
k = 2;
waveshaper(X, Y, gain) = (1/atan(k))*atan(k*X*gain), (1/atan(k))*atan(k*Y*gain);

// DSP Functions
rotary(_,_) = de.fdelay(max_delay_ms * ms_sample, high_lfo * max_delay_ms)*high_lfo, de.fdelay(max_delay_ms * ms_sample, max_delay_ms - (high_lfo * max_delay_ms))*(1-high_lfo);
vol(_,_,v) = _*v, _*v;
mixer(a,b,c) = a+c,b+c;
dry_wetST(dw,x1,x2,y1,y2) = (wet*y1 + dry*x1),(wet*y2 + dry*x2)
with {
    wet = dw;
    dry = 1.0-wet;
};

process = ba.bypass2(bpc, (_,_ <:
        _,_,(waveshaper(_,_,gain) :
        low, hi :
        mixer(_,_)) :
        dry_wetST(dw) :
        vol(_,_, volume_slider)
    )
)
with {
    low = fi.lowpass(1, 800) <: fi.lowpass(1,200), fi.highpass(1,200) : _+_*-low_lfo : _*0.4;
    hi = fi.highpass(1, 800) <: rotary(_,_) : fi.resonbp(2000,0.7,1), fi.resonbp(2000,0.7,1);
};


You can copy-paste it to the following link, where you can adjust some parameters, upload your own guitar tracks etc. Very fun!
https://faustide.grame.fr/

Now the following. First of all, I would love to hear your feedback! How does it sound? What should I improve?
I've been struggling with the maximum delay length and vibrato depth so insights there would be totally useful.

I plan to create a guitar pedal based on the Teensy (to which you can port Faust programs to). But that's something for the future... But of course I'll keep you posted! But first the DSP stuff  ;D

niektb

A few block diagrams probably make it a bit more insightfull for the quick glance :)







Digital Larry

So, is there any delay modulation yet?  I just see filtering and volume modulation.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

niektb

#3
Quote from: Digital Larry on September 09, 2021, 05:25:34 PM
So, is there any delay modulation yet?  I just see filtering and volume modulation.

Everything in the low section is AM only (as per the paper that potul linked to). But the high section delay is definitely there. It's this statement (actually there are 2 of them, left and right):
de.fdelay(max_delay_ms * ms_sample, high_lfo * max_delay_ms)*high_lfo
de.fdelay is called as fdelay(n,d) where:
Quoten: the max delay length in samples
d: the delay length as a number of samples (float)
It's a fractional delay that interpolates nicely as the rate changes :) I have currently set max_delay_ms at 30/ The high_lfo is a triangle (yes I cheated :icon_mrgreen:) wave between 0 and 1 that I multiply against the max_delay_ms to get the current delay. :) and then the whole section is agáin multiplied in amplitude with the LFO to get AM :)

But it might very well be that the tremolo effect is too pronounced :)

potul

Sounds nice to me! A very good starting point. I can't really tell if it sound like a real leslie, as I have never tried one, but it's in the ballpark of what I expect.

Fuast online IDE is improving a lot, last time I tried it was not that nice.


niektb

Thanks for the compliment Potul!

Well I'm really struggling with the 'speaker' part in rotary speaker emulation. An original speaker sits at the end of the chain but a guitar pedal emulation can sit in many places. Should it really be a cab sim (ish) type of effect? Or should it shape the sound of a guitar speaker to sound like a genuine Rotary speaker?

To make the modulation part more realistic, I should probably follow the paper more precisely and replace the bandpass filter in the high section with a parametric boost and a moving LPF. currently it almost acts like a preamp as it rolls off treble and scoops a bit of mids... But is this desirable or should I leave this functionality for specialized preamp pedals?

Digital Larry

I find this discussion interesting because it cuts to the heart of what I like to do with DSP.  I get general ideas of things to try from articles about real devices but after that I would just be using my ears (what's left of them anyway) to come up with interesting sounds.  I don't care if they sound realistic. 

Of course I will be interested to see where this winds up and I am impressed that you are using Faust!  The online IDE is WAY better than the editor I started with a couple years ago.  Faust, being an academic project, also has an active open source community.  It's quite likely that if you deep dive into Faust on Teensy you will uncover some limitation or opportunity or something... which you can then contribute to the project!
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

Ice-9

This is very interesting and I will be following this. I tried the Faust online IDE and the Leslie effect is sounding good.  :icon_cool:
www.stanleyfx.co.uk

Sanity: doing the same thing over and over again and expecting the same result. Mick Taylor

Please at least have 1 forum post before sending me a PM demanding something.

Sweetalk

Nice!, Faust is one of my main projects for 2022. I haven't tried your algorithm but I'm working on a leslie program for the FV1 for quite some time now and did a lot of research. Some things I found:

* On the Drum, frequencies below 200Hz are unaffected. The above are affected by the AM and in the mix you get some sort of phase cancelation, harmonic tremolo-ish kind of thing
* The depth of the doppler effect is increased by the speed
* On the horn you get more treble sound when the horn is facing you, so you can model a filter that varies with the LFO and is in sync with the AM (more volume- more treble).
* For the horn simulation you can do it switcheable but I think you have to include some filtering to get that mid range hump
* Sinewave is the way to go, synchornize the horn to be sine and the drum to be -sines
* The horn and drum have a slight difference in speed, beign the horn a little faster. Also has less inertia so speeds up and down faster than the drum
* A small reverb blends it all reaaaally nicely. You can make some small reflections with some all pass filters and feed them back

niektb

#9
Quote from: Ice-9 on September 11, 2021, 05:34:03 AM
This is very interesting and I will be following this. I tried the Faust online IDE and the Leslie effect is sounding good.  :icon_cool:

Thanks!

Quote from: Digital Larry on September 10, 2021, 02:44:01 PM
I find this discussion interesting because it cuts to the heart of what I like to do with DSP.  I get general ideas of things to try from articles about real devices but after that I would just be using my ears (what's left of them anyway) to come up with interesting sounds.  I don't care if they sound realistic. 

Of course I will be interested to see where this winds up and I am impressed that you are using Faust!  The online IDE is WAY better than the editor I started with a couple years ago.  Faust, being an academic project, also has an active open source community.  It's quite likely that if you deep dive into Faust on Teensy you will uncover some limitation or opportunity or something... which you can then contribute to the project!

What I really like about Faust is the concept of high-level modelling and then easily port to a beginner-friendly, DIY-friendly environment like Teensy (which can run on an Arduino). I've been toying with FPGAs and stuff but the learning curve is simply to steep to even get to the DSP part...
I'm not per se looking to make things sound realistic, but I want to try and connect to what people expect and how they use their effects :)

Quote from: Sweetalk on September 11, 2021, 06:44:33 AM
Nice!, Faust is one of my main projects for 2022. I haven't tried your algorithm but I'm working on a leslie program for the FV1 for quite some time now and did a lot of research. Some things I found:

1. On the Drum, frequencies below 200Hz are unaffected. The above are affected by the AM and in the mix you get some sort of phase cancelation, harmonic tremolo-ish kind of thing
2 The depth of the doppler effect is increased by the speed
3 On the horn you get more treble sound when the horn is facing you, so you can model a filter that varies with the LFO and is in sync with the AM (more volume- more treble).
4 For the horn simulation you can do it switcheable but I think you have to include some filtering to get that mid range hump
5 Sinewave is the way to go, synchornize the horn to be sine and the drum to be -sines
6 The horn and drum have a slight difference in speed, beign the horn a little faster. Also has less inertia so speeds up and down faster than the drum
7 A small reverb blends it all reaaaally nicely. You can make some small reflections with some all pass filters and feed them back

Thanks for your detailed pointers! 1, 4 and 6 are already implemented!
@1: the signal between 200Hz and 800Hz has AM and is subtracted from the signal below 200Hz so there is phase cancellation along the crossover :)
@2: very good pointer! Right now it's always at full depth but that's probably not the way to go as it's not very subtle :) what delay times did you use for your pitch modulation?
@3: that one was also pointed by one of the papers and makes a lot of sense! However, I'm struggling with the frequency ranges... How much treble is rolled off?
@4: currently I have a bandpass filter around 2kHz but I feel that I lose to much treble. Especially since the pitch modulation also washes the upper frequencies a bit :)
@5: right now I have both horns modulating in the opposite direction and the drums just in one direction :)
@6: Currently, I have the horn rotating 3 times as fast (as per one of the papers). Good point about the inertia, but I don't have any kind of ramping functionality yet. (although it does seem common when looking at commercial products)
@7: I kind of expect people to have amp reverb or a dedicated reverb pedal, wouldn't it be a bit superfluous?

----

To test it more properly, I've written a little amp sim. I also cleaned the code a bit. I don't know yet how to manage code in separate files and modules so here is óne file. You can swap the comments on the latest 2 lines in case you ónly want to audition the rotary effect :)


import("stdfaust.lib");
amp_volume = sp.stereoize(_*amp_vol)
with {
    amp_vol = vslider("Volume[style:knob]", 1.5, 0.001, 2, 0.01);
};

amp_preamp = sp.stereoize((1/atan(amp_k))*atan(amp_k*_*amp_gain))
with {
    amp_gain = vslider("Gain[style:knob]", 1.5, 0.1, 10, 0.1);
    amp_k = 2;
};

amp_tonestack = component("tonestacks.lib").jcm800(t,m,l), component("tonestacks.lib").jcm800(t,m,l)
with {
    t = vslider("Treble[style:knob]", 0.6, 0, 1, 0.01);
    m = vslider("Middle[style:knob]", 0.45, 0, 1, 0.01);
    l = vslider("Bass[style:knob]", 0.5, 0, 1, 0.01);
};
// cabsim = sp.stereoize(fi.highpass(4, 80) : fi.peak_eq_cq(5,120,1) : fi.peak_eq_cq(-10,1500,1.4) : fi.peak_eq_cq(-24,3820,14) : fi.highshelf(1, 7, 1820) : fi.lowpass(8, 5000));
amp_cabsim = sp.stereoize(ef.speakerbp(80,5000));

amp_dry_wetST(x1,x2,y1,y2) = (wet*y1 + dry*x1),(wet*y2 + dry*x2)
with {
    wet = dw;
    dry = 1.0-wet;
    dw = vslider("Reverb[style:knob]", 0.25, 0.0, 1, 0.01);
};

amp_sim = amp_preamp : amp_tonestack : amp_volume <: _,_,re.dattorro_rev(128, 0.99, 0.75, 0.625, 0.5, 0.7, 0.5, 0.001) : amp_dry_wetST : amp_cabsim;

/* ROTARY EFFECT */
ms_sample = ba.sec2samp(1) / 1000;
max_delay_ms = 30;

//UI Elements
rot_bpc = checkbox("Bypass");
high_lfo_freq = vslider("Rate[style:knob]",5,0.1,10,0.1);
rot_dw = vslider("Mix[style:knob]", 0.5, 0.0, 1, 0.01);

// LFOs
high_lfo = os.lf_trianglepos(high_lfo_freq);
low_lfo = os.lf_trianglepos(high_lfo_freq/3);

// Dynamics Parameters
rot_waveshaper(_) = sp.stereoize((1/atan(rot_k))*atan(rot_k*_*rot_gain))
with {
    rot_gain = vslider("Gain[style:knob]", 1.5, 0.1, 10, 0.1);
    rot_k = 2;
};

rot_volume = sp.stereoize(_*rot_vol)
with {
    rot_vol = vslider("Volume[style:knob]", 1.5, 0.001, 2, 0.01);
};

rotary(_,_) = de.fdelay(max_delay_ms * ms_sample, high_lfo * max_delay_ms)*high_lfo, de.fdelay(max_delay_ms * ms_sample, max_delay_ms - (high_lfo * max_delay_ms))*(1-high_lfo);
rot_mixer(a,b,c) = a+c,b+c;
rot_dry_wetST(x1,x2,y1,y2) = (wet*y1 + dry*x1),(wet*y2 + dry*x2)
with {
    wet = dw;
    dry = 1.0-wet;
    dw = vslider("Mix[style:knob]", 0.5, 0.0, 1, 0.01);
};

rotary_effect = ba.bypass2(rot_bpc, (_,_ <:
        _,_,(rot_waveshaper(_,_) :
        low, hi :
        rot_mixer(_,_)) :
        rot_dry_wetST :
        rot_volume
    )
)
with {
    low = fi.lowpass(1, 800) <: fi.lowpass(1,200), fi.highpass(1,200) : _+_*-low_lfo : _*0.4;
    hi = fi.highpass(1, 800) <: rotary(_,_) : rot_horn_eq;
    rot_horn_eq = sp.stereoize(fi.resonbp(2000,0.7,1));
};

//process = hgroup("Rotary", rotary_effect);
process = hgroup("Signal Chain", hgroup("Rotary",rotary_effect) : hgroup("British Amp", amp_sim));


Definitely has a better UI now  8) I only need to learn how to order those elements such that the tonestack knobs are adjacent to each other  ;)



Digital Larry

#10
Quote from: niektb on September 11, 2021, 02:09:00 PMDefinitely has a better UI now  8) I only need to learn how to order those elements such that the tonestack knobs are adjacent to each other  ;)

Figuring out how to arrange UI stuff in Faust is one of the harder things.  Using the auto-compile option of the FaustIDE helps.

I finally figured out that at any specific indent level (separated by /) the controls arrange themselves alphabetically.

So, at whatever level you're at, you can add:

.../[01]Gain
.../[02]Bass
.../[03]Treble


etc.  The stuff in the square brackets is non-specific metadata and is hidden without any other effect except it allows you to order controls explicitly.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

niektb

Aaaah that's very helpful! Thanks @Digital Larry!  :icon_neutral:

Sweetalk

@5: right now I have both horns modulating in the opposite direction and the drums just in one direction

Keep in mind that theres only one horn that sounds, the other one is just for balance when rotating.

@6: Currently, I have the horn rotating 3 times as fast (as per one of the papers). Good point about the inertia, but I don't have any kind of ramping functionality yet. (although it does seem common when looking at commercial products)

3 times the speed is really a lot, there's a slight difference, not that much or at least I never came to that conclusion.

niektb

Quote from: Sweetalk on September 13, 2021, 05:43:17 AM
@5: right now I have both horns modulating in the opposite direction and the drums just in one direction

Keep in mind that theres only one horn that sounds, the other one is just for balance when rotating.

@6: Currently, I have the horn rotating 3 times as fast (as per one of the papers). Good point about the inertia, but I don't have any kind of ramping functionality yet. (although it does seem common when looking at commercial products)

3 times the speed is really a lot, there's a slight difference, not that much or at least I never came to that conclusion.

@5: ooh, I came across different informations:
For example this paper says both are producing sound:
https://www.dafx.de/paper-archive/1999/disch.pdf
But this one confirms what you are saying:
http://www.willpirkle.com/Downloads/Rotary%20Speaker%20Sim%20App%20Note.pdf

Nevertheless, my stereo image is in both cases incorrect (as I essentially just flip the phase of one channel to create a fake stereo sound). I could fix this by either reducing the phase shift from 180 to 90 degrees (in case I decide to go with a single horn and 2 wide-spread microphones) or add some crosstalk between both channels (in case of two horns and 2 closeby microphones). I tend to lean to the first solution, but I should probably just compare the sound and see what I like best :)

@6: hmmm, I think you might be correct. Although so far I cannot find very much details, the papers I found mostly just talk about the horns and the doppler effect :)
I guess a factor around 2 would be logical I guess when using 2 horns?

Sweetalk

I made a mono effect with one virtual mic for the horn and one for the drum (the one I'm currently working on for a commercial fx) and a stereo one with two sets of mics for drum and horn. In the stereo version got both mics placements, 90° and 180° separation. The 180° is, for me, the better sounding and stereo imaging, almost like a panner.

For the speed difference I use something about 1.1, 1.2 from horn to drum (being the horn faster). With two horns I think you'll lose some of the dramatic effect of the doppler, AM and filtering as one horn is "cancelling" (for saying in some way) the other when you sum them.

niektb

#15
Quote from: Sweetalk on September 14, 2021, 05:59:19 AM
I made a mono effect with one virtual mic for the horn and one for the drum (the one I'm currently working on for a commercial fx) and a stereo one with two sets of mics for drum and horn. In the stereo version got both mics placements, 90° and 180° separation. The 180° is, for me, the better sounding and stereo imaging, almost like a panner.

For the speed difference I use something about 1.1, 1.2 from horn to drum (being the horn faster). With two horns I think you'll lose some of the dramatic effect of the doppler, AM and filtering as one horn is "cancelling" (for saying in some way) the other when you sum them.

Do you add crosstalk to the version with 180 degrees separation?
I think the cancellation effects should be less when the LFOs are orthogonal i.e. 90 degrees apart :) (but could be wrong about that, can't really wrap my head around it yet) But in case of a mono version, i think it would be better to just pick either the left or right channel and not do any summing :)

One of the papers I quoted, suggested that the amplitude tremolo should be a rectified version of a sine (which makes sense cause we're talking about an absolute distance), but how can we do this without introducing LFO harmonics that go into the audible spectrum? Any suggestions? :)

Digital Larry

Quote from: niektb on September 14, 2021, 07:10:54 AM
One of the papers I quoted, suggested that the amplitude tremolo should be a rectified version of a sine (which makes sense cause we're talking about an absolute distance), but how can we do this without introducing LFO harmonics that go into the audible spectrum? Any suggestions? :)
I'd either pass the LFO through an LPF to take off the sharp point or start with a 0-1 sine wave and square, cube it, etc. to squish towards 0 and (1 - that) to squish towards 1.  I don't know the theoretical comparison.  There is some method used to create band limited signals but I can't say I know what it is.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

niektb

#17
Quote from: Digital Larry on September 14, 2021, 03:19:31 PM
Quote from: niektb on September 14, 2021, 07:10:54 AM
One of the papers I quoted, suggested that the amplitude tremolo should be a rectified version of a sine (which makes sense cause we're talking about an absolute distance), but how can we do this without introducing LFO harmonics that go into the audible spectrum? Any suggestions? :)
I'd either pass the LFO through an LPF to take off the sharp point or start with a 0-1 sine wave and square, cube it, etc. to squish towards 0 and (1 - that) to squish towards 1.  I don't know the theoretical comparison.  There is some method used to create band limited signals but I can't say I know what it is.

I'm a little cautious to using a saturator and an LFP cause of the phase shift it would introduce in the LFO, meaning that the tremolo would lag behind the doppler effect... :/ (or use a lower order filter but that might not supress the harmonics enough)

However, I did find a function that would flatten the top of the curve a bit:
import("stdfaust.lib");

lfo1 = 0.5*os.osccos(100) - 0.125*os.osccos(200) + 0.625;
lfo2 = 0.5*os.osccos(100) + 0.5;

process = lfo1, lfo2;


However, there seems to be a bit of phase drift between the fundamental and the harmonic... See images below (second image is when I let it run for a while). Does anybody know how to sync these properly?





niektb

Okay, I do have a hard-synced wave-table oscillator now... Might be a bit overkill for such a 'small' feature :icon_eek:

import("stdfaust.lib");
ts = 1 << 16;
time = (+(1) ~ _ ) , 1 : -;
coswave = ((float(time) / float(ts)) * (2.0 * ma.PI)) : cos;

fund = os.hs_phasor(ts, freq, sync);
harm = os.hs_phasor(ts, freq * 2, sync);
sync = os.lf_imptrain(100);
fund_osc(freq) = rdtable(ts, coswave, int(fund));
harm_osc(freq) = rdtable(ts, coswave, int(harm));

lfo1 = 0.5*os.osccos(100) - 0.125*os.osccos(200) + 0.625;
lfo2 = 0.5*os.osccos(100) + 0.5;
lfo3 = abs(os.osccos(50));
lfo4 = 0.5*fund_osc(freq) - 0.125*harm_osc(freq) + 0.625;

freq = 100;
process = lfo1, lfo2, lfo3, lfo4;




niektb

#19
Rightoo!! Here is a new version! Changelog:

  • Implemented switch between two rates with a selectable ramp, taking inertia into account. Also a lower speed difference between horn and woofer
  • Removed the bandpass filter on the horn and instead used a modulated low pass filter (between 2kHz and 4kHz) and a variable Peak EQ (amount of dB boost controlled by 'Horn' knob)
  • I played around a bit with Tremolo Depth and set it up by ear. Still have to look at the Vibrato Depth. Same for the Tremolo LFO Shape.

Really, it works great when cranking the gain of the rotary effect and the horn knob (and ofc back-off the volume)  :icon_smile:
I've pretty much implemented every feature I want, so nit-picking the sound would be really welcome! :)
To tune my sound, I have used the DI track from this forum thread: https://www.kemper-amps.com/forum/index.php?thread/26817-a-dry-guitar-track-for-our-tests/


import("stdfaust.lib");
amp_volume = sp.stereoize(_*amp_vol)
with {
    amp_vol = vslider("[02]Volume[style:knob]", 7.5, 0.001, 10, 0.001) / 5;
};

amp_preamp = sp.stereoize((1/atan(amp_k))*atan(amp_k*_*amp_gain))
with {
    amp_gain = pow(vslider("[01]Gain[style:knob]", 1, 0.1, 10, 0.1),2);
    amp_k = 2;
};

amp_tonestack = component("tonestacks.lib").jcm800(t,m,l), component("tonestacks.lib").jcm800(t,m,l)
with {
    t = vslider("[05]Treble[style:knob]", 6, 0, 10, 0.01) / 10;
    m = vslider("[04]Middle[style:knob]", 4.5, 0, 10, 0.01) / 10;
    l = vslider("[03]Bass[style:knob]", 5, 0, 10, 0.01) / 10;
};
amp_cabsim = sp.stereoize(ef.speakerbp(80,5000));

amp_dry_wetST(x1,x2,y1,y2) = (wet*y1 + dry*x1),(wet*y2 + dry*x2)
with {
    wet = dw;
    dry = 1.0-wet;
    dw = vslider("[06]Reverb[style:knob]", 0.25, 0.0, 1, 0.01);
};

amp_sim = amp_preamp : amp_tonestack : amp_volume <: _,_,re.dattorro_rev(128, 0.99, 0.75, 0.625, 0.5, 0.7, 0.5, 0.001) : amp_dry_wetST : amp_cabsim;

/* ROTARY EFFECT */
ms_sample = ma.SR / 1000;

//UI Elements
rot_bpc = checkbox("Bypass");
t = checkbox("Chorale/Tremolo");
slow_rate_knob = vslider("[04]Chorale[style:knob]",.5,0.1,1.0,0.01) : si.smoo;
fast_rate_knob = vslider("[04]Tremolo[style:knob]",6,4,8,0.01) : si.smoo;
ramp_speed = vslider("[04]Ramp[style:knob]",2,1,3,0.01);

low_lfo_freq = slow_rate_knob + fast_rate_knob * en.asr(ramp_speed*3,1,ramp_speed*3,t) : /(1.1);
high_lfo_freq = slow_rate_knob + fast_rate_knob * en.asr(ramp_speed,1,ramp_speed,t);

// LFOs
high_lfo = 0.5*os.oscsin(high_lfo_freq)+0.5;
low_lfo = 0.5*os.oscsin(low_lfo_freq/1.1)+0.5;

// Dynamics Parameters
rot_waveshaper(_) = sp.stereoize((1/atan(rot_k))*atan(rot_k*_*rot_gain))
with {
    rot_gain = vslider("[02]Gain[style:knob]", 1.0, 0.1, 10, 0.1);
    rot_k = 2;
};

rot_volume = sp.stereoize(_*rot_vol)
with {
    rot_vol = vslider("[03]Volume[style:knob]", 1.0, 0.001, 2, 0.01);
};

max_delay_ms = 30;
rot_trem_depth = 0.6;
rotary(_,_) = de.fdelay(delay_buf_size, high_lfo * max_delay_ms)*aml, de.fdelay(delay_buf_size, max_delay_ms - (high_lfo * max_delay_ms))*amr : fi.resonlp(fml,0.7,1), fi.resonlp(fmr,0.7,1)
with {
    delay_buf_size = max_delay_ms * ms_sample;
    aml = 1-rot_trem_depth*high_lfo;
    amr = 1-rot_trem_depth*(1-high_lfo);
    fml = 2000 + 2000 * (1-high_lfo);
    fmr = 2000 + 2000 * high_lfo;
};

rot_mixer(a,b,c) = a+c,b+c;
rot_dry_wetST(x1,x2,y1,y2) = (wet*y1 + dry*x1),(wet*y2 + dry*x2)
with {
    wet = dw;
    dry = 1.0-wet;
    dw = vslider("[05]Mix[style:knob]", 1.0, 0.0, 1, 0.01);
};

rotary_effect = ba.bypass2(rot_bpc, (_,_ <:
        _,_,(rot_waveshaper(_,_) :
        low, hi :
        rot_mixer(_,_)) :
        rot_dry_wetST :
        rot_volume
    )
)
with {
    low = fi.lowpass(1, 800) <: fi.lowpass(1,200), fi.highpass(1,200) : _+_*-low_lfo;
    hi = fi.highpass(1, 800) <: rotary(_,_) : rot_horn_eq;
    rot_horn_eq = sp.stereoize(fi.peak_eq_cq(gain,2000,.4));
    gain = vslider("[07]Horn[style:knob]", 5, 0.0, 10.0, 0.1);
};

//process = hgroup("Rotary", rotary_effect);
//process = os.oscsin(100) <: hgroup("Signal Chain", hgroup("Rotary",rotary_effect) : hgroup("British Amp", amp_sim));
process = hgroup("Signal Chain", hgroup("Rotary",rotary_effect) : hgroup("British Amp", amp_sim));