-- Reaper Scripts for 4 point editing. This script executes "one edit" and copies source material from -- already set source in/out markers to also set destination in/out markers. -- Author: Ludwig Frühschütz -- Source: https://www.eleton-audio.de -- Git: https://files.eleton-audio.de/gitea/Ludwig/Reaper-Scripts.git -- License: GPL v3.0 -- Requires: Reaper 5 or 6 -- Requires: SWS extensions. http://standingwaterstudios.com -- Requires: This script goes with several other scripts that work closely together. local stored_cursorPos = 0 local stored_timeSelStart = 0 local stored_timeSelEnd = 0 local stored_sel_tracks = {} local stored_sel_items = {} local stored_view_start = 0 local stored_view_end = 0 local mark_srcin_pos = -1 local mark_srcin_id = -1 local mark_srcout_pos = -1 local mark_srcout_id = -1 local mark_dstin_pos = -1 local mark_dstin_id = -1 local mark_dstout_pos = -1 local mark_dstout_id = -1 local dst_track_offset = 0 -- if the uppermost item is not on the first sourcetrack, this offset needs to be applied to the dst-track selection local edit_mode = 0 local sws_present = false -- edit modes: 0=not enough markers set; 1=src-in, src-out, dst-in; 2=src-in, src-out, dst-out; -- 3=src-in, dst-in, dst-out; 4=src-out, dst-in, dst-out; 5=all four markers set; -- Send a message to the console function msg(m) reaper.ShowConsoleMsg(tostring(m) .. "\n") end -- Round number function round(num, numDecimalPlaces) local mult = 10^(numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult end -- Calculate offset between first source track and uppermost source item (how many -- tracks are empty in source selection on top). This needs to be applied to destination -- track selection, otherwise the items will be pasted shifted upwards. -- This function depends on the source tracks AND items to be already selected, -- it does not care about any markers or stored sourcetracks! function calculate_track_offset() local first_sel_track = 9999 local uppermost_item_track = 9999 -- get number of first selected track for i = 0, reaper.CountSelectedTracks(0)-1 do local nof_track = reaper.GetMediaTrackInfo_Value(reaper.GetSelectedTrack(0, i), "IP_TRACKNUMBER") if nof_track < first_sel_track then first_sel_track = nof_track end end -- get tracknumber of first selected item for i = 0, reaper.CountSelectedMediaItems(0)-1 do local trackno_item = reaper.GetMediaTrackInfo_Value(reaper.GetMediaItemTrack(reaper.GetSelectedMediaItem(0, i)), "IP_TRACKNUMBER") if trackno_item < uppermost_item_track then uppermost_item_track = trackno_item end end -- calculate offset dst_track_offset = uppermost_item_track - first_sel_track end -- Selects the source tracks, or, if not stored in rpp, selects all tracks function select_src_tracks() local tracks_str = '' local tracks = {} local retval retval, tracks_str = reaper.GetProjExtState(0, '4PointCut', 'src_tracks') if retval > 0 then -- variable exists in rpp -- separate GUIDs and populate track table for str in string.gmatch(tracks_str, "([^"..'{'.."]+)") do table.insert(tracks, reaper.BR_GetMediaTrackByGUID(0, '{' .. str)) end -- select tracks for _, track in ipairs(tracks) do reaper.SetTrackSelected(track, true) end else -- default to all tracks for i = 0, reaper.CountTracks(0)-1 do reaper.SetTrackSelected(reaper.GetTrack(0, i), true) end end end -- Select the destination tracks, or, if not stored in rpp, the first track -- Also a offset must be specified which will be applied to the tracknumber to select function select_dst_track_only() local track_str = '' local retval local raw_dst_track -- Get "raw" track, without offset retval, track_str = reaper.GetProjExtState(0, '4PointCut', 'dst_track') if retval > 0 then -- variable exists in rpp raw_dst_track = reaper.BR_GetMediaTrackByGUID(0, track_str) else --default to first track raw_dst_track = reaper.GetTrack(0, 0) end if dst_track_offset <= 0 then -- no offset to respect, easy reaper.SetOnlyTrackSelected(raw_dst_track) else local trackno_dst_track = reaper.GetMediaTrackInfo_Value(raw_dst_track, "IP_TRACKNUMBER") + dst_track_offset -- Run trough all tracks, select the one with the matching tracknumber and return for i = 0, reaper.CountTracks(0)-1 do local nof_track = reaper.GetMediaTrackInfo_Value(reaper.GetTrack(0, i), "IP_TRACKNUMBER") if nof_track == trackno_dst_track then reaper.SetOnlyTrackSelected(reaper.GetTrack(0, i)) return end end end -- In theory should never be reached, use raw track as fallback, probably something is wrong with the offset: reaper.SetOnlyTrackSelected(raw_dst_track) end -- START HERE vvvvvvvvvvvvvvvvvvvvvvvvvv -- check for SWS extensions if reaper.NamedCommandLookup('_SWS_ABOUT') > 0 then sws_present = true end -- run through all markers and get position of the 4 points local nof_markers = 0 local nof_regions = 0 _, nof_markers, nof_regions = reaper.CountProjectMarkers(0) nof_markers = nof_markers + nof_regions -- adapt count of 'CountProjectMarkers()' to 'EnumProjectMarkers()' for i = 0, nof_markers - 1 do local name = '' local pos = 0 local id = 0 local isregion = false _, isregion, pos, _, name, id = reaper.EnumProjectMarkers(i) if not isregion then if name == 'SRC-IN_4Pcut' then mark_srcin_pos = pos mark_srcin_id = id elseif name == 'SRC-OUT_4Pcut' then mark_srcout_pos = pos mark_srcout_id = id elseif name == 'DST-IN_4Pcut' then mark_dstin_pos = pos mark_dstin_id = id elseif name == 'DST-OUT_4Pcut' then mark_dstout_pos = pos mark_dstout_id = id end end end -- set edit mode depending on which markers are set (see comments next to definition of edit_mode) if mark_srcin_id >= 0 and mark_srcout_id >= 0 and mark_dstin_id >= 0 and mark_dstout_id < 0 then edit_mode = 1 elseif mark_srcin_id >= 0 and mark_srcout_id >= 0 and mark_dstin_id < 0 and mark_dstout_id >= 0 then edit_mode = 2 elseif mark_srcin_id >= 0 and mark_srcout_id < 0 and mark_dstin_id >= 0 and mark_dstout_id >= 0 then edit_mode = 3 elseif mark_srcin_id < 0 and mark_srcout_id >= 0 and mark_dstin_id >= 0 and mark_dstout_id >= 0 then edit_mode = 4 elseif mark_srcin_id >= 0 and mark_srcout_id >= 0 and mark_dstin_id >= 0 and mark_dstout_id >= 0 then edit_mode = 5 end -- error messages if edit_mode <= 0 then msg('Please set at least 3 of the 4 markers first! Aborting.') return elseif (edit_mode == 1 or edit_mode == 2 or edit_mode == 5) and (mark_srcin_pos >= mark_srcout_pos) then msg('SRC-IN Marker must be set left of SRC-OUT marker! Aborting.') return elseif (edit_mode == 3 or edit_mode == 4 or edit_mode == 5) and (mark_dstin_pos >= mark_dstout_pos) then msg('DST-IN Marker must be set left of DST-OUT marker! Aborting.') return elseif not sws_present then msg('SWS extensions are not installed but needed! Aborting.') return end -- Do stuff before actual edits... reaper.Undo_BeginBlock() --store viewport off arranger stored_view_start, stored_view_end = reaper.GetSet_ArrangeView2(0, false, 0, 0) -- Store cursor position, time selection, selected tracks, selected items stored_cursorPos = reaper.GetCursorPosition() stored_timeSelStart, stored_timeSelEnd = reaper.GetSet_LoopTimeRange(false, true, 0, 1, false) for i = 0, reaper.CountSelectedTracks(0)-1 do stored_sel_tracks[i+1] = reaper.GetSelectedTrack(0, i) end for i = 0, reaper.CountSelectedMediaItems(0)-1 do stored_sel_items[i+1] = reaper.GetSelectedMediaItem(0, i) end -- unselect items and tracks, select source tracks reaper.Main_OnCommand(40289, 0) -- Unselect all items reaper.Main_OnCommand(40297, 0) -- Unselect all tracks select_src_tracks() -- Set Source Time selection according to edit mode if edit_mode == 1 then -- src-in, src-out and dst-in defined reaper.GetSet_LoopTimeRange(true, false, mark_srcin_pos, mark_srcout_pos, false) elseif edit_mode == 2 then -- src-in, src-out and dst-out defined reaper.GetSet_LoopTimeRange(true, false, mark_srcin_pos, mark_srcout_pos, false) mark_dstin_pos = mark_dstout_pos - (mark_srcout_pos - mark_srcin_pos) -- also set dst-in position, so we have a point to paste to mark_dstin_id = reaper.AddProjectMarker(0, false, mark_dstin_pos, 0, 'DST-IN_4Pcut', -1) elseif edit_mode == 3 then -- src-in, dst-in and dst-out defined reaper.GetSet_LoopTimeRange(true, false, mark_srcin_pos, mark_srcin_pos + (mark_dstout_pos - mark_dstin_pos), false) elseif edit_mode == 4 then -- src-out, dst-in and dst-out defined reaper.GetSet_LoopTimeRange(true, false, mark_srcout_pos - (mark_dstout_pos - mark_dstin_pos), mark_srcout_pos, false) elseif edit_mode == 5 then -- all 4 markers defined, get lenght of shortest region if (mark_srcout_pos - mark_srcin_pos) < (mark_dstout_pos - mark_dstin_pos) then reaper.GetSet_LoopTimeRange(true, false, mark_srcin_pos, mark_srcout_pos, false) else reaper.GetSet_LoopTimeRange(true, false, mark_srcin_pos, mark_srcin_pos + (mark_dstout_pos - mark_dstin_pos), false) end end -- Copy items reaper.Main_OnCommand(40718, 0) -- select items on selected tracks under time selection reaper.Main_OnCommand(40060, 0) -- copy selected items under time selection 41383 -- Calculate track offset between first selected item and first dst-track calculate_track_offset() -- Move edit cursor to Dest-In and Paste reaper.GoToMarker(0, mark_dstin_id, false) select_dst_track_only() --reaper.Main_OnCommand(40058, 0) -- paste item reaper.Main_OnCommand( reaper.NamedCommandLookup('_SWS_AWPASTE'), 0 ) -- SWS Paste -- Remove content behind selected items (avoid overlapping). Only really needed if "Trim content behind media items when editing" is off. reaper.Main_OnCommand( 40930, 0 ) -- Remove content (trim) behind items -- Crossfade local selected_items_count = reaper.CountSelectedMediaItems(0) for i = 0, selected_items_count - 1 do -- select tracks with selected items... local item = reaper.GetSelectedMediaItem(0, i) local track = reaper.GetMediaItem_Track(item) reaper.SetTrackSelected(track, true) end reaper.Main_OnCommand( 40290, 0 ) -- set time selection to selected items reaper.Main_OnCommand( 40320, 0 ) -- nudge left edge of time selection left reaper.Main_OnCommand( 40323, 0 ) -- nudge right edge of time selection right reaper.Main_OnCommand( 40718, 0 ) -- select items on selected tracks and in time selection reaper.Main_OnCommand( reaper.NamedCommandLookup('_SWS_CROSSFADE'), 0 ) -- Crossfade adjacent selected items -- Move dstin marker according to edit mode if edit_mode == 1 then reaper.SetProjectMarker(mark_dstin_id, false, mark_dstin_pos + (mark_srcout_pos - mark_srcin_pos), 0, 'DST-IN_4Pcut') elseif edit_mode == 2 then reaper.SetProjectMarker(mark_dstout_id, false, mark_dstout_pos + (mark_srcout_pos - mark_srcin_pos), 0, 'DST-OUT_4Pcut') reaper.DeleteProjectMarker(0, mark_dstin_id, false) -- remove dst-in marker, this was set just for the pasting! elseif edit_mode == 3 or edit_mode == 4 then reaper.SetProjectMarker(mark_dstin_id, false, mark_dstin_pos + (mark_dstout_pos - mark_dstin_pos), 0, 'DST-IN_4Pcut') reaper.SetProjectMarker(mark_dstout_id, false, mark_dstout_pos + (mark_dstout_pos - mark_dstin_pos), 0, 'DST-OUT_4Pcut') elseif edit_mode == 5 then if (mark_srcout_pos - mark_srcin_pos) < (mark_dstout_pos - mark_dstin_pos) then reaper.SetProjectMarker(mark_dstin_id, false, mark_dstin_pos + (mark_srcout_pos - mark_srcin_pos), 0, 'DST-IN_4Pcut') reaper.SetProjectMarker(mark_dstout_id, false, mark_dstout_pos + (mark_srcout_pos - mark_srcin_pos), 0, 'DST-OUT_4Pcut') else reaper.SetProjectMarker(mark_dstin_id, false, mark_dstin_pos + (mark_dstout_pos - mark_dstin_pos), 0, 'DST-IN_4Pcut') reaper.SetProjectMarker(mark_dstout_id, false, mark_dstout_pos + (mark_dstout_pos - mark_dstin_pos), 0, 'DST-OUT_4Pcut') end end -- Restore cursor position, time selection, selected tracks, selected items reaper.MoveEditCursor(stored_cursorPos - reaper.GetCursorPosition(), false) reaper.Main_OnCommand(40297, 0) -- Unselect all tracks for _, track in ipairs(stored_sel_tracks) do reaper.SetTrackSelected(track, true) end reaper.Main_OnCommand(40289, 0) -- Unselect all items for _, item in ipairs(stored_sel_items) do reaper.SetMediaItemSelected(item, true) end reaper.GetSet_LoopTimeRange(true, true, stored_timeSelStart, stored_timeSelEnd, false) -- Restore view port of arranger reaper.GetSet_ArrangeView2(0, true, 0, 0, stored_view_start, stored_view_end) reaper.Undo_EndBlock('4 point cut: Execute', 4) -- 4 is a flag for actions concerning items