ardour { ["type"] = "EditorAction", name = "New clean playlists",
	license     = "MIT",
	author      = "Mathieu Picot (Houston4444)",
	description = [[Copy the current playlist of selected audio tracks to a new playlist with audible regions only.]]
}

function factory (params) return function ()

	-- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
	local sel = Editor:get_selection ()

	-- Track/Bus Selection -- iterate over all Editor-GUI selected tracks
	-- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:TrackSelection
	for route in sel.tracks:routelist():iter() do

		-- each of the items 'route' is-a
		-- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route
		local track = route:to_track() -- see if it's a track
		if track:isnil() then
			-- if not, skip it
			goto next_route
		end

		local is_audio = false
		if track:data_type():to_string() == "audio" then
			is_audio = true
		end

		local main_pl_name = track:name() .. "._MAIN_"
		local main_pl_ex_name = track:name() .. "_ExMAIN_"

		-- find the playlist name track_name._MAIN_
		for pl in Session:playlists():playlists_for_track (track):iter() do
			if pl:name() == main_pl_name then
				pl:set_name(main_pl_ex_name)
				break
			end
		end

		-- copy current playlist, get new playlist and rename it
		-- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Playlist
		track:use_copy_playlist()
		local playlist = track:playlist()
		playlist:set_name(track:name() .. "._MAIN_")

		-- we will stock here the values needed to modify/remove regions
		local regions_to_modify = {}
		local point_b = 0
		local point_c = 0

		for r in playlist:region_list():iter() do
			local r_front = r:position():samples()
			local r_end = r_front + r:length():samples()

			-- region can be finally split into many segments
			-- 'segments' is a table containing segments with the form
			-- {{seg1_start, seg1_end}, {seg2_start, seg2_finish}...}
			-- segments will finally be regions
			local segments = {{r_front, r_end}}

			-- values used for audio tracks to prevent cut during an audible fade
			local r_no_cut_before = r_front
			local r_no_cut_after = r_end

			if is_audio then
				local ra = r:to_audioregion()
				if ra:fade_in_active() then
					r_no_cut_before = r_front + ra:fade_in_length():samples() + 64
				end

				if ra:fade_out_active() then
					r_no_cut_after = r_end - ra:fade_out_length():samples() - 64
				end
			end

			for rg in playlist:region_list():iter() do
				-- ignore regions equal or above this one
				if rg:layer() <= r:layer() then
					goto next_rg
				end

				-- get points between which lower regions can be cut
				local cut_point_left = rg:position():samples()
				local cut_point_right = cut_point_left + rg:length():samples()

				if is_audio then
					rga = rg:to_audioregion()

					cut_point_left  = cut_point_left + 64
					cut_point_right = cut_point_right - 64

					if rga:fade_in_active() then
						cut_point_left = cut_point_left + rga:fade_in_length():samples()
					end

					if rga:fade_out_active() then
						cut_point_right = cut_point_right - rga:fade_out_length():samples()
					end
				end

				-- ignore regions with no overlap with this one
				if cut_point_left >= segments[#segments][2]
						or cut_point_right <= segments[1][1] then
					goto next_rg
				end

				-- stock here the segments ids we should remove
				local remove_id_list = {}

				-- iterate segments
				for i, s in pairs(segments) do
					if cut_point_right <= s[1] or cut_point_left >= s[2] then
						-- rg:  ___      or      ____
						-- s :      ___      ____

						-- compared region doesn't overlap this segment
						goto next_segment

					elseif cut_point_left <= s[1] and cut_point_right >= s[2] then
						-- rg:  ________
						-- s :    ____
						
						-- compared region exceed both front and end
						-- this segment will be removed
						table.insert(remove_id_list, i)
						goto next_segment

					elseif cut_point_left <= s[1] then
						-- rg:  ____
						-- s :    _____

						if is_audio then
							-- audio segment is cut 64 samples before compared region fade out
							-- in the limit of the original segment
							-- it also do not cut during the region fades
							if cut_point_right > r_no_cut_before then
								s[1] = math.min(cut_point_right,
								                r_no_cut_after)
							end
						else
							s[1] = cut_point_right
						end

					elseif cut_point_right >= s[2] then
						-- rg:     _____
						-- s :  _____

						if is_audio then
							-- audio segment is cut 64 samples after compared region fade in
							-- in the limit of the original segment
							if cut_point_left < r_no_cut_after then
								s[2] = math.max(cut_point_left,
								                r_no_cut_before)
							end
						else
							s[2] = cut_point_left
						end

					else
						-- rg:      B___C
						-- s :  A___________D

						-- worst case, compared region rg is above r,
						-- rg starts after and finish before r.

						point_b = cut_point_left
						point_c = cut_point_right

						if is_audio then
							point_b = math.max(point_b, r_no_cut_before)
							point_c = math.min(point_c, r_no_cut_after)

							if point_b > r_no_cut_after or point_c < r_no_cut_before then
								-- we must keep the fade integrity
								goto next_segment
							end
						end

						-- we can modify the table now because
						-- we break the iteration loop just after.
						-- add the new segment C->D
						table.insert(segments, i + 1, {point_c, s[2]})

						-- reduce the original segment (from A->D to A->B)
						s[2] = point_b
						break
					end
					::next_segment::
				end

				-- remove segments that need to be removed
				for i=1, #remove_id_list do
					table.remove(
						segments, remove_id_list[#remove_id_list +1 -i])
				end

				-- if all segments are removed, region will be deleted
				if segments[1] == nil then break end

				::next_rg::
			end

			-- security check
			-- prevent segments and gaps shorter than 128
			-- and overlap segments from the same region
			local last_end = -1
			local new_segments = {}

			for i, s in pairs(segments) do
				if s[2] - s[1] > 128 then
					if s[1] > last_end + 128 then
						table.insert(new_segments, s)
					else
						new_segments[#new_segments][2] = s[2]
					end
				end 
				last_end = s[2]
			end

			-- remember the regions we have to delete or modify
			table.insert(regions_to_modify, {r, new_segments})
		end

		-- Remove, split or cut needed regions
		for i=1, #regions_to_modify do
			r = regions_to_modify[i][1]
			segments = regions_to_modify[i][2]

			if #segments == 0 then
				playlist:remove_region(r)
			else
				for j=1, #segments do
					if j == #segments then
						-- last segment, cut directly this region
						rg = r
					else
						-- 2 segments or more for this region
						-- So we need to clone the region before to cut it
						rg = ARDOUR.RegionFactory.clone_region(r, true, false)
						playlist:add_region(rg, rg:position(), 1, false, 0, 0, false)
						
						while rg:layer() > r:layer()
						do
							rg:lower()
						end
					end

					-- finally cut the region if needed
					if segments[j][1] ~= rg:position():samples() then
						rg:cut_front(Temporal.timepos_t(segments[j][1]), 0)
					end

					if segments[j][2] ~= rg:position():samples() + rg:length():samples() then
						rg:cut_end(Temporal.timepos_t(segments[j][2]), 0)
					end
				end
			end
		end
		::next_route::
	end
end end
