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