diff --git a/Items Editing/az_Smart split items by mouse cursor.lua b/Items Editing/az_Smart split items by mouse cursor.lua index bce610379..ae36f7d7f 100644 --- a/Items Editing/az_Smart split items by mouse cursor.lua +++ b/Items Editing/az_Smart split items by mouse cursor.lua @@ -1,57 +1,434 @@ --- @description Smart split items by mouse cursor +-- @description Smart split items using mouse cursor context (also edit cursor, razor area and time selection) -- @author AZ --- @version 2.4 +-- @version 3.0 -- @changelog --- Fixed bugs with left/right selection. --- --- Added in-code options: --- - to move edit cursor after splitting --- - to prefer split selected items at edit cursor --- - respect grouping on razor split --- --- Added experimental fixed lanes support +-- Major update to version 3.0 +-- - A lot of options stored in user config +-- - Options window +-- - Many new features and bug fixes +-- @provides [main] az_Smart split items by mouse cursor/az_Open options for az_Smart split items by mouse cursor.lua -- @link Forum thread https://forum.cockos.com/showthread.php?t=259751 +-- @donation Donate via PayPal https://www.paypal.me/AZsound -- @about --- # Smart split items by mouse cursor +-- # Smart split items using mouse cursor context -- -- Forum thread: https://forum.cockos.com/showthread.php?t=259751 -- --- Split items respect grouping, depend on context of mouse cursor, split at razor edit or time selection if exist, split at mouse or edit cursor otherwise. +-- Split items respect grouping, depend on context of mouse cursor, split at razor edit area or time selection if exist, split at mouse or edit cursor otherwise. -- --- There are options in the user area of code: --- - to switch off time selection --- - to move edit cursor after splitting --- - to prefer split selected items at edit cursor --- (that allows use mouse placement for left/right selecting) --- - respect grouping on razor split +-- There are a lot of options. To open options window place mouse on the transport panel or mixer panel and press assigned shortcut. -- --- Also there is experimental fixed lanes support --- --- Thanks BirdBird for razor edit functions. --- https://forum.cockos.com/showthread.php?t=241604&highlight=razor+edit+scripts +-- By design it should be assigned to keyboard shortcut, not to a mouse modifier. + +--[[ +TO MODIFY SCRIPT OPTIONS +OPEN THE OPTIONS WINDOW BY RUNNING THE SCRIPT WITH MOUSE ON TRANSPORT PANEL +]] +--Start load file +ExtStateName = "SmartSplit_AZ" + +function GetExtStates() + for i, option in ipairs(OptDefaults) do + if option[3] ~= nil then + local state = reaper.GetExtState(ExtStateName, option[2]) + + if state ~= "" then + local stateType = type(option[3]) + if stateType == 'number' then state = tonumber(state) end + if stateType == 'boolean' then + if state == 'true' then state = true else state = false end + end + OptDefaults[i][3] = state + else + reaper.SetExtState(ExtStateName, option[2], tostring(option[3]), true) + end + + end + end +end + +--------------------- + +function SetExtStates() + for i, option in ipairs(OptDefaults) do + if option[3] ~= nil then + reaper.SetExtState(ExtStateName, option[2], tostring(option[3]), true) + end + end +end +--------------------- +function OptionsDefaults() + OptDefaults = {} + local text + + text = 'Select item after split by default:' + table.insert(OptDefaults, {text, 'defSelSide', 'Right', {'Left','Right'} }) + + text = 'Default crossfade position' + table.insert(OptDefaults, {text, 'CrossType', 'Left', { + 'Left', + 'Right', + 'Centered'} }) + + text = 'Time selection options' + table.insert(OptDefaults, {text, 'Separator', nil}) + + text = 'Use time selection only if mouse is close enough to TS edge' + table.insert(OptDefaults, {text, 'UseTSdistance', true}) + + text = 'Use time selection for split selected items' + table.insert(OptDefaults, {text, 'UseTSselItems', true}) + + text = 'Use time selection at all' + table.insert(OptDefaults, {text, 'UseTSall', true}) + + + text = 'Mouse context options' + table.insert(OptDefaults, {text, 'Separator', nil}) + + text = 'Mouse top / bottom placement on item is used for' + table.insert(OptDefaults, {text, 'MouseT/B', 'fade/crossfade', { + 'fade/crossfade', + 'crossfade/fade', + 'left/right crossfade', + 'select left/right item', + 'none'} }) + + text = 'Mouse left / right placement around edit cursor is used for' + table.insert(OptDefaults, {text, 'MouseL/R', 'select left/right item', { + 'left/right crossfade', + 'select left/right item', + 'none'} }) + + text = 'Respect snap for split at mouse' + table.insert(OptDefaults, {text, 'SnapMouse', true}) + + text = 'Snap mouse to edit cursor distance in pixels' + table.insert(OptDefaults, {text, 'SnapMouseEcur', 0, "%.0f"}) + + text = 'Prefer edit cursor rather than mouse cursor on selected items' + table.insert(OptDefaults, {text, 'eCurPriority', false}) -- Edit cursor have piority against mouse on selected item. + + + text = 'Move Edit Cursor with Offset is useful for immediate listening in context' + table.insert(OptDefaults, {text, 'Separator', nil}) + + text = 'Move cursor after split if mouse is over item and not recording' + table.insert(OptDefaults, {text, 'MoveEditCursor', true}) -- moves cursor after splitting if mouse is on item and not recording + + text = "Don't move edit cursor after split at Edit Cursor\n even mouse is over item" + table.insert(OptDefaults, {text, 'DontMoveECurSplit', true}) + + text = 'Offset between first split point and edit cursor in seconds' + table.insert(OptDefaults, {text, 'eCurOffset', 1, "%.1f"}) + + text = "Don't move edit cursor if it stays within the limits\n of this value before first split point" + table.insert(OptDefaults, {text, 'eCurDistance', 4, "%.1f"}) + --^^ If edit cursor placed before the split within the limits of this value it will not moved. + + + text = 'Additional options' + table.insert(OptDefaults, {text, 'Separator', nil}) + + text = 'Allow select by razor only one item of group to split them all' + table.insert(OptDefaults, {text, 'RazRespItemGroup', false}) + + text = 'Respect locked items' + table.insert(OptDefaults, {text, 'RespLock', true}) + + text = 'If no items selected use global split\n according to Preferences -> Editing Behavior' + table.insert(OptDefaults, {text, 'GlobSplit', true}) + + text = 'Global split affects hidden tracks' + table.insert(OptDefaults, {text, 'GSplitHiddenTr', false}) +end ----------------------------- -----------USER AREA---------- -use_TS_sel_Items = true -- Could selected items be splitted by TS or not. -use_TS_all = true -- Use Time selection or not in all cases. -eCurPriority = false -- Edit cursor have piority against mouse on selected item. +function msg(value) + reaper.ShowConsoleMsg(tostring(value)..'\n') +end +----------------------------------- + +-------------------------- +function rgbToHex(rgba) -- passing a table with percentage like {100, 50, 20, 90} + local hexadecimal = '0X' + + for key, value in pairs(rgba) do + local hex = '' + if value > 100 or value < 0 then return error('Color must be a percantage value\n between 0 and 100') end + value = (255/100)*value + while(value > 0)do + local index = math.floor(math.fmod(value, 16) + 1) + value = math.floor(value / 16) + hex = string.sub('0123456789ABCDEF', index, index) .. hex + end -moveEditCursor = true -- moves cursor after splitting if mouse is on item and not recording -curOffset = 1 -- offset between fade and edit cursor in sec -editCurDistance = 4 ---^^ If edit cursor placed before the split within the limits of this value it will not moved. + if(string.len(hex) == 0)then + hex = '00' -razorRespectGrouping = false -- Allow select by razor only one item of group to split all. ------------------------------ ------------------------------ + elseif(string.len(hex) == 1)then + hex = '0' .. hex + end + hexadecimal = hexadecimal .. hex + end ---FUNCTIONS-- + return hexadecimal +end +------------------------ -function msg(value) - reaper.ShowConsoleMsg(tostring(value)..'\n') +function OptionsWindow() + if reaper.APIExists( 'ImGui_GetVersion' ) ~= true then + reaper.ShowMessageBox('Please, install ReaImGui from Reapack!', 'No Imgui library', 0) + return + end + OptionsDefaults() + GetExtStates() + local fontSize = 17 + local ctx, font, fontSep + local H = fontSize + local W = fontSize + local loopcnt = 0 + local _, imgui_version_num, _ = reaper.ImGui_GetVersion() + + local esc + local enter + local space + local escMouse + local enterMouse + local spaceMouse + + local gui_colors = { + White = rgbToHex({90,90,90,100}), + Green = rgbToHex({52,85,52,100}), + Red = rgbToHex({90,10,10,100}), + Blue = rgbToHex({10,30,40,100}), + TitleBg = rgbToHex({30,20,30,100}), + Background = rgbToHex({11,14,14,95}), + Text = rgbToHex({92,92,81.5,100}), + activeText = rgbToHex({50,95,80,100}), + ComboBox = { + Default = rgbToHex({20,25,30,100}), + Hovered = rgbToHex({35,40,45,80}), + Active = rgbToHex({42,42,37,100}), + }, + --[[ + Input = { + Background = rgbToHex({50,50,50,100}), + Hover = rgbToHex({10,10,90,100}), + Text = rgbToHex({90,90,80,100}), + Label = rgbToHex({90,80,90,100}), + },]] + Button = { + Default = rgbToHex({25,30,30,100}), + Hovered = rgbToHex({35,40,45,100}), + Active = rgbToHex({42,42,37,100}), + } + } + -------------- + function frame() + reaper.ImGui_PushFont(ctx, font) + + for i, v in ipairs(OptDefaults) do + local option = v + + if type(option[3]) == 'boolean' then + local _, newval = reaper.ImGui_Checkbox(ctx, option[1], option[3]) + option[3] = newval + end + + if type(option[3]) == 'number' then + reaper.ImGui_PushItemWidth(ctx, fontSize*3 ) + local _, newval = + reaper.ImGui_InputDouble(ctx, option[1], option[3], nil, nil, option[4]) + + option[3] = newval + end + + if type(option[3]) == 'string' then + local choice + for k = 1, #option[4] do + if option[4][k] == option[3] then choice = k end + end + + reaper.ImGui_Text(ctx, option[1]) + reaper.ImGui_SameLine(ctx, nil, nil) + + reaper.ImGui_PushItemWidth(ctx, fontSize*10.3 ) + --reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Text(), gui_colors.activeText) + if reaper.ImGui_BeginCombo(ctx, '##'..i, option[3], nil) then + for k,f in ipairs(option[4]) do + local is_selected = choice == k + if reaper.ImGui_Selectable(ctx, option[4][k], is_selected) then + choice = k + end + + -- Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if is_selected then + reaper.ImGui_SetItemDefaultFocus(ctx) + end + end + reaper.ImGui_EndCombo(ctx) + end + --reaper.ImGui_PopStyleColor(ctx) + + option[3] = option[4][choice] + end + + if type(option[3]) == 'nil' then + reaper.ImGui_PushFont(ctx, fontSep) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Text(), gui_colors.White) + + reaper.ImGui_Text(ctx, '' ) + if imgui_version_num >= 18910 then + reaper.ImGui_SeparatorText( ctx, option[1] ) + else + reaper.ImGui_Text(ctx, option[1] ) + end + + reaper.ImGui_PopStyleColor(ctx, 1) + reaper.ImGui_PopFont(ctx) + end + + OptDefaults[i] = option + end -- for + + reaper.ImGui_Text(ctx, '' ) --space before buttons + reaper.ImGui_Text(ctx, '' ) --space before buttons + + --Esc button + reaper.ImGui_SameLine(ctx, fontSize*2, fontSize) + if esc == true then + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), gui_colors.Button.Active) + end + escMouse = reaper.ImGui_Button(ctx, 'Esc', nil, nil ) + if esc == true then reaper.ImGui_PopStyleColor(ctx, 1) end + + --Save button + reaper.ImGui_SameLine(ctx, nil, fontSize) + if enter == true then + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), gui_colors.Button.Active) + end + enterMouse = reaper.ImGui_Button(ctx, 'Save & Quit - Enter', nil, nil) + if enter == true then reaper.ImGui_PopStyleColor(ctx, 1) end + + --Apply button + if ExternalOpen == true then + reaper.ImGui_SameLine(ctx, nil, fontSize) + if space == true then + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), gui_colors.Button.Active) + end + spaceMouse = reaper.ImGui_Button(ctx, 'Apply - Space', nil, nil) + if space == true then reaper.ImGui_PopStyleColor(ctx, 1) end + end + + --About button + reaper.ImGui_SameLine(ctx, fontSize*25, nil) + if reaper.ImGui_Button(ctx, 'About - forum page', nil, nil) then + local doc = 'https://forum.cockos.com/showthread.php?t=259751' + if reaper.CF_ShellExecute then + reaper.CF_ShellExecute(doc) + else + reaper.MB(doc, 'Smart Split forum page', 0) + end + end + + reaper.ImGui_PopFont(ctx) + end + + -------------- + function loop() + esc = reaper.ImGui_IsKeyPressed(ctx, reaper.ImGui_Key_Escape()) + enter = reaper.ImGui_IsKeyPressed(ctx, reaper.ImGui_Key_Enter()) + space = reaper.ImGui_IsKeyPressed(ctx, reaper.ImGui_Key_Space()) + + reaper.ImGui_PushFont(ctx, font) + + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_WindowBg(), gui_colors.Background) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_TitleBgActive(), gui_colors.TitleBg) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Text(), gui_colors.Text) + + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), gui_colors.Button.Default) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_ButtonHovered(), gui_colors.Button.Hovered) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_ButtonActive(), gui_colors.Button.Active) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_CheckMark(), gui_colors.Green) + + --Combo box and check box background + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_FrameBg(), gui_colors.ComboBox.Default) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_FrameBgHovered(), gui_colors.ComboBox.Hovered) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_FrameBgActive(), gui_colors.ComboBox.Active) + --Combo box drop down list + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Header(), gui_colors.ComboBox.Default) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_HeaderHovered(), gui_colors.ComboBox.Hovered) + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_HeaderActive(), gui_colors.ComboBox.Active) + + local window_flags = reaper.ImGui_WindowFlags_MenuBar() + reaper.ImGui_SetNextWindowSize(ctx, W, H, reaper.ImGui_Cond_Once()) -- Set the size of the windows. Use in the 4th argument reaper.ImGui_Cond_FirstUseEver() to just apply at the first user run, so ImGUI remembers user resize s2 + + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Text(), gui_colors.White) + local visible, open = reaper.ImGui_Begin(ctx, 'Smart Split Options', true, window_flags) + reaper.ImGui_PopStyleColor(ctx, 1) + + if visible then + frame() + if loopcnt == 0 then reaper.ImGui_SetWindowSize(ctx, 0, 0, nil ) end + reaper.ImGui_End(ctx) + end + + reaper.ImGui_PopStyleColor(ctx, 13) + reaper.ImGui_PopFont(ctx) + + esc = escMouse or reaper.ImGui_IsKeyReleased(ctx, reaper.ImGui_Key_Escape()) + enter = enterMouse or reaper.ImGui_IsKeyReleased(ctx, reaper.ImGui_Key_Enter()) + + if ExternalOpen == true then + space = spaceMouse or reaper.ImGui_IsKeyReleased(ctx, reaper.ImGui_Key_Space()) + if space == true then SetExtStates() end + end + + if open and esc ~= true and enter ~= true then + reaper.defer(loop) + elseif enter == true then + SetExtStates() + reaper.ImGui_DestroyContext(ctx) + else + reaper.ImGui_DestroyContext(ctx) + end + loopcnt = loopcnt+1 + end + ----------------- + local fontName + ctx = reaper.ImGui_CreateContext('Smart Split Options') -- Add VERSION TODO + if reaper.GetOS():match("^Win") == nil then + reaper.ImGui_SetConfigVar(ctx, reaper.ImGui_ConfigVar_ViewportsNoDecoration(), 0) + fontName = 'sans-serif' + else fontName = 'Calibri' + end + font = reaper.ImGui_CreateFont(fontName, fontSize, reaper.ImGui_FontFlags_None()) -- Create the fonts you need + fontSep = reaper.ImGui_CreateFont(fontName, fontSize-2, reaper.ImGui_FontFlags_Italic()) + if imgui_version_num >= 18910 then + reaper.ImGui_Attach(ctx, font) + reaper.ImGui_Attach(ctx, fontSep) + else + reaper.ImGui_AttachFont(ctx, font) + reaper.ImGui_AttachFont(ctx, fontSep) + end + + loop(ctx, font) +end + +------------------------- +--End load file +------------------------- + +function SetOptGlobals() + Opt = {} + for i = 1, #OptDefaults do + local name = OptDefaults[i][2] + Opt[name] = OptDefaults[i][3] + end end ------------------------- @@ -59,20 +436,44 @@ end function MoveEditCursor(timeTable, EditCurPos) if #timeTable > 0 then local timepos = math.min(table.unpack(timeTable)) - local recState = reaper.GetToggleCommandState(1013) + local playState = reaper.GetPlayStateEx(0) - if moveEditCursor == true - and (timepos - EditCurPos > editCurDistance or timepos -0.2 <= EditCurPos) + if Opt.MoveEditCursor == true + and (timepos - EditCurPos > Opt.eCurDistance or timepos -0.2 <= EditCurPos) --^^here small coeff to avoid extra small distance - and recState == 0 then - reaper.SetEditCurPos2(0, timepos - curOffset, false, false) + and playState ~= 5 then + reaper.SetEditCurPos2(0, timepos - Opt.eCurOffset, false, false) end + end end ---------------------------------- ---------------------------------- +function PixelDistance() + local distance = 20 + local startTS, endTS = reaper.GetSet_LoopTimeRange2( 0, false, false, 0, 0, 0 ) + + if startTS ~= endTS then + local cur_pos = reaper.GetCursorPosition() + local zoom = reaper.GetHZoomLevel() + if Mcur_pos ~= nil then + + if (math.abs(MouseSnapped - startTS)*zoom <= distance) + or (math.abs(MouseSnapped - endTS)*zoom <= distance) then + return 'close' + else return 'far' + end + + end + else return 'far' end + +end + +---------------------------------- +---------------------------------- + function RazorEditSelectionExists() for i=0, reaper.CountTracks(0)-1 do @@ -116,19 +517,18 @@ function GetItemsInRange(track, areaStart, areaEnd, areaTop, areaBottom) local itemCount = reaper.CountTrackMediaItems(track) local itemTop, itemBottom - for k = 0, itemCount - 1 do + for k = 0, itemCount - 1 do local item = reaper.GetTrackMediaItem(track, k) local lock = reaper.GetMediaItemInfo_Value(item, "C_LOCK") - if lock ~= 1 then + if Opt.RespLock ~= true or lock ~= 1 then local pos = reaper.GetMediaItemInfo_Value(item, "D_POSITION") local length = reaper.GetMediaItemInfo_Value(item, "D_LENGTH") local itemEndPos = pos+length if areaBottom ~= nil then itemTop = reaper.GetMediaItemInfo_Value(item, "F_FREEMODE_Y") - itemBottom = itemTop + reaper.GetMediaItemInfo_Value(item, "F_FREEMODE_H") - --msg("area: "..tostring(areaTop).." "..tostring(areaBottom).."\n".."item: "..itemTop.." "..itemBottom.."\n\n") + itemBottom = itemTop + reaper.GetMediaItemInfo_Value(item, "F_FREEMODE_H") end --check if item is in area bounds @@ -145,7 +545,7 @@ function GetItemsInRange(track, areaStart, areaEnd, areaTop, areaBottom) end end -- if lock end --end for cycle - + return items end @@ -173,7 +573,7 @@ function ParseAreaPerLane(RawTable, itemH) --one level metatable local areaBottom = tonumber(RawTable[i][5]) if not isEnvelope then - areaWidth = math.floor(((areaBottom - areaTop)/itemH)+0.5) -- how many lanes include + local areaWidth = math.floor(((areaBottom - areaTop)/itemH)+0.5) -- how many lanes include for w=1, areaWidth do local areaLane = math.floor((areaBottom/(laneW*w))+0.5) --msg(areaLane) @@ -222,13 +622,12 @@ function GetRazorEdits() local trackCount = reaper.CountTracks(0) local areaMap = {} for i = 0, trackCount - 1 do - local track = reaper.GetTrack(0, i) - local mode = reaper.GetMediaTrackInfo_Value(track,"I_FREEMODE") - if mode ~= 0 then + local track = reaper.GetTrack(0, i) + local mode = reaper.GetMediaTrackInfo_Value(track,"I_FREEMODE") + if mode ~= 0 then ----NEW WAY---- - --reaper.ShowConsoleMsg("NEW WAY\n") - local ret, area = reaper.GetSetMediaTrackInfo_String(track, 'P_RAZOREDITS_EXT', '', false) + local ret, area = reaper.GetSetMediaTrackInfo_String(track, 'P_RAZOREDITS_EXT', '', false) if area ~= '' then --msg(area) @@ -310,7 +709,7 @@ function GetRazorEdits() i=i+1 end end - else + else --if "I_FREEMODE" == 0 ---OLD WAY for backward compatibility------- @@ -367,94 +766,235 @@ function GetRazorEdits() j = j + 3 end - end ---OLD WAY END - end - end + end + end ---OLD WAY END + end --trackCount return areaMap end ------------------------ +----------------------------------- function SplitRazorEdits(razorEdits) - local areaItems = {} - local tracks = {} + local areaItems = {} local SplitsT = {} - local GrState = reaper.GetToggleCommandState(1156) --Options: Toggle item grouping override - - if AnythingForSplit == true then - reaper.Undo_BeginBlock2( 0 ) - reaper.PreventUIRefresh(1) - for i = 1, #razorEdits do - local areaData = razorEdits[i] - if not areaData.isEnvelope then - local items = areaData.items - - --recalculate item data for tracks with previous splits - --[[ - if tracks[areaData.track] ~= nil then --msg(tracks[areaData.track]) - items = GetItemsInRange(areaData.track, areaData.areaStart, areaData.areaEnd) - end ]] + local ItemsToRegroup = {} + + local togAutoXfade = reaper.GetToggleCommandState(40912) --Options: Toggle auto-crossfade on split + local togDefFades = reaper.GetToggleCommandState(41194) --Item: Toggle enable/disable default fadein/fadeout + + if togAutoXfade == 1 or (TogAutoXfadesEditing == 1 and RespTogAutoXfades == 1) then + XfadeON = true + end + + --Remove grouped items from grouped tables if they are enclosed by razor + for r = 1, #razorEdits do + local areaData = razorEdits[r] + if not areaData.isEnvelope then + + for i = 1, #areaData.items do + local item = areaData.items[i] + for k = 1, #razorEdits do + if k ~= r then + local areaData = razorEdits[k] + _, areaData.grItems = FieldMatch(areaData.grItems, item, false) + razorEdits[k]=areaData + end + end + end + + end -- if not areaData.isEnvelope + end + + if TogItemGrouping == 1 and Opt.RazRespItemGroup == true then + TogItemGrouping = 1 else TogItemGrouping = 0 + end + + --Split at areas Start + SelSide = 'Right' + local i = #razorEdits + while i > 0 do + local areaData = razorEdits[i] + if not areaData.isEnvelope then + table.move(areaData.items, 1, #areaData.items, #areaData.grItems+1, areaData.grItems) + --msg('areaData.items') for j,v in pairs(areaData.items) do msg(v) end + --msg('areaData.grItems') for j,v in pairs(areaData.grItems) do msg(v) end + local sTime, selItems, itemsToRegroup, newItems = + Split_Items_At_Time(areaData.items, areaData.grItems, {areaData.areaStart}, areaData.prevEdge) + table.move(sTime, 1, #sTime, #SplitsT+1, SplitsT) + --table.move(newItems, 1, #newItems, #areaData.items+1, areaData.items) + table.move(newItems, 1, #newItems, #areaData.grItems+1, areaData.grItems) + razorEdits[i]['itemsToRegroup'] = itemsToRegroup + --msg(razorEdits[i]['itemsToRegroup']['SplsGrs']) + --msg(itemsToRegroup.SplGrs) + --razorEdits[i]['items'] = areaData.items + end + i=i-1 + end + + --Collect items and regroup using razorEdits.RegroupAreasIDs + for b = 1, #razorEdits.RegroupAreasIDs do + local ItemsToRegroup = {} + --ItemsToRegroup.SplGrs = {} + local block = razorEdits.RegroupAreasIDs[b] + for i = 1, #block do + local id = block[i] + local items = razorEdits[id]['itemsToRegroup'] + table.move(items, 1, #items, #ItemsToRegroup+1, ItemsToRegroup) + --table.move(items.SplsGrs, 1, #items.SplsGrs, #ItemsToRegroup.SplGrs+1 ,ItemsToRegroup.SplGrs) + end + RegroupItems(ItemsToRegroup) + end + + --Split at areas End + SelSide = 'Left' + local i = #razorEdits + while i > 0 do + local areaData = razorEdits[i] + if not areaData.isEnvelope then + --msg('areaData.items') for j,v in pairs(areaData.items) do msg(v) end + --msg('areaData.grItems') for j,v in pairs(areaData.grItems) do msg(v) end + local sTime, selItems, itemsToRegroup = + Split_Items_At_Time(areaData.items, areaData.grItems, {areaData.areaEnd}, areaData.areaStart) + table.move(sTime, 1, #sTime, #SplitsT+1, SplitsT) + table.move(selItems, 1, #selItems, #areaItems+1, areaItems) + razorEdits[i]['itemsToRegroup'] = itemsToRegroup + end + i=i-1 + end + + --Collect items and regroup using razorEdits.RegroupAreasIDs + for b = 1, #razorEdits.RegroupAreasIDs do + local ItemsToRegroup = {} + local block = razorEdits.RegroupAreasIDs[b] + for i = 1, #block do + local id = block[i] + local items = razorEdits[id]['itemsToRegroup'] + table.move(items, 1, #items, #ItemsToRegroup+1, ItemsToRegroup) + end + RegroupItems(ItemsToRegroup) + end + + + return areaItems, SplitsT +end + +----------------------------------- + +function CombineTables(A, B) + local aN = #A + local bN = #B + for a = 1, aN do + local add = true + for b = 1, bN do + if A[a] == B[b] then add = false end + end + if add == true then table.insert(B,A[a]) end + end + return B +end + +----------------------------------- + +function AddGroupInfo(AreasT) + local RegroupAreasIDs = {} + for i = 1, #AreasT do + local areasIDs = {} + local areaData = AreasT[i] + if not areaData.isEnvelope then + SelectItems(areaData.items,true, true) + reaper.Main_OnCommandEx(40034, 0,0) -- Item grouping: Select all items in groups + SelectItems(areaData.items,false, true) + areaData.grItems = CollectSelectedItems() + + local k = i + repeat + if k == 1 then areaData.prevEdge = 0 + elseif AreasT[k-1]['track'] == areaData.track + and not AreasT[k-1]['isEnvelope'] then + if AreasT[k-1]['areaEnd'] < areaData.areaStart then - for j = 1, #items do - local item = items[j] - --split items - if razorRespectGrouping == true and GrState == 1 then - reaper.SelectAllMediaItems(0, false) - reaper.SetMediaItemSelected(item, true) - - reaper.SetEditCurPos(areaData.areaStart, false, false) - reaper.Main_OnCommandEx( 40759, 0, 0 ) -- split items under edit cursor (select right) - - reaper.SetEditCurPos(areaData.areaEnd, false, false) - reaper.Main_OnCommandEx( 40758, 0, 0 ) -- split items under edit cursor (select left) - - for i=0,reaper.CountSelectedMediaItems(0) do - local selItem = reaper.GetSelectedMediaItem(0,i) - table.insert(areaItems, selItem) + if AreasT[k-1]['areaTop'] and AreasT[k-1]['areaBottom'] then + + if areaData.areaTop < AreasT[k-1]['areaBottom'] + and areaData.areaBottom > AreasT[k-1]['areaTop'] then + areaData.prevEdge = AreasT[k-1]['areaEnd'] end - reaper.SelectAllMediaItems(0, false) - else - local newItem = reaper.SplitMediaItem(item, areaData.areaStart) - if newItem == nil then - reaper.SplitMediaItem(item, areaData.areaEnd) - table.insert(areaItems, item) - table.insert(SplitsT, areaData.areaEnd) - else - reaper.SplitMediaItem(newItem, areaData.areaEnd) - table.insert(areaItems, newItem) - table.insert(SplitsT, areaData.areaStart) + + else areaData.prevEdge = AreasT[k-1]['areaEnd'] + end + + end + end + k=k-1 + until areaData.prevEdge ~= nil + + if #areaData.grItems > 0 then + for c = 1, #AreasT do + local compareArea = AreasT[c] + if compareArea.areaStart < areaData.areaEnd + and compareArea.areaEnd > areaData.areaStart then + table.insert(areasIDs, c) + end + end + + if #RegroupAreasIDs == 0 then table.insert(RegroupAreasIDs, areasIDs) + else + local AddNewGroup = true + local subtableID + + for g = 1, #RegroupAreasIDs do + + for f = 1, #areasIDs do + if FieldMatch(RegroupAreasIDs[g], areasIDs[f], nil) == true then + AddNewGroup = false + subtableID = g + break end end - end --end for - --tracks[areaData.track] = 1 - end --if not is envelope - end --end for - reaper.SetEditCurPos(cur_pos, false, false) - reaper.PreventUIRefresh(-1) - end --AnythingForSplit - - return areaItems, SplitsT + end + + + if AddNewGroup == false then + RegroupAreasIDs[subtableID] = CombineTables(RegroupAreasIDs[subtableID], areasIDs) + --break + else table.insert(RegroupAreasIDs, areasIDs) + end + + end --if #RegroupAreasIDs == 0 + end + + AreasT[i] = areaData + end -- if not isEnvelope + end -- for AreasT + --[[ + for i = 1, #RegroupAreasIDs do --servise msg + local block = RegroupAreasIDs[i] + msg(table.concat(block, ' - ')) + end]] + + AreasT.RegroupAreasIDs = RegroupAreasIDs + return AreasT end ----------------------------------- function split_byRE_andSel() local selections = GetRazorEdits() - local items, SplitsT = SplitRazorEdits(selections) + local items, SplitsT = {} + if AnythingForSplit == true then + reaper.Undo_BeginBlock2( 0 ) + reaper.PreventUIRefresh(1) + selectionsWithGrouping = AddGroupInfo(selections) + items, SplitsT = SplitRazorEdits(selectionsWithGrouping) + end if #items > 0 then - --reaper.Undo_BeginBlock2( 0 ) - reaper.PreventUIRefresh( 1 ) - for i = 1, #items do - local item = items[i] - reaper.SetMediaItemSelected(item, true) - reaper.Main_OnCommandEx(42406, 0, 0) - end - MoveEditCursor(SplitsT, cur_pos) - reaper.PreventUIRefresh( -1 ) - reaper.Undo_EndBlock2( 0, "Split at razor edit", -1 ) + SelectItems(items, true, true) + reaper.Main_OnCommandEx(42406, 0, 0) --Razor edit: Clear all areas + STime = SplitsT + UndoString = "Smart split at razor area" else reaper.defer(function()end) end @@ -462,294 +1002,739 @@ function split_byRE_andSel() end ------------------------------------ ------------------------------------ - - -function is_item_crossTS () -local splitTime - -if itemend <= start_pos or -itempos >= end_pos or -(itempos >= start_pos and itemend <= end_pos) or -use_TS_sel_Items == false then - -else - crossTS=1 - if itemend > start_pos and itempos < start_pos then splitTime = start_pos end - if splitTime == nil and itemend > end_pos and itempos < end_pos then splitTime = end_pos end -end - -return splitTime -end - - ----------------------------------------- -------------------------------------------- -function split_by_edit_cursor_or_TS() - local SplitsT = {} - local splitTime - -if TSexist==0 then --if TS doesn't exist - if mouse_cur_pos <= cur_pos and eCurPriority == true then - reaper.Main_OnCommandEx( 40758, 0, 0 ) -- split items under edit cursor (select left) - else - reaper.Main_OnCommandEx( 40759, 0, 0 ) -- 40758 split items under edit cursor (select right) - end - if reaper.CountSelectedMediaItems(0) ~= 0 then - splitTime = reaper.GetCursorPosition() - else - reaper.defer(function()end) - end -else - crossTS=0 - - if itemsNUMB == -1 then --if no one item selected - reaper.Main_OnCommandEx( 40061, 0, 0 ) -- split at TS +function split_automation_item() + if TSexist == true then + reaper.SetEditCurPos(TSstart, false, false) + reaper.Main_OnCommandEx( 42087, 0, 0 ) -- Envelope: Split automation items + -- + reaper.SetEditCurPos(TSend, false, false) + reaper.Main_OnCommandEx( 42087, 0, 0 ) -- Envelope: Split automation items + + reaper.SetEditCurPos(Ecur_pos, false, false) + + UndoString = "Split automation items by TS" else - --if any items selected here is need to check crosses with TS - - if item then - --item under mouse sec position to decide where is item crossed by TS-- - itempos = reaper.GetMediaItemInfo_Value( item, "D_POSITION" ) - itemlen = reaper.GetMediaItemInfo_Value( item, "D_LENGTH" ) - itemend = itempos + itemlen - splitTime = is_item_crossTS() - else - - for i= 0, itemsNUMB do - - local selItem = reaper.GetSelectedMediaItem( 0, i ) --zero based - --item sec position to decide where is sel item crossed by TS-- - itempos = reaper.GetMediaItemInfo_Value( selItem, "D_POSITION" ) - itemlen = reaper.GetMediaItemInfo_Value( selItem, "D_LENGTH" ) - itemend = itempos + itemlen - - splitTime = is_item_crossTS() - if splitTime then table.insert(SplitsT, splitTime) end - end - end - - if crossTS==0 then - if mouse_cur_pos <= cur_pos and eCurPriority == true then - reaper.Main_OnCommandEx( 40758, 0, 0 ) -- split items under edit cursor (select left) - else - reaper.Main_OnCommandEx( 40759, 0, 0 ) -- 40758 split items under edit cursor (select right) - end - - splitTime = reaper.GetCursorPosition() - else - reaper.Undo_BeginBlock2( 0 ) - reaper.PreventUIRefresh( 1 ) - - reaper.Main_OnCommandEx( 40061, 0, 0 ) -- split at TS - reaper.Main_OnCommandEx( 40635, 0, 0 ) -- Time selection: Remove time selection - --MoveEditCursor(SplitsT, cur_pos) - - reaper.PreventUIRefresh( -1 ) - reaper.Undo_EndBlock2( 0, "Split items at time selection", -1 ) - end + reaper.Main_OnCommandEx( 40513, 0, 0 ) -- View: Move edit cursor to mouse cursor + reaper.Main_OnCommandEx( 42087, 0, 0 ) -- Envelope: Split automation items + reaper.SetEditCurPos(Ecur_pos, false, false) + + UndoString = "Split automation item by mouse" end end -if splitTime then table.insert(SplitsT, splitTime) end -return SplitsT -end ----------------------------------------- -------------------------------------------- -function split_not_sel_item() - -reaper.Main_OnCommandEx( 40289, 0, 0 ) -- unselect all items -reaper.SetMediaItemSelected( item, 1 ) -- select founded item under mouse -reaper.Main_OnCommandEx( 40513, 0, 0 ) -- View: Move edit cursor to mouse cursor +function unsel_automation_Items() + for t=0, reaper.CountTracks(0)-1 do + local tr = reaper.GetTrack(0,t) + for e=0, reaper.CountTrackEnvelopes( tr ) -1 do + local env = reaper.GetTrackEnvelope( tr, e ) + local aiNumb = reaper.CountAutomationItems( env ) + for AI=0, aiNumb -1 do + reaper.GetSetAutomationItemInfo( env, AI, "D_UISEL", 0, true ) + end + end + end +end -reaper.Main_OnCommandEx( 40759, 0, 0 ) -- CENTRAL FUNCTION split items under edit cursor (select right) -reaper.SetEditCurPos(cur_pos, false, false) -MoveEditCursor({mouse_cur_pos}, cur_pos) +------------------------------------- +------------------------------------ +function updateMSG() + local msg = "It's major update of Smart split script!"..'\n\n'.. + "Now there are options stored in your Reaper config."..'\n'.. + "To open the options window move mouse cursor to the transport or mixer panel and press assigned shortcut."..' '.. + "Or run dedicated script from the package."..'\n\n'.. + "Also there are many new features and variants of behavior."..'\n'.. + "I set some new options ON by default to promote them for new users."..'\n'.. + "Sorry, if that's not what you're waiting for, change that in options."..'\n\n'.. + "Take a look and have fun!" + reaper.ShowMessageBox(msg, "Smart Split updated", 0) end - - ----------------------------------------- -------------------------------------------- +function is_AI_for_split() + if Window == "arrange" and Segment == "envelope" then + local envLine, takeEnvelope = reaper.BR_GetMouseCursorContext_Envelope() + + if envLine and takeEnvelope == false then + + local aiNumber = reaper.CountAutomationItems( envLine ) -1 + + for i = -1, aiNumber do + local is_ai_sel = reaper.GetSetAutomationItemInfo( envLine, i, "D_UISEL", 0, false ) + local ai_pos = reaper.GetSetAutomationItemInfo( envLine, i, "D_POSITION", 0, false ) + local ai_end = ai_pos + reaper.GetSetAutomationItemInfo( envLine, i, "D_LENGTH", 0, false ) + if is_ai_sel == 1 and ai_pos < Mcur_pos and ai_end > Mcur_pos then + return true + end + end + end + + end + return false +end +----------------------------------- +----------------------------------- + +function GetTopBottomItemHalf() +local itempart +local x, y = reaper.GetMousePosition() -function split_sel_item() +local item_under_mouse = reaper.GetItemFromPoint(x,y,true) -local eCurSplit +if item_under_mouse then -if eCurPriority == true then - local selectSide - for i=0, itemsNUMB do - local item = reaper.GetSelectedMediaItem(0,itemsNUMB) - local iPos = reaper.GetMediaItemInfo_Value( item, "D_POSITION" ) - local iEnd = iPos + reaper.GetMediaItemInfo_Value( item, "D_LENGTH" ) + local item_h = reaper.GetMediaItemInfo_Value( item_under_mouse, "I_LASTH" ) + + local OScoeff = 1 + if reaper.GetOS():match("^Win") == nil then + OScoeff = -1 + end + + local test_point = math.floor( y + (item_h-1) *OScoeff) + local test_item, take = reaper.GetItemFromPoint( x, test_point, true ) + + if item_under_mouse == test_item then + itempart = "header" + else + local test_point = math.floor( y + item_h/2 *OScoeff) + local test_item, take = reaper.GetItemFromPoint( x, test_point, true ) - if iPos < cur_pos and cur_pos < iEnd then eCurSplit = true end + if item_under_mouse ~= test_item then + itempart = "bottom" + else + itempart = "top" + end end -end -if eCurSplit == true then - --CENTRAL FUNCTION - MoveEditCursor(split_by_edit_cursor_or_TS(), cur_pos) -else - reaper.Main_OnCommandEx( 40513, 0, 0 ) -- View: Move edit cursor to mouse cursor - local splitpoints = split_by_edit_cursor_or_TS() --CENTRAL FUNCTION - reaper.SetEditCurPos(cur_pos, false, false) - MoveEditCursor(splitpoints, cur_pos) + return item_under_mouse, itempart +else return nil end + end +------------------------------ + +function GetPrefs(key) -- key need to be a string as in Reaper ini file + local retval, buf = reaper.get_config_var_string( key ) + if retval == true then return tonumber(buf) end end +------------------------------ +function FieldMatch(Table,value, AddRemoveFlag) -- can remove only first finded value + for i=1, #Table do + if value == Table[i] then + if AddRemoveFlag == false then table.remove(Table,i) end + if AddRemoveFlag ~= nil then + return true, Table + else return true + end + end + end + if AddRemoveFlag == true then table.insert(Table, value) end + if AddRemoveFlag ~= nil then + return false, Table + else return false + end +end ------------------------------------------ --------------------------------------------- +------------------------------ +function FindBiggestGroupID() + BiggestGroupID = 0 + local itNumb = reaper.CountMediaItems(0) + for i = 0, itNumb - 1 do + local item = reaper.GetMediaItem(0,i) + local groupID = reaper.GetMediaItemInfo_Value(item, 'I_GROUPID') + if groupID > BiggestGroupID then BiggestGroupID = groupID end + end +end -function split_automation_item() +------------------------------ -if TSexist == 1 then - reaper.SetEditCurPos(start_pos, false, false) - reaper.Main_OnCommandEx( 42087, 0, 0 ) -- Envelope: Split automation items - -- - reaper.SetEditCurPos(end_pos, false, false) - reaper.Main_OnCommandEx( 42087, 0, 0 ) -- Envelope: Split automation items +function CollectSelectedItems(TableToAdd,areaStart,areaEnd) + local ItemsTable = {} + if type(TableToAdd) == 'table' then + ItemsTable = TableToAdd + end - reaper.SetEditCurPos(cur_pos, false, false) -else - reaper.Main_OnCommandEx( 40513, 0, 0 ) -- View: Move edit cursor to mouse cursor - reaper.Main_OnCommandEx( 42087, 0, 0 ) -- Envelope: Split automation items - reaper.SetEditCurPos(cur_pos, false, false) + local selItNumb = reaper.CountSelectedMediaItems(0) + for i = 0, selItNumb - 1 do + local item = reaper.GetSelectedMediaItem(0,i) + local itemLocked = reaper.GetMediaItemInfo_Value(item, 'C_LOCK') + + if areaStart and areaEnd then + local iPos = reaper.GetMediaItemInfo_Value(item, 'D_POSITION') + local iEnd = itemPos + reaper.GetMediaItemInfo_Value(item, 'D_LENGTH') + if iPos > areaEnd or iEnd < areaStart then item = nil end + end + + if item and (itemLocked ~= 1 or Opt.RespLock == false) then + table.insert(ItemsTable, item) + end + + end + return ItemsTable end +------------------------------ + +function CollectAllItems() + local ItemsTable = {} + local itNumb = reaper.CountMediaItems(0) + for i = 0, itNumb - 1 do + local item = reaper.GetMediaItem(0,i) + local itemPos = reaper.GetMediaItemInfo_Value(item, 'D_POSITION') + local itemEnd = itemPos + reaper.GetMediaItemInfo_Value(item, 'D_LENGTH') + local itemLocked = reaper.GetMediaItemInfo_Value(item, 'C_LOCK') + local groupID = reaper.GetMediaItemInfo_Value(item, 'I_GROUPID') + + if not BiggestGroupID then BiggestGroupID = 0 end + if groupID > BiggestGroupID then BiggestGroupID = groupID end + + local collect = true + + if GlobalSplit == true and TSexist then + if (itemPos >= TSstart and itemEnd <= TSstart) + or (itemPos >= TSend or itemEnd <= TSstart) then collect = false end + else if itemPos >= Ecur_pos or itemEnd <= Ecur_pos then collect = false end + end + + if collect == true and (itemLocked ~= 1 or Opt.RespLock == false) then + if Opt.GSplitHiddenTr == false then + local track = reaper.GetMediaItemTrack(item) + if reaper.GetMediaTrackInfo_Value(track, 'B_SHOWINTCP') == 1 then + table.insert(ItemsTable, item) + end + else table.insert(ItemsTable, item) end + end + + end + return ItemsTable end +------------------------------ - ------------------------------------------ --------------------------------------------- - - - -function unsel_automation_Items() - for t=0, reaper.CountTracks(0)-1 do - local tr = reaper.GetTrack(0,t) - for e=0, reaper.CountTrackEnvelopes( tr ) -1 do - local env = reaper.GetTrackEnvelope( tr, e ) - for AI=0, reaper.CountAutomationItems( env ) -1 do - reaper.GetSetAutomationItemInfo( env, AI, "D_UISEL", 0, true ) +function AddGroupedItems(itemsTable, retWithoutInputs) --table, boolean + local grItems = {} + local grIDs = {} + local allCnt = reaper.CountMediaItems(0) + local inpCnt = #itemsTable + + for i = 1, inpCnt do + local item = itemsTable[i] + local groupID = reaper.GetMediaItemInfo_Value(item, 'I_GROUPID') + if groupID ~= 0 and FieldMatch(grIDs, groupID) == false then + table.insert(grIDs, groupID) + end + end + + if #grIDs > 0 then + for i = 0, allCnt-1 do + local item = reaper.GetMediaItem(0,i) + local groupID = reaper.GetMediaItemInfo_Value(item, 'I_GROUPID') + if groupID ~= 0 and FieldMatch(grIDs, groupID) == true then + table.insert(grItems, item) end end end + + if retWithoutInputs == false then + table.move(itemsTable, 1, #itemsTable, #grItems+1, grItems) + end + + return grItems end +------------------------------ -------------------------------------- ------------------------------------- - -function updateMSG() - local msg = "It seems Smart split script was updated."..'\n\n'.. - "Your settings in the user area of code may have been reset."..'\n\n'.. - "Also there are some new settings."..'\n'.. - "Take a look and have fun!" - reaper.ShowMessageBox(msg, "Smart Split updated", 0) - +function isItemsForSplit(Items,time) --Table, number + for i = 1, #Items do + local item = Items[i] + local itemPos = reaper.GetMediaItemInfo_Value(item, 'D_POSITION') + local itemEnd = itemPos + reaper.GetMediaItemInfo_Value(item, 'D_LENGTH') + local itemLocked = reaper.GetMediaItemInfo_Value(item, 'C_LOCK') + + if itemPos < time and itemEnd > time then + return true + end + end + return false end ------------------------------------------ --------------------------------------------- +------------------------------ ------------------- --------START------ -version = reaper.GetExtState("SmartSplit_AZ", "version") -if version ~= "2.4" then - updateMSG() - reaper.SetExtState("SmartSplit_AZ", "version", "2.4", true) +function remove_from_table(Table, value) + local i = #Table + while i > 0 do + local field = Table[i] + if field == value then table.remove(Table,i) end + i = i-1 + end end -cur_pos = reaper.GetCursorPosition() - -window, segment, details = reaper.BR_GetMouseCursorContext() -mouse_cur_pos = reaper.BR_GetMouseCursorContext_Position() +------------------------------- +function SelectAllMediaItems(proj, selected) --idx, boolean -- Works on hidden tracks + local itNumb = reaper.CountMediaItems(proj) + for i = 0, itNumb - 1 do + local item = reaper.GetMediaItem(proj,i) + reaper.SetMediaItemSelected(item, selected) + end +end -if RazorEditSelectionExists()==true then - split_byRE_andSel() -else +------------------------------- -item = reaper.BR_GetMouseCursorContext_Item() --what is context item or not -itemsNUMB = reaper.CountSelectedMediaItems( 0 ) -1 -- -1 to accordance Get Sel Item -start_pos, end_pos = reaper.GetSet_LoopTimeRange2( 0, false, false, 0, 0, 0 ) +function SelectItems(Items, SelDesel, exclusiveFlag) -- table, boolean, boolean + if exclusiveFlag == true and SelDesel == true then + SelectAllMediaItems(0,false) + end + for i=1, #Items do + local item = Items[i] + reaper.SetMediaItemSelected(item, SelDesel) + end +end -if start_pos == end_pos or use_TS_all == false then TSexist=0 -else -TSexist=1 +------------------------------- + +function SetItemEdges(item, startTime, endTime) + local pos = reaper.GetMediaItemInfo_Value(item, 'D_POSITION') + local isloop = reaper.GetMediaItemInfo_Value(item, 'B_LOOPSRC') + reaper.SetMediaItemInfo_Value(item, 'D_POSITION', startTime) + reaper.SetMediaItemInfo_Value(item, 'D_LENGTH', endTime - startTime) + local takesN = reaper.CountTakes(item) + for i = 0, takesN-1 do + local take = reaper.GetTake(item,i) + local offs = reaper.GetMediaItemTakeInfo_Value(take, 'D_STARTOFFS') + local rate = reaper.GetMediaItemTakeInfo_Value(take, 'D_PLAYRATE') + offs = offs + (startTime-pos)*rate + if isloop == 1 then + local src = reaper.GetMediaItemTake_Source( take ) + local length, isQN = reaper.GetMediaSourceLength( src ) + if offs < 0 then offs = length - math.fmod(-offs, length) + elseif offs > length then offs = math.fmod(offs, length) + end + end + reaper.SetMediaItemTakeInfo_Value(take, 'D_STARTOFFS', offs) + end end -autoI = "not" +------------------------------- -if window == "arrange" and segment == "envelope" then - Env_line, takeEnvelope = reaper.BR_GetMouseCursorContext_Envelope() - - if Env_line and takeEnvelope == false then - - AI_number = reaper.CountAutomationItems( Env_line ) -1 - - while AI_number > -1 do - isAIsel = reaper.GetSetAutomationItemInfo( Env_line, AI_number, "D_UISEL", 0, false ) - if isAIsel == 1 then autoI = "selected" end - AI_number = AI_number - 1 - end - end - +function RegroupItems(Items) + local SortedItems = {} + local i = #Items + while i > 0 do + local item = Items[i] + local itemGroup = reaper.GetMediaItemInfo_Value(item, 'I_GROUPID') + + if Items.SplGrs then + if FieldMatch(Items.SplGrs, itemGroup) == true then + if SortedItems[itemGroup] == nil then SortedItems[itemGroup] = {} end + table.insert(SortedItems[itemGroup],item) + end + else + if SortedItems[itemGroup] == nil then SortedItems[itemGroup] = {} end + table.insert(SortedItems[itemGroup],item) + end + i=i-1 + end + + for i, value in pairs(SortedItems) do + if not BiggestGroupID then FindBiggestGroupID() end + for v, item in pairs(value) do + reaper.SetMediaItemInfo_Value( item, 'I_GROUPID', BiggestGroupID+1 ) + end + BiggestGroupID = BiggestGroupID+1 + end end +------------------------------ +------------------------------ -if autoI == "selected" then - reaper.Undo_BeginBlock2( 0 ) - reaper.PreventUIRefresh( 1 ) - split_automation_item() - reaper.PreventUIRefresh( -1 ) - reaper.Undo_EndBlock2( 0, "Split automation item by mouse", -1 ) -else - - --If likely there is no intention to split AIs-- - unsel_automation_Items() - ----------------------------- +function Split_Items_At_Time(SelItems, ItemsToSplit, TimeTable, RazPrevEdge) --returns SplitsTable, SelItems, (ItemsToRegroup) + table.sort(TimeTable) + local newItems = {} + local SplitsTable = {} + local ItemsToRegroup = {} + SelectAllMediaItems(0,false) + + local t = #TimeTable + + while t > 0 do + if #TimeTable > 1 then + if math.fmod(t, 2) ~= 0 then SelSide = 'Right' else SelSide = 'Left' end + end + + local splitTime = TimeTable[t] + ItemsToRegroup = {} + ItemsToRegroup.SplGrs = {} + + local i = #ItemsToSplit + while i > 0 do + local item = ItemsToSplit[i] + local itemPos = reaper.GetMediaItemInfo_Value(item, 'D_POSITION') + local itemEnd = itemPos + reaper.GetMediaItemInfo_Value(item, 'D_LENGTH') + local itemGroup = reaper.GetMediaItemInfo_Value(item, 'I_GROUPID') + + if itemPos < splitTime and itemEnd > splitTime + and (TogItemGrouping == 1 or FieldMatch(SelItems,item,nil) == true ) then + + local newItem = reaper.SplitMediaItem(item, splitTime) -- newItem on the right + if newItem then + table.insert(newItems, newItem) + table.insert(SplitsTable, splitTime) + end + + if itemGroup ~= 0 then + table.insert(ItemsToRegroup, newItem) + if FieldMatch(ItemsToRegroup.SplGrs, itemGroup) == false then table.insert(ItemsToRegroup.SplGrs, itemGroup) end + end + + if SelSide == 'Right' then + table.insert(SelItems, newItem) + remove_from_table(SelItems, item) + else + table.insert(SelItems, item) + end + + --Adapt crossfade-- + if newItem then + local itemTake = reaper.GetActiveTake(item) + local takeIsMidi + local newPos + local newEnd + local newLfade + local newRfade + + if itemTake then takeIsMidi = reaper.TakeIsMIDI(itemTake) end + if itemTake and takeIsMidi == false then + if XfadeON == true then + local leftAutoFade = itemPos + math.max(0, reaper.GetMediaItemInfo_Value(item,'D_FADEINLEN_AUTO') ) + local rightAutoFade = itemEnd - math.max(0, reaper.GetMediaItemInfo_Value(newItem,'D_FADEOUTLEN_AUTO') ) + + local leftTime = TimeTable[t-1] or RazPrevEdge or itemPos + local rightTime = TimeTable[t+1] or itemEnd + + leftTime = math.max(leftTime, itemPos, leftAutoFade) + rightTime = math.min(rightTime, itemEnd, rightAutoFade) + + if not PrefCrossfadeSize then PrefCrossfadeSize = GetPrefs('defsplitxfadelen') end + if not LimitedCrossfade then + LimitedCrossfade = + (GetPrefs('splitmaxpix') * ((GetPrefs('splitautoxfade')&256) / 256) ) / reaper.GetHZoomLevel() + end + if LimitedCrossfade > 0 then + PrefCrossfadeSize = math.min(PrefCrossfadeSize, LimitedCrossfade) + end + + if Opt.CrossType == 'Left' then + newPos = splitTime - PrefCrossfadeSize + newEnd = splitTime + elseif Opt.CrossType == 'Right' then + newPos = splitTime + newEnd = splitTime + PrefCrossfadeSize + elseif Opt.CrossType == 'Centered' then + newPos = splitTime - PrefCrossfadeSize/2 + newEnd = splitTime + PrefCrossfadeSize/2 + end + + if newPos < leftTime then + newPos = leftTime + (splitTime - leftTime)/2 + end + if newEnd > rightTime then + newEnd = rightTime - (rightTime - splitTime)/2 + end + SetItemEdges(newItem, newPos, itemEnd) + SetItemEdges(item, itemPos, newEnd) + reaper.SetMediaItemInfo_Value( newItem, "D_FADEINLEN_AUTO", newEnd-newPos ) + reaper.SetMediaItemInfo_Value( item, "D_FADEOUTLEN_AUTO", newEnd-newPos ) + end --if XfadeON == true + end -- if itemTake and takeIsMidi == false + end --end of crossfade adapt + + elseif itemPos >= splitTime then + if itemGroup ~= 0 then + table.insert(ItemsToRegroup, item) + end + if SelSide == 'Left' + or (GlobalSplit == true and t == 2) then remove_from_table(SelItems, item) + end + elseif itemEnd <= splitTime then + if SelSide == 'Right' + or GlobalSplit == true then remove_from_table(SelItems, item) + end + end - if not item then --if mouse cursor not on the item - split_by_edit_cursor_or_TS() + i=i-1 + end -- for ItemsToSplit + + if not RazPrevEdge then RegroupItems(ItemsToRegroup) end + + t=t-1 end - if item then + if #newItems == 0 then UndoString = nil end + if RazPrevEdge == nil then + ItemsToRegroup = nil + newItems = nil + end + return SplitsTable, SelItems, ItemsToRegroup, newItems +end + +------------------------------ +------------------------------ + +function Main() + SetOptGlobals() + Ecur_pos = reaper.GetCursorPosition() + Mcur_pos = reaper.BR_PositionAtMouseCursor( true ) - selstate = reaper.IsMediaItemSelected( item ) + TogItemGrouping = reaper.GetToggleCommandState(1156) --Options: Toggle item grouping and track media/razor edit grouping + TogAutoXfadesEditing = reaper.GetToggleCommandState(40041) --Options: Auto-crossfade media items when editing + local splautoXConfVar = GetPrefs('splitautoxfade') + RespTogAutoXfades = (splautoXConfVar&512)/512 --Prefs: Respect toolbar auto-crossfade button + + if RazorEditSelectionExists()==true then + split_byRE_andSel() + else + + Item_mouse, Half = GetTopBottomItemHalf() --what is context item or not + if Item_mouse and Half ~= 'header' then + MouseOnItem = true else MouseOnItem = false + end + + if Opt.defSelSide then SelSide = Opt.defSelSide end + + TSstart, TSend = reaper.GetSet_LoopTimeRange2( 0, false, false, 0, 0, 0 ) + --calculations for big zoom + local startArrange, endArrange = reaper.GetSet_ArrangeView2( 0, false, 0, 0, 0, 0 ) + local distance = (endArrange - startArrange)/4 + MouseSnapped = reaper.SnapToGrid(0,Mcur_pos) + + if MouseSnapped < startArrange or MouseSnapped > endArrange + or math.abs(Mcur_pos - MouseSnapped) > distance then + MouseSnapped = Mcur_pos + end --end of calculations for big zoom + + if TSstart == TSend or Opt.UseTSall == false then TSexist = false + else TSexist = true end + if Opt.UseTSdistance == true then + if PixelDistance() == 'far' then TSexist = false end + end + + if Opt.SnapMouse == false then MouseSnapped = Mcur_pos end - if selstate==false then - reaper.Undo_BeginBlock2( 0 ) - reaper.PreventUIRefresh( 1 ) - split_not_sel_item() - reaper.PreventUIRefresh( -1 ) - reaper.Undo_EndBlock2( 0, "Split items under mouse", -1 ) - else reaper.Undo_BeginBlock2( 0 ) reaper.PreventUIRefresh( 1 ) - split_sel_item() - reaper.PreventUIRefresh( -1 ) - reaper.Undo_EndBlock2( 0, "Split items under mouse or time selection", -1 ) - end - + + if is_AI_for_split() == true then + split_automation_item() + else --If likely there is no intention to split AIs-- + + local SelectedItems = CollectSelectedItems() + local inisel= {} + local allItemsForSplit = {} + local timeT = {} + + + if #SelectedItems > 0 then + inisel = SelectedItems + if Opt.UseTSselItems == false then TSexist = false end + elseif MouseOnItem == true then + inisel = {Item_mouse} + else + if Opt.GlobSplit == true and GetPrefs('relativeedges')&256 == 0 then + GlobalSplit = true + SelectedItems = CollectAllItems() + inisel = SelectedItems + end + end + + + if MouseOnItem == true then + if Opt.RespLock ~= 0 + and reaper.GetMediaItemInfo_Value(Item_mouse, 'C_LOCK') ~= 0 then + return --no undo + else + + if Opt.SnapMouseEcur ~= 0 then + local zoom = reaper.GetHZoomLevel() + local distance = Opt.SnapMouseEcur / zoom + if math.abs(Ecur_pos - Mcur_pos) <= distance then Opt.SnapMouseEcur = true end + end + + timeT = { MouseSnapped } + UndoString = 'Smart split at mouse cursor' + + if Opt['MouseT/B'] == 'left/right crossfade' then + if Half == 'top' then Opt.CrossType = 'Left' + elseif Half == 'bottom' then Opt.CrossType = 'Right' + end + elseif Opt['MouseT/B'] == 'select left/right item' then + if Half == 'top' then SelSide = 'Left' + elseif Half == 'bottom' then SelSide = 'Right' + end + end + + if reaper.IsMediaItemSelected( Item_mouse ) == false then + SelectAllMediaItems(0, false) + inisel = {Item_mouse} + elseif Opt.eCurPriority == true then + if isItemsForSplit(inisel, Ecur_pos) == true then + timeT = {Ecur_pos} + UndoString = nil + if Opt.DontMoveECurSplit == true then Opt.MoveEditCursor = false end + end + end + + if Opt.SnapMouseEcur == true then + if isItemsForSplit(inisel, Ecur_pos) == true then + timeT = {Ecur_pos} + UndoString = nil + if Opt.DontMoveECurSplit == true then Opt.MoveEditCursor = false end + end + end + + end --if item under mouse is not locked + + else --if mouse is not over item + timeT = {Ecur_pos} + end + + + if TSexist == true and #SelectedItems > 0 then --TS can't split unselected item under mouse + if isItemsForSplit(inisel, TSstart) == true + or isItemsForSplit(inisel, TSend) == true then + timeT = {TSstart, TSend} + UndoString = 'Smart split at time selection' + end + elseif UndoString == nil then + if isItemsForSplit(inisel, Ecur_pos) == true then + UndoString = 'Smart split at edit cursor' + if Opt['MouseL/R'] == 'select left/right item' then + if Mcur_pos < Ecur_pos then SelSide = 'Left' else SelSide = 'Right' end + end + if Opt['MouseL/R'] == 'left/right crossfade' then + if Mcur_pos < Ecur_pos then Opt.CrossType = 'Left' else Opt.CrossType = 'Right' end + end + + if MouseOnItem ~= true then Opt.MoveEditCursor = false end + end + end + + + if UndoString ~= nil then + if GlobalSplit ~= true then + if reaper.CountSelectedMediaItems(0) == 0 then SelectItems(inisel, true, true) end + reaper.Main_OnCommandEx(40034, 0,0) -- Item grouping: Select all items in groups + --Warning! this ^^ action deselects not grouped items on hidden tracks. + allItemsForSplit = CollectSelectedItems() + else + allItemsForSplit = AddGroupedItems(inisel, false) + end + + local togAutoXfade = reaper.GetToggleCommandState(40912) --Options: Toggle auto-crossfade on split + local togDefFades = reaper.GetToggleCommandState(41194) --Item: Toggle enable/disable default fadein/fadeout + local defFadesON + + if togAutoXfade == 1 or (TogAutoXfadesEditing == 1 and RespTogAutoXfades == 1) then + XfadeON = true + if TSexist ~= true then + if Opt['MouseT/B'] == 'fade/crossfade' and Half == 'top' then + XfadeON = false + elseif Opt['MouseT/B'] == 'crossfade/fade' and Half == 'bottom' then + XfadeON = false + end + end + end + + if togDefFades == 1 then + defFadesON = true + if TSexist ~= true then + if Opt['MouseT/B'] == 'fade/crossfade' and Half == 'bottom' then + defFadesON = false + elseif Opt['MouseT/B'] == 'crossfade/fade' and Half == 'top' then + defFadesON = false + end + end + end + + if togAutoXfade == 1 and XfadeON == false then + reaper.Main_OnCommandEx(40912,0,0) --Options: Toggle auto-crossfade on split + end + + if TogAutoXfadesEditing == 1 and RespTogAutoXfades == 1 and XfadeON == false then -- turn crossfades off + reaper.Main_OnCommandEx(40912,0,0) -- ON Options: Toggle auto-crossfade on split + reaper.Main_OnCommandEx(40912,0,0) -- OFF Options: Toggle auto-crossfade on split + end + + if defFadesON == false and XfadeON ~= true then + reaper.Main_OnCommandEx(41194,0,0) --Item: Toggle enable/disable default fadein/fadeout + end + + local SelItems + STime, SelItems = Split_Items_At_Time(inisel, allItemsForSplit, timeT) + SelectItems(SelItems, true, true) + unsel_automation_Items() + + if togAutoXfade == 1 and XfadeON == false then + reaper.Main_OnCommandEx(40912,0,0) --Options: Toggle auto-crossfade on split + end + if defFadesON == false and XfadeON ~= true then + reaper.Main_OnCommandEx(41194,0,0) --Item: Toggle enable/disable default fadein/fadeout + end + + end -- of if UndoString ~= nil + + end -- of if is_AI_for_split() + + end -- of if RazorEditSelectionExists() + + if TogAutoXfadesEditing == 1 and RespTogAutoXfades == 1 and XfadeON == false then -- turn crossfades off + reaper.SNM_SetIntConfigVar( 'splitautoxfade', splautoXConfVar ) end + return UndoString end +---------------------------- + +------------------ +-------START------ +version = reaper.GetExtState(ExtStateName, "version") +if version ~= "3.0" then + updateMSG() + reaper.SetExtState(ExtStateName, "version", "3.0", true) + reaper.defer(function()end) +else + if reaper.APIExists( 'BR_GetMouseCursorContext' ) ~= true then + reaper.ShowMessageBox('Please, install SWS extension!', 'No SWS extension', 0) + return + end + Window, Segment, Details = reaper.BR_GetMouseCursorContext() + if Window == 'transport' or Window == 'mcp' then + OptionsWindow() + else + OptionsDefaults() + GetExtStates() + local ret = Main() + if ret ~= nil then --msg(UndoString) + if STime then MoveEditCursor(STime, Ecur_pos) end + reaper.Undo_EndBlock2( 0, UndoString, -1 ) + reaper.UpdateArrange() + else reaper.defer(function()end) end + end end diff --git a/Items Editing/az_Smart split items by mouse cursor/az_Open options for az_Smart split items by mouse cursor.lua b/Items Editing/az_Smart split items by mouse cursor/az_Open options for az_Smart split items by mouse cursor.lua new file mode 100644 index 000000000..93a912c89 --- /dev/null +++ b/Items Editing/az_Smart split items by mouse cursor/az_Open options for az_Smart split items by mouse cursor.lua @@ -0,0 +1,32 @@ +-- @noindex + +ScriptName = 'az_Smart split items by mouse cursor' + +function msg(s) reaper.ShowConsoleMsg(tostring(s)..'\n') end + +function get_script_path() + local info = debug.getinfo(1,'S'); + local script_path = info.source:match[[^@?(.*[\/])[^\/]-$]] + script_path = script_path:gsub('[^/\\]*[/\\]*$','') --one level up + return script_path +end + +local script_path = get_script_path() + +local file = script_path .. ScriptName ..'.lua' +local scriptPart = '' +local add + +for line in io.lines(file) do + if line:match('--Start load file') then add = true end + if add == true then scriptPart = scriptPart ..line ..'\n' end + if line:match('--End load file') then break end +end +--msg(scriptPart) +local func = load(scriptPart) + +if func then + func() + ExternalOpen = true + OptionsWindow() +end