when calculating average slave/master delta, use absolute value.
[ardour.git] / scripts / tomsloop.lua
1 ardour { ["type"] = "EditorAction", name = "Tom's Loop",
2         license     = "MIT",
3         author      = "Ardour Team",
4         description = [[Bounce the loop-range of all non muted audio tracks, paste N times at playhead]]
5 }
6
7 -- for minimal configuration in dialogue
8 function action_params ()
9         return { ["times"]   = { title = "Number of copies to add", default = "1"}, }
10 end
11
12 -- main method, every custom (i.e. non-ardour) method must be defined *inside* factory()
13 function factory (params) return function ()
14
15 -- when this script is called as an action, the output will be printed to the ardour log window
16         function print_help()
17                 print("")
18                 print("---------------------------------------------------------------------")
19                 print("")
20                 print("Manual for \"Tom’s Loop\" Ardour Lua Script")
21                 print("")
22                 print("---------------------------------------------------------------------")
23                 print("---------------------------------------------------------------------")
24                 print("")
25                 print("Table of Contents")
26                 print("")
27                 print("1. The first test")
28                 print("2. Using mute and solo")
29                 print("3. Combining region clouds to a defined length")
30                 print("")
31                 print("Abstract: This script for Ardour (>=4.7 git) operates on the time")
32                 print("line. It allows to copy and combine specific portions within the loop")
33                 print("range to a later point on the time line with one single action")
34                 print("command. Everything that can be heard within the loop range is")
35                 print("considered for this process, namely non-muted regions on non-muted or")
36                 print("soloed tracks that are fully or partially inside the loop range. This")
37                 print("still sounds a bit abstract and will be more obvious with the")
38                 print("following example cases of use.")
39                 print("")
40                 print("For convenience, it’s recommended to bind the script to a keyboard")
41                 print("shortcut in order to quickly and easily access the \"Tom’s Loop\"")
42                 print("scripted action.")
43                 print("")
44                 print("-Open dialog \"Script Manager\" via menu Edit/Scripted Actions/Script")
45                 print("Manager")
46                 print("")
47                 print("-In tab \"Action Scripts\", select a line and press button \"Add/Set\"")
48                 print("")
49                 print("-In dialog \"Add Lua Action\", select \"Tom’s Loop\" from the drop down")
50                 print("menu and hit \"Add\"")
51                 print("")
52                 print("-In dialog \"Set Script Parameter\" just hit \"Add\" again")
53                 print("")
54                 print("-Close dialog \"Script Manager\"")
55                 print("")
56                 print("-Open dialog \"Bindings Editor\" via menu Window/Bindings Editor")
57                 print("")
58                 print("-In tab \"Editor\", expand \"Editor\", look for entry \"Tom’s loop\",")
59                 print("select it")
60                 print("")
61                 print("-Hit the keyboard shortcut to assign to this scripted action")
62                 print("")
63                 print("-Close dialog \"Key Bindings\"")
64                 print("")
65                 print("An alternative way to quickly access a scripted action is to enable")
66                 print("\"Action Script Button Visibility\" in \"Preferences/GUI\".")
67                 print("")
68                 print("---------------------------------------------------------------------")
69                 print("")
70                 print("1. The first test")
71                 print("")
72                 print("---------------------------------------------------------------------")
73                 print("")
74                 print("-Record a short sequence of audio input or import a wave file to a")
75                 print("track to get a region")
76                 print("")
77                 print("-Set a loop range inside that one region")
78                 print("")
79                 print("-Place the playhead after the loop range, possibly after the region,")
80                 print("non-rolling")
81                 print("")
82                 print("     _L====L_              V")
83                 print(" .____|____|____________.  |")
84                 print(" |R1__|_x__|____________|  |")
85                 print("")
86                 print("-Call \"Tom’s Loop\" via the previously created shortcut")
87                 print("")
88                 print("This results in a new region created at the playhead, with the length")
89                 print("of the loop range, containing audio of the original region. The")
90                 print("playhead moved to the end of this new region so that subsequent calls")
91                 print("to \"Tom’s Loop\" will result in a gap less series of regions.")
92                 print("")
93                 print("     _L====L_               --> V")
94                 print(" .____|____|____________.  .____|")
95                 print(" |R1__|_x__|____________|  |_x__|")
96                 print("")
97                 print("-Repeat calling \"Tom’s Loop\"")
98                 print("")
99                 print("This creates multiple copies of the loop range to line up one after")
100                 print("each other.")
101                 print("")
102                 print("     _L====L_                         --> V")
103                 print(" .____|____|____________.  .______________|")
104                 print(" |R1__|_x__|____________|  |_x__|_x__|_x__|")
105                 print("")
106                 print("-Set a different loop range and call \"Tom’s Loops\" again")
107                 print("")
108                 print("This will create a new region with the length of the new loop range")
109                 print("at the playhead.")
110                 print("")
111                 print("        _L=======L_                           --> V")
112                 print(" ._______|_______|______.  .______________________|")
113                 print(" |R1_____|_X_____|______|  |_x__|_x__|_x__|_X_____|")
114                 print("")
115                 print("By using \"Tom’s Loop\", the loop range - which can be easily set with")
116                 print("the handles - and the playhead it’s easy to create any sequence of")
117                 print("existing regions on the time line. This can be useful during the")
118                 print("arrangement phase where macro parts of the session are already")
119                 print("temporally layed out (in the loop) but not part of the final")
120                 print("arrangement yet. The process is non-destructive in a sense that the")
121                 print("existing regions layout in the current loop range won’t be touched or")
122                 print("replaced. The newly created regions are immediately visible on the")
123                 print("time line at the playhead position.")
124                 print("")
125                 print("")
126                 print("---------------------------------------------------------------------")
127                 print("")
128                 print("2. Using mute and solo")
129                 print("")
130                 print("---------------------------------------------------------------------")
131                 print("")
132                 print("Creating a sequence of regions like described above respects the")
133                 print("current mute and solo state of a track. Variations of the loop are")
134                 print("thus easy to create, further supporting the arrangement process.")
135                 print("")
136                 print("      _L====L_                         --> V")
137                 print("  .____|____|____________.  ._________.    |")
138                 print("  |R1__|_x__|____________|  |_x__|_x__|    |")
139                 print(" .__|R2|_y__|________|_.    |_y__|_________|")
140                 print(" |R3___|_z__|__________|         |_z__|_z__|")
141                 print("")
142                 print("")
143                 print("---------------------------------------------------------------------")
144                 print("")
145                 print("3. Combining region clouds to a defined length")
146                 print("")
147                 print("---------------------------------------------------------------------")
148                 print("")
149                 print("Multiple small regions say on a percussive track can be simplified")
150                 print("for later arrangement keeping the temporal relations by combining")
151                 print("them. Using \"Tom’s Loop\", the resulting regions will not only combine")
152                 print("the regions but also automatically extend or shrink the new regions")
153                 print("start and end point so that it is exactly of the wished length equal")
154                 print("to the loop range.")
155                 print("")
156                 print("_L======================L_                            --> V")
157                 print(" |   .____  .___.  _____|_______.  .______________________|")
158                 print(" |   |R1_|  |R2_|  |R3__|_______|  |______________________|")
159                 print("")
160                 print("See also: Lua Action Bounce+Replace Regions")
161                 print("")
162                 print("")
163         end -- print_help()
164
165         -- get options
166         local p = params or {}
167         local n_paste  = tonumber (p["times"] or 1)
168         assert (n_paste > 0)
169
170         local proc     = ARDOUR.LuaAPI.nil_proc () -- bounce w/o processing
171         local itt      = ARDOUR.InterThreadInfo () -- bounce progress info (unused)
172
173         local loop     = Session:locations ():auto_loop_location ()
174         local playhead = Session:transport_frame ()
175
176         -- make sure we have a loop, and the playhead (edit point) is after it
177         if not loop then
178                 print_help();
179                 print ("Error: A Loop range must be set.")
180                 goto errorout
181         end
182         assert (loop:start () < loop:_end ())
183         if loop:_end () >= playhead then
184                 print_help();
185                 print ("Error: The Playhead (paste point) needs to be after the loop.")
186                 goto errorout
187         end
188
189         -- prepare undo operation
190         Session:begin_reversible_command ("Tom's Loop")
191         local add_undo = false -- keep track if something has changed
192
193         -- prefer solo'ed tracks
194         local soloed_track_found = false
195         for route in Session:get_tracks ():iter () do
196                 if route:soloed () then
197                         soloed_track_found = true
198                         break
199                 end
200         end
201
202         -- count regions that are bounced
203         local n_regions_created = 0
204
205         -- loop over all tracks in the session
206         for route in Session:get_tracks ():iter () do
207                 if soloed_track_found then
208                         -- skip not soloed tracks
209                         if not route:soloed () then
210                                 goto continue
211                         end
212                 end
213
214                 -- skip muted tracks (also applies to soloed + muted)
215                 if route:muted () then
216                         goto continue
217                 end
218
219                 -- at this point the track is either soloed (if at least one track is soloed)
220                 -- or not muted (if no track is soloed)
221
222                 -- test if bouncing is possible
223                 local track = route:to_track ()
224                 if not track:bounceable (proc, false) then
225                         goto continue
226                 end
227
228                 -- only audio tracks
229                 local playlist = track:playlist ()
230                 if playlist:data_type ():to_string () ~= "audio" then
231                         goto continue
232                 end
233
234                 -- check if there is at least one unmuted region in the loop-range
235                 local reg_unmuted_count = 0
236                 for reg in playlist:regions_touched (loop:start (), loop:_end ()):iter () do
237                         if not reg:muted() then
238                                 reg_unmuted_count = reg_unmuted_count + 1
239                         end
240                 end
241
242                 if reg_unmuted_count < 1 then
243                         goto continue
244                 end
245
246                 -- clear existing changes, prepare "diff" of state for undo
247                 playlist:to_stateful ():clear_changes ()
248
249                 -- do the actual work
250                 local region = track:bounce_range (loop:start (), loop:_end (), itt, proc, false)
251                 playlist:add_region (region, playhead, n_paste, false, 0, 0, false)
252
253                 n_regions_created = n_regions_created + 1
254
255                 -- create a diff of the performed work, add it to the session's undo stack
256                 -- and check if it is not empty
257                 if not Session:add_stateful_diff_command (playlist:to_statefuldestructible ()):empty () then
258                         add_undo = true
259                 end
260
261                 ::continue::
262         end -- for all routes
263
264         --advance playhead so it's just after the newly added regions
265         if n_regions_created > 0 then
266                 Session:request_locate((playhead + loop:length() * n_paste),false)
267         end
268
269         -- all done, commit the combined Undo Operation
270         if add_undo then
271                 -- the 'nil' Command here mean to use the collected diffs added above
272                 Session:commit_reversible_command (nil)
273         else
274                 Session:abort_reversible_command ()
275         end
276
277         print ("bounced " .. n_regions_created .. " regions from loop range (" .. loop:length() ..  " frames) to playhead @ frame # " .. playhead)
278         ::errorout::
279 end -- end of anonymous action script function
280 end -- end of script factory
281
282
283 function icon (params) return function (ctx, width, height)
284         local x = width * .5
285         local y = height * .5
286         local r = math.min (x, y)
287
288         ctx:set_line_width (1)
289
290         function stroke_outline ()
291                 ctx:set_source_rgba (0, 0, 0, 1)
292                 ctx:stroke_preserve ()
293                 ctx:set_source_rgba (1, 1, 1, 1)
294                 ctx:fill ()
295         end
296
297         ctx:rectangle (x - r * .6, y - r * .05, r * .6, r * .3)
298         stroke_outline ()
299
300         ctx:arc (x, y, r * .61, math.pi, 0.2 * math.pi)
301         ctx:arc_negative (x, y, r * .35, 0.2 * math.pi, math.pi);
302         stroke_outline ()
303
304         function arc_arrow (rad, ang)
305                 return x - rad * math.sin (ang * 2.0 * math.pi), y - rad * math.cos (ang * 2.0 * math.pi)
306         end
307
308         ctx:move_to (arc_arrow (r * .36, .72))
309         ctx:line_to (arc_arrow (r * .17, .72))
310         ctx:line_to (arc_arrow (r * .56, .60))
311         ctx:line_to (arc_arrow (r * .75, .72))
312         ctx:line_to (arc_arrow (r * .62, .72))
313
314         ctx:set_source_rgba (0, 0, 0, 1)
315         ctx:stroke_preserve ()
316         ctx:close_path ()
317         ctx:set_source_rgba (1, 1, 1, 1)
318         ctx:fill ()
319 end end