Formula One

A script/formula evaluation module for VCVRack. This module can do almost anything, and it is quite fast.

It is based on the exprtk expression library
which has very good benchmarks.

It is not intended for replacing native modules because they still will be faster i.e. consume less CPU
and provide more usability. It is for experimenting,learning and making special things.

Examples and Usecases

The examples are available as factory presets.

Simple polyphonic oscillator

var freq := 261.626 * pow(2,w);
var p:= bufr(chn);
var o:=a*4*(
   sin(2*pi*p)+
   sin(6*pi*p)/3+
   sin(10*pi*p)/5+
   sin(14*pi*p)/7
);
var phs:= p+stim*freq;
phs-=trunc(phs);
bufw(chn,phs);
var out:=o; 

Step by Step

var freq := 261.626 * pow(2,w); Declare a variable freq and assign
the computed frequency from the input w (V/Oct).

var p:= bufr(chn); Read the current phase for chn from the buffer
into the variable p. The script is evaluated for every
channel which is detected in the w input and provides the
value of the input w for the current channel via the global variable w.
The current channel number is set in the global variable chn.

var o:=a*4*(sin(2*pi*p)+sin(6*pi*p)/3+sin(10*pi*p)/5+sin(14*pi*p)/7);
compute the output sample from the current phase. the global variable a holds the current
value of the knob a;

var phs:= p+stim*freq; phs-=trunc(phs); compute the next phase using the global variable
stim which holds the current sample time (1/sampleRate).

bufw(chn,phs); write the new phase into the buffer.

var out:=o; the last expression value in the script is returned to the module and written
into the output out.

External Phase Controlled Oscillator

a*5*(
   sin(2*pi*t)+
   sin(6*pi*t)/3+
   sin(10*pi*t)/5+
   sin(14*pi*t)/7
)

This example does the same as the one above but uses the special input t as phase.
The values of t are normalized from -5V/5V to 0V/1V.

Wave Folder

sin((c*5)*t*2*pi+b*5)*a*5

The same principle can be used to make a wave folder.
The knob c controls the depth and the knob b controls the offset.

Simple Polyphonic Filter

This example shows how to implement a simple LP filter. The algorithm is directly taken from the VCV Rack
source

var f := clamp(2^(c*8),0.001,0.5);
var fc := 2/f;
var out:= (x + bufr(chn) - buf1r(chn) * (1 - fc)) /(1 + fc);
bufw(chn,x); 
buf1w(chn,out);
dcb(chn,out);

Step by Step

var f := clamp(2^(c*8),0.001,0.5); var fc := 2/f; Compute the cutoff frequency from the knob c.

var out:= (x + bufr(chn) - buf1r(chn) * (1 - fc)) /(1 + fc);
bufw(chn,x); 
buf1w(chn,out);

Compute the next filter sample. The two needed state variables are stored per channel in the
buffer (0) and buffer 1. There are 4 buffers of size 4096 available which can be read
via bufr,buf1r,buf2r,buf3r and written via bufw,buf1w,buf2w,buf3w

dcb(chn,out); output the dc blocked sample.

Polyphonic Comb Filter (Chorus,Flanger or whatever)

var delay_ms:= 2.1;
var len:= delay_ms/1000*sr;
rblen(chn,len);
var c0 := clamp((c+1)/2+y/10*a,0,0.99);
var x0 := rbget(chn,c0*len); 
var nx := x/2 + x0 * d;
rbpush(chn,nx);
var out:=x0+nx;
var x1 := rbget(chn,c0*len/2);
out1:=dcb2(chn,x1+nx);
dcb(chn,out);

This example shows the use of the provided ring buffers.

Step by Step

var delay_ms:= 2.1; var len:= delay_ms/1000*sr; Define the delay length and compute
the buffer size in samples using the global variabel sr (sample rate).

rblen(chn,len); set the ring buffer length for each channel. There are 16 ring buffers available with
maximum length of 48000.

var c0 := clamp((c+1)/2+y/10*a,0,0.99);. Compute the relative read position of the buffer
using the knob value c and the input y attenuated with the knob value a.

var x0 := rbget(chn,c0*len); read the buffer value at the computed index.

var nx := x/2 + x0 * d; rbpush(chn,nx); mix with the input,
add feedback attenuated with knob dand write it into the ring buffer.

