Add script to create LFO-like plugin automation
This commit is contained in:
parent
3c446a5275
commit
5fb949267d
173
scripts/lfo_automation.lua
Normal file
173
scripts/lfo_automation.lua
Normal file
@ -0,0 +1,173 @@
|
||||
ardour {
|
||||
["type"] = "EditorAction",
|
||||
name = "Add LFO automation to region",
|
||||
version = "0.1.1",
|
||||
license = "MIT",
|
||||
author = "Daniel Appelt",
|
||||
description = [[Add LFO-like plugin automation to selected region]]
|
||||
}
|
||||
|
||||
function factory (unused_params)
|
||||
return function ()
|
||||
-- Retrieve the first selected region
|
||||
-- TODO: the following statement should do just that, no!?
|
||||
-- local region = Editor:get_selection().regions:regionlist():front()
|
||||
local region = nil
|
||||
for r in Editor:get_selection().regions:regionlist():iter() do
|
||||
if region == nil then region = r end
|
||||
end
|
||||
|
||||
-- Bail out if no region was selected
|
||||
if region == nil then
|
||||
LuaDialog.Message("Add LFO automation to region", "Please select a region first!",
|
||||
LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
|
||||
return
|
||||
end
|
||||
|
||||
-- Identify the track the region belongs to. There really is no better way?!
|
||||
local track = nil
|
||||
for route in Session:get_tracks():iter() do
|
||||
for r in route:to_track():playlist():region_list():iter() do
|
||||
if r == region then track = route:to_track() end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get a list of all available plugin parameters on the track. This looks ugly. For the original code
|
||||
-- see https://github.com/Ardour/ardour/blob/master/scripts/midi_cc_to_automation.lua
|
||||
local targets = {}
|
||||
local i = 0
|
||||
while true do -- iterate over all plugins on the route
|
||||
if track:nth_plugin(i):isnil() then break end
|
||||
|
||||
local proc = track:nth_plugin(i) -- ARDOUR.LuaAPI.plugin_automation() expects a proc not a plugin
|
||||
local plug = proc:to_insert():plugin(0)
|
||||
local plug_label = i .. ": " .. plug:name() -- Handle ambiguity if there are multiple plugin instances
|
||||
local n = 0 -- Count control-ports separately. ARDOUR.LuaAPI.plugin_automation() only returns those.
|
||||
for j = 0, plug:parameter_count() - 1 do -- Iterate over all parameters
|
||||
if plug:parameter_is_control(j) then
|
||||
local label = plug:parameter_label(j)
|
||||
if plug:parameter_is_input(j) and label ~= "hidden" and label:sub(1,1) ~= "#" then
|
||||
-- We need two return values: the plugin-instance and the parameter-id. We use a function to
|
||||
-- return both values in order to avoid another sub-menu level in the dropdown.
|
||||
local nn = n -- local scope for return value function
|
||||
targets[plug_label] = targets[plug_label] or {}
|
||||
targets[plug_label][label] = function() return {["p"] = proc, ["n"] = nn} end
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- Bail out if there are no plugin parameters
|
||||
if next(targets) == nil then
|
||||
LuaDialog.Message("Add LFO automation to region", "No plugin parameters found.",
|
||||
LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
|
||||
region, track, targets = nil, nil, nil
|
||||
collectgarbage()
|
||||
return
|
||||
end
|
||||
|
||||
-- Display dialog to select (plugin and) plugin parameter, and LFO cycle type + min / max
|
||||
local dialog_options = {
|
||||
{ type = "heading", title = "Add LFO automation to region", align = "left"},
|
||||
{ type = "dropdown", key = "param", title = "Plugin parameter", values = targets },
|
||||
{ type = "dropdown", key = "wave", title = "Waveform", values = {
|
||||
["Ramp up"] = 1, ["Ramp down"] = 2, ["Triangle"] = 3, ["Sine"] = 4,
|
||||
["Exp up"] = 5, ["Exp down"] = 6, ["Log up"] = 7, ["Log down"] = 8 } },
|
||||
{ type = "number", key = "cycles", title = "No. of cycles", min = 1, max = 16, step = 1, digits = 0 },
|
||||
{ type = "slider", key = "min", title = "Minimum in %", min = 0, max = 100, digits = 1 },
|
||||
{ type = "slider", key = "max", title = "Maximum in %", min = 0, max = 100, digits = 1, default = 100 }
|
||||
}
|
||||
local rv = LuaDialog.Dialog("Select target", dialog_options):run()
|
||||
|
||||
-- Return if the user cancelled
|
||||
if not rv then
|
||||
region, track, targets = nil, nil, nil
|
||||
collectgarbage()
|
||||
return
|
||||
end
|
||||
|
||||
-- Parse user response
|
||||
assert(type(rv["param"]) == "function")
|
||||
local pp = rv["param"]() -- evaluate function, retrieve table {["p"] = proc, ["n"] = nn}
|
||||
local al, _, pd = ARDOUR.LuaAPI.plugin_automation(pp["p"], pp["n"])
|
||||
local wave = rv["wave"]
|
||||
local cycles = rv["cycles"]
|
||||
-- Compute minimum and maximum requested parameter values
|
||||
local lower = pd.lower + rv["min"] / 100 * (pd.upper - pd.lower)
|
||||
local upper = pd.lower + rv["max"] / 100 * (pd.upper - pd.lower)
|
||||
track, targets, rv, pd = nil, nil, nil, nil
|
||||
assert(not al:isnil())
|
||||
|
||||
-- Define lookup tables for our waves. Empty ones will be calculated in a separate step.
|
||||
-- TODO: at this point we already know which one is needed, still we compute all.
|
||||
local lut = {
|
||||
{ 0, 1 }, -- ramp up
|
||||
{ 1, 0 }, -- ramp down
|
||||
{ 0, 1, 0 }, -- triangle
|
||||
{}, -- sine
|
||||
{}, -- exp up
|
||||
{}, -- exp down
|
||||
{}, -- log up
|
||||
{} -- log down
|
||||
}
|
||||
|
||||
-- Calculate missing look up tables
|
||||
local log_min = math.exp(-2 * math.pi)
|
||||
for i = 0, 20 do
|
||||
-- sine
|
||||
lut[4][i+1] = 0.5 * math.sin(i * math.pi / 10) + 0.5
|
||||
-- exp up
|
||||
lut[5][i+1] = math.exp(-2 * math.pi + i * math.pi / 10)
|
||||
-- log up
|
||||
lut[7][i+1] = -math.log(1 + (i / log_min - i) / 20) / math.log(log_min)
|
||||
end
|
||||
-- "down" variants just need the values in reverse order
|
||||
for i = 21, 1, -1 do
|
||||
-- exp down
|
||||
lut[6][22-i] = lut[5][i]
|
||||
-- log down
|
||||
lut[8][22-i] = lut[7][i]
|
||||
end
|
||||
|
||||
-- Initialize undo
|
||||
Session:begin_reversible_command("Add LFO automation to region")
|
||||
local before = al:get_state() -- save previous state (for undo)
|
||||
al:clear_list() -- clear target automation-list
|
||||
|
||||
local values = lut[wave]
|
||||
local last = nil
|
||||
for i = 0, cycles - 1 do
|
||||
-- cycle length = region:length() / cycles
|
||||
local cycle_start = region:position() - region:start() + i * region:length() / cycles
|
||||
local offset = region:length() / cycles / (#values - 1)
|
||||
|
||||
for k, v in pairs(values) do
|
||||
local pos = cycle_start + (k - 1) * offset
|
||||
if k == 1 and v ~= last then
|
||||
-- Move event one sample further. A larger offset might be needed to avoid unwanted effects.
|
||||
pos = pos + 1
|
||||
end
|
||||
|
||||
if k > 1 or v ~= last then
|
||||
-- Create automation point re-scaled to parameter target range. Do not create a new point
|
||||
-- at cycle start if the last cycle ended on the same value.
|
||||
al:add(pos, lower + v * (upper - lower), false, true)
|
||||
end
|
||||
last = v
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: display the modified automation lane in the time line in order to make the change visible!
|
||||
|
||||
-- Save undo
|
||||
-- TODO: in Ardour 5.12 this does not lead to an actual entry in the undo list?!
|
||||
Session:add_command(al:memento_command(before, al:get_state()))
|
||||
Session:commit_reversible_command(nil)
|
||||
|
||||
region, al, lut = nil, nil, nil
|
||||
collectgarbage()
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user