Also use xjadeo 64bit windows version
[ardour.git] / scripts / HiAndLowPass.lua
1 ardour {
2         ["type"]    = "dsp",
3         name        = "a-High/Low Pass Filter",
4         category    = "Filter",
5         license     = "GPLv2",
6         author      = "Ardour Team",
7         description = [[High and Low Pass Filter with de-zipped controls, written in Ardour-Lua]]
8 }
9
10 function dsp_ioconfig ()
11         return
12         {
13                 -- allow any number of I/O as long as port-count matches
14                 { audio_in = -1, audio_out = -1},
15         }
16 end
17
18
19 function dsp_params ()
20         return
21         {
22                 { ["type"] = "input", name = "High Pass Steepness", min = 0, max = 4, default = 1, enum = true, scalepoints =
23                         {
24                                 ["Off"] = 0,
25                                 ["12dB/oct"] = 1,
26                                 ["24dB/oct"] = 2,
27                                 ["36dB/oct"] = 3,
28                                 ["48dB/oct"] = 4,
29                         }
30                 },
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 },
33
34                 { ["type"] = "input", name = "Low Pass Steepness", min = 0, max = 4, default = 1, enum = true, scalepoints =
35                         {
36                                 ["Off"] = 0,
37                                 ["12dB/oct"] = 1,
38                                 ["24dB/oct"] = 2,
39                                 ["36dB/oct"] = 3,
40                                 ["48dB/oct"] = 4,
41                         }
42                 },
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 },
45         }
46 end
47
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
55 local lpf_chunk = 0 -- chunk size for audio processing when interpolating parameters
56 local max_freq = 20000
57
58 local mem = nil -- memory x-fade buffer
59
60 function dsp_init (rate)
61         -- allocate some mix-buffer
62         mem = ARDOUR.DSP.DspShm (8192)
63
64         -- max allowed cut-off frequency
65         max_freq = .499 * rate
66
67         -- create a table of objects to share with the GUI
68         local tbl = {}
69         tbl['samplerate'] = rate
70         tbl['max_freq'] = max_freq
71         self:table ():set (tbl)
72
73
74         -- Parameter smoothing: we want to filter out parameter changes that are
75         -- faster than 15Hz, and interpolate between parameter values.
76         -- For performance reasons, we want to ensure that two consecutive values
77         -- of the interpolated "steepness" are less that 1 apart. By choosing the
78         -- interpolation chunk size to be 64 in most cases, but 32 if the rate is
79         -- strictly less than 22kHz (there's only 8kHz in standard rates), we can
80         -- ensure that steepness interpolation will never change the parameter by
81         -- more than ~0.86.
82         lpf_chunk = 64
83         if rate < 22000 then lpf_chunk = 32 end
84         -- We apply a discrete version of the standard RC low-pass, with a cutoff
85         -- frequency of 15Hz. For more information about the underlying math, see
86         -- https://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization
87         -- (here Δt is lpf_chunk / rate)
88         local R = 2 * math.pi * lpf_chunk * 15 -- Hz
89         lpf = R / (R + rate)
90 end
91
92 function dsp_configure (ins, outs)
93         assert (ins:n_audio () == outs:n_audio ())
94         local tbl = self:table ():get () -- get shared memory table
95
96         chn = ins:n_audio ()
97         cur = {0, 0, 0, 0, 0, 0}
98
99         hp = {}
100         lp = {}
101
102         collectgarbage ()
103
104         for c = 1, chn do
105                 hp[c] = {}
106                 lp[c] = {}
107                 -- initialize filters
108                 -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:Biquad
109
110                 -- A different Biquad is needed for each pass and channel because they
111                 -- remember the last two samples seen during the last call of Biquad:run().
112                 -- For continuity these have to come from the previous audio chunk of the
113                 -- same channel and pass and would be clobbered if the same Biquad was
114                 -- called several times by cycle.
115                 for k = 1,4 do
116                         hp[c][k] = ARDOUR.DSP.Biquad (tbl['samplerate'])
117                         lp[c][k] = ARDOUR.DSP.Biquad (tbl['samplerate'])
118                 end
119         end
120 end
121
122 function santize_params (ctrl)
123         -- don't allow manual cross-fades. enforce enums
124         ctrl[1] = math.floor(ctrl[1] + .5)
125         ctrl[4] = math.floor(ctrl[4] + .5)
126
127         -- high pass, clamp range
128         ctrl[2] = math.min (max_freq, math.max (5, ctrl[2]))
129         ctrl[3] = math.min (6, math.max (0.1, ctrl[3]))
130
131         -- low pass, clamp range
132         ctrl[5] = math.min (max_freq, math.max (20, ctrl[5]))
133         ctrl[6] = math.min (6, math.max (0.1, ctrl[6]))
134         return ctrl
135 end
136
137 -- helper functions for parameter interpolation
138 function param_changed (ctrl)
139         for p = 1,6 do
140                 if ctrl[p] ~= cur[p] then
141                         return true
142                 end
143         end
144         return false
145 end
146
147 function low_pass_filter_param (old, new, limit)
148         if math.abs (old - new) < limit  then
149                 return new
150         else
151                 return old + lpf * (new - old)
152         end
153 end
154
155 -- apply parameters, re-compute filter coefficients if needed
156 function apply_params (ctrl)
157         if not param_changed (ctrl) then
158                 return
159         end
160
161         -- low-pass filter ctrl parameter values, smooth transition
162         cur[1] = low_pass_filter_param (cur[1], ctrl[1], 0.05) -- HP order x-fade
163         cur[2] = low_pass_filter_param (cur[2], ctrl[2], 1.0)  -- HP freq/Hz
164         cur[3] = low_pass_filter_param (cur[3], ctrl[3], 0.01) -- HP quality
165         cur[4] = low_pass_filter_param (cur[4], ctrl[4], 0.05) -- LP order x-fade
166         cur[5] = low_pass_filter_param (cur[5], ctrl[5], 1.0)  -- LP freq/Hz
167         cur[6] = low_pass_filter_param (cur[6], ctrl[6], 0.01) -- LP quality
168
169         for c = 1, chn do
170                 for k = 1,4 do
171                         hp[c][k]:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
172                         lp[c][k]:compute (ARDOUR.DSP.BiquadType.LowPass,  cur[5], cur[6], 0)
173                 end
174         end
175 end
176
177
178 -- the actual DSP callback
179 function dsp_run (ins, outs, n_samples)
180         assert (n_samples <= 8192)
181         assert (#ins == chn)
182         local ctrl = santize_params (CtrlPorts:array ())
183
184         local changed = false
185         local siz = n_samples
186         local off = 0
187
188         -- if a parameter was changed, process at most lpf_chunk samples
189         -- at a time and interpolate parameters until the current settings
190         -- match the target values
191         if param_changed (ctrl) then
192                 changed = true
193                 siz = lpf_chunk
194         end
195
196         while n_samples > 0 do
197                 if changed then apply_params (ctrl) end
198                 if siz > n_samples then siz = n_samples end
199
200                 local ho = math.floor(cur[1])
201                 local lo = math.floor(cur[4])
202
203                 -- process all channels
204                 for c = 1, #ins do
205
206                         -- High Pass
207                         local xfade = cur[1] - ho
208
209                         -- prepare scratch memory
210                         ARDOUR.DSP.copy_vector (mem:to_float (off), ins[c]:offset (off), siz)
211
212                         -- run at least |ho| biquads...
213                         for k = 1,ho do
214                                 hp[c][k]:run (mem:to_float (off), siz)
215                         end
216                         ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
217
218                         -- mix the output of |ho| biquads (with weight |1-xfade|)
219                         -- with the output of |ho+1| biquads (with weight |xfade|)
220                         if xfade > 0 then
221                                 ARDOUR.DSP.apply_gain_to_buffer (outs[c]:offset (off), siz, 1 - xfade)
222                                 hp[c][ho+1]:run (mem:to_float (off), siz)
223                                 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
224                                 -- also run the next biquad because it needs to have the correct state
225                                 -- in case it start affecting the next chunck of output. Higher order
226                                 -- ones are guaranteed not to be needed for the next run because the
227                                 -- interpolated order won't increase more than 0.86 in one step thanks
228                                 -- to the choice of the value of |lpf|.
229                                 if ho + 2 <= 4 then hp[c][ho+2]:run (mem:to_float (off), siz) end
230                         elseif ho + 1 <= 4 then
231                                 -- run the next biquad in case it is used next chunk
232                                 hp[c][ho+1]:run (mem:to_float (off), siz)
233                         end
234
235                         -- Low Pass
236                         xfade = cur[4] - lo
237
238                         -- prepare scratch memory (from high pass output)
239                         ARDOUR.DSP.copy_vector (mem:to_float (off), outs[c]:offset (off), siz)
240
241                         -- run at least |lo| biquads...
242                         for k = 1,lo do
243                                 lp[c][k]:run (mem:to_float (off), siz)
244                         end
245                         ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
246
247                         -- mix the output of |lo| biquads (with weight |1-xfade|)
248                         -- with the output of |lo+1| biquads (with weight |xfade|)
249                         if xfade > 0 then
250                                 ARDOUR.DSP.apply_gain_to_buffer (outs[c]:offset (off), siz, 1 - xfade)
251                                 lp[c][lo+1]:run (mem:to_float (off), siz)
252                                 ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
253                                 -- also run the next biquad in case it start affecting the next
254                                 -- chunck of output.
255                                 if lo + 2 <= 4 then lp[c][lo+2]:run (mem:to_float (off), siz) end
256                         elseif lo + 1 <= 4 then
257                                 -- run the next biquad in case it is used next chunk
258                                 lp[c][lo+1]:run (mem:to_float (off), siz)
259                         end
260
261                 end
262
263                 n_samples = n_samples - siz
264                 off = off + siz
265         end
266
267         if changed then
268                 -- notify display
269                 self:queue_draw ()
270         end
271 end
272
273
274 -------------------------------------------------------------------------------
275 --- inline display
276
277 function round (n)
278         return math.floor (n + .5)
279 end
280
281 function freq_at_x (x, w)
282         -- frequency in Hz at given x-axis pixel
283         return 20 * 1000 ^ (x / w)
284 end
285
286 function x_at_freq (f, w)
287         -- x-axis pixel for given frequency, power-scale
288         return w * math.log (f / 20.0) / math.log (1000.0)
289 end
290
291 function db_to_y (db, h)
292         -- y-axis gain mapping
293         if db < -60 then db = -60 end
294         if db >  12 then db =  12 end
295         return -.5 + round (0.2 * h) - h * db / 60
296 end
297
298 function grid_db (ctx, w, h, db)
299         -- draw horizontal grid line
300         -- note that a cairo pixel at Y spans [Y - 0.5 to Y + 0.5]
301         local y = -.5 + round (db_to_y (db, h))
302         ctx:move_to (0, y)
303         ctx:line_to (w, y)
304         ctx:stroke ()
305 end
306
307 function grid_freq (ctx, w, h, f)
308         -- draw vertical grid line
309         local x = -.5 + round (x_at_freq (f, w))
310         ctx:move_to (x, 0)
311         ctx:line_to (x, h)
312         ctx:stroke ()
313 end
314
315 function response (ho, lo, f)
316         -- calculate transfer function response for given
317         -- hi/po pass order at given frequency [Hz]
318         local db = ho * filt['hp']:dB_at_freq (f)
319         return db + lo * filt['lp']:dB_at_freq (f)
320 end
321
322 function render_inline (ctx, w, max_h)
323         if not filt then
324                 local tbl = self:table ():get () -- get shared memory table
325                 -- instantiate filter (to calculate the transfer function's response)
326                 filt = {}
327                 filt['hp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
328                 filt['lp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
329                 max_freq   = tbl['max_freq']
330         end
331
332         local ctrl = santize_params (CtrlPorts:array ())
333         -- set filter coefficients if they have changed
334         if param_changed (ctrl) then
335                 for k = 1,6 do cur[k] = ctrl[k] end
336                 filt['hp']:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
337                 filt['lp']:compute (ARDOUR.DSP.BiquadType.LowPass,  cur[5], cur[6], 0)
338         end
339
340         -- calc height of inline display
341         local h = 1 | math.ceil (w * 9 / 16) -- use 16:9 aspect, odd number of y pixels
342         if (h > max_h) then h = max_h end -- but at most max-height
343
344         -- ctx is a http://cairographics.org/ context
345         -- http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
346
347         -- clear background
348         ctx:rectangle (0, 0, w, h)
349         ctx:set_source_rgba (.2, .2, .2, 1.0)
350         ctx:fill ()
351         ctx:rectangle (0, 0, w, h)
352         ctx:clip ()
353
354         -- set line width: 1px
355         ctx:set_line_width (1.0)
356
357         -- draw grid
358         local dash3 = C.DoubleVector ()
359         local dash2 = C.DoubleVector ()
360         dash2:add ({1, 2})
361         dash3:add ({1, 3})
362         ctx:set_dash (dash2, 2) -- dotted line: 1 pixel 2 space
363         ctx:set_source_rgba (.5, .5, .5, .8)
364         grid_db (ctx, w, h, 0)
365         ctx:set_dash (dash3, 2) -- dashed line: 1 pixel 3 space
366         ctx:set_source_rgba (.5, .5, .5, .5)
367         grid_db (ctx, w, h, -12)
368         grid_db (ctx, w, h, -24)
369         grid_db (ctx, w, h, -36)
370         grid_freq (ctx, w, h, 100)
371         grid_freq (ctx, w, h, 1000)
372         grid_freq (ctx, w, h, 10000)
373         ctx:unset_dash ()
374
375         -- draw transfer function line
376         local ho = math.floor(cur[1])
377         local lo = math.floor(cur[4])
378
379         ctx:set_source_rgba (.8, .8, .8, 1.0)
380         ctx:move_to (-.5, db_to_y (response(ho, lo, freq_at_x (0, w)), h))
381         for x = 1,w do
382                 local db = response(ho, lo, freq_at_x (x, w))
383                 ctx:line_to (-.5 + x, db_to_y (db, h))
384         end
385         -- stoke a line, keep the path
386         ctx:stroke_preserve ()
387
388         -- fill area to zero under the curve
389         ctx:line_to (w, -.5 + round (db_to_y (0, h)))
390         ctx:line_to (0, -.5 + round (db_to_y (0, h)))
391         ctx:close_path ()
392         ctx:set_source_rgba (.5, .5, .5, .5)
393         ctx:fill ()
394
395         return {w, h}
396 end