var out:=x0+nx; store the output in the variable out.

var x1 := rbget(chn,c0*len/2); read a second value from the buffer for a stereo effect.

out1:=dcb2(chn,x1+nx); output the ‘right’ stereo channel in output out1 via
setting the global variable out1. There are two dc blockers available per channel.

dcb(chn,out); output the left channel in out. (The value of the last expression is
always written into the output out).

Stereo Delay

var delayR:= 0.375;
var delayL:= 0.750;
var lenR:= delayR*sr;
var lenL:= delayL*sr;
rblen(0,lenL);
rblen(1,lenR);

var xL := rbget(0,0);
var xR := rbget(1,0);
rbpush(0,x+a*xL);
rbpush(1,x+a*xR);
out1:=dcb(0,(xR*b)+(x*(1-b)));
dcb(1,xL*b+x*(1-b));

Similar to the last example. The knob a controls the feedback and the knob b dry/wet.
The position in the call rbget is relative to the last write position + 1. With position 0 it returns the
sample which was written length samples before. rbget(0,-1) would return the last written sample.

Polyphonic Random and Hold

if(st(chn,z)>0) {
  bufw(chn,rnd());
}
var out := bufr(chn)*10*a;

There are 4 Schmitt Triggers available per channel (st,st1,st2,st3).
The buffer is used to hold the value until the next trigger arrives.
NB the polyphony is determined by the input channels in the following way:
If the input t is connected the channels of t is used, otherwise the maximum of the
channels of the inputs w,x,y,z. The image above shows the case with one channel.

Simple Sequencer

var seq[8] := { 0,3/12,5/12,
             7/12,10/12,7/12,
             5/12,3/12};

if(st(0,z)>0) { 
 v1:=v1+1;
 if(v1>=8) v1:=0;
} 
if(st1(0,y)>0) v1:=0;
var out := seq[v1];

This example shows the use of one of the 8 global variables v1-v8.
v1 is used as the current position of the sequence which is advanced on arrival
on a trigger on input z. The input y is used for resetting the sequence.

Chord Sequencer

if(v3==0) {
  var n := -1;
  for(var i:=0;i<36;i+=1) {
    bufw(i,n);
    n+=1/12;
  };
  v3:=1;
};
var chord[16]:={9,12,16,19, 9,12,14,18,
                7,11,14,18, 7,11,12,16};
if(st(0,z)>0) {v1+=1; if(v1>=4) v1:=0;}
var out: = bufr(chord[chn+v1*4]);

All variables and buffers are cleared if the script is compiled due to a change.
Here the variable v3 is used to fill a buffer with note values (V/Oct) only once.
(otherwise it costs much CPU).

Similar to the RndH example, a trigger on the input z advances the sequence. The notes of a single
chord are distributed in 4 channels.

Gate Sequencer

var data[16] := { 
1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,1};
var dur := 10; var gate:=0;
if(st(chn,z)>0) {
    if(data[v1]>0) {
      bufw(chn,dur);
    } else {
      bufw(chn,0);
    } 
};
if(bufr(chn)>0) { 
  gate:=10; bufw(chn,bufr(chn)-1);
};
if(st1(0,z)>0) {
  v1+=1;
  if(v1>=16) v1:=0;
};
var out:= gate;

This example shows how to make pulses. The duration is defined in samples.
When a trigger arrives and the current position (v1) has a one, a state variable
is set to the duration and decremented on each run until it is zero.
While greater than zero the output is set to 10V.

A Line Segment Envelope Generator

var vals[6]:={0,5,3,2,2,0};
var durs[5]:={0.3,0.2,0.5,0.3,0.2};
if(st(0,w)>0) { v1:=0; v2:=0; v3:=0; }
var end:=0;
if(v1>=v2+durs[v3]) { // advance
  if(v3>=5) end:=1;
  else { v2+=durs[v3]; v3+=1; }
};
var o;var pct;
if(end==1) {
  o:=vals[5]; 
} else {
  pct:= (v1-v2)/durs[v3];
  o:=vals[v3]+(vals[v3+1]-vals[v3])*pct;  
};
v1+=stim;
o;

Outputs line segments according to the values and duration arrays. It is triggered via the
w input.

GitHub

View Github