Vactrol VTL5C3 Modeling

Just something I’ve been trying to work out, someone else might have some fun with it. This is an attempt to model the VTL5C3 vactrol chip that is used by Whitwell and other hardware.

What I’ve done is take the spec sheet and then bash my head in to the wall with simulators to tune two things:

  • Curve fitting, to find a quadratic curve that decently matches the sensor.
  • Gen alg, to tune EWMA weights for the faster “up” and slower “down” ramps.

Might get around to posting the Jupyter notebook for this.

EWMA Note: I don’t know if any literature explicitly approves of using exponentially moving weighted averages for this. I noticed they do a good job of gate smoothing in the antipop I posted earlier. An obvious extension being abused here is to use two separate weights for an “up” or “down” sample to emulate a slew limiter of sorts. I can’t say if the slew limiting here is fully accurate to how light inside the capsules work, but it does emulate some kind of signal drag which the LEDs actually have.

Note: Using this to rapidly drive a VCA will result in popping. This is interesting to me because the physical chip only has a 30ms window to go from “full” to “zero” and is what the real Vactrol Mix Expander uses. There is also a 2.5ms window to go from “zero” to “full.” Yet the real one doesn’t exhibit this popping. I suspect there might be some play going on with the op-amps which gets rid of the final remaining DC offsets, or probably I just modeled the thing entirely wrong.

04/05: Tweaked the way simulations worked and re-ran tuning; there is still a little bit of popping due to the short “up” windows, but it seems a lot more believable now (the Turing Machine pages warn that using “too fast” of a vactrol chip will result in popping.) Still not sure if the op-amps are rounding off the final parts of the pop or not since the datasheet for that part is refusing to download. Code sample edited to use the new constants that work much better.

Code (C++, CC-BY-SA)

// Copyright (c) 2019 by Joshua Cearley
// Creative Commons: CC-BY-SA

#pragma once

#include <cmath>
#include <cassert>

struct vtl5c3 {
   double m_up_eta;
   double m_down_eta;
   double m_value;


   // if you don't set the sample rate, you are going to have a bad time
   void set_samplerate(double rate) {
      assert(rate >= 8000.0);

      double inverse = 1.0 / rate;
      double a = 209.616712;
      double b = 0.000880319056;
      double c = 48113.5069;

      m_down_eta = (pow(inverse, 2) * c) + (inverse * a) + b;

      a = 2746.38887;
      b = 0.000319227063;
      c = -3665711.27;

      m_up_eta = (pow(inverse, 2) * c) + (inverse * a) + b;

   // emulates the response curve of the VTL51C; was derived by curve
   // fitting against the spec sheet provided by the manufacturer
   inline double curve(double x) {
      static const double a = 19977.0579;
      static const double b = 4.72586603;
      static const double c = 22.9420751;
      double resistance = (a * (std::pow(2.71828, (-b * x)))) + c;
      return (1.0 - (resistance / 20000.0));

   // input is within [0, 1] instead of miliamps
   double step(double input) {
      // figure out which ramp we're using
      if (input > m_value) {
	 double np = 1.0 - m_up_eta;
	 m_value = (m_up_eta * input) + (np * m_value);
      } else {
	 double np = 1.0 - m_down_eta;
	 m_value = (m_down_eta * input) + (np * m_value);
      return curve(m_value);

hey! did you ever post the Jupyter notebook for this? I’d be interested in taking a look, particularly at the GA portion.

1 Like