Created
January 13, 2022 11:52
-
-
Save interstar/f0c4ccd1cf7422506cee9c541f100391 to your computer and use it in GitHub Desktop.
Noisy Square Wave Synth #5 : Now with low-pass filter
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
--[[ | |
name: square demo 5 | |
description: > generate a squarewave with pulse width control and filter | |
author: synaesmedia | |
--]] | |
require "include/protoplug" | |
local makeFilter = require "include/dsp/cookbook filters" | |
function freq_to_delta(f) | |
local sr = plugin.isSampleRateKnown() and plugin.getSampleRate() or 44100 | |
return 2*math.pi*f/sr | |
end | |
function make_env(a,d,s,r) | |
o = { | |
attack = a, | |
decay = d, | |
sustain = s, | |
release = r, | |
da = 1 / a, | |
dd = (1-s) / d, | |
dr = s / r, | |
v = 0, | |
stage = 3, | |
set_attack = function(self,a) | |
if a < 1 then a = 1 end | |
self.attack = a | |
self.da = 1/a | |
end, | |
set_decay = function(self,d) | |
if d < 1 then d = 1 end | |
self.decay = d | |
self.dd = (1-self.sustain)/d | |
end, | |
set_sustain = function(self,s) | |
self.sustain = s | |
end, | |
set_release = function(self,r) | |
if r < 1 then r = 1 end | |
self.release = r | |
self.dr = self.sustain / r | |
end, | |
noteOn = function(self) | |
self.stage = 0 -- go into attack stage | |
end, | |
noteOff = function(self) | |
self.stage = 3 -- go into release stage | |
end, | |
notePerm = function(self,v2) | |
self.stage = 5 -- go into permanent on stage | |
self.v = v2 | |
end, | |
next = function(self) | |
if self.stage == 0 then -- attack stage | |
self.v = self.v + self.da | |
if self.v >= 1 then self.stage = 1 end | |
return self.v | |
elseif self.stage == 1 then -- decay stage | |
self.v = self.v - self.dd | |
if self.v <= self.sustain then self.stage = 2 end | |
return self.v | |
elseif self.stage == 2 then -- sustain stage | |
return self.v | |
elseif self.stage == 3 then -- release stage | |
self.v = self.v - self.dr | |
if self.v <= 0 then self.stage = 4 end | |
return self.v | |
else | |
-- unknown stage, do nothing | |
return self.v | |
end | |
end, | |
} | |
return o | |
end | |
function make_osc(init_freq, init_amp,init_pw) | |
o = { | |
freq=init_freq, | |
amp=init_amp, | |
delta=0.06, | |
phase=0, | |
pulse_width=init_pw, | |
next=function(self) | |
local v = 1 | |
local s = math.sin(self.phase) | |
if s > self.pulse_width then | |
v = 1 | |
else | |
v = -1 | |
end | |
self.phase=self.phase+self.delta | |
return v*self.amp | |
end, | |
set_freq=function(self,f) | |
self.freq = f | |
self.delta = freq_to_delta(f) | |
end, | |
set_amp=function(self,a) | |
self.amp=a | |
end, | |
set_pw=function(self,pw) | |
self.pulse_width=pw | |
end, | |
set_midi_note = function(self,mn) | |
a = 440 -- frequency of A (common value is 440Hz) | |
n = (a / 32) * math.pow(2,((mn - 9) / 12)) -- (2 ** ((mn - 9) / 12)) | |
self:set_freq(n) | |
self.phase = 0 | |
end | |
} | |
o:set_freq(init_freq) -- so that delta is initialized properly | |
return o | |
end | |
local osc1 = make_osc(440,0.5,0.5) | |
local osc2 = make_osc(441,0.5,0.5) | |
local o12ratio = 1.001 | |
local lfo1 = make_osc(4,1,0.5) | |
local lfo_amp = 0 | |
local env = make_env(1000, 5000, 0.5, 5000) | |
local pw_env = make_env(1000, 5000, 0.5, 4000) | |
local theFilter = makeFilter | |
{ | |
-- initialize filters with current param values | |
type = "lp"; | |
f = 220; | |
gain = 0; | |
Q = 1; | |
} | |
function plugin.processBlock(samples, smax, midiBuf) | |
-- Handle Midi | |
for ev in midiBuf:eachEvent() do | |
if ev:isNoteOn() then | |
note = ev:getNote() | |
osc1:set_midi_note(note) | |
osc2:set_freq(osc1.freq*o12ratio) | |
env:noteOn() | |
pw_env:noteOn() | |
elseif ev:isNoteOff() then | |
env:noteOff() | |
pw_env:noteOff() | |
end | |
end | |
-- Handle sound synthesis | |
for i=0,smax do | |
vlfo = lfo1:next() | |
pw = pw_env:next() | |
osc1:set_pw(pw) | |
osc2:set_pw(pw) | |
v1 = osc1:next() | |
v2 = osc2:next() | |
v = (v1+v2)/2 | |
vf = ((v*lfo_amp*vlfo)+(v*(1-lfo_amp)))/2 | |
filtered = theFilter.process(vf) | |
filtered = filtered * env:next() | |
samples[0][i] = filtered | |
samples[1][i] = filtered | |
end | |
end | |
plugin.manageParams { | |
{ | |
name = "OSC1 Amplitude"; | |
changed = function(val) | |
osc1:set_amp(val) | |
end; | |
}; | |
{ | |
name = "OSC2 Amplitude"; | |
changed = function(val) | |
osc2:set_amp(val) | |
end; | |
}; | |
{ | |
name = "2:1 Freq Ratio"; | |
max = 2; | |
changed = function(val) | |
o12ratio = val | |
end; | |
}; | |
{ | |
name = "Attack"; | |
max = 10000; | |
changed = function(val) | |
env:set_attack(val) | |
end; | |
}; | |
{ | |
name = "Decay"; | |
max = 10000; | |
changed = function(val) | |
env:set_decay(val) | |
end; | |
}; | |
{ | |
name = "Sustain"; | |
changed = function(val) | |
env:set_sustain(val) | |
end; | |
}; | |
{ | |
name = "Release"; | |
max = 100000; | |
changed = function(val) | |
env:set_release(val) | |
end; | |
}; | |
-- | |
{ | |
name = "PW Env Attack"; | |
max = 10000; | |
changed = function(val) | |
pw_env:set_attack(val) | |
end; | |
}; | |
{ | |
name = "PW Env Decay"; | |
max = 10000; | |
changed = function(val) | |
pw_env:set_decay(val) | |
end; | |
}; | |
{ | |
name = "PW Env Sustain"; | |
changed = function(val) | |
pw_env:set_sustain(val) | |
end; | |
}; | |
{ | |
name = "PW Env Release"; | |
max = 100000; | |
changed = function(val) | |
pw_env:set_release(val) | |
end; | |
}; | |
-- | |
{ | |
name = "LFO1 Amplitude"; | |
changed = function(val) | |
lfo_amp = val | |
end; | |
}; | |
{ | |
name = "LFO1 Freq"; | |
changed = function(val) | |
lfo1:set_freq(val*val*1000) | |
end; | |
}; | |
{ | |
name = "LFO1 Pulse Width"; | |
max = 2; | |
changed = function(val) | |
lfo1:set_pw(val-1) | |
end; | |
}; | |
-- | |
{ | |
name = "LPF Cutoff"; | |
min = 10; | |
max = 20000; | |
default = 440; | |
changed = function(val) | |
theFilter.update{f=val}; | |
end; | |
}; | |
{ | |
name = "LFP Resonance"; | |
min = 0.1; | |
max = 30; | |
default = 1; | |
changed = function(val) | |
theFilter.update{Q=val}; | |
end; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment