HISE Logo Forum
    • Categories
    • Register
    • Login

    This is my reverb in FAUST, what do you think?

    Scheduled Pinned Locked Moved Faust Development
    10 Posts 3 Posters 589 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

      Ideas:

      • Adjust gain curve to be more "exponential" for natural decay
      • Add smoothing to crossover frequencies to prevent zipper noise
      • Increase filter orders for steeper cutoffs
      • Use fractional delays for smoother modulation (?)
      import("stdfaust.lib");
      
      // Early Reflections
      earlyReflections = par(i, 4, de.delay(ma.SR, delayTime(i)) * gain(i))
      with {
          delayTime(i) = ba.take(i+1, (13, 17, 19, 23)) * 1.5;
          gain(i) = ba.take(i+1, (0.7, 0.6, 0.5, 0.4));
      };
      
      FDN = si.bus(2) : (preDelay, preDelay) <: (earlyReflections, earlyReflections, MIMO_Network ~ MATRIX) :> par(i, 2, (+:applyFilters))
      with {    
          // User-controllable parameters
          decayMs = hslider("Decay[unit:ms][style:knob]", 1000, 100, 10000, 1);
          preDelayMs = hslider("Pre-Delay[unit:ms][style:knob]", 20, 0, 200, 1);
          modDepth = hslider("Modulation Depth[unit:%][style:knob]", 0.5, 0, 2, 0.01) : *(0.01);
          modRate = hslider("Modulation Rate[unit:Hz][style:knob]", 0.5, 0.1, 2, 0.1);
      
          // 
          // High crossover scales with decay time - longer reverbs need lower high crossover
          highCrossover = min(12000, max(2000, 8000 - (decayMs - 1000) * 0.4));
          
          // Low crossover is related to high crossover but stays within reasonable bounds
          lowCrossover = min(500, max(100, highCrossover * 0.06));
          
          // Decay ratios adapt based on decay time
          // Longer reverbs typically have more low-end build-up, so we reduce low decay ratio
          lowDecayRatio = max(0.4, min(1.2, 1.0 - (decayMs - 1000) * 0.0001));
          
          // High frequencies decay faster in longer reverbs
          highDecayRatio = max(0.3, min(1.0, 0.8 - (decayMs - 1000) * 0.0002));
      
          // Pre-delay
          maxPreDelay = 200;
          preDelay = de.sdelay(ma.SR * maxPreDelay / 1000, 512, preDelayMs * ma.SR / 1000);
          
          // Calculate decay factor based on milliseconds and frequency band
          calcDecayFactor(delayLengthSamples, decayMs, ratio) = pow(0.001, delayLengthSamples / (decayMs * ratio * ma.SR / 1000.0));
          
          // Matrix
          MATRIX = ro.hadamard(16) : par(i, 16, *(1/4));
          
          // Network
          MIMO_Network(fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16, in1, in2) = 
              par(i, 16, DEL(i))
          with {
              baseDelays = (3797, 3541, 3323, 3109, 2939, 2801, 2693, 2593, 2503, 2423, 2351, 2287, 2237, 2179, 2131, 2083);
              DEL(i) = de.fdelay(5000, modulatedDelay(i), fb(i) + input(i)) : multiband_decay
              with {
                  delay = baseDelays : ba.selectn(16, i);
                  modulatedDelay(i) = delay + os.osc(modRate + 0.01*i) * delay * modDepth;
                  fb(i) = ba.selectn(16, i, fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16);
                  input(i) = select2(i < 2, 0, ba.selectn(2, i, in1, in2));
                  
                  // Multi-band decay using automatic parameters
                  multiband_decay = _ <: (
                      fi.lowpass(2, lowCrossover) * calcDecayFactor(delay, decayMs, lowDecayRatio),
                      fi.bandpass(2, lowCrossover, highCrossover) * calcDecayFactor(delay, decayMs, 1.0),
                      fi.highpass(2, highCrossover) * calcDecayFactor(delay, decayMs, highDecayRatio)
                  ) :> _;
              };
          };
          
          // Apply filters to each channel (oh! I see colours, that's so trippy :P)
          applyFilters = fi.lowpass(1, highCrossover * 1.5) : fi.highpass(1, lowCrossover * 0.5);
      };    
      
      process = FDN;
      

      Free Party, Free Tekno & Free Software too

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

        @Mighty23 if you use filterbank, you can be passing an argument to the function and decide on the number of bands that way.

        Link Preview Image
        filters - Faust Libraries

        favicon

        (faustlibraries.grame.fr)

        Also, your reverb will behave differently on different sampling rates because you're hardcoding the delay times in samples.

        Modulation is also super expensive, and even if it's off, you're still running the calculations. You can bypass that using the hidden enable primitive (with ondemand coming soon)

        modulatedDelay(i) = delay + enable(os.osc(modRate + 0.01*i) * delay * modDepth, modDepth>0);
        

        b1d11e31-30fb-4736-969f-1725bcfc860f-image.png
        e4b9eefb-85ef-4117-a7fc-475c941a82b2-image.png

        M 2 Replies Last reply Reply Quote 1
        • M
          Mighty23 @aaronventure
          last edited by

          @aaronventure Thanks for all these tips. I think I'll keep the post updated.

          Free Party, Free Tekno & Free Software too

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

            @aaronventure

            import("stdfaust.lib");
            
            // User-controllable parameters
            decayMs = hslider("Decay[unit:ms][style:knob]", 1000, 100, 10000, 1);
            preDelayMs = hslider("Pre-Delay[unit:ms][style:knob]", 20, 0, 200, 1);
            modDepth = hslider("Modulation Depth[unit:%][style:knob]", 0.5, 0, 2, 0.01) : *(0.01);
            modRate = hslider("Modulation Rate[unit:Hz][style:knob]", 0.5, 0.1, 2, 0.1);
            freeze = checkbox("Freeze[style:knob]") : si.smoo;
            freezeInputMix = hslider("Freeze Input Mix[style:knob]", 0.5, 0, 1, 0.01) : si.smoo;
            
            // Early Reflections with dynamic timing and gains based on decay
            earlyReflections = par(i, 4, de.delay(ma.SR, delayTime(i)) * gain(i)) with {
                // Base delay times in milliseconds
                baseDelayRatios = (0.013, 0.017, 0.019, 0.023); // Represented as ratios of decay time
                minDelay = 10; // Minimum delay time in ms
                maxDelayRatio = 0.1; // Maximum delay time as ratio of decay time
                
                // Calculate delay time based on decay time and base ratios
                delayTime(i) = min(
                    ba.take(i+1, baseDelayRatios) * decayMs,   // Proportional to decay
                    maxDelayRatio * decayMs                     // Capped at max ratio
                ) : max(minDelay)                              // Ensure minimum delay
                  * ma.SR / 1000.0;                           // Convert to samples
                
                // Dynamic gain calculation based on delay time and decay
                gain(i) = pow(0.7, (i+1)) * (1.0 - (delayTime(i)/(ma.SR/2)) * 0.3);
            };
            
            FDN = si.bus(2) : (preDelay, preDelay) <: (earlyReflections, earlyReflections, MIMO_Network ~ MATRIX) :> par(i, 2, (+:applyFilters)) with {
                // Filter bank crossover frequencies
                numBands = 4; // Number of frequency bands
                crossoverFreqs = (200, 800, 3200); // Crossover frequencies for 4 bands
                
                // Base decay ratios for each band - indexed using selectn
                baseDecayRatios = (1.2, 1.0, 0.8, 0.6);
                
                // Get decay ratio for a specific band
                getDecayRatio(band) = ba.selectn(4, band, baseDecayRatios);
                
                // Adapt decay ratio based on decay time
                adaptDecayRatio(ratio) = max(0.3, min(1.2, ratio - (decayMs - 1000) * 0.0002));
                
                // Pre-delay
                maxPreDelay = 200;
                preDelay = de.sdelay(ma.SR * maxPreDelay / 1000, 512, preDelayMs * ma.SR / 1000);
                
                // Calculate decay factor based on milliseconds and frequency band
                calcDecayFactor(delayLengthSamples, ratio) = 
                    select2(freeze > 0.5,
                        pow(0.001, delayLengthSamples / (decayMs * ratio * ma.SR / 1000.0)),
                        1.0);
                
                // Matrix
                MATRIX = ro.hadamard(16) : par(i, 16, *(1/4));
                
                // Network
                MIMO_Network(fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16, in1, in2) =
                    par(i, 16, DEL(i))
                with {
                    baseDelaysMs = (86.1, 80.3, 75.4, 70.5, 66.7, 63.5, 61.1, 58.8, 56.8, 55.0, 53.3, 51.9, 50.7, 49.4, 48.3, 47.2);
                    
                    DEL(i) = de.fdelay(maxDelay, modulatedDelay(i), processInput(fb(i), input(i))) : multiband_decay
                    with {
                        maxDelay = 200 * ma.SR / 1000;
                        delay = baseDelaysMs : ba.selectn(16, i) * ma.SR / 1000;
                        modulatedDelay(i) = delay + os.osc(modRate + 0.01*i) * delay * modDepth;
                        fb(i) = ba.selectn(16, i, fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16);
                        input(i) = select2(i < 2, 0, ba.selectn(2, i, in1, in2));
                        
                        // Input processing
                        freezeGain = freeze * freezeInputMix;
                        normalGain = 1.0 - freeze;
                        processInput(fbSignal, inSignal) = fbSignal + (inSignal * (normalGain + freezeGain));
                        
                        // Multi-band decay using filterbank
                        multiband_decay = _ <: fi.filterbank(3, crossoverFreqs) : 
                            par(i, numBands, * (calcDecayFactor(delay, adaptDecayRatio(getDecayRatio(i))))) :> _;
                    };
                };
                
                // Final filtering
                applyFilters = fi.lowpass(2, 18000) : fi.highpass(2, 20); 
            };
            
            process = FDN;
            

            This is a new version with filterbank and less hardcoded early reflection. I also tried to use your suggestions but I didn't quite understand how to optimize the use of resources in modulation depth.

            Free Party, Free Tekno & Free Software too

            A 1 Reply Last reply Reply Quote 0
            • A
              aaronventure @Mighty23
              last edited by

              @Mighty23 nice, you're converting to samples based on sampling rate properly.

              You can always test this using the oversampling nodes.

              The modulation optimization is the following

              modulatedDelay(i) = delay + enable(os.osc(modRate + 0.01*i) * delay * modDepth, modDepth>0);
              M 1 Reply Last reply Reply Quote 1
              • M
                Mighty23 @aaronventure
                last edited by

                @aaronventure
                When I test it in HISE I notice that the left channel volume is higher than the right channel volume.
                I thought it was caused by modulation but even bypassing it in the implementation, I get no improvement. Could you advise me where to investigate?

                2f63ddef-fa54-4852-a2dc-29fcc5a2b025-image.png

                PS: The oversampling suggestion is great.

                Free Party, Free Tekno & Free Software too

                A LindonL 2 Replies Last reply Reply Quote 1
                • A
                  aaronventure @Mighty23
                  last edited by

                  @Mighty23 it's probably your array of delay times due to how they're fed into the matrix. Try shuffling them around in the list.

                  1 Reply Last reply Reply Quote 0
                  • LindonL
                    Lindon @Mighty23
                    last edited by

                    @Mighty23 I did some playing with this - the problem appears to be in the dynamic gain calculation, sorry I dont have a fix....

                    HISE Development for hire.
                    www.channelrobot.com

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

                      @Lindon I tackled it with brute force, the 'stereoBalance' parameter must always be at 100% and must not be exposed to the user.

                      import("stdfaust.lib");
                      // User-controllable parameters
                      decayMs = hslider("Decay[unit:ms][style:knob]", 1000, 100, 10000, 1);
                      preDelayMs = hslider("Pre-Delay[unit:ms][style:knob]", 20, 0, 200, 1);
                      modDepth = hslider("Modulation Depth[unit:%][style:knob]", 0.5, 0, 2, 0.01) : *(0.01);
                      modRate = hslider("Modulation Rate[unit:Hz][style:knob]", 0.5, 0.1, 2, 0.1);
                      freeze = checkbox("Freeze[style:knob]") : si.smoo;
                      freezeInputMix = hslider("Freeze Input Mix[style:knob]", 0.5, 0, 1, 0.01) : si.smoo;
                      width = hslider("Width[style:knob]", 1, 0, 1, 0.01) : si.smoo;
                      
                      // Early Reflections with dynamic timing and gains based on decay
                      earlyReflections = par(i, 4, de.delay(ma.SR, delayTime(i)) * gain(i)) :> *(0.25) with {
                          // Base delay times in milliseconds
                          baseDelayRatios = (0.013, 0.017, 0.019, 0.023); // Represented as ratios of decay time
                          minDelay = 10; // Minimum delay time in ms
                          maxDelayRatio = 0.1; // Maximum delay time as ratio of decay time
                          
                          // Calculate delay time based on decay time and base ratios
                          delayTime(i) = min(
                              ba.take(i+1, baseDelayRatios) * decayMs,   // Proportional to decay
                              maxDelayRatio * decayMs                     // Capped at max ratio
                          ) : max(minDelay)                              // Ensure minimum delay
                            * ma.SR / 1000.0;                           // Convert to samples
                          
                          // Dynamic gain calculation based on delay time and decay
                          gain(i) = pow(0.7, (i+1)) * (1.0 - (delayTime(i)/(ma.SR/2)) * 0.3);
                      };
                      
                      FDN = si.bus(2) : (preDelay, preDelay) <: (earlyReflections, earlyReflections, MIMO_Network ~ MATRIX) :> 
                          stereoBalance : applyFilters with {
                          // Filter bank crossover frequencies
                          numBands = 4; // Number of frequency bands
                          crossoverFreqs = (200, 800, 3200); // Crossover frequencies for 4 bands
                          
                          // Base decay ratios for each band - indexed using selectn
                          baseDecayRatios = (1.2, 1.0, 0.8, 0.6);
                          
                          // Get decay ratio for a specific band
                          getDecayRatio(band) = ba.selectn(4, band, baseDecayRatios);
                          
                          // Adapt decay ratio based on decay time
                          adaptDecayRatio(ratio) = max(0.3, min(1.2, ratio - (decayMs - 1000) * 0.0002));
                          
                          // Pre-delay
                          maxPreDelay = 200;
                          preDelay = de.sdelay(ma.SR * maxPreDelay / 1000, 512, preDelayMs * ma.SR / 1000);
                          
                          // Calculate decay factor based on milliseconds and frequency band
                          calcDecayFactor(delayLengthSamples, ratio) = 
                              select2(freeze > 0.5,
                                  pow(0.001, delayLengthSamples / (decayMs * ratio * ma.SR / 1000.0)),
                                  1.0);
                          
                          // Matrix
                          MATRIX = ro.hadamard(16) : par(i, 16, *(0.25));
                          
                          // Network
                          MIMO_Network(fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16, in1, in2) =
                              par(i, 16, DEL(i))
                          with {
                              baseDelaysMs = (61.1, 53.3, 80.3, 58.8, 55.0, 49.4, 75.4, 51.9, 66.7, 48.3, 63.5, 86.1, 70.5, 56.8, 50.7, 47.2);
                              
                              DEL(i) = de.fdelay(maxDelay, modulatedDelay(i), processInput(fb(i), input(i))) : multiband_decay
                              with {
                                  maxDelay = 200 * ma.SR / 1000;
                                  delay = baseDelaysMs : ba.selectn(16, i) * ma.SR / 1000;
                                  modulatedDelay(i) = delay + os.osc(modRate + 0.01*i) * delay * modDepth;
                                  fb(i) = ba.selectn(16, i, fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16);
                                  
                                  // Balanced input distribution - first 8 lines get in1, next 8 get in2
                                  input(i) = select2(i < 8, 
                                            select2(i >= 8, 0, in2 * 0.25), 
                                            in1 * 0.25);
                                  
                                  // Input processing
                                  freezeGain = freeze * freezeInputMix;
                                  normalGain = 1.0 - freeze;
                                  processInput(fbSignal, inSignal) = fbSignal + (inSignal * (normalGain + freezeGain));
                                  
                                  // Multi-band decay using filterbank
                                  multiband_decay = _ <: fi.filterbank(3, crossoverFreqs) : 
                                      par(i, numBands, * (calcDecayFactor(delay, adaptDecayRatio(getDecayRatio(i))))) :> _;
                              };
                          };
                          
                          // Stereo balance control
                          stereoBalance(l, r) = l * (1 + comp), r * (1 - comp)
                          with {
                              comp = (1 - width) * 0.5;
                          };
                          
                          // Final filtering with separate stereo filters
                          applyFilters = fi.lowpass(2, 18000), fi.lowpass(2, 18000) : fi.highpass(2, 20), fi.highpass(2, 20);
                      };
                      
                      process = FDN;
                      

                      Free Party, Free Tekno & Free Software too

                      LindonL 1 Reply Last reply Reply Quote 0
                      • LindonL
                        Lindon @Mighty23
                        last edited by

                        @Mighty23 well thats one way to do it....

                        HISE Development for hire.
                        www.channelrobot.com

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

                        21

                        Online

                        1.7k

                        Users

                        11.8k

                        Topics

                        102.6k

                        Posts