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,171
    Likes Received:
    1,714
    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
  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,171
    Likes Received:
    1,714
    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,171
    Likes Received:
    1,714
    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:
    71
    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,171
    Likes Received:
    1,714
    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:
    71
    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:
     
Loading...
Loading...