]> Shamusworld >> Repos - ardour-manual/blob - _manual/27_lua-scripting/01_brain_dump.html
529b70f7a960d63e34e2dc38b6f2dc44c81f68d0
[ardour-manual] / _manual / 27_lua-scripting / 01_brain_dump.html
1 ---
2 layout: default
3 title: Scripting Documentation
4 ---
5
6 <p class="warning">
7 This Documentation is Work in Progress and far from complete. Also the documented API may be subject to change.
8 </p>
9
10 <h2>Preface</h2>
11 <p>
12 There are cases that a Ardour cannot reasonably cater for with core functionality by itself, either because they're session specific or user specific edge cases.
13 </p><p>
14 Examples for these include voice-activate (record-arm specific tracks and roll transport depending on signal levels),
15 rename all regions after a specific timecode, launch an external application when a certain track is soloed, generate automation curves
16 or simply provide a quick shortcut for a custom batch operation.
17 </p><p>
18 Cases like this call for means to extend the DAW without actually changing the DAW itself. This is where scripting comes in.
19 </p><p>
20 "Scripting" refers to tasks that could alternatively be executed step-by-step by a human operator.
21 </p><p>
22 Lua is a tiny and simple language which is easy to learn, yet allows for comprehensive solutions.
23 Lua is also a glue language it allows to tie existing component in Ardour together in unprecedented ways,
24 and most importantly Lua is one of the few scripting-languages which can be safely used in a real-time environment.
25 </p><p>
26 A good introduction to Lua is the book <a href="http://www.lua.org/pil/">Programming in Lua</a>. The first edition is available online,
27 but if you have the means buy a copy of the book, it not only helps to support the Lua project,
28 but provides for a much nicer reading and learning experience.
29 </p>
30
31 <h2>Overview</h2>
32 <p>
33 The core of ardour is a real-time audio engine that runs and processes audio. One interfaces with than engine by sending it commands.
34 Scripting can be used to interact with or modify active Ardour session. Just like a user uses the Editor/Mixer GUI to modify the state or parameters of the session.
35 </p><p>
36 Doing this programmatically requires some knowledge about the objects used internally.
37 Most Ardour C++ objects and their methods are directly exposed to Lua and one can call functions or modify variables:
38 </p>
39
40 <div style="width:80%; margin:.5em auto;">
41         <div style="width:45%; float:left;">
42                 C++<br/>
43                 <code class="cxx">
44                         session-&gt;set_transport_speed (1.0);
45                 </code>
46         </div>
47         <div style="width:45%; float:right;">
48                 Lua<br/>
49                 <code class="lua">
50                         Session:set_transport_speed (1.0)
51                 </code>
52         </div>
53 </div>
54 <div style="clear:both;"></div>
55
56 <p>
57 You may notice that there is only a small syntactic difference, in this case.
58 While C++ requires recompiling the application for every change, Lua script can be loaded, written or modified while the application is running.
59 Lua also abstracts away many of the C++ complexities such as object lifetime, type conversion and null-pointer checks.
60 </p><p>
61 Close ties with the underlying C++ components is where the power of scripting comes from.
62 A script can orchestrate interaction of lower-level components which take the bulk of the CPU time of the final program.
63 </p>
64 </p><p>
65 At the time of writing Ardour integrates Lua 5.3.2: <a href="http://www.lua.org/manual/5.3/manual.html">Lua 5.3 reference manual</a>.
66 </p>
67
68 <h2>Integration</h2>
69 <p>
70 Like Control surfaces and the GUI, Lua Scripts are confined to certain aspects of the program. Ardour provides the framework and runs Lua (not the other way around).
71 </p>
72 <p>
73 In Ardour's case Lua is available:
74 </p>
75
76 <dl>
77         <dt>Editor Action Scripts</dt><dd>User initiated actions (menu, shortcuts) for batch processing</dd>
78         <dt>Editor Hooks/Callbacks</dt><dd>Event triggered actions for the Editor/Mixer GUI</dd>
79         <dt>Session Scripts</dt><dd>Scripts called at the start of every audio cycle (session, real-time)</dd>
80         <dt>DSP Scripts</dt><dd>Audio/Midi processor - plugins with access to the Ardour session (per track/bus, real-time)</dd>
81         <dt>Script Console</dt><dd>Action Script commandline</dd>
82 </dl>
83
84 <p>
85 There are is also a special mode:
86 </p>
87 <dl>
88         <dt>Commandline Tool</dt><dd>Replaces the complete Editor GUI, direct access to libardour (no GUI) from the commandline.<br/>
89         <em>Be aware that the vast majority of complex functionality is provided by the Editor UI.</em></dd>
90 </dl>
91
92 <h2>Managing Scripts</h2>
93
94 <p>
95 Ardour searches for Lua scripts in the <code>scripts</code> folder in <code>$ARDOUR_DATA_PATH</code>,
96 Apart from scripts included directly with Ardour, this includes</p>
97 <table>
98         <tr><th>GNU/Linux</th><td><code>$HOME/.config/ardour5/scripts</code></td></tr>
99         <tr><th>Mac OS X</th><td><code>$HOME/Library/Preferences/Ardour5/scripts</code></td></tr>
100         <tr><th>Windows</th><td><code>%localappdata%\ardour5\scripts</code></td></tr>
101 </table>
102
103 <p>Files must end with <code>.lua</code> file extension.</p>
104
105 <p>Scripts are managed via the GUI</p>
106 <dl>
107         <dt>Editor Action Scripts</dt><dd>Menu &rarr; Edit &rarr; Scripted Actions &rarr; Manage</dd>
108         <dt>Editor Hooks/Callbacks</dt><dd>Menu &rarr; Edit &rarr; Scripted Actions &rarr; Manage</dd>
109         <dt>Session Scripts</dt><dd>Menu &rarr; Session &rarr; Scripting &rarr; Add/Remove Script</dd>
110         <dt>DSP Scripts</dt><dd>Mixer-strip &rarr; context menu (right click) &rarr; New Lua Proc</dd>
111         <dt>Script Console</dt><dd>Menu &rarr; Window &rarr; Scripting</dd>
112 </dl>
113
114 <h2>Script Layout</h2>
115 <ul>
116         <li>Every script must include an <code>ardour</code> descriptor table. Required fields are "Name" and "Type".</li>
117         <li>A script must provide a <em>Factory method</em>: A function with optional instantiation parameters which returns the actual script.</li>
118         <li>[optional]: list of parameters for the "factory".</li>
119         <li>in case of DSP scripts, an optional list of automatable parameters and possible audio/midi port configurations, and a <code>dsp_run</code> function, more on that later.</li>
120 </ul>
121
122
123 <p>A minimal example script looks like:</p>
124 <div>
125 <pre><code class="lua">
126         ardour {
127           ["type"]    = "EditorAction",
128           name        = "Rewind",
129         }
130
131         function factory (unused_params)
132           return function ()
133            Session:goto_start()  -- rewind the transport
134           end
135         end
136 </code></pre>
137 </div>
138
139 <p>
140 The common part for all scripts is the "Descriptor". It's a Lua function which returns a table (key/values) with the following keys (the keys are case-sensitive):
141 </p>
142 <dl>
143         <dt>type [required]</dt><dd>one of "<code>DSP</code>", "<code>Session</code>", "<code>EditorHook</code>", "<code>EditorAction</code>" (the type is not case-sensitive)</dd>
144         <dt>name [required]</dt><dd>Name/Title of the script</dd>
145         <dt>author</dt><dd>Your Name</dd>
146         <dt>license</dt><dd>The license of the script (e.g. "GPL" or "MIT")</dd>
147         <dt>description</dt><dd>A longer text explaining to the user what the script does</dd>
148 </dl>
149
150 <p class="note">
151 Scripts that come with Ardour (currently mostly examples) can be found in the <a href="https://github.com/Ardour/ardour/tree/master/scripts">Source Tree</a>.
152 </p>
153
154 <h3>Action Scripts</h3>
155 <p>Action scripts are the simplest form. An anonymous Lua function is called whenever the action is triggered. A simple action script is shown above.</p>
156 <p>There are 10 action script slots available, each of which is a standard GUI action available from the menu and hence can be bound to a keyboard shortcut</p>
157
158 <h3>Session Scripts</h3>
159 <p>Session scripts similar to Actions Scripts, except the anonymous function is called periodically every process cycle.
160 The function receives a single parameter - the number of audio samples which are processed in the given cycle</p>
161 <div>
162 <pre><code class="lua">
163 ardour {
164   ["type"]    = "session",
165   name        = "Example Session Script",
166   description = [[
167   An Example Ardour Session Script.
168   This example stops the transport after rolling for a specific time.]]
169 }
170
171
172 -- instantiation options, these are passed to the "factory" method below
173 function sess_params ()
174   return
175   {
176     ["print"]  = { title = "Debug Print (yes/no)", default = "no", optional = true },
177     ["time"] = { title = "Timeout (sec)", default = "90", optional = false },
178   }
179 end
180
181 function factory (params)
182   return function (n_samples)
183     local p = params["print"] or "no"
184     local timeout = params["time"] or 90
185     a = a or 0
186     if p ~= "no" then print (a, n_samples, Session:frame_rate (), Session:transport_rolling ()) end -- debug output (not rt safe)
187     if (not Session:transport_rolling()) then
188       a = 0
189       return
190     end
191     a = a + n_samples
192     if (a &gt; timeout * Session:frame_rate()) then
193       Session:request_transport_speed(0.0, true)
194     end
195   end
196 end
197 </code></pre>
198 </div>
199
200 <h3>Action Hooks</h3>
201 <p>Action hook scripts must define an additional function which returns a <em>Set</em> of Signal that which trigger the callback (documenting available slots and their parameters remains to be done).</p>
202 <div>
203 <pre><code class="lua">
204 ardour {
205   ["type"]    = "EditorHook",
206   name        = "Hook Example",
207   description = "Rewind On Solo Change, Write a file when regions are moved.",
208 }
209
210 function signals ()
211   s = LuaSignal.Set()
212   s:add (
213     {
214       [LuaSignal.SoloActive] = true,
215       [LuaSignal.RegionPropertyChanged] = true
216     }
217   )
218   return s
219 end
220
221 function factory (params)
222   return function (signal, ref, ...)
223     -- print (signal, ref, ...)
224
225     if (signal == LuaSignal.SoloActive) then
226       Session:goto_start()
227     end
228
229     if (signal == LuaSignal.RegionPropertyChanged) then
230       obj,pch = ...
231       file = io.open ("/tmp/test" ,"a")
232       io.output (file
233       io.write (string.format ("Region: '%s' pos-changed: %s, length-changed: %s\n",
234         obj:name (),
235         tostring (pch:containsFramePos (ARDOUR.Properties.Start)),
236         tostring (pch:containsFramePos (ARDOUR.Properties.Length))
237         ))
238       io.close (file)
239     end
240   end
241 end
242 </code></pre>
243 </div>
244
245 <h3>DSP Scripts</h3>
246 <p>See the scripts folder for examples for now.</p>
247 <p>Some notes for further doc:</p>
248 <ul>
249         <li>required function: <code>dsp_ioconfig ()</code>: return a list of possible audio I/O configurations - follows Audio Unit conventions.</li>
250         <li>optional function: <code>dsp_dsp_midi_input ()</code>: return true if the plugin can receive midi input</li>
251         <li>optional function: <code>dsp_params ()</code>: return a table of possible parameters (automatable)</li>
252         <li>optional function: <code>dsp_init (samplerate)</code>: called when instantiation the plugin with given samplerate.</li>
253         <li>optional function: <code>dsp_configure (in, out)</code>: called after instantiation with configured plugin i/o.</li>
254         <li>required function: <code>dsp_run (ins, outs, n_samples)</code> OR <code>dsp_runmap (bufs, in_map, out_map, n_samples, offset)</code>: DSP process callback. The former is a convenient abstraction that passes mapped buffers (as table). The latter is a direct pass-through matching Ardour's internal <code>::connect_and_run()</code> API, which requires the caller to map and offset raw buffers.</li>
255         <li>plugin parameters are handled via the global variable <code>CtrlPorts</code>.</li>
256         <li>midi data is passed via the global variable <code>mididata</code> which is valid during <code>dsp_run</code> only. (dsp_runmap requires the script to pass raw data from the buffers according to in_map)</li>
257         <li>The script has access to the current session via the global variable Session, but access to the session methods are limited to realtime safe functions</li>
258 </ul>
259
260 <h2>Accessing Ardour Objects</h2>
261 <p>
262 The top most object in Ardour is the <code>ARDOUR::Session</code>.
263 Fundamentally, a Session is just a collection of other things:
264 Routes (tracks, busses), Sources (Audio/Midi), Regions, Playlists, Locations, Tempo map, Undo/Redo history, Ports, Transport state &amp; controls, etc.
265 </p><p>
266 Every Lua interpreter can access it via the global variable <code>Session</code>.
267 </p><p>
268 GUI context interpreters also have an additional object in the global environment: The Ardour <code>Editor</code>. The Editor provides access to high level functionality which is otherwise triggered via GUI interaction such as undo/redo, open/close windows, select objects, drag/move regions. It also holds the current UI state: snap-mode, zoom-range, etc.
269 The Editor also provides complex operations such as "import audio" which under the hood, creates a new Track, adds a new Source Objects (for every channel) with optional resampling, creates both playlist and regions and loads the region onto the Track all the while displaying a progress information to the user.
270 </p>
271
272 <p class="note">
273 Documenting the bound C++ methods and class hierarchy is somewhere on the ToDo list.
274 Meanwhile <a href="https://github.com/Ardour/ardour/blob/master/libs/ardour/luabindings.cc">luabindings.cc</a> is the best we can offer.
275 </p>
276
277 <h2>Concepts</h2>
278 <ul>
279         <li>There are no bound constructors: Lua asks Ardour to create objects (e.g. add a new track), then receives a reference to the object to modify it.</li>
280         <li>Scripts, once loaded, are saved with the Session (no reference to external files). This provides for portable Sessions.</li>
281         <li>Lua Scripts are never executed directly. They provide a "factory" method which can have optional instantiation parameters, which returns a lua closure.</li>
282         <li>No external lua modules/libraries can be used, scripts need to be self contained (portable across different systems (libs written in Lua can be used, and important c-libs/functions can be included with ardour if needed).</li>
283 </ul>
284 <p>
285 Ardour is a highly multithreaded application and interaction between the different threads, particularly real-time threads, needs to to be done with care.
286 This part has been abstracted away by providing separate Lua interpreters in different contexts and restricting available interaction:
287 </p>
288 <ul>
289         <li>Editor Actions run in a single instance interpreter in the GUI thread.</li>
290         <li>Editor Hooks connect to libardour signals. Every Callback uses a dedicated lua interpreter which is in the GUI thread context.</li>
291         <li>All Session scripts run in a single instance in the main real-time thread (audio callback)</li>
292         <li>DSP scripts have a separate instance per script and run in one of the DSP threads.</li>
293 </ul>
294 <p>
295 The available interfaces differ between contexts. e.g. it is not possible to create new tracks or import audio from real-time context; while it is not possible to modify audio buffers from the GUI thread.
296 </p>
297
298 <h2>Current State</h2>
299 Fully functional, yet still in a prototyping stage:
300
301 <ul>
302         <li>The GUI to add/configure scripts is rather minimalistic.</li>
303         <li>The interfaces may change (particularly DSP, and Session script <code>run()</code>.</li>
304         <li>Further planned work includes:
305                 <ul>
306                         <li>Built-in Script editor (customize/modify Scripts in-place)</li>
307                         <li>convenience methods (wrap more complex Ardour actions into a library). e.g set plugin parameters, write automation lists from a lua table</li>
308                         <li>Add some useful scripts and more examples</li>
309                         <li>Documentation (Ardour API), also usable for tab-exansion, syntax highlighting</li>
310                         <li>bindings for GUI Widgets (plugin UIs, message boxes, etc)</li>
311                 </ul>
312                 <li>
313 </ul>
314
315
316 <h2>Examples</h2>
317 <p>Apart from the <a href="https://github.com/Ardour/ardour/tree/master/scripts">scripts included with the source-code</a> here are a few examples without further comments...
318
319 <h3>Editor Console Examples</h3>
320 <div>
321 <pre><code class="lua">
322 print (Session:route_by_remote_id(1):name())
323
324 a = Session:route_by_remote_id(1);
325 print (a:name());
326
327 print(Session:get_tracks():size())
328
329 for i, v in ipairs(Session:unknown_processors():table()) do print(v) end
330 for i, v in ipairs(Session:get_tracks():table()) do print(v:name()) end
331
332 for t in Session:get_tracks():iter() do print(t:name()) end
333 for r in Session:get_routes():iter() do print(r:name()) end
334
335
336 Session:tempo_map():add_tempo(ARDOUR.Tempo(100,4), Timecode.BBT_TIME(4,1,0))
337
338
339 Editor:set_zoom_focus(Editing.ZoomFocusRight)
340 print(Editing.ZoomFocusRight);
341 Editor:set_zoom_focus(1)
342
343
344 files = C.StringVector();
345 files:push_back("/home/rgareus/data/coding/ltc-tools/smpte.wav")
346 pos = -1
347 Editor:do_import(files, Editing.ImportDistinctFiles, Editing.ImportAsTrack, ARDOUR.SrcQuality.SrcBest, pos, ARDOUR.PluginInfo())
348
349 #or in one line:
350 Editor:do_import(C.StringVector():add({"/path/to/file.wav"}), Editing.ImportDistinctFiles, Editing.ImportAsTrack, ARDOUR.SrcQuality.SrcBest, -1, ARDOUR.PluginInfo())
351
352 # called when a new session is loaded:
353 function new_session (name) print("NEW SESSION:", name) end
354
355
356 # read/set/describe a plugin parameter
357 route = Session:route_by_remote_id(1)
358 processor = route:nth_plugin(0)
359 plugininsert = processor:to_insert()
360
361 plugin = plugininsert:plugin(0)
362 print (plugin:label())
363 print (plugin:parameter_count())
364
365 x = ARDOUR.ParameterDescriptor ()
366 _, t = plugin:get_parameter_descriptor(2, x) -- port #2
367 paramdesc = t[2]
368 print (paramdesc.lower)
369
370 ctrl = Evoral.Parameter(ARDOUR.AutomationType.PluginAutomation, 0, 2)
371 ac = plugininsert:automation_control(ctrl, false)
372 print (ac:get_value ())
373 ac:set_value(1.0, PBD.GroupControlDisposition.NoGroup)
374
375 # the same using a convenience wrapper:
376 route = Session:route_by_remote_id(1)
377 proc = t:nth_plugin (i)
378 ARDOUR.LuaAPI.set_processor_param (proc, 2, 1.0)
379
380 </code></pre>
381 </div>
382
383 <h3>Commandline Session</h3>
384 <p>The standalone tool <code>luasession</code> allows one to access an Ardour session directly from the commandline.
385 Interaction is limited by the fact that most actions in Ardour are provided by the Editor GUI.
386 </p><p>
387 <code>luasession</code> provides only two special functions <code>load_session</code> and <code>close_session</code> and exposes the <code>AudioEngine</code> instance as global variable.
388 </p>
389
390 <div>
391 <pre><code class="lua">
392 for i,_ in AudioEngine:available_backends():iter() do print (i.name) end
393
394 backend = AudioEngine:set_backend("ALSA", "", "")
395 print (AudioEngine:current_backend_name())
396
397 for i,_ in backend:enumerate_devices():iter() do print (i.name) end
398
399 backend:set_input_device_name("HDA Intel PCH")
400 backend:set_output_device_name("HDA Intel PCH")
401
402 print (backend:buffer_size())
403 print (AudioEngine:get_last_backend_error())
404
405 s = load_session ("/home/rgareus/Documents/ArdourSessions/lua2/", "lua2")
406 s:request_transport_speed (1.0)
407 print (s:transport_rolling())
408 s:goto_start()
409 close_session()
410
411 </code></pre>
412 </div>