LUA script: Create Crossfades

I find it very handy to have a command at hand to quickly create crossfades for overlapping regions. (As you know, Ardour always adds tiny fades to prevent clicks, but I mean “proper” crossfades. :wink:)

Since Ardour 6 it is possible to script that functionality! On execution of the script, crossfades are created for the length of the overlaps at the selected region’s/regions’ start(s) and/or end(s).

I’ve been using it on a couple of projects now and for my needs it works great - so here I’m sharing it… I left some helpful links in there intentionally, so this can also be used to learn from it…

If you add the script in the script manager, you can choose to use it on fade ins, fade outs or both…

I hope it is self-explanatory. If you have any additions or improvements, please comment!

ardour {
	["type"]    = "EditorAction",
	name        = "000 _ Crossfade",
	license     = "MIT",
	author      = "Alexander Lang",
	description = [[Crossfade selected region(s) with overlapping region(s) (2020-08-08)]]
}

function action_params ()
	return
	{
		["fadeIns"]  = { title = "Create fade in(s) of selected region(s) (yes/no)", default = "yes" },
		["fadeOuts"] = { title = "Create fade out(s) of selected region(s) (yes/no)", default = "yes" },
	}
end

function factory (params) return function ()
	-- ################# config ##################
	local DO_FADE_IN = true
	local DO_FADE_OUT = true
	local BRING_SELECTION_TO_FRONT = false
	local FADE_SHAPE = ARDOUR.FadeShape.FadeLinear
	local ADJUST_LOWER_REGIONS_FADES = true 	-- set fades of covererd regions to minimum
	local MINIMAL_FADE_LENGTH = 64 			-- mini fade on region boundaries to prevent clicks (in samples)

	-- get configuration parameters
	local p = params or {}
	local config_fadeIns = p["fadeIns"] or true
	local config_fadeOuts = p["fadeOuts"] or true
	if config_fadeIns == "no" then
		--print ("NO fade ins - param")
		DO_FADE_IN = false
	end
	if config_fadeOuts == "no" then
		--print ("NO fade outs - param")
		DO_FADE_OUT = false
	end
	-- ###########################################

	-- prepare undo operation
	Session:begin_reversible_command ("Crossfade")
	local add_undo = false -- keep track if something has changed

	-- ensure globally that fades are used and visible
	-- (Session > Properties > Fades)
	assert (Session:cfg():get_use_region_fades())
	assert (Session:cfg():get_show_region_fades())

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

	-- iterate over selected Regions
	-- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection
	for r in sel.regions:regionlist ():iter () do
		-- test if it is an audio region
		-- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Region
		local ar = r:to_audioregion ()
		if ar:isnil () then goto nextSelectedRegion end
        
		--local regionName = r:name ()
		--print ("Selected Region:", regionName)

		local rStart = r:position()
		local rLength = r:length()
		local rEnd = rStart+rLength
		--print ("startPosition: ", rStart)
		--print ("length: ", rLength)
		--print ("endPosition: ", rEnd)

		-- get playlist of selected region
		local rPL = r:playlist()
		--print("regPL: ", rPL:name())

		local regFadeInLen = MINIMAL_FADE_LENGTH
		local regFadeOutLen = MINIMAL_FADE_LENGTH
	
		-- get other regions within the selected region's boundaries
		local regionsTouched = rPL:regions_touched(rStart, rEnd)

		-- prepare selected region(s) for undo
		r:to_stateful ():clear_changes ()

		if BRING_SELECTION_TO_FRONT then
			Editor:access_action("Region","raise-region-to-top")
		end

		for touchedRegion in regionsTouched:iter () do
			local touchedAudioRegion = touchedRegion:to_audioregion ()
			if touchedAudioRegion:isnil () then goto nextTouchedRegion end

			if touchedRegion == r then
				--print("found itself")
			else
				-- prepare touched region(s) for undo
				touchedRegion:to_stateful ():clear_changes ()

				local arIsAlreadyOnTop = true
				if ar:layer() < touchedAudioRegion:layer() then
					arIsAlreadyOnTop = false
				end

				--print ("Region touched: ", touchedRegion:name ())
				local touchedRegionStart = touchedRegion:position()
				local touchedRegionEnd = touchedRegionStart+touchedRegion:length()

				if DO_FADE_OUT and touchedRegionStart > rStart then
					regFadeOutLen = rEnd - touchedRegionStart
					--print ("   >>> Fade OUT - length: ", regFadeOutLen)

					if BRING_SELECTION_TO_FRONT or arIsAlreadyOnTop then

						ar:set_fade_out_shape (FADE_SHAPE)
						ar:set_fade_out_length (regFadeOutLen)
						ar:set_fade_out_active (true)

						if ADJUST_LOWER_REGIONS_FADES then
							touchedAudioRegion:set_fade_in_length (MINIMAL_FADE_LENGTH)
						end
					else
						touchedAudioRegion:set_fade_in_shape (FADE_SHAPE)
						touchedAudioRegion:set_fade_in_length (regFadeOutLen)
						touchedAudioRegion:set_fade_in_active (true)

						if ADJUST_LOWER_REGIONS_FADES then
							ar:set_fade_out_length (MINIMAL_FADE_LENGTH)
						end
					end

				elseif DO_FADE_IN and touchedRegionEnd < rEnd then
					regFadeInLen = touchedRegionEnd - rStart
					--print ("   >>> Fade IN - length: ", regFadeInLen)

					if BRING_SELECTION_TO_FRONT or arIsAlreadyOnTop then

						ar:set_fade_in_shape (FADE_SHAPE)
						ar:set_fade_in_length (regFadeInLen)
						ar:set_fade_in_active (true)

						if ADJUST_LOWER_REGIONS_FADES then
							touchedAudioRegion:set_fade_out_length (MINIMAL_FADE_LENGTH)
						end
					else
						touchedAudioRegion:set_fade_out_shape (FADE_SHAPE)
						touchedAudioRegion:set_fade_out_length (regFadeInLen)
						touchedAudioRegion:set_fade_out_active (true)

						if ADJUST_LOWER_REGIONS_FADES then
							ar:set_fade_in_length (MINIMAL_FADE_LENGTH)
						end
					end
				end

			end

			-- save changes for touched region(s) (if any) to undo command
			if not Session:add_stateful_diff_command (touchedRegion:to_statefuldestructible ()):empty () then
				add_undo = true
			end

			::nextTouchedRegion::
		end

		-- save changes for selected region(s) (if any) to undo command
		if not Session:add_stateful_diff_command (r:to_statefuldestructible ()):empty () then
			add_undo = true
		end

		--print("############# END OF REGION #############")

		::nextSelectedRegion::
		
	end

    -- all done, commit the combined Undo Operation
    if add_undo then
        -- the 'nil' Command here means to use the collected diffs added above
        Session:commit_reversible_command (nil)
    else
        Session:abort_reversible_command ()
    end

