2017-04-25 10:46:21 -04:00
ardour { [ " type " ] = " EditorAction " , name = " MIDI CC to Plugin Automation " ,
license = " MIT " ,
author = " Ardour Team " ,
description = [[Parse a given MIDI control changes (CC) from all selected MIDI regions and convert them into plugin parameter automation]]
}
2017-04-23 22:15:16 -04:00
function factory ( ) return function ( )
2017-04-27 09:36:50 -04:00
-- find possible target parameters, collect them in a nested table
-- [track-name] -> [plugin-name] -> [parameters]
-- to allow selection in a dropdown menu
2017-04-25 08:00:29 -04:00
local targets = { }
local have_entries = false
2017-04-27 09:36:50 -04:00
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
2017-04-25 10:46:21 -04:00
local i = 0
2017-04-27 09:36:50 -04:00
while true do -- iterate over all plugins on the route
2017-04-25 08:00:29 -04:00
local proc = r : nth_plugin ( i )
if proc : isnil ( ) then break end
2017-04-27 09:36:50 -04:00
local plug = proc : to_insert ( ) : plugin ( 0 ) -- we know it's a plugin-insert (we asked for nth_plugin)
local n = 0 -- count control-ports
2017-04-25 08:00:29 -04:00
for j = 0 , plug : parameter_count ( ) - 1 do -- iterate over all plugin parameters
2017-04-27 09:36:50 -04:00
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
local nn = n --local scope for return value function
-- create table parents only if needed (if there's at least one parameter)
2017-04-25 10:46:21 -04:00
if not targets [ r : name ( ) ] then targets [ r : name ( ) ] = { } end
2017-04-27 09:36:50 -04:00
-- TODO handle ambiguity if there are 2 plugins with the same name on the same track
2017-04-25 10:46:21 -04:00
if not targets [ r : name ( ) ] [ proc : display_name ( ) ] then targets [ r : name ( ) ] [ proc : display_name ( ) ] = { } end
2017-04-27 09:36:50 -04:00
-- 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
2017-04-25 08:00:29 -04:00
have_entries = true
end
n = n + 1
end
end
i = i + 1
end
2017-04-27 09:36:50 -04:00
:: nextroute ::
2017-04-25 08:00:29 -04:00
end
2017-04-23 22:15:16 -04:00
2017-04-25 08:00:29 -04:00
-- bail out if there are no parameters
if not have_entries then
2017-04-27 09:36:50 -04:00
LuaDialog.Message ( " CC to Plugin Automation " , " No Plugins found " , LuaDialog.MessageType . Info , LuaDialog.ButtonType . Close ) : run ( )
targets = nil
2017-04-25 08:00:29 -04:00
collectgarbage ( )
return
end
2017-04-23 22:15:16 -04:00
2017-04-25 08:00:29 -04:00
-- create a dialog, ask user which MIDI-CC to map and to what parameter
local dialog_options = {
2017-04-25 10:46:21 -04:00
{ type = " heading " , title = " MIDI CC Source " , align = " left " } ,
2017-04-25 08:00:29 -04:00
{ type = " number " , key = " channel " , title = " Channel " , min = 1 , max = 16 , step = 1 , digits = 0 } ,
2017-04-25 10:46:21 -04:00
{ type = " number " , key = " ccparam " , title = " CC Parameter " , min = 0 , max = 127 , step = 1 , digits = 0 } ,
{ type = " heading " , title = " Target Track and Plugin " , align = " left " } ,
{ type = " dropdown " , key = " param " , title = " Target Parameter " , values = targets }
2017-04-25 08:00:29 -04:00
}
2017-04-27 09:36:50 -04:00
local rv = LuaDialog.Dialog ( " Select Taget " , dialog_options ) : run ( )
2017-04-25 08:00:29 -04:00
2017-04-27 09:36:50 -04:00
targets = nil -- drop references (the table holds shared-pointer references to all plugins)
collectgarbage ( ) -- and release the references immediately
if not rv then return end -- user cancelled
2017-04-25 08:00:29 -04:00
-- parse user response
2017-04-27 09:36:50 -04:00
assert ( type ( rv [ " param " ] ) == " function " )
local midi_channel = rv [ " channel " ] - 1 -- MIDI channel 0..15
2017-04-25 08:00:29 -04:00
local cc_param = rv [ " ccparam " ]
2017-04-27 09:36:50 -04:00
local pp = rv [ " param " ] ( ) -- evaluate function, retrieve table {["p"] = proc, ["n"] = nn}
2017-04-25 08:00:29 -04:00
local al , _ , pd = ARDOUR.LuaAPI . plugin_automation ( pp [ " p " ] , pp [ " n " ] )
2017-04-27 09:36:50 -04:00
rv = nil -- drop references
assert ( not al : isnil ( ) )
2017-04-25 08:00:29 -04:00
assert ( midi_channel >= 0 and midi_channel < 16 )
assert ( cc_param >= 0 and cc_param < 128 )
-- all systems go
2017-04-23 22:15:16 -04:00
local add_undo = false
Session : begin_reversible_command ( " CC to Automation " )
2017-04-27 09:36:50 -04:00
local before = al : get_state ( ) -- save previous state (for undo)
al : clear_list ( ) -- clear target automation-list
2017-04-23 22:15:16 -04:00
-- for all selected MIDI regions
local sel = Editor : get_selection ( )
for r in sel.regions : regionlist ( ) : iter ( ) do
local mr = r : to_midiregion ( )
if mr : isnil ( ) then goto next end
2017-04-27 09:36:50 -04:00
-- get list of MIDI-CC events for the given channel and parameter
2017-04-23 22:15:16 -04:00
local ec = mr : control ( Evoral.Parameter ( ARDOUR.AutomationType . MidiCCAutomation , midi_channel , cc_param ) , false )
if ec : isnil ( ) then goto next end
2017-04-27 09:36:50 -04:00
if ec : list ( ) : events ( ) : size ( ) == 0 then goto next end
2017-04-23 22:15:16 -04:00
2017-04-27 09:36:50 -04:00
-- 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
2017-04-23 22:15:16 -04:00
for av in ec : list ( ) : events ( ) : iter ( ) do
2017-04-27 09:36:50 -04:00
-- re-scale event to target range
2017-04-23 22:15:16 -04:00
local val = pd.lower + ( pd.upper - pd.lower ) * av.value / 127
2017-04-27 09:36:50 -04:00
-- and add it to the target-parameter automation-list
al : add ( r : position ( ) - r : start ( ) + bfc : to ( av.when ) , val , false , true )
2017-04-23 22:15:16 -04:00
add_undo = true
end
:: next ::
end
-- save undo
if add_undo then
2017-04-27 09:36:50 -04:00
local after = al : get_state ( )
Session : add_command ( al : memento_command ( before , after ) )
2017-04-23 22:15:16 -04:00
Session : commit_reversible_command ( nil )
else
Session : abort_reversible_command ( )
2017-04-27 09:36:50 -04:00
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 ( )
2017-04-23 22:15:16 -04:00
end
2017-04-27 09:36:50 -04:00
collectgarbage ( )
2017-04-23 22:15:16 -04:00
end end
2017-04-25 10:46:21 -04:00
function icon ( params ) return function ( ctx , width , height , fg )
2017-04-27 09:36:50 -04:00
local txt = Cairo.PangoLayout ( ctx , " ArdourMono " .. math.ceil ( width * .45 ) .. " px " )
2017-04-25 10:46:21 -04:00
txt : set_text ( " CC \n PA " )
local tw , th = txt : get_pixel_size ( )
ctx : set_source_rgba ( ARDOUR.LuaAPI . color_to_rgba ( fg ) )
ctx : move_to ( .5 * ( width - tw ) , .5 * ( height - th ) )
txt : show_in_cairo_context ( ctx )
end end