3 name = "a-High/Low Pass Filter",
6 author = "Ardour Team",
7 description = [[Example Ardour Lua DSP Plugin]]
10 function dsp_ioconfig ()
13 -- allow any number of I/O as long as port-count matches
14 { audio_in = -1, audio_out = -1},
19 function dsp_params ()
22 { ["type"] = "input", name = "High Pass Steepness", min = 0, max = 4, default = 1, enum = true, scalepoints =
31 { ["type"] = "input", name = "High Pass Cut off frequency", min = 5, max = 20000, default = 100, unit="Hz", logarithmic = true },
32 { ["type"] = "input", name = "High Pass Resonance", min = 0.1, max = 6, default = .707, logarithmic = true },
34 { ["type"] = "input", name = "Low Pass Steepness", min = 0, max = 4, default = 1, enum = true, scalepoints =
43 { ["type"] = "input", name = "Low Pass Cut off frequency", min = 20, max = 20000, default = 18000, unit="Hz", logarithmic = true },
44 { ["type"] = "input", name = "Low Pass Resonance", min = 0.1, max = 6, default = .707, logarithmic = true },
48 -- these globals are *not* shared between DSP and UI
49 local hp = {} -- the biquad high-pass filter instances (DSP)
50 local lp = {} -- the biquad high-pass filter instances (DSP)
51 local filt = nil -- the biquad filter instance (GUI, response)
52 local cur = {0, 0, 0, 0, 0, 0, 0} -- current parameters
53 local lpf = 0.03 -- parameter low-pass filter time-constant
54 local chn = 0 -- channel/filter count
56 local mem = nil -- memory x-fade buffer
58 function dsp_init (rate)
59 -- allocate some mix-buffer
60 mem = ARDOUR.DSP.DspShm (8192)
62 -- create a table of objects to share with the GUI
64 tbl['samplerate'] = rate
65 self:table ():set (tbl)
67 -- interpolation time constant, 64fpp
71 function dsp_configure (ins, outs)
72 assert (ins:n_audio () == outs:n_audio ())
73 local tbl = self:table ():get () -- get shared memory table
76 cur = {0, 0, 0, 0, 0, 0}
87 -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:Biquad
89 hp[c][k] = ARDOUR.DSP.Biquad (tbl['samplerate'])
90 lp[c][k] = ARDOUR.DSP.Biquad (tbl['samplerate'])
95 -- helper functions for parameter interpolation
96 function param_changed (ctrl)
98 if ctrl[p] ~= cur[p] then
105 function low_pass_filter_param (old, new, limit)
106 if math.abs (old - new) < limit then
109 return old + lpf * (new - old)
113 -- apply parameters, re-compute filter coefficients if needed
114 function apply_params (ctrl)
115 if not param_changed (ctrl) then
119 -- low-pass filter ctrl parameter values, smooth transition
120 cur[1] = low_pass_filter_param (cur[1], ctrl[1], 0.05) -- HP order x-fade
121 cur[2] = low_pass_filter_param (cur[2], ctrl[2], 1.0) -- HP freq/Hz
122 cur[3] = low_pass_filter_param (cur[3], ctrl[3], 0.01) -- HP quality
123 cur[4] = low_pass_filter_param (cur[4], ctrl[4], 0.05) -- LP order x-fade
124 cur[5] = low_pass_filter_param (cur[5], ctrl[5], 1.0) -- LP freq/Hz
125 cur[6] = low_pass_filter_param (cur[6], ctrl[6], 0.01) -- LP quality
129 hp[c][k]:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
130 lp[c][k]:compute (ARDOUR.DSP.BiquadType.LowPass, cur[5], cur[6], 0)
136 -- the actual DSP callback
137 function dsp_run (ins, outs, n_samples)
138 assert (n_samples < 8192)
141 local changed = false
142 local siz = n_samples
145 -- if a parameter was changed, process at most 64 samples at a time
146 -- and interpolate parameters until the current settings match
148 if param_changed (CtrlPorts:array ()) then
153 while n_samples > 0 do
154 if changed then apply_params (CtrlPorts:array ()) end
155 if siz > n_samples then siz = n_samples end
157 local ho = math.floor(cur[1])
158 local lo = math.floor(cur[4])
160 -- process all channels
163 local xfade = cur[1] - ho
164 assert (xfade >= 0 and xfade < 1)
166 ARDOUR.DSP.copy_vector (mem:to_float (off), ins[c]:offset (off), siz)
170 -- high pass is disabled, just copy data.
171 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
173 -- clear output, The filter mixes into the output buffer
174 ARDOUR.DSP.memset (outs[c]:offset (off), 0, siz)
178 -- allways run all filters so that we can interplate as needed.
180 if xfade > 0 and k == ho + 1 then
181 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, 1 - xfade)
184 hp[c][k]:run (mem:to_float (off), siz)
186 if k == ho and xfade == 0 then
187 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
188 elseif k == ho + 1 then
189 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
195 assert (xfade >= 0 and xfade < 1)
197 -- copy output of high-pass into "processing memory"
198 ARDOUR.DSP.copy_vector (mem:to_float (off), outs[c]:offset (off), siz)
201 -- clear output, Low-pass mixes interpolated data into output,
202 -- in which case we just keep the output
203 ARDOUR.DSP.memset (outs[c]:offset (off), 0, siz)
207 if xfade > 0 and k > lo and k <= lo + 1 then
208 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, 1 - xfade)
211 lp[c][k]:run (mem:to_float (off), siz)
213 if k == lo and xfade == 0 then
214 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
215 elseif k > lo and k <= lo + 1 then
216 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
222 n_samples = n_samples - siz
233 -------------------------------------------------------------------------------
237 return math.floor (n + .5)
240 function freq_at_x (x, w)
241 -- frequency in Hz at given x-axis pixel
242 return 20 * 1000 ^ (x / w)
245 function x_at_freq (f, w)
246 -- x-axis pixel for given frequency, power-scale
247 return w * math.log (f / 20.0) / math.log (1000.0)
250 function db_to_y (db, h)
251 -- y-axis gain mapping
252 if db < -60 then db = -60 end
253 if db > 12 then db = 12 end
254 return -.5 + round (0.2 * h) - h * db / 60
257 function grid_db (ctx, w, h, db)
258 -- draw horizontal grid line
259 -- note that a cairo pixel at Y spans [Y - 0.5 to Y + 0.5]
260 local y = -.5 + round (db_to_y (db, h))
266 function grid_freq (ctx, w, h, f)
267 -- draw vertical grid line
268 local x = -.5 + round (x_at_freq (f, w))
274 function response (ho, lo, f)
275 -- calculate transfer function response for given
276 -- hi/po pass order at given frequency [Hz]
277 local db = ho * filt['hp']:dB_at_freq (f)
278 return db + lo * filt['lp']:dB_at_freq (f)
281 function render_inline (ctx, w, max_h)
283 local tbl = self:table ():get () -- get shared memory table
284 -- instantiate filter (to calculate the transfer function's response)
286 filt['hp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
287 filt['lp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
290 -- set filter coefficients if they have changed
291 if param_changed (CtrlPorts:array ()) then
292 local ctrl = CtrlPorts:array ()
293 for k = 1,6 do cur[k] = ctrl[k] end
294 filt['hp']:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
295 filt['lp']:compute (ARDOUR.DSP.BiquadType.LowPass, cur[5], cur[6], 0)
298 -- calc height of inline display
299 local h = 1 | math.ceil (w * 9 / 16) -- use 16:9 aspect, odd number of y pixels
300 if (h > max_h) then h = max_h end -- but at most max-height
302 -- ctx is a http://cairographics.org/ context
303 -- http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
306 ctx:rectangle (0, 0, w, h)
307 ctx:set_source_rgba (.2, .2, .2, 1.0)
309 ctx:rectangle (0, 0, w, h)
312 -- set line width: 1px
313 ctx:set_line_width (1.0)
316 local dash3 = C.DoubleVector ()
317 local dash2 = C.DoubleVector ()
320 ctx:set_dash (dash2, 2) -- dotted line: 1 pixel 2 space
321 ctx:set_source_rgba (.5, .5, .5, .8)
322 grid_db (ctx, w, h, 0)
323 ctx:set_dash (dash3, 2) -- dashed line: 1 pixel 3 space
324 ctx:set_source_rgba (.5, .5, .5, .5)
325 grid_db (ctx, w, h, -12)
326 grid_db (ctx, w, h, -24)
327 grid_db (ctx, w, h, -36)
328 grid_freq (ctx, w, h, 100)
329 grid_freq (ctx, w, h, 1000)
330 grid_freq (ctx, w, h, 10000)
333 -- draw transfer function line
334 local ho = math.floor(cur[1])
335 local lo = math.floor(cur[4])
337 ctx:set_source_rgba (.8, .8, .8, 1.0)
338 ctx:move_to (-.5, db_to_y (response(ho, lo, freq_at_x (0, w)), h))
340 local db = response(ho, lo, freq_at_x (x, w))
341 ctx:line_to (-.5 + x, db_to_y (db, h))
343 -- stoke a line, keep the path
344 ctx:stroke_preserve ()
346 -- fill area to zero under the curve
347 ctx:line_to (w, -.5 + round (db_to_y (0, h)))
348 ctx:line_to (0, -.5 + round (db_to_y (0, h)))
350 ctx:set_source_rgba (.5, .5, .5, .5)