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.
    • 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
                                    • clevername27C
                                      clevername27 @griffinboy
                                      last edited by

                                      This post is deleted!
                                      1 Reply Last reply Reply Quote 0
                                      • clevername27C
                                        clevername27 @griffinboy
                                        last edited by

                                        @griffinboy Doing each partial statically will model non-linearity.

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

                                        26

                                        Online

                                        1.7k

                                        Users

                                        11.8k

                                        Topics

                                        102.5k

                                        Posts