Collection of custom reaper scripts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

688 lines
25 KiB

-- Reaper Script to add take management functions, like:
-- - Take counter with unique number ("how often REC was pressed")
-- - Write takenumber to name of recorded file (by changing tracknames)
-- - Group recorded items after stop (all or just horizontally)
-- - Lock to active take (in lanes) after record
-- - Write clean takenames after record, for easy recognition. E.g. "T042 (Violin 1)"
-- - Crossfade items after record (fill gaps resulting from overlapping takes) (needs SWS)
-- 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 for "Fill gaps" option (http://standingwaterstudios.com/)
-- Basic gui stuff by forum user "spk77": https://forum.cockos.com/showthread.php?t=161557
-- Some adjustable settings:
-- Initial values if no settings stored in project
local groupItemsAfterRecord = true
local groupItemsHAfterRecord = false
local fillGapsAfterRecord = true
local lockToActiveTakeAfterRecord = true
local writeTakeNamesAfterRecord = true
-- Nothing to adjust here anymore...
local script_path
local mouse
local Menu
local gui = {} -- contains some settings for the GUI
local increaseTodo = false -- used to detect transition form "REC" to "STOP" and then inc. takenumber
local quit = false
local sws_present = false
-- Send a message to the console
function msg(m)
reaper.ShowConsoleMsg(tostring(m) .. "\n")
end
-- Write Takenumber and Trackname to selected items active takenames
function writeTakeNames()
local k = 0
local thisItem = reaper.GetSelectedMediaItem(0, k)
local takestring
local name = ' '
-- try to get takenumber from rpp. If not present, leave at "-1"
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'nexttake')
if retval > 0 then
takestring = 'T' .. string.format('%03d', tonumber(val)) -- format string to prepend to takename, e.g. "T042"
else
takestring = 'T???'
end
-- run through all selected items
while (thisItem)
do
local theTake = reaper.GetActiveTake(thisItem)
local track = reaper.GetMediaItem_Track(thisItem)
-- get trackname without takenumber
name = select( 2, reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name, false) )
if name:sub(-5,-4) == '_T' then
name = name:sub(1,-6)
elseif name:sub(-4,-3) == '_T' then
name = name:sub(1,-5)
elseif name:sub(-3,-2) == '_T' then
name = name:sub(1,-4)
elseif name:sub(-2,-1) == '_T' then
name = name:sub(1,-3)
end
reaper.GetSetMediaItemTakeInfo_String(theTake, 'P_NAME', takestring .. ' (' .. name .. ')', true)
k = k+1
thisItem = reaper.GetSelectedMediaItem(0, k)
end
end
-- Lock to active take in lanes (to not accidentally select one by clicking)
function setLockToActiveTake()
reaper.Main_OnCommand(41340, 0) -- cant get momentary state, so simply set it every time...
end
-- Get the highest group-ID in use
function maxProjectGroupID()
local floor = math.floor
local all_item_count = reaper.CountMediaItems(0)
local MaxGroupID = 0
for i = 0, all_item_count - 1 do
local item = reaper.GetMediaItem(0, i)
local item_group_id = floor(reaper.GetMediaItemInfo_Value(item, "I_GROUPID"))
if item_group_id > MaxGroupID then
MaxGroupID = item_group_id
end
end
return MaxGroupID
end
-- Group all selected items
function groupItems()
local groupID = maxProjectGroupID() + 1
local k = 0
local thisItem = reaper.GetSelectedMediaItem(0, k)
while (thisItem)
do
reaper.SetMediaItemInfo_Value(thisItem, "I_GROUPID", groupID)
k = k+1
thisItem = reaper.GetSelectedMediaItem(0, k)
end
end
-- Group selected items horizontally
function groupItemsH()
local init_sel_tracks = {}
local groupID = maxProjectGroupID() + 1
local j = 0
local k = 0
-- store selected tracks before work...
for i = 0, reaper.CountSelectedTracks(0)-1 do
init_sel_tracks[i+1] = reaper.GetSelectedTrack(0, i)
end
local thisTrack = reaper.GetTrack(0, j)
while (thisTrack) do
local thisItem = reaper.GetTrackMediaItem(thisTrack, k)
while (thisItem)
do
if reaper.IsMediaItemSelected(thisItem) then
reaper.SetMediaItemInfo_Value(thisItem, "I_GROUPID", groupID)
end
k = k+1
thisItem = reaper.GetTrackMediaItem(thisTrack, k)
end
k = 0
j = j+1
thisTrack = reaper.GetTrack(0, j)
groupID = maxProjectGroupID() + 1
end
-- restore selected tracks after work
reaper.Main_OnCommand(40297, 0) -- Unselect all tracks
for _, track in ipairs(init_sel_tracks) do
reaper.SetTrackSelected(track, true)
end
end
function fillGapsBetweenItems()
if not sws_present then return end
local init_sel_tracks = {}
local init_sel_items = {}
local timesel_start
local timesel_end
-- store selected tracks and items before work...
for i = 0, reaper.CountSelectedTracks(0)-1 do
init_sel_tracks[i+1] = reaper.GetSelectedTrack(0, i)
end
for i = 0, reaper.CountSelectedMediaItems(0)-1 do
init_sel_items[i+1] = reaper.GetSelectedMediaItem(0, i)
end
-- store time selection
timesel_start, timesel_end = reaper.GetSet_LoopTimeRange(false, true, 0, 1, false)
-- select track with selected items
reaper.Main_OnCommand(40297, 0) -- Unselect all tracks
local selected_items_count = reaper.CountSelectedMediaItems(0)
for i = 0, selected_items_count - 1 do
local item = reaper.GetSelectedMediaItem(0, i)
local track = reaper.GetMediaItem_Track(item)
reaper.SetTrackSelected(track, true)
end
-- Set time selection to selected items and extend it a little to the right and left, so it includes adjacent items
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
-- Select adjacent items and fill gaps (and crossfade)
reaper.Main_OnCommand( 40718, 0 ) -- select items on selected tracks and in time selection
reaper.Main_OnCommand( reaper.NamedCommandLookup('_SWS_AWFILLGAPSQUICKXFADE'), 0 ) -- fill gaps between selected items and crossfade
-- restore selected tracks and items after work
reaper.Main_OnCommand(40297, 0) -- Unselect all tracks
for _, track in ipairs(init_sel_tracks) do
reaper.SetTrackSelected(track, true)
end
reaper.Main_OnCommand(40289, 0) -- Unselect all items
for _, item in ipairs(init_sel_items) do
reaper.SetMediaItemSelected(item, true)
end
reaper.GetSet_LoopTimeRange(true, true, timesel_start, timesel_end, false)
end
-- Increase the takenumber and write to tracknames
function increaseTake()
local i_track = 0
local track = reaper.GetTrack(0, i_track)
local name = 'empty'
local take = 1
local retval
local val
-- load takenumber from rpp
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'nexttake')
if retval > 0 then take = tonumber(val) + 1 end -- variable exists? if not keep take=1
takestring = '_T' .. string.format('%03d', take) -- format string to append to tracks, e.g. "_T042"
-- run through all tracks and append takenumber
while(track)
do
name = select( 2, reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name, false) )
-- check if a takenumber already exists, even if without leading zeros
if name:sub(-5,-4) == '_T' then
name = name:sub(1,-6)
elseif name:sub(-4,-3) == '_T' then
name = name:sub(1,-5)
elseif name:sub(-3,-2) == '_T' then
name = name:sub(1,-4)
elseif name:sub(-2,-1) == '_T' then
name = name:sub(1,-3)
end
-- set new trackname and get next track
reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name .. takestring, true)
i_track = i_track + 1
track = reaper.GetTrack(0, i_track)
end
-- write new takenumber to rpp
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', take)
end
-- decreases takenumber and write to tracknames
function decreaseTake()
local i_track = 0
local track = reaper.GetTrack(0, i_track)
local name = 'empty'
local take = 1
local retval
local val
-- load takenumber from rpp
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'nexttake')
if retval > 0 then take = tonumber(val) - 1 end -- variable exists? if not keep take=1
takestring = '_T' .. string.format('%03d', take) -- format string to append to tracks, e.g. "_T042"
-- run through all tracks and append takenumber
while(track)
do
name = select( 2, reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name, false) )
-- check if a takenumber already exists, even without leading zeros
if name:sub(-5,-4) == '_T' then
name = name:sub(1,-6)
elseif name:sub(-4,-3) == '_T' then
name = name:sub(1,-5)
elseif name:sub(-3,-2) == '_T' then
name = name:sub(1,-4)
elseif name:sub(-2,-1) == '_T' then
name = name:sub(1,-3)
end
-- set new trackname and get next track
reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name .. takestring, true)
i_track = i_track + 1
track = reaper.GetTrack(0, i_track)
end
-- write new takenumber to rpp
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', take)
end
-- Initialisation for takenumber and tracknames
function initTracknames()
local i_track = 0
local track = reaper.GetTrack(0, i_track)
local name = 'empty'
local take = 1
local retval
local val
-- try to get takenumber from rpp. If not present, set to "1"
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'nexttake')
if retval > 0 then
take = tonumber(val)
else
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', take)
end
takestring = '_T' .. string.format('%03d', take) -- format string to append to tracks, e.g. "_T042"
-- run through all tracks and append takenumber
while(track)
do
name = select( 2, reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name, false) )
-- check if a takenumber already exists, even without leading zeros
if name:sub(-5,-4) == '_T' then
name = name:sub(1,-6)
elseif name:sub(-4,-3) == '_T' then
name = name:sub(1,-5)
elseif name:sub(-3,-2) == '_T' then
name = name:sub(1,-4)
elseif name:sub(-2,-1) == '_T' then
name = name:sub(1,-3)
end
-- set new trackname and get next track
reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name .. takestring, true)
i_track = i_track + 1
track = reaper.GetTrack(0, i_track)
end
end
-- Removes Takenumber from tracknames
function cleanTracknames()
local i_track = 0
local track = reaper.GetTrack(0, i_track)
local name = 'empty'
-- run through all tracks and remove takenumber
while(track)
do
name = select( 2, reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name, false) )
-- check if a takenumber exists, even without leading zeros or without numbers
if name:sub(-5,-4) == '_T' then
name = name:sub(1,-6)
elseif name:sub(-4,-3) == '_T' then
name = name:sub(1,-5)
elseif name:sub(-3,-2) == '_T' then
name = name:sub(1,-4)
elseif name:sub(-2,-1) == '_T' then
name = name:sub(1,-3)
end
-- set new trackname and get next track
reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name, true)
i_track = i_track + 1
track = reaper.GetTrack(0, i_track)
end
end
-- Read takenumber from name of first track and store it to rpp
function getTakeFromTrackOne()
local track = reaper.GetTrack(0, 0)
local name = 'empty'
name = select( 2, reaper.GetSetMediaTrackInfo_String(track, 'P_NAME', name, false) )
-- reads takenumber, even without leading zeros
if name:sub(-5,-4) == '_T' then
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', tonumber(name:sub(-3,-1)))
elseif name:sub(-4,-3) == '_T' then
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', tonumber(name:sub(-2,-1)))
elseif name:sub(-3,-2) == '_T' then
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', tonumber(name:sub(-1,-1)))
end
end
-- Store state to project variables
function storeExtState()
reaper.SetProjExtState(0, 'takenumber_window', 'dockState', gfx.dock(-1))
if groupItemsAfterRecord then
reaper.SetProjExtState(0, 'takenumber_window', 'groupItems', '1')
else
reaper.SetProjExtState(0, 'takenumber_window', 'groupItems', '0')
end
if groupItemsHAfterRecord then
reaper.SetProjExtState(0, 'takenumber_window', 'groupItemsH', '1')
else
reaper.SetProjExtState(0, 'takenumber_window', 'groupItemsH', '0')
end
if lockToActiveTakeAfterRecord then
reaper.SetProjExtState(0, 'takenumber_window', 'lockToActiveTake', '1')
else
reaper.SetProjExtState(0, 'takenumber_window', 'lockToActiveTake', '0')
end
if fillGapsAfterRecord then
reaper.SetProjExtState(0, 'takenumber_window', 'fillGaps', '1')
else
reaper.SetProjExtState(0, 'takenumber_window', 'fillGaps', '0')
end
if writeTakeNamesAfterRecord then
reaper.SetProjExtState(0, 'takenumber_window', 'writeTakenames', '1')
else
reaper.SetProjExtState(0, 'takenumber_window', 'writeTakenames', '0')
end
end
---------------------------
-- functions for graphics...
---------------------------
-- Returns current script's path
function get_script_path()
local info = debug.getinfo(1,'S');
local script_path = info.source:match[[^@?(.*[\/])[^\/]-$]]
return script_path
end
-- gui init stuff, including rightclick-menu
function gui_init()
-- Get "script path"
script_path = get_script_path()
--msg(script_path)
-- Modify "package.path"
package.path = package.path .. ";" .. script_path .. "?.lua"
--msg(package.path)
-- Import files ("classes", functions etc.)--
require "class" -- import "base class"
mouse = require "mouse"
Menu = require "menu class"
-- Create "right click" menu --
-- Create a "Menu" instance
rc_menu = Menu("rc_menu")
-- Add menu items to "rc_menu"
-- ">" at the start spawns a submenu, | at the end creates a spacer
rc_menu:add_item({label = "Remove takenumber from tracknames"})
rc_menu:add_item({label = "Write takenumber to tracknames|"})
rc_menu:add_item({label = "Reset takenumber to one"})
rc_menu:add_item({label = "Get takenumber from first track"})
rc_menu:add_item({label = "Increase takenumber by one"})
rc_menu:add_item({label = "Decrease takenumber by one|"})
rc_menu:add_item({label = "Group all items of same take",
toggleable = true,
selected = groupItemsAfterRecord})
rc_menu:add_item({label = "Group items of same take horizontally|",
toggleable = true,
selected = groupItemsAfterRecordH})
rc_menu:add_item({label = "Crossfade after record (needs SWS)",
toggleable = true,
active = sws_present,
selected = fillGapsAfterRecord and sws_present})
rc_menu:add_item({label = "Lock to active take after record",
toggleable = true,
selected = lockToActiveTakeAfterRecord})
rc_menu:add_item({label = "Write clean Takenames after record|",
toggleable = true,
selected = writeTakeNamesAfterRecord})
rc_menu:add_item({label = "Quit"})
-- Let's add a command to all created items:
rc_menu.items[1].command = function() cleanTracknames() end
rc_menu.items[2].command = function() initTracknames() end
rc_menu.items[3].command = function()
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', '1')
initTracknames()
end
rc_menu.items[4].command = function()
getTakeFromTrackOne()
initTracknames()
end
rc_menu.items[5].command = function() increaseTake() end
rc_menu.items[6].command = function() decreaseTake() end
rc_menu.items[7].command = function()
if rc_menu.items[7].selected then
groupItemsAfterRecord = true
groupItemsHAfterRecord = false
rc_menu.items[8].selected = false
else
groupItemsAfterRecord = false
end
storeExtState()
end
rc_menu.items[8].command = function()
if rc_menu.items[8].selected then
groupItemsHAfterRecord = true
groupItemsAfterRecord = false
rc_menu.items[7].selected = false
else
groupItemsHAfterRecord = false
end
storeExtState()
end
rc_menu.items[9].command = function()
if rc_menu.items[9].selected then
fillGapsAfterRecord = true
else
fillGapsAfterRecord = false
end
storeExtState()
end
rc_menu.items[10].command = function()
if rc_menu.items[10].selected then
lockToActiveTakeAfterRecord = true
else
lockToActiveTakeAfterRecord = false
end
storeExtState()
end
rc_menu.items[11].command = function()
if rc_menu.items[11].selected then
writeTakeNamesAfterRecord = true
else
writeTakeNamesAfterRecord = false
end
storeExtState()
end
rc_menu.items[12].command = function() quit = true end
end
-- Draw GUI --
function drawGui()
local take = 1
local retval
local val
local thisnext = 'Next'
-- try to get takenumber from rpp. If not present, set to "1"
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'nexttake')
if retval > 0 then
take = tonumber(val)
else
reaper.SetProjExtState(0, 'takenumber_window', 'nexttake', take)
end
-- Are we in "record" mode?
if reaper.GetAllProjectPlayStates(0) == 5 then
increaseTodo = true -- after rec-stop we have to increase the takenumber
thisnext = 'This' -- during record the label says "This Take: 42"
gfx.clear = 255 + 20*256 + 20*65536 -- background is red
else -- no, we're not
if increaseTodo then -- we just stopped from record
if writeTakeNamesAfterRecord then --so we do all the magic, here eventually we write nice takenames
writeTakeNames()
end
increaseTake() -- and have to increase the takenumber
if groupItemsAfterRecord then -- and do post-record-stuff
groupItems()
elseif groupItemsHAfterRecord then
groupItemsH()
end
if lockToActiveTakeAfterRecord then
setLockToActiveTake()
end
if fillGapsAfterRecord then
fillGapsBetweenItems()
end
thisnext = 'Next' -- during "not-record" the label says "Next Take: 43"
increaseTodo = false -- and we're done and reset this flag
end
gfx.clear = 3355443 -- background is dark grey
end
-- If window is docked, use different scaling
if gfx.dock(-1) > 0 then
-- landscape format
if gfx.w > gfx.h then
gfx.x = 20
gfx.y = ( gfx.h-select(2, gfx.measurestr("Take")) ) / 2
gui.settings.font_size = gfx.h / 2
gfx.setfont(1,"Arial", gui.settings.font_size)
-- check if string fits into window, if not decrease fontsize a little, check again and loop
while gfx.measurestr(thisnext .. " Take: " .. take) > (gfx.w - 2*gfx.x) do
gui.settings.font_size = gui.settings.font_size / 1.1
gfx.setfont(1,"Arial", gui.settings.font_size)
end
gfx.printf(thisnext .. " Take: " .. take)
-- portrait format
else
gfx.x = gfx.w/15
gfx.y = ( gfx.h-select(2, gfx.measurestr("Next\nTake\nT123")) ) / 2
gui.settings.font_size = gfx.w / 3
gfx.setfont(1,"Arial", gui.settings.font_size)
gfx.printf(thisnext .. "\nTake:\n" .. take)
end
else -- window is not docked
-- landscape format
if gfx.w > gfx.h then
gfx.x = gfx.w/20
gfx.y = ( gfx.h-select(2, gfx.measurestr("Take")) ) / 2
gui.settings.font_size = gfx.w / 6
gfx.setfont(1,"Arial", gui.settings.font_size)
-- check if string fits into window, if not decrease fontsize a little, check again and loop
while gfx.measurestr(thisnext .. " Take: " .. take) > (gfx.w - 2*gfx.x) do
gui.settings.font_size = gui.settings.font_size / 1.1
gfx.setfont(1,"Arial", gui.settings.font_size)
end
gfx.printf(thisnext .. " Take: " .. take)
-- portrait format
else
gfx.x = gfx.w/20
gfx.y = ( gfx.h-select(2, gfx.measurestr("Next\nTake\nT123")) ) / 2
gui.settings.font_size = gfx.w / 3
gfx.setfont(1,"Arial", gui.settings.font_size)
gfx.printf(thisnext .. "\nTake:\n" .. take)
end
end
end
-- Called on script termination. Store stuff...
function onExit()
storeExtState()
end
-- Main init function
function init()
-- check for SWS extensions
if reaper.NamedCommandLookup('_SWS_ABOUT') > 0 then sws_present = true end
-- load settings from rpp
local retval
local val
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'groupItems')
if retval > 0 then
if tonumber(val) > 0 then
groupItemsAfterRecord = true
else
groupItemsAfterRecord = false
end
end
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'groupItemsH')
if retval > 0 then
if tonumber(val) > 0 then
groupItemsAfterRecordH = true
else
groupItemsAfterRecordH = false
end
end
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'lockToActiveTake')
if retval > 0 then
if tonumber(val) > 0 then
lockToActiveTakeAfterRecord = true
else
lockToActiveTakeAfterRecord = false
end
end
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'fillGaps')
if retval > 0 then
if tonumber(val) > 0 then
fillGapsAfterRecord = true
else
fillGapsAfterRecord = false
end
end
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'writeTakenames')
if retval > 0 then
if tonumber(val) > 0 then
writeTakeNamesAfterRecord = true
else
writeTakeNamesAfterRecord = false
end
end
-- init stuff...
gui_init()
initTracknames()
-- Add stuff to "gui" table
gui.settings = {} -- Add "settings" table to "gui" table
gui.settings.font_size = 50 -- font size
gui.settings.docker_id = 0 -- try 0, 1, 257, 513, 1027 etc.
-- Initialize gfx window --
gfx.init("Rec Take", 350, 100, gui.settings.docker_id)
gfx.setfont(1,"Arial", gui.settings.font_size)
gfx.clear = 3355443 -- background is dark grey
-- Restore docked state
retval, val = reaper.GetProjExtState(0, 'takenumber_window', 'dockState')
if retval > 0 then
gfx.dock(val)
end
end
-- Main loop --
function mainloop()
-- mouseclicks?
local RMB_state = mouse.cap(mouse.RB)
local LMB_state = mouse.cap(mouse.LB)
local mx = gfx.mouse_x
local my = gfx.mouse_y
if not mouse.last_RMB_state and gfx.mouse_cap&2 == 2 then
-- right click pressed down -> show "right click menu" at mouse cursor and focus arranger afterwards
rc_menu:show(mx, my)
reaper.Main_OnCommand( reaper.NamedCommandLookup('_BR_FOCUS_ARRANGE_WND'), 0 ) -- focus arranger
end
if not mouse.last_LMB_state and gfx.mouse_cap&1 == 1 then
-- left click pressed down -> only focus arranger
reaper.Main_OnCommand( reaper.NamedCommandLookup('_BR_FOCUS_ARRANGE_WND'), 0 ) -- focus arranger
end
mouse.last_RMB_state = RMB_state -- store current right mouse button state
mouse.last_LMB_state = LMB_state -- store current left mouse button state
drawGui()
gfx.update()
if gfx.getchar() >= 0 and not quit then reaper.defer(mainloop) end
end
-- START HERE...
reaper.atexit(onExit)
init()
mainloop()