OSC: Don't look for which stripable is selected until we actually need it, It may...
[ardour.git] / scripts / tomsloop.lua
1 ardour { ["type"] = "EditorAction", name = "Tom's Loop",
2         license     = "MIT",
3         author      = "Robin Gareus",
4         email       = "robin@gareus.org",
5         site        = "http://gareus.org",
6         description = [[Bounce the loop-range of all non muted audio tracks, paste N times at playhead]]
7 }
8
9 function action_params ()
10         return { ["times"]   = { title = "Number of copies to add", default = "1"}, }
11 end
12
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 ("A Loop range must be set.")
28                 goto errorout
29         end
30         assert (loop:start () < loop:_end ())
31         if loop:_end () >= playhead then
32                 print ("The Playhead (paste point) needs to be after the loop.")
33                 goto errorout
34         end
35
36         -- prepare undo operation
37         Session:begin_reversible_command ("Tom's Loop")
38         local add_undo = false -- keep track if something has changed
39
40         -- prefer solo'ed tracks
41         local soloed_track_found = false
42         for route in Session:get_tracks ():iter () do
43                 if route:soloed () then
44                         soloed_track_found = true
45                         break
46                 end
47         end
48
49         -- count regions that are bounced
50         local n_regions_created = 0
51
52         -- loop over all tracks in the session
53         for route in Session:get_tracks ():iter () do
54                 if soloed_track_found then
55                         -- skip not soloed tracks
56                         if not route:soloed () then
57                                 goto continue
58                         end
59                 end
60
61                 -- skip muted tracks (also applies to soloed + muted)
62                 if route:muted () then
63                         goto continue
64                 end
65
66                 -- at this point the track is either soloed (if at least one track is soloed)
67                 -- or not muted (if no track is soloed)
68
69                 -- test if bouncing is possible
70                 local track = route:to_track ()
71                 if not track:bounceable (proc, false) then
72                         goto continue
73                 end
74
75                 -- only audio tracks
76                 local playlist = track:playlist ()
77                 if playlist:data_type ():to_string () ~= "audio" then
78                         goto continue
79                 end
80
81                 -- check if there is at least one unmuted region in the loop-range
82                 local reg_unmuted_count = 0
83                 for reg in playlist:regions_touched (loop:start (), loop:_end ()):iter () do
84                         if not reg:muted() then
85                                 reg_unmuted_count = reg_unmuted_count + 1
86                         end
87                 end
88
89                 if reg_unmuted_count < 1 then
90                         goto continue
91                 end
92
93                 -- clear existing changes, prepare "diff" of state for undo
94                 playlist:to_stateful ():clear_changes ()
95
96                 -- do the actual work
97                 local region = track:bounce_range (loop:start (), loop:_end (), itt, proc, false)
98                 playlist:add_region (region, playhead, n_paste, false)
99
100                 n_regions_created = n_regions_created + 1
101
102                 -- create a diff of the performed work, add it to the session's undo stack
103                 -- and check if it is not empty
104                 if not Session:add_stateful_diff_command (playlist:to_statefuldestructible ()):empty () then
105                         add_undo = true
106                 end
107
108                 ::continue::
109         end
110
111         --advance playhead so it's just after the newly added regions
112         if n_regions_created > 0 then
113                 Session:request_locate((playhead + loop:length() * n_paste),false)
114         end
115
116         -- all done, commit the combined Undo Operation
117         if add_undo then
118                 -- the 'nil' Command here mean to use the collected diffs added above
119                 Session:commit_reversible_command (nil)
120         else
121                 Session:abort_reversible_command ()
122         end
123
124         print ("bounced " .. n_regions_created .. " regions from loop range (" .. loop:length() ..  " frames) to playhead @ frame # " .. playhead)
125
126         ::errorout::
127 end end