end end

I admire your enterprise in learning to create something with Lua, but I hope you are aware that it’s easy to lengthen those short crossfades at either end of a region by a simple mouse drag, to any length, and also to choose from a variety of fade profiles. Is there a particular reason why you need to use the region overlap to define the length of the crossfade?

Thanks for your comment! Yes, I am very well aware of the great fading capabilities, which I do also use…!

Maybe it is just the way I am (and always have been (since 20 years ago in Cubase)) used to do it, but I am a lot faster when cutting stuff, to have multiple regions, see where I want the transition, have them overlap there and just hit a key (I use “x”), instead of using the mouse to drag the fade around (which can be tiny and hard to hit sometimes)…

So maybe it is just trading one mouse-drag for one key-stroke, or my habits might be complicated altogether - but I kinda need that functionality for my ease of mind… :sweat_smile:

IIRC, Mixbus even has an option to always create crossfades automatically wherever regions overlap. So maybe it is not only me thinking in this direction…?

Earlier versions of Ardour worked that way. You always got a crossfade where regions overlapped, and you set the length of the crossfade by adjusting the region bounds. It looks like Mixbus has retained that feature, or maybe you were using a version of Mixbus based on that earlier Ardour version.
The current Ardour has the advantage that the same tool for adjusting the crossfade also works as a fade-in or fade-out tool if there isn’t an overlapping region. One you’re used to it, I think it’s easier to understand.

That is still the case. Fade behavior of overlapping regions has not changed since Ardour 3.5.

I’m talking about the change from 2.x to 3.0, which, as I understand it, is when crossfades were first introduced to the beginning and end of every region.
Prior to that, if I remember correctly, a crossfade happened over the overlap between regions and if you wanted to change its timing, you had to move the region bounds.
Perhaps what I’m forgetting is that with v2.x you didn’t get a crossfade at all unless you asked for one.
Never mind, it’s all history now…

The more I think about it, maybe my old habit of doing it “with overlaps” should be reconsidered… When I learned this stuff, I probably had to do it this way because the tools were different. I will try hard to challenge my habit and maybe improve it - but in any case I am happy to have the option of “x”… :sunglasses:

FWIW, when I was doing editing in REAPER, I created a custom action that did exactly you achieved in Ardour. I turned off auto-crossfade, lined up transients, positioned the left-hand edge of the right-hand region and then pressed my “X” shortcut key for an instant invisible short xfade. But, yeah, Ardour does this by default at this point :wink:

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.