72e7607c0180a8ac85259b519a4bcf4dc3e2684e
[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         -- get options
15         local p = params or {}
16         local n_paste  = tonumber (p["times"] or 1)
17         assert (n_paste > 0)
18
19         local proc     = ARDOUR.LuaAPI.nil_proc () -- bounce w/o processing
20         local itt      = ARDOUR.InterThreadInfo () -- bounce progress info (unused)
21
22         local loop     = Session:locations ():auto_loop_location ()
23         local playhead = Session:transport_frame ()
24
25         -- make sure we have a loop, and the playhead (edit point) is after it
26         if not loop then
27                 print_help();
28                 print ("Error: A Loop range must be set.")
29                 goto errorout
30         end
31         assert (loop:start () < loop:_end ())
32         if loop:_end () >= playhead then
33                 print_help();
34                 print ("Error: The Playhead (paste point) needs to be after the loop.")
35                 goto errorout
36         end
37
38         -- prepare undo operation
39         Session:begin_reversible_command ("Tom's Loop")
40         local add_undo = false -- keep track if something has changed
41
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
47                         break
48                 end
49         end
50
51         -- count regions that are bounced
52         local n_regions_created = 0
53
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
59                                 goto continue
60                         end
61                 end
62
63                 -- skip muted tracks (also applies to soloed + muted)
64                 if route:muted () then
65                         goto continue
66                 end
67
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)
70
71                 -- test if bouncing is possible
72                 local track = route:to_track ()
73                 if not track:bounceable (proc, false) then
74                         goto continue
75                 end
76
77                 -- only audio tracks
78                 local playlist = track:playlist ()
79                 if playlist:data_type ():to_string () ~= "audio" then
80                         goto continue
81                 end
82
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
88                         end
89                 end
90
91                 if reg_unmuted_count < 1 then
92                         goto continue
93                 end
94
95                 -- clear existing changes, prepare "diff" of state for undo
96                 playlist:to_stateful ():clear_changes ()
97
98                 -- do the actual work
99                 local region = track:bounce_range (loop:start (), loop:_end (), itt, proc, false)
100                 playlist:add_region (region, playhead, n_paste, false, 0)
101
102                 n_regions_created = n_regions_created + 1
103
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
107                         add_undo = true
108                 end
109
110                 ::continue::
111         end -- for all routes
112
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)
116         end
117
118         -- all done, commit the combined Undo Operation
119         if add_undo then
120                 -- the 'nil' Command here mean to use the collected diffs added above
121                 Session:commit_reversible_command (nil)
122         else
123                 Session:abort_reversible_command ()
124         end
125
126         print ("bounced " .. n_regions_created .. " regions from loop range (" .. loop:length() ..  " frames) to playhead @ frame # " .. playhead)
127         ::errorout::
128
129 -- when this script is called as an action, the output will be printed to the ardour log window
130         function print_help()
131                 print("")
132                 print("---------------------------------------------------------------------")
133                 print("")
134                 print("Manual for \"Tom’s Loop\" Ardour Lua Script")
135                 print("")
136                 print("---------------------------------------------------------------------")
137                 print("---------------------------------------------------------------------")
138                 print("")
139                 print("Table of Contents")
140                 print("")
141                 print("1. The first test")
142                 print("2. Using mute and solo")
143                 print("3. Combining region clouds to a defined length")
144                 print("")
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.")
153                 print("")
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.")
157                 print("")
158                 print("-Open dialog \"Script Manager\" via menu Edit/Scripted Actions/Script")
159                 print("Manager")
160                 print("")
161                 print("-In tab \"Action Scripts\", select a line and press button \"Add/Set\"")
162                 print("")
163                 print("-In dialog \"Add Lua Action\", select \"Tom’s Loop\" from the drop down")
164                 print("menu and hit \"Add\"")
165                 print("")
166                 print("-In dialog \"Set Script Parameter\" just hit \"Add\" again")
167                 print("")
168                 print("-Close dialog \"Script Manager\"")
169                 print("")
170                 print("-Open dialog \"Bindings Editor\" via menu Window/Bindings Editor")
171                 print("")
172                 print("-In tab \"Editor\", expand \"Editor\", look for entry \"Tom’s loop\",")
173                 print("select it")
174                 print("")
175                 print("-Hit the keyboard shortcut to assign to this scripted action")
176                 print("")
177                 print("-Close dialog \"Key Bindings\"")
178                 print("")
179                 print("An alternative way to quickly access a scripted action is to enable")
180                 print("\"Action Script Button Visibility\" in \"Preferences/GUI\".")
181                 print("")
182                 print("---------------------------------------------------------------------")
183                 print("")
184                 print("1. The first test")
185                 print("")
186                 print("---------------------------------------------------------------------")
187                 print("")
188                 print("-Record a short sequence of audio input or import a wave file to a")
189                 print("track to get a region")
190                 print("")
191                 print("-Set a loop range inside that one region")
192                 print("")
193                 print("-Place the playhead after the loop range, possibly after the region,")
194                 print("non-rolling")
195                 print("")
196                 print("     _L====L_              V")
197                 print(" .____|____|____________.  |")
198                 print(" |R1__|_x__|____________|  |")
199                 print("")
200                 print("-Call \"Tom’s Loop\" via the previously created shortcut")
201                 print("")
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.")
206                 print("")
207                 print("     _L====L_               --> V")
208                 print(" .____|____|____________.  .____|")
209                 print(" |R1__|_x__|____________|  |_x__|")
210                 print("")
211                 print("-Repeat calling \"Tom’s Loop\"")
212                 print("")
213                 print("This creates multiple copies of the loop range to line up one after")
214                 print("each other.")
215                 print("")
216                 print("     _L====L_                         --> V")
217                 print(" .____|____|____________.  .______________|")
218                 print(" |R1__|_x__|____________|  |_x__|_x__|_x__|")
219                 print("")
220                 print("-Set a different loop range and call \"Tom’s Loops\" again")
221                 print("")
222                 print("This will create a new region with the length of the new loop range")
223                 print("at the playhead.")
224                 print("")
225                 print("        _L=======L_                           --> V")
226                 print(" ._______|_______|______.  .______________________|")
227                 print(" |R1_____|_X_____|______|  |_x__|_x__|_x__|_X_____|")
228                 print("")
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.")
238                 print("")
239                 print("")
240                 print("---------------------------------------------------------------------")
241                 print("")
242                 print("2. Using mute and solo")
243                 print("")
244                 print("---------------------------------------------------------------------")
245                 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.")
249                 print("")
250                 print("      _L====L_                         --> V")
251                 print("  .____|____|____________.  ._________.    |")
252                 print("  |R1__|_x__|____________|  |_x__|_x__|    |")
253                 print(" .__|R2|_y__|________|_.    |_y__|_________|")
254                 print(" |R3___|_z__|__________|         |_z__|_z__|")
255                 print("")
256                 print("")
257                 print("---------------------------------------------------------------------")
258                 print("")
259                 print("3. Combining region clouds to a defined length")
260                 print("")
261                 print("---------------------------------------------------------------------")
262                 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.")
269                 print("")
270                 print("_L======================L_                            --> V")
271                 print(" |   .____  .___.  _____|_______.  .______________________|")
272                 print(" |   |R1_|  |R2_|  |R3__|_______|  |______________________|")
273                 print("")
274                 print("See also: Lua Action Bounce+Replace Regions")
275                 print("")
276         end -- print_help()
277 end -- end of anonymous action script function
278 end -- end of script factory