1 ardour { ["type"] = "EditorAction", name = "Tom's Loop",
3 author = "Ardour Team",
4 description = [[Bounce the loop-range of all non muted audio tracks, paste at playhead]]
7 -- main method, every custom (i.e. non-ardour) method must be defined *inside* factory()
8 function factory (params) return function ()
10 -- when this script is called as an action, the output will be printed to the ardour log window
12 printf("See source for help.")
15 print("---------------------------------------------------------------------")
17 print("Manual for \"Tom’s Loop\" Ardour Lua Script")
19 print("---------------------------------------------------------------------")
20 print("---------------------------------------------------------------------")
22 print("Table of Contents")
24 print("1. The first test")
25 print("2. Using mute and solo")
26 print("3. Combining region clouds to a defined length")
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.")
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.")
41 print("-Open dialog \"Script Manager\" via menu Edit/Scripted Actions/Script")
44 print("-In tab \"Action Scripts\", select a line and press button \"Add/Set\"")
46 print("-In dialog \"Add Lua Action\", select \"Tom’s Loop\" from the drop down")
47 print("menu and hit \"Add\"")
49 print("-In dialog \"Set Script Parameter\" just hit \"Add\" again")
51 print("-Close dialog \"Script Manager\"")
53 print("-Open dialog \"Bindings Editor\" via menu Window/Bindings Editor")
55 print("-In tab \"Editor\", expand \"Editor\", look for entry \"Tom’s loop\",")
58 print("-Hit the keyboard shortcut to assign to this scripted action")
60 print("-Close dialog \"Key Bindings\"")
62 print("An alternative way to quickly access a scripted action is to enable")
63 print("\"Action Script Button Visibility\" in \"Preferences/GUI\".")
65 print("---------------------------------------------------------------------")
67 print("1. The first test")
69 print("---------------------------------------------------------------------")
71 print("-Record a short sequence of audio input or import a wave file to a")
72 print("track to get a region")
74 print("-Set a loop range inside that one region")
76 print("-Place the playhead after the loop range, possibly after the region,")
80 print(" .____|____|____________. |")
81 print(" |R1__|_x__|____________| |")
83 print("-Call \"Tom’s Loop\" via the previously created shortcut")
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.")
90 print(" _L====L_ --> V")
91 print(" .____|____|____________. .____|")
92 print(" |R1__|_x__|____________| |_x__|")
94 print("-Repeat calling \"Tom’s Loop\"")
96 print("This creates multiple copies of the loop range to line up one after")
99 print(" _L====L_ --> V")
100 print(" .____|____|____________. .______________|")
101 print(" |R1__|_x__|____________| |_x__|_x__|_x__|")
103 print("-Set a different loop range and call \"Tom’s Loops\" again")
105 print("This will create a new region with the length of the new loop range")
106 print("at the playhead.")
108 print(" _L=======L_ --> V")
109 print(" ._______|_______|______. .______________________|")
110 print(" |R1_____|_X_____|______| |_x__|_x__|_x__|_X_____|")
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.")
123 print("---------------------------------------------------------------------")
125 print("2. Using mute and solo")
127 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.")
133 print(" _L====L_ --> V")
134 print(" .____|____|____________. ._________. |")
135 print(" |R1__|_x__|____________| |_x__|_x__| |")
136 print(" .__|R2|_y__|________|_. |_y__|_________|")
137 print(" |R3___|_z__|__________| |_z__|_z__|")
140 print("---------------------------------------------------------------------")
142 print("3. Combining region clouds to a defined length")
144 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.")
153 print("_L======================L_ --> V")
154 print(" | .____ .___. _____|_______. .______________________|")
155 print(" | |R1_| |R2_| |R3__|_______| |______________________|")
157 print("See also: Lua Action Bounce+Replace Regions")
164 local p = params or {}
168 local proc = ARDOUR.LuaAPI.nil_proc () -- bounce w/o processing
169 local itt = ARDOUR.InterThreadInfo () -- bounce progress info (unused)
171 local loop = Session:locations ():auto_loop_location ()
172 local playhead = Session:transport_sample ()
174 -- make sure we have a loop, and the playhead (edit point) is after it
177 print ("Error: A Loop range must be set.")
180 assert (loop:start () < loop:_end ())
181 if loop:_end () > playhead then
183 print ("Error: The Playhead (paste point) needs to be after the loop.")
187 -- prepare undo operation
188 Session:begin_reversible_command ("Tom's Loop")
189 local add_undo = false -- keep track if something has changed
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
200 -- count regions that are bounced
201 local n_regions_created = 0
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
212 -- skip muted tracks (also applies to soloed + muted)
213 if route:muted () then
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)
220 -- test if bouncing is possible
221 local track = route:to_track ()
222 if not track:bounceable (proc, false) then
227 local playlist = track:playlist ()
228 if playlist:data_type ():to_string () ~= "audio" then
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
240 if reg_unmuted_count < 1 then
244 -- clear existing changes, prepare "diff" of state for undo
245 playlist:to_stateful ():clear_changes ()
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)
251 n_regions_created = n_regions_created + 1
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
260 end -- for all routes
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
267 -- all done, commit the combined Undo Operation
269 -- the 'nil' Command here mean to use the collected diffs added above
270 Session:commit_reversible_command (nil)
272 Session:abort_reversible_command ()
275 print ("bounced " .. n_regions_created .. " regions from loop range (" .. loop:length() .. " samples) to playhead @ sample # " .. playhead)
277 end -- end of anonymous action script function
278 end -- end of script factory
281 function icon (params) return function (ctx, width, height)
283 local y = height * .5
284 local r = math.min (x, y)
286 ctx:set_line_width (1)
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)
295 ctx:rectangle (x - r * .6, y - r * .05, r * .6, r * .3)
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);
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)
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))
312 ctx:set_source_rgba (0, 0, 0, 1)
313 ctx:stroke_preserve ()
315 ctx:set_source_rgba (1, 1, 1, 1)