-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
1,444 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
/* | ||
= 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.0; | ||
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.