Advertisement
poulhoi

phoi_Reposition items, treating contiguous groups of items as single items

Mar 21st, 2023 (edited)
509
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 5.85 KB | None | 0 0
  1. -- CONFIG
  2. local EPSILON = 0.001 -- maximum distance for items to be considered contiguous in seconds, i.e. 1 millisecond
  3. local SAVE_VALS = true -- set to true for settings to be retained between invocations of this script
  4. local VALS_PERSIST = true -- set to true for settings to be retained between sessions; if SAVE_VALS is not true, this does nothing
  5.  
  6. -- NAME
  7. local scriptName = ({reaper. get_action_context()})[2]:match("([^/\\]+)%.lua$") -- generate default scriptName from file
  8.  
  9. --FUNCTIONS FOR DEBUG
  10. function msg(msg)
  11.     reaper.ShowConsoleMsg(tostring(msg) .. "\n")
  12. end
  13.  
  14. function getSelectedItems()
  15.     local items = {}
  16.     local iCount = reaper.CountSelectedMediaItems(0)
  17.     for i = 1, iCount do
  18.         items[i] = reaper.GetSelectedMediaItem(0, i - 1)
  19.     end
  20.     return items
  21. end
  22.  
  23. function getSelectedItemsOnTrack(track)
  24.     local items = {}
  25.     for i = 0, reaper.CountTrackMediaItems(track) - 1 do
  26.         local it = reaper.GetTrackMediaItem(track, i)
  27.         if reaper.IsMediaItemSelected(it) then items[#items+1] = it end
  28.     end
  29.     return items
  30. end
  31.  
  32. function toCSV(tt)
  33. -- Convert from table to CSV string
  34.     local function escapeCSV(s) --Used to escape "'s by toCSV
  35.         if string.find(s, '[,"]') then
  36.                 s = '"' .. string.gsub(s, '"', '""') .. '"'
  37.         end
  38.         return s
  39.     end
  40.     local s = ""
  41.     for _,p in ipairs(tt) do   
  42.         s = s .. "," .. escapeCSV(p)
  43.     end
  44.     return string.sub(s, 2)         -- remove first comma
  45. end
  46.    
  47. function fromCSV(vals_csv, outputTypes)
  48.     local t = {}
  49.     local i = 0
  50.     for line in vals_csv:gmatch("[^" .. "," .. "]*") do
  51.         i = i + 1  
  52.         if #outputTypes == 1 and outputTypes[1] == "number" then
  53.             t[i] = tonumber(line)
  54.         elseif outputTypes[i] == "number" then
  55.             t[i] = tonumber(line)
  56.         else
  57.             t[i] = line
  58.         end
  59.     end
  60.     return t
  61. end
  62.  
  63. function multiUserInput(outputTypes, windowName, numberOfParams, paramNames, defaultVals, saveVals, valsPersist, valsKeySuffix)
  64. --[[ returns retval, input values as a table
  65. Examples:
  66.  
  67. retval, inputs = multiUserInput({"number"}, "Test", 3, {"Threshold", "Ratio", "Output"}, {-18, 4, 6}, true, false, "")
  68. if retval then msg(inputs[1]) end
  69.  
  70. Note: outputTypes can take a 1-element table, which will apply that value type to all inputs.
  71. Otherwise specify output type for each value
  72. --]]
  73.     paramNames_csv = toCSV(paramNames)
  74.     if saveVals and reaper.HasExtState(scriptName, scriptName .. "_Vals" .. valsKeySuffix) then -- restore previous values on run if saveVals is true and the values exist
  75.         defaultVals_csv = reaper.GetExtState(scriptName, scriptName .. "_Vals" .. valsKeySuffix)
  76.     else
  77.         defaultVals_csv = toCSV(defaultVals)
  78.     end
  79.     retval, retvals_csv = reaper.GetUserInputs(tostring(windowName), numberOfParams, paramNames_csv, defaultVals_csv)
  80.     if retval and saveVals then
  81.         reaper.SetExtState(scriptName, scriptName .. "_Vals" .. valsKeySuffix, retvals_csv, valsPersist) -- save new defaults
  82.     end
  83.     if retval then
  84.         vals = fromCSV(retvals_csv, outputTypes)
  85.     else
  86.         vals = nil
  87.     end
  88.     return retval, vals
  89. end
  90.  
  91. function itemEnd(it)
  92.     local p = reaper.GetMediaItemInfo_Value(it, "D_POSITION")
  93.     local e = reaper.GetMediaItemInfo_Value(it, "D_LENGTH")
  94.     return p + e
  95. end
  96.  
  97. function getOverlap(it1, it2)
  98.     return reaper.GetMediaItemInfo_Value(it2, "D_POSITION") - itemEnd(it1)
  99. end
  100.  
  101. function getIfOverlap(it1, it2)
  102.     return getOverlap(it1, it2) <= EPSILON
  103. end
  104.  
  105. function name(it)
  106.     return reaper.GetTakeName(reaper.GetActiveTake(it))
  107. end
  108.  
  109. function mv(its, pos)
  110.     local dposs = {0}
  111.     for i = 1, #its do
  112.         local it = its[i]
  113.         local len = reaper.GetMediaItemInfo_Value(it, "D_LENGTH")
  114.         local ol
  115.         if i < #its then ol = getOverlap(it, its[i+1]) else ol = 0 end
  116.         dposs[#dposs+1] = dposs[#dposs] + len + ol
  117.     end
  118.     for i = 1, #its do
  119.         reaper.SetMediaItemPosition(its[i], pos+dposs[i], false)
  120.     end
  121. end
  122.  
  123. function nextPos(it, fromStart, offset)
  124.     local p = reaper.GetMediaItemInfo_Value(it, "D_POSITION")
  125.     if fromStart <= 0 then p = p + reaper.GetMediaItemInfo_Value(it, "D_LENGTH") end
  126.     if offset > 0 then p = p + reaper.GetMediaItemInfo_Value(it, "D_SNAPOFFSET") end
  127.     return p
  128. end
  129.  
  130. function reposition(items, dist, fromStart, offset) -- fromStart and offset 0/1
  131.     local times = {}
  132.     local j = 1
  133.     while j <= #items do
  134.         local it = items[j]
  135.         local its = {it}
  136.         while j < #items and getIfOverlap(it, items[j+1]) do
  137.             j = j + 1
  138.             it = items[j]
  139.             its[#its+1] = it
  140.         end
  141.         if #times > 0 then -- if this is not first iteration
  142.             mv(its, times[#times] + dist)
  143.         end
  144.         local p = nextPos(its[#its], fromStart, offset)
  145.         times[#times+1] = p
  146.         j = j + 1
  147.     end
  148. end
  149.  
  150. function main()
  151.     reaper.Undo_BeginBlock()
  152.     local retval, inputs = multiUserInput(
  153.         {"number"},
  154.         scriptName,
  155.         5,
  156.         {"Distance", "Seconds / Beats (0/1)", "From end / start of prev. item (0/1)", "Per track (0/1)", "Include snap offset (0/1)"},
  157.         {"1", "0", "0", "1", "1"},
  158.         SAVE_VALS,
  159.         VALS_PERSIST,
  160.         '')
  161.     if not retval then return end
  162.     local input_dist, input_beats, input_fromStart, input_perTrack, input_offset = inputs[1], inputs[2], inputs[3], inputs[4], inputs[5]
  163.     if input_beats > 0 then input_dist = reaper.TimeMap2_beatsToTime(0, input_dist) end
  164.     if input_perTrack > 0 then
  165.         local tracks = {}
  166.         for i = 0, reaper.CountSelectedMediaItems(0) - 1 do
  167.             local it = reaper.GetSelectedMediaItem(0, i)
  168.             local tr = reaper.GetMediaItemTrack(it)
  169.             if tr ~= tracks[#tracks] then tracks[#tracks+1] = tr end
  170.         end
  171.         for i = 1, #tracks do
  172.             local tr = tracks[i]
  173.             local items = getSelectedItemsOnTrack(tr)
  174.             reposition(items, input_dist, input_fromStart, input_offset)
  175.         end
  176.     else
  177.         items = getSelectedItems()
  178.         reposition(items, input_dist, input_fromStart, input_offset)
  179.     end
  180.     reaper.Undo_EndBlock(scriptName, -1)
  181. end
  182.  
  183. reaper.PreventUIRefresh(1)
  184. main()
  185. reaper.PreventUIRefresh(-1)
  186. reaper.UpdateArrange()
Tags: reaper
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement