implement fetch_valid_settings_file() to avoid the user having to see stub files
[ardour.git] / scripts / mixer_settings_recall.lua
1 ardour {
2         ["type"]    = "EditorAction",
3         name        = "Recall Mixer Settings",
4         author      = "Mixbus Team",
5         description = [[
6
7         Recalls mixer settings outined by files
8         created by Store Mixer Settings.
9
10         Allows for some room to change Source
11         and Destination.
12
13         ]]
14 }
15
16 function factory () return function ()
17
18         local user_cfg = ARDOUR.user_config_directory(-1)
19         local local_path = ARDOUR.LuaAPI.build_filename(Session:path(), 'mixer_settings')
20         local global_path = ARDOUR.LuaAPI.build_filename(user_cfg, 'mixer_settings')
21
22         local invalidate = {}
23
24         function exists(file)
25                 local ok, err, code = os.rename(file, file)
26                 if not ok then
27                         if code == 13 then -- Permission denied, but it exists
28                                 return true
29                         end
30                 end return ok, err
31         end
32
33         function whoami()
34                 if not pcall(function() local first_check = Session:get_mixbus(0) end) then
35                         return "Ardour"
36                 else
37                         local second_check = Session:get_mixbus(11)
38                         if second_check:isnil() then
39                                 return "Mixbus"
40                         else
41                                 return "32C"
42                         end
43                 end
44         end
45
46         function isdir(path)
47                 return exists(path.."/")
48         end
49
50         function fetch_valid_settings_file(directory, fallback)
51                 local i, t, popen = 0, {}, io.popen
52                 local pfile = popen('ls "'..directory..'"')
53                 for filename in pfile:lines() do
54                         i = i + 1
55                         if string.find(filename, ".lua") then
56                                 return filename
57                         end
58                 end
59                 pfile:close()
60                 return fallback
61         end
62
63         function get_processor_by_name(track, name)
64                 local i = 0
65                 local proc = track:nth_processor(i)
66                         repeat
67                                 if(proc:display_name() == name) then
68                                         return proc
69                                 else
70                                         i = i + 1
71                                 end
72                                 proc = track:nth_processor(i)
73                         until proc:isnil()
74                 end
75
76         function new_plugin(name)
77                 for x = 0, 6 do
78                         local plugin = ARDOUR.LuaAPI.new_plugin(Session, name, x, "")
79                         if not(plugin:isnil()) then return plugin end
80                 end
81         end
82
83         function group_by_id(id)
84                 local id  = tonumber(id)
85                 for g in Session:route_groups():iter() do
86                         local group_id = tonumber(g:to_stateful():id():to_s())
87                         if group_id == id then return g end
88                 end
89         end
90
91         function group_by_name(name)
92                 for g in Session:route_groups():iter() do
93                         if g:name() == name then return g end
94                 end
95         end
96
97         function route_groupid_interrogate(t)
98                 local group = false
99                 for g in Session:route_groups():iter() do
100                         for r in g:route_list():iter() do
101                                 if r:name() == t:name() then group = g:to_stateful():id():to_s() end
102                         end
103                 end return group
104         end
105
106         function route_group_interrogate(t)
107                 for g in Session:route_groups():iter() do
108                         for r in g:route_list():iter() do
109                                 if r:name() == t:name() then return g end
110                         end
111                 end
112         end
113
114         function recall(debug, path, dry_run)
115                 local file = io.open(path, "r")
116                 assert(file, "File not found!")
117                 local bypass_routes = {}
118
119                 local i = 0
120                 for l in file:lines() do
121                         --print(i, l)
122
123                         local exec_line = dry_run["dothis-"..i]
124                         local skip_line = false
125                         if not(exec_line == nil) and not(exec_line) then
126                                 skip_line = true
127                         end
128
129                         local plugin, route, group = false, false, false
130                         local f = load(l)
131
132                         if debug then
133                                 print(i, string.sub(l, 0, 29), f)
134                         end
135
136                         if f then f() end
137
138                         if instance["route_id"]  then route = true end
139                         if instance["plugin_id"] then plugin = true end
140                         if instance["group_id"]  then group = true end
141
142                         if group then
143                                 if skip_line then goto nextline end
144
145                                 local g_id   = instance["group_id"]
146                                 local routes = instance["routes"]
147                                 local name   = instance["name"]
148                                 local group  = group_by_id(g_id)
149                                 if not(group) then
150                                         local group = Session:new_route_group(name)
151                                         for _, v in pairs(routes) do
152                                                 local rt = Session:route_by_id(PBD.ID(v))
153                                                 if rt:isnil() then rt = Session:route_by_name(name) end
154                                                 if not(rt:isnil()) then group:add(rt) end
155                                         end
156                                 end
157                         end
158
159                         if route then
160                                 local substitution = tonumber(dry_run["destination-"..i])
161                                 if skip_line or (substitution == 0) then
162                                         bypass_routes[#bypass_routes + 1] = instance["route_id"]
163                                         goto nextline
164                                 end
165
166                                 local old_order = ARDOUR.ProcessorList()
167                                 local route_id = instance["route_id"]
168                                 local r_id = PBD.ID(instance["route_id"])
169                                 local muted, soloed = instance["muted"], instance["soloed"]
170                                 local order = instance["order"]
171                                 local cache = instance["cache"]
172                                 local group = instance["group"]
173                                 local group_name = instance["group_name"]
174                                 local name  = instance["route_name"]
175                                 local gc, tc, pc = instance["gain_control"], instance["trim_control"], instance["pan_control"]
176
177                                 if not(substitution == instance["route_id"]) then
178                                         print('SUBSTITUTION FOR: ', name, substitution, Session:route_by_id(PBD.ID(substitution)):name())
179                                         --bypass_routes[#bypass_routes + 1] = route_id
180                                         was_subbed = true
181                                         r_id = PBD.ID(substitution)
182                                 end
183
184                                 local rt = Session:route_by_id(r_id)
185                                 if rt:isnil() then rt = Session:route_by_name(name) end
186                                 if rt:isnil() then goto nextline end
187
188                                 local cur_group_id = route_groupid_interrogate(rt)
189                                 if not(group) and (cur_group_id) then
190                                         local g = group_by_id(cur_group_id)
191                                         if g then g:remove(rt) end
192                                 end
193
194                                 local rt_group = group_by_name(group_name)
195                                 if rt_group then rt_group:add(rt) end
196
197                                 well_known = {'PRE', 'Trim', 'EQ', 'Comp', 'Fader', 'POST'}
198
199                                 for k, v in pairs(order) do
200                                         local proc = Session:processor_by_id(PBD.ID(1))
201                                         if not(was_subbed) then
202                                                 proc = Session:processor_by_id(PBD.ID(v))
203                                         end
204                                         if proc:isnil() then
205                                                 for id, name in pairs(cache) do
206                                                         if v == id then
207                                                                 proc = new_plugin(name)
208                                                                 for _, control in pairs(well_known) do
209                                                                         if name == control then
210                                                                                 proc = get_processor_by_name(rt, control)
211                                                                                 invalidate[v] = proc:to_stateful():id():to_s()
212                                                                                 goto nextproc
213                                                                         end
214                                                                 end
215                                                                 if not(proc) then goto nextproc end
216                                                                 if not(proc:isnil()) then
217                                                                         rt:add_processor_by_index(proc, 0, nil, true)
218                                                                         invalidate[v] = proc:to_stateful():id():to_s()
219                                                                 end
220                                                         end
221                                                 end
222                                         end
223                                         ::nextproc::
224                                         if proc and not(proc:isnil()) then old_order:push_back(proc) end
225                                 end
226                                 rt:reorder_processors(old_order, nil)
227                                 if muted  then rt:mute_control():set_value(1, 1) else rt:mute_control():set_value(0, 1) end
228                                 if soloed then rt:solo_control():set_value(1, 1) else rt:solo_control():set_value(0, 1) end
229                                 rt:gain_control():set_value(gc, 1)
230                                 rt:trim_control():set_value(tc, 1)
231                                 if pc ~= false then rt:pan_azimuth_control():set_value(pc, 1) end
232                         end
233
234                         if plugin then
235                                 if skip_line then goto nextline end
236
237                                 --if the plugin is owned by a route
238                                 --we decided not to use, skip it
239                                 for _, v in pairs(bypass_routes) do
240                                         if instance["owned_by_route_id"] == v then
241                                                 goto nextline
242                                         end
243                                 end
244
245                                 local enable = {}
246                                 local params = instance["parameters"]
247                                 local p_id   = instance["plugin_id"]
248                                 local act    = instance["active"]
249
250                                 for k, v in pairs(invalidate) do --invalidate any deleted plugin's id
251                                         if p_id == k then
252                                                 p_id = v
253                                         end
254                                 end
255
256                                 local proc = Session:processor_by_id(PBD.ID(p_id))
257                                 if proc:isnil() then goto nextline end
258                                 local plug = proc:to_insert():plugin(0)
259
260                                 for k, v in pairs(params) do
261                                         local label = plug:parameter_label(k)
262                                         if string.find(label, "Assign") or string.find(label, "Enable") then --@ToDo: Check Plugin type == LADSPA or VST?
263                                                 enable[k] = v --queue any assignments/enables for after the initial parameter recalling to duck the 'in-on-change' feature
264                                         end
265                                         ARDOUR.LuaAPI.set_processor_param(proc, k, v)
266                                 end
267
268                                 for k, v in pairs(enable) do
269                                         ARDOUR.LuaAPI.set_processor_param(proc, k, v)
270                                 end
271                                 if act then proc:activate() else proc:deactivate() end
272                         end
273
274                         ::nextline::
275                         i = i + 1
276
277                 end
278         end
279
280         function dry_run(debug, path)
281                 --returns a dialog-able table of
282                 --everything we do (logically)
283                 --in the recall function
284                 local route_values = {['----'] = "0"}
285                 for r in Session:get_routes():iter() do
286                         route_values[r:name()] =  r:to_stateful():id():to_s()
287                 end
288
289                 local i = 0
290                 local dry_table = {
291                         {type = "label", align="right", key="col-1-title", col=0, colspan=1, title = 'Source:'},
292                         {type = "label", align="left", key="col-2-title", col=1, colspan=1, title = 'Destination:'},
293                 }
294                 local file = io.open(path, "r")
295                 assert(file, "File not found!")
296
297                 for l in file:lines() do
298                         local do_plugin, do_route, do_group = false, false, false
299                         local f = load(l)
300
301                         if debug then
302                                 print(i, string.sub(l, 0, 29), f)
303                         end
304
305                         if f then f() end
306
307                         if instance["route_id"]  then do_route = true end
308                         if instance["plugin_id"] then do_plugin = true end
309                         if instance["group_id"]  then do_group = true end
310
311                         if do_group then
312                                 local group_id   = instance["group_id"]
313                                 local group_name = instance["name"]
314                                 local dlg_title, action_title  = "", ""
315
316                                 local group_ptr  = group_by_id(group_id)
317
318                                 if not(group_ptr) then
319                                         new_group = Session:new_route_group(group_name)
320                                         dlg_title = string.format("(Group) %s.", group_name, new_group:name())
321                                         --action_title = "will create and use settings"
322                                 else
323                                         dlg_title = string.format("(Group) %s.", group_ptr:name())
324                                         --action_title = "will use group settings"
325                                 end
326                                 table.insert(dry_table, {
327                                         type = "label", align="right", key =  "type-"..i , col = 0, colspan = 1, title = ""
328                                 })
329                                 table.insert(dry_table, {
330                                         type = "label", align="right", key =  "group-"..i , col = 1, colspan = 1, title = dlg_title
331                                 })
332                         end
333
334                         if do_route then
335                                 local route_id   = instance["route_id"]
336                                 local route_name = instance["route_name"]
337                                 local dlg_title = ""
338
339                                 local route_ptr = Session:route_by_id(PBD.ID(route_id))
340
341                                 if route_ptr:isnil() then
342                                         route_ptr = Session:route_by_name(route_name)
343                                         if not(route_ptr:isnil()) then
344                                                 dlg_title = string.format("%s", route_ptr:name())
345                                                 --action_title = "will use route settings"
346                                         else
347                                                 dlg_title = string.format("%s", route_name)
348                                                 --action_title = "will be ignored"
349                                         end
350                                 else
351                                         dlg_title = string.format("%s", route_ptr:name())
352                                         --action_title = "will use route settings"
353                                 end
354                                 if route_ptr:isnil() then name = route_name else name = route_ptr:name() end
355
356                                 table.insert(dry_table, {
357                                         type = "label",    align="right", key = "route-"..i , col = 1, colspan = 1, title = dlg_title
358                                 })
359                                 table.insert(dry_table, {
360                                         type = "dropdown", align="left", key = "destination-"..i, col = 2, colspan = 1, title = "", values = route_values, default = name or "----"
361                                 })
362                         end
363                         i = i + 1
364                 end
365                 return dry_table
366         end
367
368         local global_vs_local_dlg = {
369                 { type = "label", col=0, colspan=20, align="left", title = "" },
370                 {
371                         type = "radio", col=0, colspan=20, align="left", key = "recall-dir", title = "", values =
372                         {
373                                 ['Pick from Global Settings'] = 1, ['Pick from Local Settings'] = 2
374                         },
375                         default = 'Pick from Local Settings'
376                 },
377                 { type = "label", col=0, colspan=20, align="left", title = ""},
378         }
379
380         local recall_options = {
381                 { type = "label", col=0, colspan=10, align="left", title = "" },
382                 { type = "file",  col=0, colspan=15, align="left", key = "file", title = "Select a Settings File:",  path = ARDOUR.LuaAPI.build_filename(Session:path(), "export", "params.lua") },
383                 { type = "label", col=0, colspan=10, align="left", title = "" },
384         }
385
386         local gvld = LuaDialog.Dialog("Recall Mixer Settings:", global_vs_local_dlg):run()
387
388         if not(gvld) then
389                 return
390         else
391                 if gvld['recall-dir'] == 1 then
392                         local global_ok = isdir(global_path)
393                         local global_default_path = ARDOUR.LuaAPI.build_filename(global_path, fetch_valid_settings_file(global_path, string.format("FactoryDefault-%s.lua", whoami())))
394                         print(global_default_path)
395                         if global_ok then
396                                 recall_options[2]['path'] = global_default_path
397                                 local rv = LuaDialog.Dialog("Recall Mixer Settings:", recall_options):run()
398                                 if not(rv) then return end
399                                 local dry_return = LuaDialog.Dialog("Mixer Store:", dry_run(false, rv['file'])):run()
400                                 if dry_return then
401                                         recall(false, rv['file'], dry_return)
402                                 else
403                                         return
404                                 end
405                         else
406                                 LuaDialog.Message ("Recall Mixer Settings:",
407                                         global_path .. ' does not exist!\nPlease run Store Mixer Settings first.',
408                                         LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
409                         end
410                 end
411
412                 if gvld['recall-dir'] == 2 then
413                         local local_ok = isdir(local_path)
414                         local local_default_path = ARDOUR.LuaAPI.build_filename(local_path, fetch_valid_settings_file(local_path))
415                         print(local_default_path)
416                         if local_ok then
417                                 recall_options[2]['path'] = local_default_path
418                                 local rv = LuaDialog.Dialog("Recall Mixer Settings:", recall_options):run()
419                                 if not(rv) then return end
420                                 local dry_return = LuaDialog.Dialog("Mixer Store:", dry_run(false, rv['file'])):run()
421                                 if dry_return then
422                                         recall(false, rv['file'], dry_return)
423                                 else
424                                         return
425                                 end
426                         else
427                                 LuaDialog.Message ("Recall Mixer Settings:",
428                                         local_path .. 'does not exist!\nPlease run Store Mixer Settings first.',
429                                         LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
430                         end
431                 end
432         end
433
434 end end