13
0

Annotate CC-to-Automation Lua-script and fix start/pos offset.

This commit is contained in:
Robin Gareus 2017-04-27 15:36:50 +02:00
parent f9f9006a90
commit 4521c1d525

View File

@ -5,23 +5,32 @@ ardour { ["type"] = "EditorAction", name = "MIDI CC to Plugin Automation",
} }
function factory () return function () function factory () return function ()
-- find target parameters -- find possible target parameters, collect them in a nested table
-- [track-name] -> [plugin-name] -> [parameters]
-- to allow selection in a dropdown menu
local targets = {} local targets = {}
local have_entries = false local have_entries = false
for r in Session:get_routes ():iter () do -- for every track/bus for r in Session:get_routes ():iter () do -- for every track/bus
if r:is_monitor () or r:is_auditioner () then goto nextroute end -- skip special routes
local i = 0 local i = 0
while 1 do -- iterate over all plugins on the route while true do -- iterate over all plugins on the route
local proc = r:nth_plugin (i) local proc = r:nth_plugin (i)
if proc:isnil () then break end if proc:isnil () then break end
local plug = proc:to_insert ():plugin (0) local plug = proc:to_insert ():plugin (0) -- we know it's a plugin-insert (we asked for nth_plugin)
local n = 0 local n = 0 -- count control-ports
for j = 0, plug:parameter_count () - 1 do -- iterate over all plugin parameters for j = 0, plug:parameter_count () - 1 do -- iterate over all plugin parameters
if plug:parameter_is_control (j) then if plug:parameter_is_control (j) then
if plug:parameter_is_input(j) then local label = plug:parameter_label (j)
local nn = n if plug:parameter_is_input (j) and label ~= "hidden" and label:sub (1,1) ~= "#" then
local nn = n --local scope for return value function
-- create table parents only if needed (if there's at least one parameter)
if not targets [r:name ()] then targets [r:name ()] = {} end if not targets [r:name ()] then targets [r:name ()] = {} end
-- TODO handle ambiguity if there are 2 plugins with the same name on the same track
if not targets [r:name ()][proc:display_name ()] then targets [r:name ()][proc:display_name ()] = {} end if not targets [r:name ()][proc:display_name ()] then targets [r:name ()][proc:display_name ()] = {} end
targets [r:name ()][proc:display_name ()][plug:parameter_label(j)] = function () return {["p"] = proc, ["n"] = nn} end -- we need 2 return values: the plugin-instance and the parameter-id, so we use a table (associative array)
-- however, we cannot directly use a table: the dropdown menu would expand it as another sub-menu.
-- so we produce a function that will return the table.
targets [r:name ()][proc:display_name ()][label] = function () return {["p"] = proc, ["n"] = nn} end
have_entries = true have_entries = true
end end
n = n + 1 n = n + 1
@ -29,11 +38,13 @@ function factory () return function ()
end end
i = i + 1 i = i + 1
end end
::nextroute::
end end
-- bail out if there are no parameters -- bail out if there are no parameters
if not have_entries then if not have_entries then
LuaDialog.Message ("CC to Plugin Automation", "No Plugins found", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run () LuaDialog.Message ("CC to Plugin Automation", "No Plugins found", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
targets = nil
collectgarbage () collectgarbage ()
return return
end end
@ -46,30 +57,30 @@ function factory () return function ()
{ type = "heading", title = "Target Track and Plugin", align = "left"}, { type = "heading", title = "Target Track and Plugin", align = "left"},
{ type = "dropdown", key = "param", title = "Target Parameter", values = targets } { type = "dropdown", key = "param", title = "Target Parameter", values = targets }
} }
local od = LuaDialog.Dialog ("Select Taget", dialog_options) local rv = LuaDialog.Dialog ("Select Taget", dialog_options):run ()
local rv = od:run()
if not rv then targets = nil -- drop references (the table holds shared-pointer references to all plugins)
od = nil collectgarbage () collectgarbage () -- and release the references immediately
return
end if not rv then return end -- user cancelled
-- parse user response -- parse user response
local midi_channel = rv["channel"] - 1 assert (type (rv["param"]) == "function")
local midi_channel = rv["channel"] - 1 -- MIDI channel 0..15
local cc_param = rv["ccparam"] local cc_param = rv["ccparam"]
local pp = rv["param"]() local pp = rv["param"]() -- evaluate function, retrieve table {["p"] = proc, ["n"] = nn}
local al, _, pd = ARDOUR.LuaAPI.plugin_automation (pp["p"], pp["n"]) local al, _, pd = ARDOUR.LuaAPI.plugin_automation (pp["p"], pp["n"])
od = nil collectgarbage () rv = nil -- drop references
assert (al) assert (not al:isnil ())
assert (midi_channel >= 0 and midi_channel < 16) assert (midi_channel >= 0 and midi_channel < 16)
assert (cc_param >= 0 and cc_param < 128) assert (cc_param >= 0 and cc_param < 128)
-- all systems go -- all systems go
local add_undo = false local add_undo = false
Session:begin_reversible_command ("CC to Automation") Session:begin_reversible_command ("CC to Automation")
local before = al:get_state() local before = al:get_state () -- save previous state (for undo)
al:clear_list () al:clear_list () -- clear target automation-list
-- for all selected MIDI regions -- for all selected MIDI regions
local sel = Editor:get_selection () local sel = Editor:get_selection ()
@ -77,14 +88,21 @@ function factory () return function ()
local mr = r:to_midiregion () local mr = r:to_midiregion ()
if mr:isnil () then goto next end if mr:isnil () then goto next end
local bfc = ARDOUR.DoubleBeatsFramesConverter (Session:tempo_map (), r:position ()) -- get list of MIDI-CC events for the given channel and parameter
local ec = mr:control (Evoral.Parameter (ARDOUR.AutomationType.MidiCCAutomation, midi_channel, cc_param), false) local ec = mr:control (Evoral.Parameter (ARDOUR.AutomationType.MidiCCAutomation, midi_channel, cc_param), false)
if ec:isnil () then goto next end if ec:isnil () then goto next end
if ec:list ():events ():size () == 0 then goto next end if ec:list ():events ():size () == 0 then goto next end
-- MIDI events are timestamped in "bar-beat" units, we need to convert those
-- using the tempo-map, relative to the region-start
local bfc = ARDOUR.DoubleBeatsFramesConverter (Session:tempo_map (), r:start ())
-- iterate over CC-events
for av in ec:list ():events ():iter () do for av in ec:list ():events ():iter () do
-- re-scale event to target range
local val = pd.lower + (pd.upper - pd.lower) * av.value / 127 local val = pd.lower + (pd.upper - pd.lower) * av.value / 127
al:add (bfc:to (av.when), val, false, true) -- and add it to the target-parameter automation-list
al:add (r:position () - r:start () + bfc:to (av.when), val, false, true)
add_undo = true add_undo = true
end end
::next:: ::next::
@ -97,12 +115,11 @@ function factory () return function ()
Session:commit_reversible_command (nil) Session:commit_reversible_command (nil)
else else
Session:abort_reversible_command () Session:abort_reversible_command ()
LuaDialog.Message ("CC to Plugin Automation", "No data was converted. Was a MIDI-region selected?", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run() LuaDialog.Message ("CC to Plugin Automation", "No data was converted. Was a MIDI-region with CC-automation selected? ", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
collectgarbage ()
end end
collectgarbage ()
end end end end
function icon (params) return function (ctx, width, height, fg) function icon (params) return function (ctx, width, height, fg)
local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil (width * .45) .. "px") local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil (width * .45) .. "px")
txt:set_text ("CC\nPA") txt:set_text ("CC\nPA")