3 name = "a-High and 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])
162 -- process all channels
165 local xfade = hox - ho
166 assert (xfade >= 0 and xfade < 1)
168 ARDOUR.DSP.copy_vector (mem:to_float (off), ins[c]:offset (off), siz)
172 -- high pass is disabled, just copy data.
173 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
175 -- clear output, The filter mixes into the output buffer
176 ARDOUR.DSP.memset (outs[c]:offset (off), 0, siz)
180 -- allways run all filters so that we can interplate as needed.
182 if xfade > 0 and k > ho and k <= ho + 1 then
183 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, 1 - xfade)
186 hp[c][k]:run (mem:to_float (off), siz)
188 if k == ho and xfade == 0 then
189 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
190 elseif k > ho and k <= ho + 1 then
191 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
197 assert (xfade >= 0 and xfade < 1)
199 -- copy output of high-pass into "processing memory"
200 ARDOUR.DSP.copy_vector (mem:to_float (off), outs[c]:offset (off), siz)
203 -- clear output, Low-pass mixes interpolated data into output,
204 -- in which case we just keep the output
205 ARDOUR.DSP.memset (outs[c]:offset (off), 0, siz)
209 if xfade > 0 and k > lo and k <= lo + 1 then
210 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, 1 - xfade)
213 lp[c][k]:run (mem:to_float (off), siz)
215 if k == lo and xfade == 0 then
216 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
217 elseif k > lo and k <= lo + 1 then
218 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
224 n_samples = n_samples - siz
235 -------------------------------------------------------------------------------
239 return math.floor (n + .5)
242 function freq_at_x (x, w)
243 -- frequency in Hz at given x-axis pixel
244 return 20 * 1000 ^ (x / w)
247 function x_at_freq (f, w)
248 -- x-axis pixel for given frequency, power-scale
249 return w * math.log (f / 20.0) / math.log (1000.0)
252 function db_to_y (db, h)
253 -- y-axis gain mapping
254 if db < -60 then db = -60 end
255 if db > 12 then db = 12 end
256 return -.5 + round (0.2 * h) - h * db / 60
259 function grid_db (ctx, w, h, db)
260 -- draw horizontal grid line
261 -- note that a cairo pixel at Y spans [Y - 0.5 to Y + 0.5]
262 local y = -.5 + round (db_to_y (db, h))
268 function grid_freq (ctx, w, h, f)
269 -- draw vertical grid line
270 local x = -.5 + round (x_at_freq (f, w))
276 function response (ho, lo, f)
277 -- calculate transfer function response for given
278 -- hi/po pass order at given frequency [Hz]
279 local db = ho * filt['hp']:dB_at_freq (f)
280 return db + lo * filt['lp']:dB_at_freq (f)
283 function render_inline (ctx, w, max_h)
285 local tbl = self:table ():get () -- get shared memory table
286 -- instantiate filter (to calculate the transfer function's response)
288 filt['hp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
289 filt['lp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
292 -- set filter coefficients if they have changed
293 if param_changed (CtrlPorts:array ()) then
294 local ctrl = CtrlPorts:array ()
295 for k = 1,6 do cur[k] = ctrl[k] end
296 filt['hp']:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
297 filt['lp']:compute (ARDOUR.DSP.BiquadType.LowPass, cur[5], cur[6], 0)
300 -- calc height of inline display
301 local h = 1 | math.ceil (w * 9 / 16) -- use 16:9 aspect, odd number of y pixels
302 if (h > max_h) then h = max_h end -- but at most max-height
304 -- ctx is a http://cairographics.org/ context
305 -- http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
308 ctx:rectangle (0, 0, w, h)
309 ctx:set_source_rgba (.2, .2, .2, 1.0)
311 ctx:rectangle (0, 0, w, h)
314 -- set line width: 1px
315 ctx:set_line_width (1.0)
318 local dash3 = C.DoubleVector ()
319 local dash2 = C.DoubleVector ()
322 ctx:set_dash (dash2, 2) -- dotted line: 1 pixel 2 space
323 ctx:set_source_rgba (.5, .5, .5, .8)
324 grid_db (ctx, w, h, 0)
325 ctx:set_dash (dash3, 2) -- dashed line: 1 pixel 3 space
326 ctx:set_source_rgba (.5, .5, .5, .5)
327 grid_db (ctx, w, h, -12)
328 grid_db (ctx, w, h, -24)
329 grid_db (ctx, w, h, -36)
330 grid_freq (ctx, w, h, 100)
331 grid_freq (ctx, w, h, 1000)
332 grid_freq (ctx, w, h, 10000)
335 -- draw transfer function line
336 local ho = math.floor(cur[1])
337 local lo = math.floor(cur[4])
339 ctx:set_source_rgba (.8, .8, .8, 1.0)
340 ctx:move_to (-.5, db_to_y (response(ho, lo, freq_at_x (0, w)), h))
342 local db = response(ho, lo, freq_at_x (x, w))
343 ctx:line_to (-.5 + x, db_to_y (db, h))
345 -- stoke a line, keep the path
346 ctx:stroke_preserve ()
348 -- fill area to zero under the curve
349 ctx:line_to (w, -.5 + round (db_to_y (0, h)))
350 ctx:line_to (0, -.5 + round (db_to_y (0, h)))
352 ctx:set_source_rgba (.5, .5, .5, .5)