HISE Logo Forum
    • Categories
    • Register
    • Login

    Saturation Models (Neve, Tweaker, Oxford Inflator) in FAUST

    Scheduled Pinned Locked Moved ScriptNode
    22 Posts 8 Posters 1.1k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • MorphoiceM
      Morphoice
      last edited by Morphoice

      Here's my attempt at different saturation models in faust. Have fun!

      I'm hoping WAVES, KUSH and UAD won't have me whacked for sharing these.
      Feel free to add gain correction

      import("stdfaust.lib");
      
      drive_twk = hslider("TWK",0,0,1,0.001);
      drive_neve = hslider("Neve",0,0,1,0.001);
      inflate = hslider("Inflate",0,0,1,0.001);
      gain = hslider("Gain",1,0,2,0.001);
      
      // Neve
      neve(x) =x + 0.04 * ma.tanh(x*40); 
      
      // Tweaker
      twk(x) = x*2/(1+abs(x*2));             
      
      // Oxford Inflator
      oxford(x) = sin(ma.PI/2*sin(ma.PI * 2 * x)) * inflate + sin(ma.PI * 2 * x) * (1 - inflate);
      
      
      saturator = ef.dryWetMixerConstantPower(drive_twk,twk) :
                  ef.dryWetMixerConstantPower(drive_neve,neve) : 
                  ef.dryWetMixerConstantPower(inflate,oxford) * gain;
      
      process = saturator,saturator;
      

      https://instagram.com/morphoice - 80s inspired Synthwave Music, Arcade & Gameboy homebrew!

      LindonL griffinboyG clevername27C ChazroxC 4 Replies Last reply Reply Quote 5
      • LindonL
        Lindon @Morphoice
        last edited by

        @Morphoice how did you arrive at these values/algorithms?

        HISE Development for hire.
        www.channelrobot.com

        1 Reply Last reply Reply Quote 1
        • griffinboyG
          griffinboy @Morphoice
          last edited by griffinboy

          @Morphoice

          The Inflator yes, is a completely a linear transfer function, split into 3 bands.
          You can create programs to measure and derive the transfer functions.

          1 Reply Last reply Reply Quote 1
          • A
            Allen
            last edited by Allen

            Thank you for sharing these!

            the inflator algorithm looks a lil bit weird, i think it should be like this:

            a1=1+(curve+50)/100
            a2=curve/50
            a3=(curve-50)/100
            a4=0.0625-curve/400+curve^2/40000

            x=a1・x+a2・x^2+a3・x^3-a4・(x^2-2・x^3+x^4)

            the curve value should be in the range of -50 to 50 but you could intentionally set it to somewhere over 50 to achieve some weird distortion effect.

            also, the inflator is mainly control by input gain and curve so you may need to add a input gain by doing something like:

            x=x0・preGain

            (the effect param in the original plugin is actually a little bit misleading, it's just a dry wet mix and seems like it was only designed to be set to 100% or 0% to make it sounds "natural")

            I'm sure several inflator clone use this algorithm and with linear phase oversampling it somehow even sounds better then the original one :)

            clevername27C 1 Reply Last reply Reply Quote 4
            • M
              Mighty23
              last edited by

              I’m a simple person—I like loudness! 😄

              I’ve been experimenting with an inflator-like idea these past few days, and here’s the code I came up with:

              declare name "Inflator-like test1)";
              
              import("stdfaust.lib");
              
              // Interface
              input_slider = hslider("Input (dB)", 0, -6, 12, 0.01);
              effect_slider = hslider("Effect (%)", 0, 0, 100, 0.1);
              curve_slider = hslider("Curve", 0, -50, 50, 0.1);
              clip_slider = checkbox("Clip 0 dB");
              band_split_slider = checkbox("Band Split");
              effect_in_slider = checkbox("Effect In");
              output_slider = hslider("Output (dB)", 0, -12, 0, 0.01);
              
              // Utility functions
              clamp(x, minv, maxv) = min(maxv, max(minv, x));
              sign(x) = (x > 0) - (x < 0);
              
              // Convert UI values to processing parameters
              pre = input_slider : ba.db2linear;
              post = output_slider : ba.db2linear;
              wet = effect_slider * 0.01 * 0.99999955296;
              dry = 1 - (effect_slider * 0.01);
              
              // Curve coefficients
              A = (curve_slider * 0.01 + 1.5);
              B = (curve_slider * -0.02);
              C = (curve_slider * 0.01 - 0.5);
              D = (0.0625 - curve_slider * 0.0025 + (curve_slider * curve_slider) * 0.000025);
              
              // SVF Filter implementation
              svf_coefficient(cutoff) = tan(ma.PI * (cutoff/ma.SR - 0.25)) * 0.5 + 0.5;
              
              svf_lpf(cutoff) = _ : fi.tf2(b0, b1, b2, a1, a2)
              with {
                  c = svf_coefficient(cutoff);
                  b0 = c * c;
                  b1 = 2 * b0;
                  b2 = b0;
                  a1 = 2 * (b0 - 1);
                  a2 = 1 - 2 * c + b0;
              };
              
              svf_hpf(cutoff) = _ : fi.tf2(b0, b1, b2, a1, a2)
              with {
                  c = svf_coefficient(cutoff);
                  b0 = 1;
                  b1 = -2;
                  b2 = 1;
                  a1 = 2 * (c * c - 1);
                  a2 = 1 - 2 * c + c * c;
              };
              
              // Band splitting with integrated gain calculation
              band_split(x) = low, (mid * mid_gain), high
              with {
                  low_cutoff = 240;
                  high_cutoff = 2400;
                  
                  c_low = svf_coefficient(low_cutoff);
                  c_high = svf_coefficient(high_cutoff);
                  mid_gain = c_high * (1 - c_low) / (c_high - c_low);
                  
                  low = x : svf_lpf(low_cutoff);
                  high = x : svf_hpf(high_cutoff);
                  mid = x - low - high;
              };
              
              // Waveshaping function
              waveshaper(x) = select2(abs(x) < 1,
                                     select2(abs(x) < 2,
                                            x,  // > 2
                                            (2 * abs(x) - abs(x) * abs(x)) * sign(x)), // 1 < x < 2
                                     (A * abs(x) + B * (abs(x) * abs(x)) + 
                                      C * abs(x) * (abs(x) * abs(x)) - 
                                      D * ((abs(x) * abs(x)) - 
                                          2 * (abs(x) * abs(x)) * abs(x) + 
                                          (abs(x) * abs(x) * abs(x) * abs(x)))) * sign(x)) // x < 1
                              * wet + x * dry;
              
              // Main processing function
              process = par(i, 2, channel_process)
              with {
                  channel_process(x) = x : input_stage : effect_stage : output_stage;
                  
                  input_stage(x) = x * pre <: 
                                   select2(clip_slider,
                                          clamp(_, -2, 2),
                                          clamp(_, -1, 1));
                  
                  effect_stage(x) = select2(effect_in_slider,
                                           x,
                                           select2(band_split_slider,
                                                  waveshaper(x),
                                                  band_split_process(x)));
                  
                  band_split_process(x) = band_split(x) : 
                                         (waveshaper, 
                                          waveshaper,
                                          waveshaper) :> _;
                  
                  output_stage(x) = select2(abs(x) < 0.0000000000000000555111512312578270211815834045,
                                           x * post,
                                           0);
              };
              

              Free Party, Free Tekno & Free Software too

              MorphoiceM A clevername27C 3 Replies Last reply Reply Quote 4
              • MorphoiceM
                Morphoice @Mighty23
                last edited by

                @Mighty23 looks complicated but sounds nice as far as I can tell. just the band split does nothing

                https://instagram.com/morphoice - 80s inspired Synthwave Music, Arcade & Gameboy homebrew!

                S 1 Reply Last reply Reply Quote 1
                • S
                  sletz @Morphoice
                  last edited by sletz

                  @Morphoice abs(x) < 0.0000000000000000555111512312578270211815834045

                  hum seems like a non human intervention here 😊 ?

                  M 1 Reply Last reply Reply Quote 1
                  • M
                    Mighty23 @sletz
                    last edited by

                    @sletz
                    Yes, it's not just a non-human intervention—it's a lifesaving one :)

                    I’ve been dealing with crashes, and after investigating, I realized I need denormal protection. I received several suggestions and implemented the one that made the most intuitive sense to me—though perhaps I’m falling into some cognitive biases?

                    How can I test for denormal issues?
                    Can you explain real-world methods for denormal protection?

                    Free Party, Free Tekno & Free Software too

                    S 1 Reply Last reply Reply Quote 0
                    • S
                      sletz @Mighty23
                      last edited by sletz

                      @Mighty23

                      We have tools to help debugging, read:
                      https://faustdoc.grame.fr/manual/debugging/#debugging-at-runtime
                      https://faustdoc.grame.fr/tutorials/debugging/

                      This interp-tracer tool is currently to be used in the terminal, so requires a local installation. But in theory this kind of tool could be integrated in HISE 😊, since the libfaust library used in HISE also embeds the needed Interpreter backend.

                      For more local NAN protection, using ma.EPSILON is a more portable solution, since is adapts the single/double compilation option.

                      1 Reply Last reply Reply Quote 2
                      • A
                        Allen @Mighty23
                        last edited by

                        @Mighty23
                        Nice work!
                        For the band split, you may want to use the linkwitz riley instead of svf.

                        About the denormal protection, may I know what CPU you're are running this code on? Does this directly cause the crashing?
                        This might be useful for some legacy CPUs and it won't likely really be a problem for modern x64 CPU afaik.

                        1 Reply Last reply Reply Quote 0
                        • clevername27C
                          clevername27 @Mighty23
                          last edited by

                          @Mighty23 Very interesting, and thank you for sharing. Am I correct that this is multiband distortion, or is there also some dynamics processing as well?

                          M 1 Reply Last reply Reply Quote 0
                          • clevername27C
                            clevername27 @Morphoice
                            last edited by

                            @Morphoice Thank you for sharing. Not a whacking, but I work with two of these companies; could I ask you to pls characterise these as more "…in the spirit of…" and not "…copies of…"? 🙏

                            1 Reply Last reply Reply Quote 1
                            • clevername27C
                              clevername27 @Allen
                              last edited by

                              @Allen I assume nobody is here posting actual transfer functions they measured.

                              MorphoiceM 1 Reply Last reply Reply Quote 0
                              • M
                                Mighty23 @clevername27
                                last edited by

                                @clevername27 said in Saturation Models (Neve, Tweaker, Oxford Inflator) in FAUST:

                                or is there also some dynamics processing as well?

                                there is no compressor/limiter/gate in the processing. I would consider it 100% multiband waveshaping.

                                @Allen said in Saturation Models (Neve, Tweaker, Oxford Inflator) in FAUST:

                                For the band split, you may want to use the linkwitz riley instead of svf.

                                Yes, for sure. Many Thanks.

                                @Allen said in Saturation Models (Neve, Tweaker, Oxford Inflator) in FAUST:

                                may I know what CPU you're are running this code on?

                                10510u i7 on Windows
                                Late 2016 Mini Mac overclocked and open-core operating system.

                                Free Party, Free Tekno & Free Software too

                                1 Reply Last reply Reply Quote 1
                                • MorphoiceM
                                  Morphoice @clevername27
                                  last edited by Morphoice

                                  @clevername27 they're not measured, someone on reddit reverse enginered them so they are somewhat common knowledge among the DSP community, I'm not claiming any of those is a copy of something, it's just knowledge I gathered off the web. This is an old post though, I'm making my own functions for saturation now, and I'm using desmos to create them, closely matching stuff I can indeed measure from real hardware and then bring them over in faust. Unfortunately it's all done by hand, I have no Idea on how to "automatically" transfer measurements into transfer functions. It's a lot of guesswork until the curve looks somewhat similar

                                  Those are the two waveshaper curves I find most pleasing, sonically, anything in between those is great and much faster calculated than the popular tanh for saturation

                                  Screenshot 2025-01-30 at 14.08.55.png

                                  https://instagram.com/morphoice - 80s inspired Synthwave Music, Arcade & Gameboy homebrew!

                                  clevername27C griffinboyG 2 Replies Last reply Reply Quote 3
                                  • clevername27C
                                    clevername27 @Morphoice
                                    last edited by

                                    @Morphoice Thank you for sharing. 🍸

                                    1 Reply Last reply Reply Quote 0
                                    • griffinboyG
                                      griffinboy @Morphoice
                                      last edited by

                                      @Morphoice

                                      If you want to find the functions automatically it's not that hard I can show you. It can be done with python or MATLAB very easily.

                                      You'll quicky discover that a static waveshaper cannot represent the measurements of analog distortion, but you can however create the 'best fit' automatically.

                                      MorphoiceM 1 Reply Last reply Reply Quote 0
                                      • MorphoiceM
                                        Morphoice @griffinboy
                                        last edited by

                                        @griffinboy it's probably a good idea to morph between waveshaping curves according to signal strength but then again here we are considering hysteresis again ;)

                                        https://instagram.com/morphoice - 80s inspired Synthwave Music, Arcade & Gameboy homebrew!

                                        griffinboyG 1 Reply Last reply Reply Quote 0
                                        • griffinboyG
                                          griffinboy @Morphoice
                                          last edited by

                                          @Morphoice

                                          yeah no, morphing a waveshaper based on signal strength doesn't really have much effect unless it has memory or smoothing. Else you've just done a static transformation of the curve and created another static curve. It has to have memory / hysteresis to actually represent anything nonlinear.

                                          But collecting transfer function data from analog devices can still be super useful and can inform approximations.

                                          clevername27C 2 Replies Last reply Reply Quote 0
                                          • ChazroxC
                                            Chazrox @Morphoice
                                            last edited by

                                            @Morphoice Where can I learn how to apply this? Maybe you can tell me what this is and I can do y research. Thanks brotha.

                                            1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post

                                            62

                                            Online

                                            1.7k

                                            Users

                                            11.7k

                                            Topics

                                            102.1k

                                            Posts