Skip to content

Instantly share code, notes, and snippets.

@modlfo
Last active August 26, 2016 16:07
Show Gist options
  • Select an option

  • Save modlfo/77bc427f231a1d5b7d8a to your computer and use it in GitHub Desktop.

Select an option

Save modlfo/77bc427f231a1d5b7d8a to your computer and use it in GitHub Desktop.
Vult examples
/*
= A simple synthesizer with one LFO and a Delay effect
CC30 - Volume
CC31 - Detune/Resonance
CC32 - LFO rate
CC33 - LFO amount (bipolar)
CC34 - Delay time
CC35 - Delay feedback
*/
// Used to soften the transitions of controls
fun smooth(input:real) : real {
mem x;
x = x+(input-x)*0.005;
return x;
}
// Returns true every time the input value changes
fun change(x:real):bool {
mem pre_x;
val v:bool = pre_x<>x;
pre_x = x;
return v;
}
// Returns true if the value changes from 0 to anything
fun edge(x:bool):bool {
mem pre_x;
val v:bool = (pre_x<>x) && (pre_x==false);
pre_x = x;
return v;
}
// Returns true every 'n' calls
fun each(n:int) : bool {
mem count;
val ret = (count == 0);
count = (count + 1) % n;
return ret;
}
// Converts the MIDI note to increment rate at a 44100 sample rate
fun pitchToRate(d) return 8.1758*exp(0.0577623*d)/44100.0;
fun phasor(pitch:real,reset:bool){
mem rate,phase;
if(change(pitch))
rate = pitchToRate(pitch);
phase = if reset then 0.0 else (phase + rate) % 1.0;
return phase;
}
// A simple LFO with reset signal
fun lfo(f:real,gate:bool) : real {
mem phase;
val rate = f * 10.0/44100.0;
if(edge(gate)) phase = 0.0;
phase = phase + rate;
if(phase>1.0) phase = phase-1.0;
return sin(phase*2.0*3.141592653589793)-0.5;
}
// Implements the resonant filter simulation as shown in
// http://en.wikipedia.org/wiki/Phase_distortion_synthesis
fun phd_osc(pitch:real,detune:real) : real {
mem pre_phase1; // used to detect when the phase wrapps from 1 to 0
val phase1 = phasor(pitch,false);
val comp = 1.0 - phase1;
val reset = (pre_phase1 - phase1) > 0.5;
pre_phase1 = phase1;
val phase2 = phasor(pitch+smooth(detune)*32.0,reset);
val sine = sin(2.0*3.14159265359*phase2);
return sine*comp;
}
// Simple delay.
fun delay(x:real, time:real, feedback:real) : real {
mem buffer : array(real,44100);
mem write_pos;
// Constraints the parameter values
time = clip(time,0.0,1.0);
feedback = clip(feedback,0.0,1.0);
// Gets the position in the buffer to read
val index_r = real(size(buffer)) * time;
val index_i = int(floor(index_r));
val delta = write_pos - index_i;
val read_pos = if delta < 0 then size(buffer)+delta else delta;
// Gets the decimal part of the position
val decimal = index_r - real(index_i);
// Reads the values in the buffer
val x1 = get(buffer,read_pos);
val x2 = get(buffer,(read_pos+1) % size(buffer));
// Interpolates the value
val ret = (x2-x1)*decimal + x1;
// Write the data to the buffer
write_pos = (write_pos+1) % size(buffer);
_ = set(buffer,write_pos,clip(x+feedback*ret,-1.0,1.0));
return ret;
}
/* These three functions handle midi on/off events in order to behave
* like a monophonic sinthesizer that can hold 4 notes */
// Activates a note and returns the current note value
fun mono_noteOn(n:int){
mem count,pre;
mem notes : array(int,4);
// Do not add more that the size of array
if(count < size(notes)) {
_ = set(notes,count,n);
pre = n;
if(count < size(notes)) count = count + 1;
}
return pre;
}
// Deactivates a note and returns the following note value;
and mono_noteOff(n:int){
mem count,pre;
mem notes : array(int,4);
val found = false;
val pos;
val i = 0;
// if there are no notes, no dot do anything
if(count == 0)
return pre;
// Finds the location of the note
while(i < size(notes) && not(found)){
if(get(notes,i) == n) {
pos = i;
found = true;
}
i = i + 1;
}
// if the note was found moves all the notes one location
if(found) {
val k = pos + 1;
while(k < size(notes)) {
_ = set(notes,k-1,get(notes,k));
k = k + 1;
}
// If found, decrease the number of active notes
if(found && count>0) {
count = count - 1;
pre = get(notes,count - 1);
}
}
return pre;
}
// Returns 1 if any note is active
and mono_isGateOn() {
mem count;
return count > 0;
}
// Main processing function
fun process(input:real){
mem volume,detune; // values set in 'controlChange'
mem pitch;
mem lfo_rate,lfo_amt;
mem time, feedback;
val gate = notes:mono_isGateOn();
// creates one LFO
val lfo_val = lfo(lfo_rate,gate)*lfo_amt;
// creates one oscillator
val o1 = phd_osc(pitch,detune+lfo_val);
// gets the amplification by using a low-pass on the gate
val amp = smooth(if gate then 1.0 else 0.0);
val osc_out = o1 * amp;
val delay_out = delay(osc_out,smooth(time),smooth(feedback));
return volume * (osc_out+delay_out) /2.0;
}
// Called when a note On is received
and noteOn(note:int,velocity:int){
mem pitch = real(notes:mono_noteOn(note));
}
// Called when a note Off is received
and noteOff(note){
mem pitch = real(notes:mono_noteOff(note));
}
// Called when a control changes
and controlChange(control,value){
mem volume;
mem detune;
mem lfo_rate, lfo_amt;
mem time, feedback;
// Control 30 defines the volume
if(control==30) volume = value/127.0;
if(control==31) detune = value/127.0;
if(control==32) lfo_rate = value/127.0;
if(control==33) lfo_amt = 2.0*((real(value)/127.)-0.5);
if(control==34) time = value/127.0;
if(control==35) feedback = value/127.0;
}
// Called on initialization to define initial values
and default(){
mem volume = 0.5;
mem pitch = 45.0;
mem detune = 0.8;
mem lfo_rate = 0.07;
mem lfo_amt = -0.8;
mem time = 0.5;
mem feedback = 0.5;
}
/*
= Phase distortion oscillator =
CC30 - Volume
CC31 - Detune/Resonance
Keyboard - Pitch
*/
// Used to soften the transitions of controls
fun smooth(input){
mem x;
x = x+(input-x)*0.005;
return x;
}
// Returns true every time the input value changes
fun change(x):bool {
mem pre_x;
val v:bool = pre_x<>x;
pre_x = x;
return v;
}
// Converts the MIDI note to increment rate at a 44100 sample rate
fun pitchToRate(d) return 8.1758*exp(0.0577623*d)/44100.0;
// Produces a non-bandwith-limited saw used as phase of oscillators
fun phasor(pitch,reset){
mem rate,phase;
if(change(pitch))
rate = pitchToRate(pitch);
phase = if reset then 0.0 else (phase + rate) % 1.0;
return phase;
}
// Implements the resonant filter simulation as shown in
// http://en.wikipedia.org/wiki/Phase_distortion_synthesis
fun phd_osc(pitch:real,detune:real) : real {
mem pre_phase1; // used to detect when the phase wrapps from 1 to 0
val phase1 = phasor(pitch,false);
val comp = 1.0 - phase1;
val reset = (pre_phase1 - phase1) > 0.5;
pre_phase1 = phase1;
val phase2 = phasor(pitch+smooth(detune)*32.0,reset);
val sine = sin(2.0*3.14159265359*phase2);
return sine*comp;
}
// Main processing function
fun process(input:real){
mem volume,detune; // values set in 'controlChange'
mem pitch;
// Generates one oscilator
val o1 = phd_osc(pitch,detune);
return smooth(volume)*o1;
}
// Called when a note On is received
and noteOn(note:int,velocity:int){
mem pitch = real(note);
}
// Called when a note Off is received
and noteOff(note:int){
}
// Called when a control changes
and controlChange(control:int,value:int){
mem volume;
mem detune;
// Control 30 defines the volume
if(control==30) volume = real(value)/127.0;
if(control==31) detune = real(value)/127.0;
}
// Called on initialization to define initial values
and default(){
mem volume = 0.5;
mem pitch = 45.0;
mem detune = 0.0;
}
/*
= A simple synthesizer with one LFO
CC30 - Volume
CC31 - Detune/Resonance
CC32 - LFO rate
CC33 - LFO amount (bipolar)
*/
// Used to soften the transitions of controls
fun smooth(input:real) : real {
mem x;
x = x+(input-x)*0.005;
return x;
}
// Returns true every time the input value changes
fun change(x:real):bool {
mem pre_x;
val v:bool = pre_x<>x;
pre_x = x;
return v;
}
// Returns true if the value changes from 0 to anything
fun edge(x:bool):bool {
mem pre_x;
val v:bool = (pre_x<>x) && (pre_x==false);
pre_x = x;
return v;
}
// Returns true every 'n' calls
fun each(n:int) : bool {
mem count;
val ret = (count == 0);
count = (count + 1) % n;
return ret;
}
// Converts the MIDI note to increment rate at a 44100 sample rate
fun pitchToRate(d) return 8.1758*exp(0.0577623*d)/44100.0;
fun phasor(pitch:real,reset:bool){
mem rate,phase;
if(change(pitch))
rate = pitchToRate(pitch);
phase = if reset then 0.0 else (phase + rate) % 1.0;
return phase;
}
// A simple LFO with reset signal
fun lfo(f:real,gate:bool) : real {
mem phase;
val rate = f * 10.0/44100.0;
if(edge(gate)) phase = 0.0;
phase = phase + rate;
if(phase>1.0) phase = phase-1.0;
return sin(phase*2.0*3.141592653589793)-0.5;
}
// Implements the resonant filter simulation as shown in
// http://en.wikipedia.org/wiki/Phase_distortion_synthesis
fun phd_osc(pitch:real,detune:real) : real {
mem pre_phase1; // used to detect when the phase wrapps from 1 to 0
val phase1 = phasor(pitch,false);
val comp = 1.0 - phase1;
val reset = (pre_phase1 - phase1) > 0.5;
pre_phase1 = phase1;
val phase2 = phasor(pitch+smooth(detune)*32.0,reset);
val sine = sin(2.0*3.14159265359*phase2);
return sine*comp;
}
/* These three functions handle midi on/off events in order to behave
* like a monophonic sinthesizer that can hold 4 notes */
// Activates a note and returns the current note value
fun mono_noteOn(n:int){
mem count,pre;
mem notes : array(int,4);
// Do not add more that the size of array
if(count < size(notes)) {
_ = set(notes,count,n);
pre = n;
if(count < size(notes)) count = count + 1;
}
return pre;
}
// Deactivates a note and returns the following note value;
and mono_noteOff(n:int){
mem count,pre;
mem notes : array(int,4);
val found = false;
val pos;
val i = 0;
// if there are no notes, no dot do anything
if(count == 0)
return pre;
// Finds the location of the note
while(i < size(notes) && not(found)){
if(get(notes,i) == n) {
pos = i;
found = true;
}
i = i + 1;
}
// if the note was found moves all the notes one location
if(found) {
val k = pos + 1;
while(k < size(notes)) {
_ = set(notes,k-1,get(notes,k));
k = k + 1;
}
// If found, decrease the number of active notes
if(found && count>0) {
count = count - 1;
pre = get(notes,count - 1);
}
}
return pre;
}
// Returns 1 if any note is active
and mono_isGateOn() {
mem count;
return count > 0;
}
// Main processing function
fun process(input:real){
mem volume,detune; // values set in 'controlChange'
mem pitch;
mem lfo_rate,lfo_amt;
val gate = notes:mono_isGateOn();
// creates one LFO
val lfo_val = lfo(lfo_rate,gate)*lfo_amt;
// creates one oscillator
val o1 = phd_osc(pitch,detune+lfo_val);
// gets the amplification by using a low-pass on the gate
val amp = smooth(if gate then 1.0 else 0.0);
return volume * o1 * amp;
}
// Called when a note On is received
and noteOn(note:int,velocity:int){
mem pitch = real(notes:mono_noteOn(note));
}
// Called when a note Off is received
and noteOff(note){
mem pitch = real(notes:mono_noteOff(note));
}
// Called when a control changes
and controlChange(control,value){
mem volume;
mem detune;
mem lfo_rate;
mem lfo_amt;
// Control 30 defines the volume
if(control==30) volume = value/127.0;
if(control==31) detune = value/127.0;
if(control==32) lfo_rate = value/127.0;
if(control==33) lfo_amt = 2.0*((real(value)/127.)-0.5);
}
// Called on initialization to define initial values
and default(){
mem volume = 0.5;
mem pitch = 45.0;
mem detune = 0.8;
mem lfo_rate = 0.07;
mem lfo_amt = -0.8;
}
/*
= A synthesizer with a bandwidth-limited multi-oscillator
and a state variable filter
CC30 - Volume
CC31 - Wave selector (pulse,saw,triangle)
CC32 - Pulse width
CC33 - LFO rate
CC34 - LFO amount (bipolar, connected to PW)
CC35 - Filter cut
CC36 - Filter resonance
*/
// Minimum value represented with fixed-point q16
fun minFixed() return 0.0000152588;
// Returns true every time the input value changes
fun change(x:real):bool {
mem pre_x;
val v:bool = pre_x<>x;
pre_x = x;
return v;
}
// Returns true every time the input value changes
fun bchange(x:bool):bool {
mem pre_x;
val v:bool = pre_x<>x;
pre_x = x;
return v;
}
// Returns true if the value changes from 0 to anything
fun edge(x):bool {
mem pre_x;
val v:bool = (pre_x<>x) && (pre_x==true);
pre_x = x;
return v;
}
// Returns true every 'n' calls
fun each(n){
mem count;
val ret = (count == 0);
count = (count + 1) % n;
return ret;
}
// Returns true if the input value is near zero (< 1e-2)
fun near_zero(x) : bool return abs(x)<2e-2;
// Filters the DC component of a signal
fun dcblock(x0:real) : real {
mem x1,y1;
val y0 = x0-x1+y1*0.995;
x1,y1 = x0,y0;
return y0;
}
// Used to soften the transitions of controls
fun smooth(input:real) : real {
mem x;
x = x+(input-x)*0.005;
return x;
}
// Average two samples
fun lpfilter(x:real) : real {
mem pre_x;
val ret = (x+pre_x)/2.;
pre_x = x;
return ret;
}
// ==== OSCILLATOR =====
// Converts the MIDI note to increment rate at a 44100 sample rate
fun pitchToRate(d) : real
return 8.1758*exp(0.0577623*d)/44100.0;
// Generates a BW-limited pulse train given the phase and the number of harmonics
fun pulse_train(m:real,phase:real) : real {
val pi_phase = phase * 3.141592653589793;
val denominator1 = sin(pi_phase);
val tmp1 = 0.;
if(near_zero(denominator1)) {
tmp1 = 1.;
}
else {
tmp1 = sin(m * pi_phase);
tmp1 = tmp1 / (m * denominator1);
}
return tmp1;
}
// Generates BW-limited waveforms using the blit algorithm.
// It can generate PWM puses, saws and triangles.
fun osc(pitch:real,pw:real,wave:real) : real {
mem m;
mem rate;
mem phase;
mem state_triang;
mem state_pulse;
mem state_saw;
mem output;
val fixed_pitch = 0.;
if(wave < 2. / 3.) {
fixed_pitch = pitch;
}
else {
fixed_pitch = pitch + 12.;
}
// Updates the parameters if the pitch changed
if(change(fixed_pitch)) {
rate = pitchToRate(fixed_pitch);
val p = 1. / rate;
val maxHarmonics = floor(p/2.);
m = 2. * maxHarmonics + 1.;
}
// Generates a shifted version of the phase
val shift05 = 0.5 + pw * 0.49;
val shift = phase + shift05;
if(shift > 1.) {
shift = shift - 1.;
}
// Generates the first pulse train
val tmp1 = pulse_train(m,phase);
// Generates the second pulse train
val tmp2 = pulse_train(m,shift);
// Updates the phase
phase = phase + rate;
if(phase > 1.) {
phase = phase - 1.;
}
// Calculates the waveforms based on the pulse trains
state_pulse = clip(state_pulse * 0.9995 + tmp1 - tmp2, -1., 1.);
state_saw = clip(state_saw * 0.9995 + (tmp1 + tmp2 - 2. * rate)/shift05/2., -1.,1.);
state_triang = clip(state_triang * 0.9995 + 2. * state_pulse * rate, -1.,1.);
// Select the wave to output
if(wave < 1. / 3.) {
output = state_pulse;
}
else if(wave < 2. / 3.) {
output = 2. * state_saw;
}
else {
output = 2. * state_triang*(1. + pw);
}
output = dcblock(output);
return clip(output/4.,-1.,1.);
}
// ==== FILTER =====
// Calculates one step of the oversampled state-variable filter
fun svf_step(input:real,g:real,q:real,sel:int) : real {
mem dlow, dband;
val low = dlow + g * dband;
val high = input - low - q*dband;
val band = g * high + dband;
val notch = high + low;
dband = clip(band,-1.,1.);
dlow = clip(low,-1.,1.);
val output =
if sel == 0 then low else
if sel == 1 then high else
if sel == 2 then band else
notch;
return output;
}
// Main function for the state-variable filter with 2x of oversampling
fun svf(input,fc,q,sel){
mem g;
fc = clip(fc, 0., 1.);
q = clip(q, 0., 1.);
val fix_q = 2. * (1. - q);
if(change(fc)){
g = fc/2.;
}
// In Vult oversamplig in very easy!
val x1 = step:svf_step(input,g,fix_q,sel);
val x2 = step:svf_step(input,g,fix_q,sel);
return (x1+x2)/2.;
}
// ======= LFO ======
fun lfo(f,gate){
mem phase;
val rate = f * 100. * minFixed() + minFixed();
if(edge(gate)) phase = 0.;
if(each(4))
phase = phase + rate;
if(phase>1.) phase = phase-1.;
return sin(phase * 2. * 3.141592653589793)+0.5;
}
// ==== MONOPHONIC VOICE =====
/* These three functions handle midi on/off events in order to behave
* like a monophonic sinthesizer that can hold 4 notes */
// Activates a note and returns the current note value
fun mono_noteOn(n:int){
mem count,pre;
mem notes : array(int,4);
// Do not add more that the size of array
if(count < size(notes)) {
_ = set(notes,count,n);
pre = n;
if(count < size(notes)) count = count + 1;
}
return pre;
}
// Deactivates a note and returns the following note value;
and mono_noteOff(n:int){
mem count,pre;
mem notes : array(int,4);
val found = false;
val pos;
val i = 0;
// if there are no notes, no dot do anything
if(count == 0)
return pre;
// Finds the location of the note
while(i < size(notes) && not(found)){
if(get(notes,i) == n) {
pos = i;
found = true;
}
i = i + 1;
}
// if the note was found moves all the notes one location
if(found) {
val k = pos + 1;
while(k < size(notes)) {
_ = set(notes,k-1,get(notes,k));
k = k + 1;
}
// If found, decrease the number of active notes
if(found && count>0) {
count = count - 1;
pre = get(notes,count - 1);
}
}
return pre;
}
// Returns 1 if any note is active
and mono_isGateOn() {
mem count;
return count > 0;
}
// Main process function
fun process(i:real){
mem volume;
mem pitch, pw, wave; // oscillator parameters
mem lfo_rate,lfo_amt; // LFO parameter
mem cut, res; // Filter parameters
val gate = monoin:mono_isGateOn();
// Creates one LFO
val lfo1 = lfo(lfo_rate,gate)*lfo_amt;
// Creates one oscillator
val o1 = osc(pitch,pw+lfo1,wave);
// gets the amplification by using a low-pass on the gate
val amp_env = smooth(if gate then 1.0 else 0.0);
// creates one state-variable filter
val output = amp_env * svf(o1,cut,res,0);
return volume * output;
}
and noteOn(note:int,velocity:int){
mem pitch = real(monoin:mono_noteOn(note));
}
and noteOff(note:int){
mem pitch = real(monoin:mono_noteOff(note));
}
// Called when a control changes
and controlChange(control:int,value:int){
mem volume;
mem pitch, pw, wave; // oscillator parameters
mem lfo_rate,lfo_amt; // LFO parameter
mem cut, res; // Filter parameters
val value_0_1 = real(value) / 127.0;
val value_m1_1 = value_0_1 * 2.0 - 1.0;
if(control == 30) volume = value_0_1;
if(control == 31) wave = value_0_1;
if(control == 32) pw = value_0_1;
if(control == 33) lfo_rate = value_0_1;
if(control == 34) lfo_amt = value_m1_1;
if(control == 35) cut = value_0_1;
if(control == 36) res = value_0_1;
}
// Called on initialization to define initial values
and default() @[init] {
mem pw = 0.0;
mem pitch = 42.0;
mem cut = 1.0;
mem res = 0.0;
mem amp_s = 1.0;
mem lfo_amt = 0.5;
mem lfo_rate = 0.0;
mem volume = 0.5;
}
/* You can use a this template to start a program */
// Main processing function
// 'input' is by default a sine wave at 440 Hz
fun process(input:real){
return input;
}
// Called when a note On is received
and noteOn(note:int,velocity:int){
}
// Called when a note Off is received
and noteOff(note:int){
}
// Called when a control changes
and controlChange(control:int,value:int){
}
// Called on initialization to define initial values
and default(){
}
/*
= A simple volume control =
CC30 - Volume
*/
// Used to soften the transitions of controls
fun smooth(input){
mem x;
x = x+(input-x)*0.005;
return x;
}
// Main processing function
// 'input' is by default a sine wave at 440 Hz
fun process(input:real){
mem volume; // the value is set in 'controlChange'
return input*smooth(volume);
}
// Called when a note On is received
and noteOn(note:int,velocity:int){
}
// Called when a note Off is received
and noteOff(note:int){
}
// Called when a control changes
and controlChange(control:int,value:int){
mem volume;
// Control 30 defines the volume
if(control==30) volume = real(value)/127.0;
}
// Called on initialization to define initial values
and default(){
mem volume = 0.5;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment