VU Meter (ZenoMOD)

Discussion in 'Reaper' started by Sinus Well, Jan 10, 2022.

  1. Sinus Well

    Sinus Well Audiosexual

    Joined:
    Jul 24, 2019
    Messages:
    2,173
    Likes Received:
    1,715
    Location:
    Sanatorium
    I'm currently working at the component level on the driver circuit (full-wave rectifier with RC element) – specifically, simulating diodes, capacitors, and resistors. My goal is to finally replace the dummy circuit in VU Meter 2 so it behaves as intended.

    What do you think – does it make sense (apart from response and RC time constants) to offer component parameters for adjustment?
    Things like settings for forward voltage, diode knee, leakage, tolerances, DC filter, etc.?

    This would give users who are interested the ability to essentially "swap out" diodes and capacitors, allowing them to get even closer to emulating their own analog VU meter. It would allow for the simulation of VU Meters from high-end to low-grade... Too much? Or Cool?

    Please, let me know!

    Here are the measurements with my set default settings:

    elko_dyn.png elko_hammer.png elko_harm.png elko_ir.png
    View attachment 58115
    elko_wavesh.png
     
    Last edited: Jun 28, 2025 at 12:03 AM
    • Like Like x 1
    • Interesting Interesting x 1
    • List
  2. blasterx

    blasterx Kapellmeister

    Joined:
    Nov 1, 2016
    Messages:
    161
    Likes Received:
    57
    Is it possible port to VST3??
     
  3. Macta

    Macta Member

    Joined:
    Nov 8, 2013
    Messages:
    24
    Likes Received:
    9
    Cool
     
  4. Sinus Well

    Sinus Well Audiosexual

    Joined:
    Jul 24, 2019
    Messages:
    2,173
    Likes Received:
    1,715
    Location:
    Sanatorium
    Plugin is in alpha phase. When VU Meter 2 has successfully passed the beta phase, then potentially yes.

    Anyone here fluent in C++ or Rust? :winker::rofl:
     
  5. Sinus Well

    Sinus Well Audiosexual

    Joined:
    Jul 24, 2019
    Messages:
    2,173
    Likes Received:
    1,715
    Location:
    Sanatorium
    The Rectifier/RC-Simulation is done. If you want to play around with it as a single component, here it is:
    Code:
    desc:Rectifier/RC-Simulation (ZenoMOD)
    author:   ZenoMOD
    released: 2025
    version:  1.00
    
    A detailed rectifier and RC-filter simulation.
    
    //////// CREDITS ////////
    ADAA implementations are based on the methods described in:
    Parker et al (2016), DAFx-16, "Reducing the Aliasing of Nonlinear Waveshaping Using Continuous-Time Convolution".
    Bilbao et al (2017), IEEE, "Antiderivative Antialiasing for Memoryless Nonlinearities".
    
    slider1:dc_filter=1<0,1,1{Off,On}>Input DC Filter
    slider10:charge_ms=0.22<0.01,2,0.01>Response Time (ms)
    slider11:discharge_ms=350<150,1500,5>RC Time Constant (ms)
    slider20:diode_db=-80<-100,-60,1>Forward Voltage (dB)
    slider21:diode_knee_db=6<0.1,12,0.1>Diode Knee (dB)
    slider30:leakage_pct=0.05<0.01,5,0.001>Leakage Current (%)
    slider31:tolerance_pct=5<0,20,0.1>Aging-Tolerance (%)
    slider40:noise_onoff=1<0,1,1{Off,On}>Noise
    slider41:noise_db=-98<-120,-60,1>Electronic Noise (dB)
    slider51:rect_adaa_mode=0<0,2,1{Off,ADAA 1,ADAA 2}>Rectifier Anti-Aliasing
    slider52:follower_adaa_mode=1<0,2,1{Off,ADAA 1,ADAA 2}>Follower Anti-Aliasing
    slider60:monitor_output=0<0,1,1{Off,On}>Monitor Output?
    
    
    options:no_meter
    
    @init
      epsilon = 0.000000000001;
    
      function lin_to_db(lin) ( 6 * log(max(epsilon, abs(lin))) / log(2) );
      function db_to_lin(db) ( 2^(db / 6) );
      function ms_to_coeff(ms) ( exp(-1 / (max(epsilon, ms) * 0.001 * srate)) );
      function tanh(x) (
        abs(x) > 20 ? (
          x > 0 ? 1 : -1
        ) : (
          (em2ax = exp(-2*abs(x))) >= 0 ? (
            (1-em2ax) / (1+em2ax) * (x >= 0 ? 1 : -1)
          ) : (
            x > 0 ? 1 : -1
          )
        )
      );
     
      function leakage_to_coeff(pct_s) ( pow(max(0.01, 1 - pct_s / 100), 1 / srate) );
    
    
      function F1_abs(val) ( 0.5 * val * abs(val) );
      function F2_abs(val) ( (1/6) * val * val * abs(val) );
     
      function adaa_abs(in_val)
        instance(prev_s1, prev_s2)
        local(out_val, dx, dx1, dx2, d_sum, term1, term2)
      (
        rect_adaa_mode == 0 ? (
          out_val = abs(in_val);
        ) :
        rect_adaa_mode == 1 ? (
          dx = in_val - prev_s1;
          out_val = (abs(dx) < epsilon) ? abs((in_val + prev_s1) * 0.5) : (F1_abs(in_val) - F1_abs(prev_s1)) / dx;
        ) : (
          dx1 = in_val - prev_s1;
          term1 = (abs(dx1) < epsilon) ? F1_abs((in_val + prev_s1) * 0.5) : (F2_abs(in_val) - F2_abs(prev_s1)) / dx1;
          dx2 = prev_s1 - prev_s2;
          term2 = (abs(dx2) < epsilon) ? F1_abs((prev_s1 + prev_s2) * 0.5) : (F2_abs(prev_s1) - F2_abs(prev_s2)) / dx2;
          d_sum = in_val - prev_s2;
          out_val = (abs(d_sum) < epsilon) ? ( (abs(dx_fallback=prev_s1-prev_s2) < epsilon) ? abs((prev_s1+prev_s2)*0.5) : (F1_abs(prev_s1)-F1_abs(prev_s2))/dx_fallback ) : ( 2 * (term1 - term2) / d_sum );
        );
        prev_s2 = prev_s1;
        prev_s1 = in_val;
        out_val;
      );
    
      function follower_delta_func(d) ( d > 0 ? d * c1_charge : d * c1_release );
      function follower_delta_antiderivative1(d) ( d > 0 ? 0.5 * d*d * c1_charge : 0.5 * d*d * c1_release );
      function follower_delta_antiderivative2(d) ( d > 0 ? (1/6) * d*d*d * c1_charge : (1/6) * d*d*d * c1_release );
    
      function process_channel(input, cap_voltage)
        instance(x_prev, y_prev, d_prev, d_prev2)
        local(filtered_input, abs_input, gate_multiplier, rectified_input, d, delta, diff_d1, diff_d2, diff_d_sum, term1, term2)
      (
        dc_filter ? (
          filtered_input = input - x_prev + dc_block_coeff * y_prev;
          this.x_prev = input;
          this.y_prev = filtered_input;
        ) : (
          filtered_input = input;
        );
     
        abs_input = this.adaa_abs(filtered_input);
        gate_multiplier = (tanh((abs_input - linear_threshold) * sharpness) + 1) * 0.5;
        rectified_input = max(0, abs_input * gate_multiplier + (rand(2) - 1) * linear_noise);
     
        follower_adaa_mode == 0 ? (
          rectified_input > cap_voltage ? (
            cap_voltage = rectified_input + charge_coeff * (cap_voltage - rectified_input);
          ) : (
            cap_voltage = rectified_input + discharge_coeff * (cap_voltage - rectified_input);
          );
        ) : (
          d = rectified_input - cap_voltage;
     
          follower_adaa_mode == 1 ? (
            diff_d1 = d - d_prev;
            delta = (abs(diff_d1) < epsilon) ?
              follower_delta_func((d + d_prev) * 0.5) :
              (follower_delta_antiderivative1(d) - follower_delta_antiderivative1(d_prev)) / diff_d1;
          ) : (
            diff_d1 = d - d_prev;
            term1 = (abs(diff_d1) < epsilon) ?
              follower_delta_antiderivative1((d + d_prev) * 0.5) :
              (follower_delta_antiderivative2(d) - follower_delta_antiderivative2(d_prev)) / diff_d1;
     
            diff_d2 = d_prev - d_prev2;
            term2 = (abs(diff_d2) < epsilon) ?
              follower_delta_antiderivative1((d_prev + d_prev2) * 0.5) :
              (follower_delta_antiderivative2(d_prev) - follower_delta_antiderivative2(d_prev2)) / diff_d2;
     
            diff_d_sum = d - d_prev2;
            delta = (abs(diff_d_sum) < epsilon) ?
              ( (abs(diff_d1) < epsilon) ? follower_delta_func((d+d_prev)*0.5) : (follower_delta_antiderivative1(d) - follower_delta_antiderivative1(d_prev)) / diff_d1 ) :
              2 * (term1 - term2) / diff_d_sum;
          );
     
          this.d_prev2 = d_prev;
          this.d_prev = d;
          cap_voltage += delta;
        );
    
        cap_voltage *= leakage_coeff;
        cap_voltage;
      );
    
      function update_coeffs() (
        dc_block_coeff = exp(-2 * $pi * 5 / srate);
        tolerance_factor = 1 + tolerance_pct / 100;
        true_diode_db = diode_db * (1 - tolerance_pct / 200);
        linear_threshold = db_to_lin(true_diode_db);
        knee_width_lin = db_to_lin(true_diode_db) - db_to_lin(true_diode_db - diode_knee_db);
        sharpness = knee_width_lin > epsilon ? 4 / knee_width_lin : 4/epsilon;
     
        true_charge_ms = charge_ms * tolerance_factor;
        true_discharge_ms = discharge_ms * tolerance_factor;
        true_leakage_pct = leakage_pct * tolerance_factor;
     
        charge_coeff = ms_to_coeff(true_charge_ms);
        discharge_coeff = ms_to_coeff(true_discharge_ms);
     
        c1_charge = 1 - charge_coeff;
        c1_release = 1 - discharge_coeff;
     
        leakage_coeff = leakage_to_coeff(max(0, true_leakage_pct));
        linear_noise = noise_onoff ? db_to_lin(noise_db);
      );
     
      capacitor_voltage_L = 0;
      capacitor_voltage_R = 0;
      L.x_prev = 0;
      L.y_prev = 0;
      R.x_prev = 0;
      R.y_prev = 0;
      L.prev_s1 = 0;
      L.prev_s2 = 0;
      R.prev_s1 = 0;
      R.prev_s2 = 0;
      L.d_prev = 0;
      L.d_prev2 = 0;
      R.d_prev = 0;
      R.d_prev2 = 0;
      last_dc_filter_state = dc_filter;
     
      update_coeffs();
     
      pdc_delay = (rect_adaa_mode == 1 ? 0.5 : rect_adaa_mode == 2 ? 1 : 0) +
                  (follower_adaa_mode == 1 ? 0.5 : follower_adaa_mode == 2 ? 1 : 0);
    
    @slider
      update_coeffs();
     
      dc_filter != last_dc_filter_state ? (
        L.x_prev = L.y_prev = R.x_prev = R.y_prev = 0;
        last_dc_filter_state = dc_filter;
      );
     
      pdc_delay = (rect_adaa_mode == 1 ? 0.5 : rect_adaa_mode == 2 ? 1 : 0) +
                  (follower_adaa_mode == 1 ? 0.5 : follower_adaa_mode == 2 ? 1 : 0);
    
    @sample
      capacitor_voltage_L = L.process_channel(spl0, capacitor_voltage_L);
      capacitor_voltage_R = R.process_channel(spl1, capacitor_voltage_R);
    
      comp_gain =
        rect_adaa_mode == 0 ? (
          follower_adaa_mode == 0 ? 1.000000 :
          follower_adaa_mode == 1 ? 1.001500 :
                                    1.004038
        ) :
        rect_adaa_mode == 1 ? (
          follower_adaa_mode == 0 ? 1.001844 :
          follower_adaa_mode == 1 ? 1.003344 :
                                    1.006124
        ) : (
          follower_adaa_mode == 0 ? 1.005779 :
          follower_adaa_mode == 1 ? 1.007283 :
                                    1.009724
        );
    
      output_L = capacitor_voltage_L * comp_gain;
      output_R = capacitor_voltage_R * comp_gain;
     
      monitor_output ? (
        spl0 = output_L;
        spl1 = output_R;
      );
    
    
    @gfx
      gfx_setfont(1, "Arial", 24);
     
      margin = floor(gfx_texth) * 0.5;
      spacing = margin * 0.5;
     
      sprintf(#capvolt_str, "Capacitor Voltage  =  %.1f dB  |  %.1f dB", lin_to_db(output_L), lin_to_db(output_R) );
     
      gfx_set(1,1,1);
      gfx_x = margin;
      gfx_y = margin;
      gfx_drawstr(#capvolt_str);
     
      bars_y = gfx_y + gfx_texth + spacing;
      bars_max_h = gfx_h - bars_y - margin;
      bar_h = floor((bars_max_h - spacing) / 2);
      max_w = gfx_w - margin * 2;
     
      channel = 0;
      loop(2,
        bar_y = bars_y + channel * (bar_h + spacing);
        bar_w = (channel == 0 ? output_L : output_R) * max_w;
     
        gfx_set(0, 1, 0.7);
        gfx_rect(margin, bar_y, bar_w, bar_h);
     
        channel += 1;
      );

    cap_harm.png cap_imd.png cap_hammer.png cap_dyn.png
     
    Last edited: Jul 2, 2025 at 5:49 PM
  6. paul_audioz

    paul_audioz Kapellmeister

    Joined:
    Feb 21, 2023
    Messages:
    165
    Likes Received:
    73
    I don't know if C++ is a dream stage, but I am quite good at resting if I may say so. I am old, so I have to rest every day and I am particularly good at that. My secret is to go to the bathroom before taking a rest so I don't get waked up by a full bladder. So, lots of experience :winker:
     
  7. Sinus Well

    Sinus Well Audiosexual

    Joined:
    Jul 24, 2019
    Messages:
    2,173
    Likes Received:
    1,715
    Location:
    Sanatorium
    okay, I get it. you're not sure what C++ is, but you're very rusty :like:
     
  8. paul_audioz

    paul_audioz Kapellmeister

    Joined:
    Feb 21, 2023
    Messages:
    165
    Likes Received:
    73
    I am good at resty because I am very rusty. And I think C++ is the American way of school grades, so C++ is a plus more than a C+ which is of course a + more than a plain C. So you see, you are really a smart person, but I am a clever old fartz :winker:
     
  9. reticular

    reticular Producer

    Joined:
    Apr 14, 2022
    Messages:
    210
    Likes Received:
    137
    I´ve heard your next project has a code name "1073".

    :)
     
  10. Lieglein

    Lieglein Audiosexual

    Joined:
    Nov 23, 2018
    Messages:
    1,143
    Likes Received:
    662
    https://audiosex.pro/attachments/cap_harm-png.58199/
    Just a hint for the use of Plugindoctor:
    As you can see the frequency is set to 1kHz but the frequency displayed is 2kHz. This happens with samplerate changes. One always has to adjust the fundamental after. :snuffy:
     
  11. Sinus Well

    Sinus Well Audiosexual

    Joined:
    Jul 24, 2019
    Messages:
    2,173
    Likes Received:
    1,715
    Location:
    Sanatorium
    This is exactly what you want to see when measuring a full-wave rectifier, right? :winker:
     
  12. Lieglein

    Lieglein Audiosexual

    Joined:
    Nov 23, 2018
    Messages:
    1,143
    Likes Received:
    662
    I don't get it.
     
  13. Sinus Well

    Sinus Well Audiosexual

    Joined:
    Jul 24, 2019
    Messages:
    2,173
    Likes Received:
    1,715
    Location:
    Sanatorium
    When you feed a sine wave into a full-wave rectifier, the negative half-cycle of the signal gets "flipped up." This means you now have 2 peaks per period of the original signal, which corresponds to a doubling of the waveform's dominant frequency.
    So, 1kHz becomes 2kHz!
    [​IMG]
    After rectification, we filter the signal to smooth it out a bit and effectively obtain an envelope follower. This is what happens in most detector paths of compressors and meters. The frequency doubling doesn't matter here because we don't hear the signal. It's a control voltage used to drive other processes, for example, to drive the coil of a VU Meter...

    Got it? :wink:
     
    Last edited: Jul 4, 2025 at 10:37 AM
  14. Lieglein

    Lieglein Audiosexual

    Joined:
    Nov 23, 2018
    Messages:
    1,143
    Likes Received:
    662
    Ah, I see. To me this happens also without a rectifier. Maybe my version is just too old. :sad:
     
Loading...
Loading...