3 name = "a-High/Low Pass Filter",
6 author = "Ardour Team",
7 description = [[High Low Pass filter with up to 48dB / octave]]
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} -- 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, ~15Hz @ 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
165 ARDOUR.DSP.copy_vector (mem:to_float (off), ins[c]:offset (off), siz)
169 -- high pass is disabled, just copy data.
170 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
172 -- clear output, The filter mixes into the output buffer
173 ARDOUR.DSP.memset (outs[c]:offset (off), 0, siz)
177 -- allways run all filters so that we can interplate as needed.
179 if xfade > 0 and k == ho + 1 then
180 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, 1 - xfade)
183 hp[c][k]:run (mem:to_float (off), siz)
185 if k == ho and xfade == 0 then
186 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
187 elseif k == ho + 1 then
188 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
195 -- copy output of high-pass into "processing memory"
196 ARDOUR.DSP.copy_vector (mem:to_float (off), outs[c]:offset (off), siz)
199 -- Clear output, Low-pass mixes interpolated data into output.
200 -- (Except if the filter is disabled (0) in which case we just keep the output.)
201 ARDOUR.DSP.memset (outs[c]:offset (off), 0, siz)
205 if xfade > 0 and k == lo + 1 then
206 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, 1 - xfade)
209 lp[c][k]:run (mem:to_float (off), siz)
211 if k == lo and xfade == 0 then
212 ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
213 elseif k == lo + 1 then
214 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
220 n_samples = n_samples - siz
231 -------------------------------------------------------------------------------
235 return math.floor (n + .5)
238 function freq_at_x (x, w)
239 -- frequency in Hz at given x-axis pixel
240 return 20 * 1000 ^ (x / w)
243 function x_at_freq (f, w)
244 -- x-axis pixel for given frequency, power-scale
245 return w * math.log (f / 20.0) / math.log (1000.0)
248 function db_to_y (db, h)
249 -- y-axis gain mapping
250 if db < -60 then db = -60 end
251 if db > 12 then db = 12 end
252 return -.5 + round (0.2 * h) - h * db / 60
255 function grid_db (ctx, w, h, db)
256 -- draw horizontal grid line
257 -- note that a cairo pixel at Y spans [Y - 0.5 to Y + 0.5]
258 local y = -.5 + round (db_to_y (db, h))
264 function grid_freq (ctx, w, h, f)
265 -- draw vertical grid line
266 local x = -.5 + round (x_at_freq (f, w))
272 function response (ho, lo, f)
273 -- calculate transfer function response for given
274 -- hi/po pass order at given frequency [Hz]
275 local db = ho * filt['hp']:dB_at_freq (f)
276 return db + lo * filt['lp']:dB_at_freq (f)
279 function render_inline (ctx, w, max_h)
281 local tbl = self:table ():get () -- get shared memory table
282 -- instantiate filter (to calculate the transfer function's response)
284 filt['hp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
285 filt['lp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
288 -- set filter coefficients if they have changed
289 if param_changed (CtrlPorts:array ()) then
290 local ctrl = CtrlPorts:array ()
291 for k = 1,6 do cur[k] = ctrl[k] end
292 filt['hp']:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
293 filt['lp']:compute (ARDOUR.DSP.BiquadType.LowPass, cur[5], cur[6], 0)
296 -- calc height of inline display
297 local h = 1 | math.ceil (w * 9 / 16) -- use 16:9 aspect, odd number of y pixels
298 if (h > max_h) then h = max_h end -- but at most max-height
300 -- ctx is a http://cairographics.org/ context
301 -- http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
304 ctx:rectangle (0, 0, w, h)
305 ctx:set_source_rgba (.2, .2, .2, 1.0)
307 ctx:rectangle (0, 0, w, h)
310 -- set line width: 1px
311 ctx:set_line_width (1.0)
314 local dash3 = C.DoubleVector ()
315 local dash2 = C.DoubleVector ()
318 ctx:set_dash (dash2, 2) -- dotted line: 1 pixel 2 space
319 ctx:set_source_rgba (.5, .5, .5, .8)
320 grid_db (ctx, w, h, 0)
321 ctx:set_dash (dash3, 2) -- dashed line: 1 pixel 3 space
322 ctx:set_source_rgba (.5, .5, .5, .5)
323 grid_db (ctx, w, h, -12)
324 grid_db (ctx, w, h, -24)
325 grid_db (ctx, w, h, -36)
326 grid_freq (ctx, w, h, 100)
327 grid_freq (ctx, w, h, 1000)
328 grid_freq (ctx, w, h, 10000)
331 -- draw transfer function line
332 local ho = math.floor(cur[1])
333 local lo = math.floor(cur[4])
335 ctx:set_source_rgba (.8, .8, .8, 1.0)
336 ctx:move_to (-.5, db_to_y (response(ho, lo, freq_at_x (0, w)), h))
338 local db = response(ho, lo, freq_at_x (x, w))
339 ctx:line_to (-.5 + x, db_to_y (db, h))
341 -- stoke a line, keep the path
342 ctx:stroke_preserve ()
344 -- fill area to zero under the curve
345 ctx:line_to (w, -.5 + round (db_to_y (0, h)))
346 ctx:line_to (0, -.5 + round (db_to_y (0, h)))
348 ctx:set_source_rgba (.5, .5, .5, .5)