From 52cb507dd58c5a658188a503e66d4d1a4c32d710 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sat, 18 Jan 2025 23:20:31 +0100 Subject: [PATCH] Add a Lua script to capture Impulse Resoponses --- share/scripts/ir_capture.lua | 186 +++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 share/scripts/ir_capture.lua diff --git a/share/scripts/ir_capture.lua b/share/scripts/ir_capture.lua new file mode 100644 index 0000000000..cbb18127cf --- /dev/null +++ b/share/scripts/ir_capture.lua @@ -0,0 +1,186 @@ +ardour { + ["type"] = "dsp", + name = "Lua IR Capture", + license = "MIT", + author = "Ardour Team", + description = [[ + 1. Calibrate I/O Latency (Menu > Window > Audio/MIDI Setup) + 2. Create a mono track - this will be used to record the IR + 3. Disconnect the input and output of that track + 4. Add a mono bus and load this plugin on the bus + 5. Connect the left output of the bus to the mono track's input. + The deconvolved Impulse Response will be output on that channel + 6. Connect the right output of the bus to a hardware playback port, matching the system you want to capture + A sine-sweep will be played on that channel. + 7. Connect the bus input to the return signal (hardware capture) of the system under test. + 8. Record Arm the track (from 2) + 9. Record Arm the session + 10. open the GUI of this plugin, enable "Capture" + 11. Roll the transport + 12. Wait 10 sec. (recording stops automatically) + 13. Select the region on the mono track: Region > Edit > Strip Silence, Region > Gain > Normalize + 14. Disable region-fades on the region: Region > Fades + 15. Export the region: Region > Export + ]] +} + +function dsp_ioconfig () return + { + connect_all_audio_outputs = true, -- override strict-i/o + { audio_in = 1, audio_out = 2}, + } +end + +function dsp_params () + return { + { ["type"] = "input", name = "Capture", min = 0, max = 1, default = 0, toggled = true }, + { ["type"] = "output", name = "State", min = 0, max = 2, enum = true, scalepoints = + { + ["Idle"] = 0, + ["Capturing"] = 1, + ["Finalize"] = 2, + } + }, + { ["type"] = "output", name = "IR Signal Level", min = -120, max = 0 }, + } +end + +local conv +local state +local sweep_sin +local peak +local rec_len +local n_captured +local n_playback + +function gen_sweep (fmin, fmax, t_sec, rate) + local n_samples_pre = rate * 0.1 + local n_samples_sin = rate * t_sec + local n_samples_end = rate * 0.03 + local n_samples = n_samples_pre + n_samples_sin + n_samples_end + + local amp = 0.5 + + local a = math.log (fmax / fmin) / n_samples_sin + local b = fmin / (a * rate) + local r = 4.0 * a * a / amp + + local cmem = ARDOUR.DSP.DspShm (n_samples * 2) + local ss = cmem:to_float (0):array() + local si = cmem:to_float (n_samples):array() + + for i = 0, n_samples - 1 do + local j = n_samples - i - 1 + + local gain = 1.0 + + if i < n_samples_pre then + gain = math.sin (0.5 * math.pi * i / n_samples_pre) + elseif j < n_samples_end then + gain = math.sin (0.5 * math.pi * j / n_samples_end) + end + + local d = b * math.exp (a * (i - n_samples_pre)) + local p = d - b + local x = gain * math.sin (2 * math.pi * (p - math.floor (p))) + + ss[i + 1] = x * amp + si[j + 1] = x * d * r + end + + sweep_sin = ARDOUR.AudioRom.new_rom (cmem:to_float (0), n_samples) + + -- de-convolver + local sweep_inv = ARDOUR.AudioRom.new_rom (cmem:to_float (n_samples), n_samples) + conv = ARDOUR.DSP.Convolution (Session, 1, 1) + conv:add_impdata (0, 0, sweep_inv, 1.0, 0, 0, 0, 0) + conv:restart () + + sweep_inv = nil + cmem = nil + collectgarbage () +end + +function dsp_init (rate) + local fmax = 20000 + local slen = 5 -- sec + local rlen = slen + 5 -- sec + + if fmax > rate * .47 then + fmax = rate * .47 + end + gen_sweep (20, fmax, 5, rate) + + rec_len = rlen * rate + + n_captured = 0 + n_playback = 0 + peak = 0 + state = 0 +end + +function dsp_latency () + return conv:latency() +end + +function reset () + state = 0 + if n_captured > 0 then + conv:restart () + end + n_captured = 0 + n_playback = 0 +end + +function dsp_runmap (bufs, in_map, out_map, n_samples, offset) + local ctrl = CtrlPorts:array() -- get control port array + local capture = ctrl[1] > 0 + + if state == 0 and capture and Session:actively_recording () then + state = 1 + peak = 0 + print ("START CAPTURE") + end + if state == 2 then + Session:request_stop (false, false, ARDOUR.TransportRequestSource.TRS_UI) + reset () + end + + -- check I/O mapping + local input = in_map:get (ARDOUR.DataType ("audio"), 0) + local outir = out_map:get (ARDOUR.DataType ("audio"), 0) + local outsw = out_map:get (ARDOUR.DataType ("audio"), 1) + + if input == ARDOUR.ChanMapping.Invalid or outir == ARDOUR.ChanMapping.Invalid or outsw == ARDOUR.ChanMapping.Invalid or input ~= outir then + reset (); + end + + if state ~= 1 then + bufs:get_audio (outir):silence (n_samples, offset) + end + + if state == 1 then -- capture + conv:run_mono_buffered (bufs:get_audio(input):data (offset), n_samples) + + if n_samples + n_captured > rec_len then + state = 2 + end + + peak = ARDOUR.DSP.compute_peak (bufs:get_audio(input):data (offset), n_samples, peak) -- compute digital peak + n_captured = n_captured + n_samples + end + + if state == 1 then + -- play back sine-sweep + local copied = sweep_sin:read (bufs:get_audio (outsw):data (offset), n_playback, n_samples, 0) + n_playback = n_playback + copied + if copied < n_samples then + bufs:get_audio (outsw):silence (n_samples - copied, offset + copied) + end + else + bufs:get_audio (outsw):silence (n_samples, offset) + end + + ctrl[2] = state + ctrl[3] = ARDOUR.DSP.accurate_coefficient_to_dB (peak) +end