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 N times at playhead]]
7 -- for minimal configuration in dialogue
8 function action_params ()
9 return { ["times"] = { title = "Number of copies to add", default = "1"}, }
12 -- main method, every custom (i.e. non-ardour) method must be defined *inside* factory()
13 function factory (params) return function ()
15 local p = params or {}
16 local n_paste = tonumber (p["times"] or 1)
19 local proc = ARDOUR.LuaAPI.nil_proc () -- bounce w/o processing
20 local itt = ARDOUR.InterThreadInfo () -- bounce progress info (unused)
22 local loop = Session:locations ():auto_loop_location ()
23 local playhead = Session:transport_frame ()
25 -- make sure we have a loop, and the playhead (edit point) is after it
28 print ("Error: A Loop range must be set.")
31 assert (loop:start () < loop:_end ())
32 if loop:_end () >= playhead then
34 print ("Error: The Playhead (paste point) needs to be after the loop.")
38 -- prepare undo operation
39 Session:begin_reversible_command ("Tom's Loop")
40 local add_undo = false -- keep track if something has changed
42 -- prefer solo'ed tracks
43 local soloed_track_found = false
44 for route in Session:get_tracks ():iter () do
45 if route:soloed () then
46 soloed_track_found = true
51 -- count regions that are bounced
52 local n_regions_created = 0
54 -- loop over all tracks in the session
55 for route in Session:get_tracks ():iter () do
56 if soloed_track_found then
57 -- skip not soloed tracks
58 if not route:soloed () then
63 -- skip muted tracks (also applies to soloed + muted)
64 if route:muted () then
68 -- at this point the track is either soloed (if at least one track is soloed)
69 -- or not muted (if no track is soloed)
71 -- test if bouncing is possible
72 local track = route:to_track ()
73 if not track:bounceable (proc, false) then
78 local playlist = track:playlist ()
79 if playlist:data_type ():to_string () ~= "audio" then
83 -- check if there is at least one unmuted region in the loop-range
84 local reg_unmuted_count = 0
85 for reg in playlist:regions_touched (loop:start (), loop:_end ()):iter () do
86 if not reg:muted() then
87 reg_unmuted_count = reg_unmuted_count + 1
91 if reg_unmuted_count < 1 then
95 -- clear existing changes, prepare "diff" of state for undo
96 playlist:to_stateful ():clear_changes ()
99 local region = track:bounce_range (loop:start (), loop:_end (), itt, proc, false)
100 playlist:add_region (region, playhead, n_paste, false, 0)
102 n_regions_created = n_regions_created + 1
104 -- create a diff of the performed work, add it to the session's undo stack
105 -- and check if it is not empty
106 if not Session:add_stateful_diff_command (playlist:to_statefuldestructible ()):empty () then
111 end -- for all routes
113 --advance playhead so it's just after the newly added regions
114 if n_regions_created > 0 then
115 Session:request_locate((playhead + loop:length() * n_paste),false)
118 -- all done, commit the combined Undo Operation
120 -- the 'nil' Command here mean to use the collected diffs added above
121 Session:commit_reversible_command (nil)
123 Session:abort_reversible_command ()
126 print ("bounced " .. n_regions_created .. " regions from loop range (" .. loop:length() .. " frames) to playhead @ frame # " .. playhead)
129 -- when this script is called as an action, the output will be printed to the ardour log window
130 function print_help()
132 print("---------------------------------------------------------------------")
134 print("Manual for \"Tom’s Loop\" Ardour Lua Script")
136 print("---------------------------------------------------------------------")
137 print("---------------------------------------------------------------------")
139 print("Table of Contents")
141 print("1. The first test")
142 print("2. Using mute and solo")
143 print("3. Combining region clouds to a defined length")
145 print("Abstract: This script for Ardour (>=4.7 git) operates on the time")
146 print("line. It allows to copy and combine specific portions within the loop")
147 print("range to a later point on the time line with one single action")
148 print("command. Everything that can be heard within the loop range is")
149 print("considered for this process, namely non-muted regions on non-muted or")
150 print("soloed tracks that are fully or partially inside the loop range. This")
151 print("still sounds a bit abstract and will be more obvious with the")
152 print("following example cases of use.")
154 print("For convenience, it’s recommended to bind the script to a keyboard")
155 print("shortcut in order to quickly and easily access the \"Tom’s Loop\"")
156 print("scripted action.")
158 print("-Open dialog \"Script Manager\" via menu Edit/Scripted Actions/Script")
161 print("-In tab \"Action Scripts\", select a line and press button \"Add/Set\"")
163 print("-In dialog \"Add Lua Action\", select \"Tom’s Loop\" from the drop down")
164 print("menu and hit \"Add\"")
166 print("-In dialog \"Set Script Parameter\" just hit \"Add\" again")
168 print("-Close dialog \"Script Manager\"")
170 print("-Open dialog \"Bindings Editor\" via menu Window/Bindings Editor")
172 print("-In tab \"Editor\", expand \"Editor\", look for entry \"Tom’s loop\",")
175 print("-Hit the keyboard shortcut to assign to this scripted action")
177 print("-Close dialog \"Key Bindings\"")
179 print("An alternative way to quickly access a scripted action is to enable")
180 print("\"Action Script Button Visibility\" in \"Preferences/GUI\".")
182 print("---------------------------------------------------------------------")
184 print("1. The first test")
186 print("---------------------------------------------------------------------")
188 print("-Record a short sequence of audio input or import a wave file to a")
189 print("track to get a region")
191 print("-Set a loop range inside that one region")
193 print("-Place the playhead after the loop range, possibly after the region,")
197 print(" .____|____|____________. |")
198 print(" |R1__|_x__|____________| |")
200 print("-Call \"Tom’s Loop\" via the previously created shortcut")
202 print("This results in a new region created at the playhead, with the length")
203 print("of the loop range, containing audio of the original region. The")
204 print("playhead moved to the end of this new region so that subsequent calls")
205 print("to \"Tom’s Loop\" will result in a gap less series of regions.")
207 print(" _L====L_ --> V")
208 print(" .____|____|____________. .____|")
209 print(" |R1__|_x__|____________| |_x__|")
211 print("-Repeat calling \"Tom’s Loop\"")
213 print("This creates multiple copies of the loop range to line up one after")
216 print(" _L====L_ --> V")
217 print(" .____|____|____________. .______________|")
218 print(" |R1__|_x__|____________| |_x__|_x__|_x__|")
220 print("-Set a different loop range and call \"Tom’s Loops\" again")
222 print("This will create a new region with the length of the new loop range")
223 print("at the playhead.")
225 print(" _L=======L_ --> V")
226 print(" ._______|_______|______. .______________________|")
227 print(" |R1_____|_X_____|______| |_x__|_x__|_x__|_X_____|")
229 print("By using \"Tom’s Loop\", the loop range - which can be easily set with")
230 print("the handles - and the playhead it’s easy to create any sequence of")
231 print("existing regions on the time line. This can be useful during the")
232 print("arrangement phase where macro parts of the session are already")
233 print("temporally layed out (in the loop) but not part of the final")
234 print("arrangement yet. The process is non-destructive in a sense that the")
235 print("existing regions layout in the current loop range won’t be touched or")
236 print("replaced. The newly created regions are immediately visible on the")
237 print("time line at the playhead position.")
240 print("---------------------------------------------------------------------")
242 print("2. Using mute and solo")
244 print("---------------------------------------------------------------------")
246 print("Creating a sequence of regions like described above respects the")
247 print("current mute and solo state of a track. Variations of the loop are")
248 print("thus easy to create, further supporting the arrangement process.")
250 print(" _L====L_ --> V")
251 print(" .____|____|____________. ._________. |")
252 print(" |R1__|_x__|____________| |_x__|_x__| |")
253 print(" .__|R2|_y__|________|_. |_y__|_________|")
254 print(" |R3___|_z__|__________| |_z__|_z__|")
257 print("---------------------------------------------------------------------")
259 print("3. Combining region clouds to a defined length")
261 print("---------------------------------------------------------------------")
263 print("Multiple small regions say on a percussive track can be simplified")
264 print("for later arrangement keeping the temporal relations by combining")
265 print("them. Using \"Tom’s Loop\", the resulting regions will not only combine")
266 print("the regions but also automatically extend or shrink the new regions")
267 print("start and end point so that it is exactly of the wished length equal")
268 print("to the loop range.")
270 print("_L======================L_ --> V")
271 print(" | .____ .___. _____|_______. .______________________|")
272 print(" | |R1_| |R2_| |R3__|_______| |______________________|")
274 print("See also: Lua Action Bounce+Replace Regions")
277 end -- end of anonymous action script function
278 end -- end of script factory