ardour/share/scripts/ir_capture.lua
2025-01-19 15:39:50 +01:00

188 lines
5 KiB
Lua

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 and/or manually tweak the region start:
move it close to the initial impulse.
14. Region > Gain > Normalize, or manually adjust gain.
15. Disable region-fades of the region: Region > Fades
16. Export the region to save the IR file (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, slen, 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