--[[ Description: Quick Adder 2 About: Unified solution for adding FX, inserting track templates and running actions in REAPER. Version: 2.49.4 Author: Neutronic Donation: https://paypal.me/SIXSTARCOS License: GNU GPL v3 Links: Neutronic's REAPER forum profile https://forum.cockos.com/member.php?u=66313 Quick Adder 2 forum thread https://forum.cockos.com/showthread.php?t=232928 Quick Adder 2 video demo http://bit.ly/seeQA2 Changelog: + add context menu (right-click) for search bar + add "Clear Search", "Copy" and "Paste" to search context menu + add "Ctrl(Cmd) + C" shortcut to copy selected search text + add "Ctrl(Cmd) + V" shortcut to paste text from clipboard # use up-to-date command IDs with favorite scripts/extensions # set initial GUI size to 4k for retina screens --]] local rpr = {} local scr = {} rpr.x64 = reaper.GetAppVersion():match(".*(64)") and true or nil if reaper.CF_GetSWSVersion then rpr.sws = reaper.CF_GetSWSVersion(""):gsub("%.", "") rpr.sws = tonumber(rpr.sws) end local cur_os = reaper.GetOS() local os_is = {win = cur_os:lower():match("win") and true or false, mac = cur_os:lower():match("osx") or cur_os:lower():match("macos") and true or false, mac_arm = cur_os:lower():match("macos") and true or false, lin = cur_os:lower():match("other") and true or false} function getContent(path) local file = io.open(path) if not file then return "" end local content = file:read("*a") file:close() return content end function findContentKey(content, key, self) if self then content = content:match("%-%-%[%[.-%-%-%]%]") for match in content:gmatch("(%w.-:.-)\n") do local key, val = match:match("(.-): (.+)") if val and not val:match("http") then scr[key:lower()] = val end end scr.links = {} for match in content:gmatch("(http.-)\n") do table.insert(scr.links, match) end return else content = content:match(key .. "[:=].-\n") end if not content and key:match("vstpath") then content = os_is.win and (rpr.x64 and os.getenv("ProgramFiles(x86)").."\\vstplugins;" or "").. os.getenv("ProgramFiles").."\\vstplugins;".. os.getenv("CommonProgramFiles").."\\VST3\n" or os_is.mac and "/Library/Audio/Plug-Ins/VST;/Library/Audio/Plug-Ins/VST3;".. os.getenv("HOME").."/Library/Audio/Plug-Ins/VST;".. os.getenv("HOME").."/Library/Audio/Plug-Ins/VST3\n" end return content and content:gsub(key.. "[:=]%s?", "") or false end scr.path, scr.secID, scr.cmdID = select(2, reaper.get_action_context()) scr.dir = scr.path:match(".+[\\/]") scr.no_ext = scr.path:match("(.+)%.") scr.config = scr.no_ext .. "_cfg" scr.fav = scr.no_ext .. "_fav" scr.plugs = scr.no_ext .. "_db" findContentKey(getContent(scr.path), "", true) scr.name = "Quick Adder v" .. scr.version .. " | Neutronic" scr.actions = {} scr.ext_refresh = reaper.GetExtState("Quick Adder", "ACT") ~= "" and true or nil rpr.ver = tonumber(reaper.GetAppVersion():match("[%d%.]+")) if rpr.ver < 5.985 then reaper.MB("This script is designed to work with REAPER v5.985+", scr.name, 0) return end if reaper.GetExtState("Quick Adder", "MSG") == "2" then return elseif reaper.GetExtState("Quick Adder", "MSG") ~= "1" then local msg = scr.ext_refresh and 2 or 1 reaper.SetExtState("Quick Adder", "MSG", msg, false) reaper.SetToggleCommandState(scr.secID, scr.cmdID, 1) reaper.RefreshToolbar2(scr.secID, scr.cmdID) else reaper.SetExtState("Quick Adder", "MSG", "reopen", false) return end local timers = {} local db = {} local gui = {} rpr.path = reaper.GetResourcePath():gsub("\\", "/") function getResolution(wantWorkArea) local _, _ , vp_w, vp_h = reaper.my_getViewport(0,0,0,0,0,0,0,0, wantWorkArea) local isRetina = function() gfx.ext_retina = 1 gfx.init() local retina = gfx.ext_retina == 2 and true or nil gfx.quit() return retina end return vp_w, vp_h, isRetina() end local res_multi = {["|720p"] = 1, ["|1080p"] = 1.4, ["|4k"] = 2.82, ["|5k"] = 3.8, ["|8k"] = 4.8 } function getResolutionMulti() local h, retina = select(2, getResolution(false)) if h >= 4320 then -- 8k and up return res_multi["|8k"] elseif h >= 2880 then -- 5k and up return res_multi["|5k"] elseif h >= 2160 or retina then -- 4k and up return res_multi["|4k"] elseif h >= 1080 then -- full HD and up return res_multi["|1080p"] else -- lower than full HD return res_multi["|720p"] end end function parseKeyVal(key) local tbl = {} for cap in key:gmatch("(.-)[;,\n]") do table.insert(tbl, cap) end return tbl end if rpr.x64 then rpr.vst = rpr.path .. (os_is.mac_arm and "/reaper-vstplugins_arm64.ini" or "/reaper-vstplugins64.ini") if os_is.mac then rpr.au = rpr.path .. (os_is.mac_arm and "/reaper-auplugins_arm64.ini" or "/reaper-auplugins64.ini") end else rpr.vst = rpr.path .. "/reaper-vstplugins.ini" if os_is.mac then rpr.au = rpr.path .. "/reaper-auplugins.ini" end end rpr.fx_folders = rpr.path .. "/reaper-fxfolders.ini" local white_ch = {del = 6579564, bs = 8} local ignore_ch = {quit = -1, no_ch = 0, enter = 13, tab = 9, esc = 27, --dot = 46, --colon = 58, --comma = 44, --semicolon = 59, --vert_bar = 124, --backslash = 92, left = 1818584692, right = 1919379572, up = 30064, down = 1685026670, home = 1752132965, end_key = 6647396, pgup = 1885828464, pgdown = 1885824110, tilde = 96, f1 = 26161, f2 = 26162, f3 = 26163, f4 = 26164, f5 = 26165, f6 = 26166, f7 = 26167, f8 = 26168, f9 = 26169, f10 = 6697264, f11 = 6697265, f12 = 6697266} local mouse_mod = {ctrl = 4, shift = 8, alt = 16, lmb = 1, rmb = 2, win = 32, mmb = 64, no_mod = 0, [1] = "LMB", [2] = "RMB", [4] = function()return os_is.mac and (literal and "Command" or utf8.char(8984)) or "Ctrl" end, [8] = function()return os_is.mac and not literal and utf8.char(8679) or "Shift" end, [16] = function()return os_is.mac and (literal and "Option" or utf8.char(8997)) or "Alt" end, [32] = function()return os_is.mac and (literal and "Control" or "^") or "Win" end, [64] = "MMB", [0] = "No Mod" } mouse_mod["clear"] = mouse_mod.ctrl mouse_mod["input"] = mouse_mod.shift mouse_mod["track"] = mouse_mod.no_mod mouse_mod["take"] = mouse_mod.alt mouse_mod.dds = mouse_mod.ctrl + mouse_mod.shift + mouse_mod.alt -- drag and drop send local enter = os_is.mac and "Return" or "Enter" local keep_state_names = { {"ISBUS", "Folder state"}, {"GROUP_FLAGS", "Grouping"}, {"TRACKHEIGHT", "Height"}, {"ITEMS", "Items"}, {"LAYOUTS", "Layouts"}, {"MAINSEND", "Master send"}, {"MUTESOLO", "Mute and solo"}, {"NAME", "Name"}, {"REC", "Record arm"}, {"VOLPAN", "Volume and pan"}} function doFile(str) dofile(str) end function magicFix(str) return str:gsub("[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1") end function escSeqFix(str) return str:gsub("[\a\b\f\n\r\v\0]", ""):gsub("[\'\"\\]", "\\%0") end function a_macYoffset()end function macYoffset(cur_h, new_h, y) if not cur_h then return y end if os_is.mac then y = y + cur_h - new_h local gui_x, gui_y, gui_w, gui_h = select(2, gfx.dock(-1, 0, 0, 0, 0)) local scr_l, scr_t, scr_r, scr_b = reaper.my_getViewport(0, 0, 0, 0, gui_x, gui_y, gui_x, gui_y, true) if y + new_h >= scr_b then y = scr_b - new_h <= scr_t and scr_t or scr_b - new_h-- + scr_t elseif y < 0 and scr_b > 0 then local scr_l, scr_t, scr_r, scr_b = reaper.my_getViewport(0, 0, 0, 0, gui_x, gui_y+new_h, gui_x+gui_w, gui_y+new_h, true) y = scr_t end end return y end function a_tableToSTring()end function tableToString(name, tbl, do_yield) local str = name .. " = {\n" local tbl_sorted = {} for n in pairs(tbl) do table.insert(tbl_sorted, tostring(n)) end table.sort(tbl_sorted) function keyParse(k, mode) local k_temp = tonumber(k) if k_temp then return k_temp elseif mode == 1 then return k elseif mode == 2 then return '"' .. k .. '"' end end function valueParse(v) if type(v) == "string" then return '"' .. escSeqFix(v) .. '"' elseif type(v) == "boolean" then return tostring(v) else return v end end for i = 1, #tbl_sorted do if do_yield and select(2, math.modf(i / 100)) == 0 then coroutine.yield() end str = str .. '\t[' .. keyParse(tbl_sorted[i], 2) .. '] = ' .. valueParse(tbl[keyParse(tbl_sorted[i], 1)]) .. ",\n" end str = (str:match("(.+),\n") or str:match("(.+)\n")) .. "\n}\n" return str end function a_writeFile()end function writeFile(path, str, manual) local f = assert(io.open(path, manual or "w")) f:write(str) f:close() end function getFXfolder(str, type_n) str = str:lower() local fx_folder = "" if not str then return fx_folder end if config and not config.fol_search then return fx_folder end if type_n == 3 then -- if VST local vst_id, vst_file = str:match("(.-)//(.+)") vst_id = vst_id:gsub("{%w+", "") for i = 1, fx_folders and #fx_folders or 0 do if fx_folders[i].content and (vst_id ~= "0" and fx_folders[i].content:find(vst_id) or fx_folders[i].content:find(vst_file .. "[\n\r]")) then fx_folder = fx_folder .. "\t" .. fx_folders[i].name end end else str = str:gsub("[^%w%.\n\r]", "_") for i = 1, fx_folders and #fx_folders or 0 do if fx_folders[i].content and fx_folders[i].content:match("item%d+_" .. magicFix(str)) then local fx_n = fx_folders[i].content:match("item(%d+)_" .. magicFix(str)) if fx_folders[i].content:find("type" .. fx_n .. "_" .. type_n) then fx_folder = fx_folder .. "\t" .. fx_folders[i].name end end end end return fx_folder end function listDir(path) local dir_list = {} local i = 0 while not dir do local dir = reaper.EnumerateSubdirectories(path, i) if not dir or dir:match(".+%.component") or dir:match(".+%.vst%d?") then break end local path = path .. "/" .. dir table.insert(dir_list, path) local subdir_list = listDir(path) for i = 1, #subdir_list do table.insert(dir_list, subdir_list[i]) end i = i + 1 end return dir_list end function listFiles(dir_list, ext) local cntr = 0 local file_list = {} local getDirFiles = function(path) local file_list = {} local i = 0 while not file do local path = not path:match("/$") and path .. "/" or path:match("/$") and path local file = reaper.EnumerateFiles(path, i) if not file then break end if file:match("[^%.]-$") == ext then if ext == "RfxChain" and not path:match("/FXChains/") then goto SKIP end file = file:gsub("%." .. ext, "") local fx_type = ext == "RfxChain" and "CHAIN" or ext == "RTrackTemplate" and "TEMPLATE" if rpr.def_fx_filt and ext == "RfxChain" and fxExclCheck(fx_type .. ":" .. file:lower()) then goto SKIP end if rpr.def_fx_filt and ext == "RfxChain" and not fxExclCheck(fx_type .. ":" .. file:lower(), true) then goto SKIP end local fx_folder = getFXfolder(file, 1000) cntr = cntr + 1 if select(2, math.modf(cntr / 10)) == 0 then coroutine.yield(#file_list) end table.insert(file_list, fx_type .. ":" .. file .. fx_folder .. "|,|" .. [[]] .. path .. [[]] .. "|,||,||,|") ::SKIP:: elseif file:match("^.+jsfx$") or not ext and (not file:match("%.") or file:match("%d%.%d")) then -- if JS table.insert(file_list, path .. file) end i = i + 1 end return file_list end for i = 1, #dir_list do local files = getDirFiles(dir_list[i]) for i = 1, #files do table.insert(file_list, files[i]) end end return file_list end function getFiles(match, ext) local dir_list = {} local file_list = {} local dir_list = {} local i = 0 while not dir do local dir = reaper.EnumerateSubdirectories(rpr.path, i) if not dir then break end if dir:match("^"..match..(match and not ext and "$" or "")) then local path = rpr.path .. "/" .. dir-- .. "/" table.insert(dir_list, path) local subdir_list = listDir(path) for i = 1, #subdir_list do table.insert(dir_list, subdir_list[i]) end end i = i + 1 end file_list = listFiles(dir_list, ext) if ext then local key = ext == "RfxChain" and "CHAIN" or ext == "RTrackTemplate" and "TEMPLATE" db[key] = file_list db[key] = sortAbc(db[key]) coChain = nil coTemplate = nil else return file_list end end function getFxDir(path) local dir_list for i = 1, #path do if i == 1 then dir_list = listDir(path[i]) table.insert(dir_list, 1, path[i]) else local dir_list2 = listDir(path[i]) table.insert(dir_list2, 1, path[i]) for i, v in ipairs(dir_list2) do dir_list[#dir_list+1] = v end end end return dir_list end function a_config() end function initGlobalTypesOrder() global_types = {CHAIN = true, VST2 = true, VST3 = true, JS = true, AU = os_is.mac and true or nil, ACTION = config.act_search and true or nil, TEMPLATE = true} for k in pairs(global_types) do config.global_types_n = config.global_types_n + 1 end function getGlobalType(str) if global_types[str] then return str end end global_types_order = {getGlobalType("CHAIN"), getGlobalType("AU"), getGlobalType("VST2"), getGlobalType("VST3"), getGlobalType("JS"), getGlobalType("TEMPLATE"), getGlobalType("ACTION")} function sortTable(tbl) local tbl_temp = {} for i = 1, #tbl do if tbl[i] then table.insert(tbl_temp, tbl[i]) end end tbl = tbl_temp return tbl end global_types_order = sortTable(global_types_order) end if not pcall(doFile, scr.config) then config = {multi = getResolutionMulti(), --wnd_w = 372, row_h = 37, theme = rpr.ver < 6 and "light" or "dark", mode = "ALL", pin = true, reminder = true, results_max = 7, global_types_n = 0, os = cur_os, db_scan = 3, --wnd_w_prefs = 424, search_delay = 0, float_mode = 4, } initGlobalTypesOrder() keep_states = { -- what original track info to preserve when applying track templates GROUP_FLAGS = true, -- track group membership ISBUS = true, -- folder states (affects only templates containing a single track) ITEMS = true, -- track items LAYOUTS = true, -- track TCP + MCP layouts MAINSEND = true, -- master send / parent channels MUTESOLO = false, -- mute / solo NAME = false, -- track name REC = true, -- track record arm status / input / monitoring TRACKHEIGHT = true, -- track height VOLPAN = false -- volume / pan } keep_states.GROUP_FLAGS_HIGH = keep_states.GROUP_FLAGS end if config.db_scan == 2 then db.saved = false elseif config.db_scan == 1 and reaper.GetExtState("Quick Adder", "SCAN") ~= "1" then db.saved = false reaper.SetExtState("Quick Adder", "SCAN", "1", false) elseif config.db_scan == 3 or reaper.GetExtState("Quick Adder", "SCAN") == "1" then db.saved = pcall(doFile, scr.plugs) end config.dbl_click_speed = config.dbl_click_speed and config.dbl_click_speed or 0.25 if config.fav_persist == nil then config.fav_persist = true end if config.results_ph == nil then config.results_ph = true end if config.undock == nil then config.undock = true end if config.act_search == nil and reaper.CF_EnumerateActions then db.saved = false config.act_search = true global_types.ACTION = true config.global_types_n = config.global_types_n + 1 table.insert(global_types_order, "ACTION") if filter_modes then filter_modes.ACTION = true end end if not filter_modes then filter_modes = {ALL = true, CHAIN = true, FAV = true, FOLDER = config.fol_search and true or nil, FX = false, INSTRUMENT = false, JS = true, TEMPLATE = true, VST2 = true, VST3 = true, AU = os_is.mac and true or nil, ACTION = config.act_search and true or nil } end if reaper.CF_EnumerateActions then config.act_search = config.act_search elseif config.act_search then config.act_search = nil filter_modes.ACTION = nil global_types.ACTION = nil config.global_types_n = config.global_types_n - 1 for i, v in ipairs(global_types_order) do if v == "ACTION" then table.remove(global_types_order, i) break end end end if config.fol_search == nil then config.fol_search = true filter_modes.FOLDER = true db.saved = false VST2 = nil VST3 = nil JS = nil CHAIN = nil TEMPLATE = nil AU = nil ACTION = nil end if config.invert_mw == nil then config.invert_mw = false end config2 = config config = nil local config = config2 config2 = keep_states keep_states = nil local keep_states = config2 config2 = global_types global_types = nil local global_types = config2 config2 = global_types_order global_types_order = nil local global_types_order = config2 config2 = filter_modes filter_modes = nil local filter_modes = config2 config2 = nil if not config.no_sel_tracks then config.no_sel_tracks = 1 end if os_is.win and config.mode == "AU" then config.mode = "ALL" end if config.os ~= cur_os then config.os = cur_os if os_is.mac then config.global_types_n = not global_types.AU and config.global_types_n + 1 or config.global_types_n global_types.AU = true filter_modes.AU = true else config.global_types_n = global_types.AU and config.global_types_n - 1 or config.global_types_n global_types.AU = nil filter_modes.AU = nil for i = 1, #global_types_order do if global_types_order[i] == "AU" then table.remove(global_types_order, i) break end end end end if os_is.mac and not global_types.AU then config.global_types_n = not global_types.AU and config.global_types_n + 1 or config.global_types_n global_types.AU = true end if not config.version or config.version ~= scr.version then if not reaper.CF_EnumerateActions or not reaper.JS_Mouse_LoadCursor then config.ext_check = true end end if not config.version then config.version = scr.version end if config.version ~= scr.version then config.reminder = true end local sh_list = {["2"] = "VST2", ["3"] = "VST3", c = "CHAIN", u = os_is.mac and "AU" or nil, a = "ALL", x = "FX", j = "JS", f = "FAV", t = "TEMPLATE", i = "INSTRUMENT", o = config.fol_search and "FOLDER" or nil, n = config.act_search and "ACTION" or nil} if not pcall(doFile, scr.fav) then FAV = {} end db.FAV = FAV FAV = nil function ignoreCh(ch) local ch_found for key, v in pairs(ignore_ch) do if v == ch then ch_found = true break end end return ch_found end function whiteCh(ch) local ch_found for key, v in pairs(white_ch) do if v == ch then ch_found = true break end end return ch_found end local timer = {} function timer:new(o) local o = o or {} setmetatable(o, self) self.__index = self return o end function timer:start(dur) self.time_init = reaper.time_precise() self:count(dur) return self end function timer:count(dur) local time_new = reaper.time_precise() if time_new - self.time_init >= dur then self.up = true return end reaper.defer(function()self:count(dur)end) end function truncateString(x1, x2, str, str_w, offset) if x1 + str_w + offset * config.multi > x2 then while x1 + str_w > x2 do if not str then break end str = str:match("(.+).") str_w = gfx.measurestr(str) end str = (str and str:match("(.+)...") or "") .. "..." end return str end function retinaDivide(val) if not val then return end if not os_is.mac then return val end val = config.retina and val / 2 or val return math.floor(val) end function getResolutionKey(res_multi) for k, v in pairs(res_multi) do if v == config.multi then return k end end end function exit_states() if scr.quit then return end scr.quit = true local _, wnd_x, wnd_y, _, h = gfx.dock(-1, 0, 0, 0, 0) if not gui.open then goto SKIP end wnd_y = macYoffset(retinaDivide(scr.temp_undock and os_is.mac and gui.wnd_h_save or gui.wnd_h), retinaDivide((config.ext_check or scr.temp_undock and os_is.mac) and gui.wnd_h or gui.Row1.h + gui.row_h + gui.border * 2), wnd_y) config.wnd_x = wnd_x config.wnd_y = not config.undock and config.wnd_y or wnd_y config.version = scr.version if config.default_mode then config.mode = config.default_mode end if scr.temp_undock then config.undock = false end ::SKIP:: if get_db_txt then os.remove(scr.plugs) end writeFile(scr.config, tableToString("config", config)) writeFile(scr.config, tableToString("global_types", global_types), "a") writeFile(scr.config, tableToString("global_types_order", global_types_order), "a") writeFile(scr.config, tableToString("filter_modes", filter_modes), "a") writeFile(scr.config, tableToString("keep_states", keep_states), "a") writeFile(scr.fav, tableToString("FAV", db.FAV)) reaper.SetToggleCommandState(scr.secID, scr.cmdID, 0) reaper.RefreshToolbar2(scr.secID, scr.cmdID) reaper.DeleteExtState("Quick Adder", "MSG", false) reaper.DeleteExtState("Quick Adder", "ACT", false) end local m_track = reaper.GetMasterTrack() function close_undo() reaper.Undo_EndBlock("ReaScript: Run", -1) end function notFound(is_tt) local item = is_tt and "Track template" or "FX" reaper.MB(item .. " not found." .. (is_tt and "" or "\n\nPlease perform \"Clear cache/re-scan\" in\nREAPER Preferences " .. "--> Plug-ins --> VST\nto remove non-existent FX " .. "from the data-\nbase" .. " and then press F5 in Quick Adder."), "Quick Adder 2 error", 0) end function sortAbc(tbl, skip) local tbl_cap = {} local temp_tbl = {} local tbl_sorted = {} for i = 1, #tbl do local v = tbl[i]:upper() tbl_cap[i] = v table.insert(temp_tbl, v) end table.sort(temp_tbl) for i = 1, #temp_tbl do for k, v in pairs(tbl_cap) do if temp_tbl[i] == v then table.insert(tbl_sorted, tbl[k]) break end end end return tbl_sorted end function listArrayDirFiles(dir_list, ext) local file_list = {} for n = 1, #dir_list do local path = dir_list[n] .. "/" local i = 0 while not file do local file = os_is.win and reaper.EnumerateFiles(path, i) or reaper.EnumerateSubdirectories(path, i) if not file then break end if file:lower():match(".+%.dll") or file:lower():match(".+%.vst") or file:lower():match(".+%.component") then file = file:gsub("[^.%w]", "_") -- replace all but alphanumerical and periods --table.insert(file_list, file) file_list[file] = true end i = i + 1 end end return file_list end function parseIniFxFilt(str) if not str then return end if str:match(" OR ") then return end str = str:gsub("AND", "") str = str:gsub("\"", "") local tbl = {excl = {}, incl = {}} for match in str:gmatch("NOT %( .- %)%s") do local match_ins = match:match("NOT %( (.+ )%)") if match_ins:match("NOT") then return end local tbl2 = {} for phrase in match_ins:gmatch("%(.-%)%s") do table.insert(tbl2, phrase:match("(%(.-%))%s"):lower()) match_ins = match_ins:gsub(magicFix(phrase), "") end for word in match_ins:gmatch(".-%s") do table.insert(tbl2, word:match("(.-)%s"):lower()) end table.insert(tbl.excl, tbl2) str = str:gsub(magicFix(match), "") end for match in str:gmatch("NOT .-%s") do local match_ins = match:match("NOT (.-%s)") if match_ins == "(" then return end local tbl2 = {} for word in match_ins:gmatch(".-%s") do table.insert(tbl2, word:match("(.-)%s"):lower()) end table.insert(tbl.excl, tbl2) str = str:gsub(magicFix(match), "") end for match in str:gmatch("%( .- %)%s") do table.insert(tbl.incl, match:match("%( (.+ )%)"):lower()) str = str:gsub(magicFix(match), "") end for match in str:gmatch("%(.-%)%s") do table.insert(tbl.incl, {match:match("%(.+%)"):lower()}) str = str:gsub(magicFix(match), "") end for match in str:gmatch(".-%s") do local match_ins = match:match("(.-)%s") if match_ins == "" then goto SKIP end table.insert(tbl.incl, {match:match("(.-)%s"):lower()}) ::SKIP:: end return tbl end function getDb(refresh) if not db.saved then function dbDefer() if gui.ch == -1 then return end get_db = true if config.fol_search and not fx_folders then local fx_folders_ini = getContent(rpr.fx_folders) if fx_folders_ini then fx_folders_ini = fx_folders_ini .. "\n\n" local folder_names = fx_folders_ini:match("%[Folders%](.-)\n[\n%[]") if folder_names then fx_folders = {} for match in folder_names:gmatch("Name%d+=.-\n") do local n, name = match:match("Name(%d+)=(.+)\n") fx_folders[n+1] = {name = name} end for match in fx_folders_ini:gmatch("(Folder%d+%].-)\n[\n%[]") do local n, content = match:match("Folder(%d+)%](.+)") fx_folders[n+1].content = content:gsub("[^%w%.\n\r]", "_"):lower() end fx_folders_ini = nil end end end local r_ini = getContent(reaper.get_ini_file()) --[[rpr.vstpath = parseKeyVal(findContentKey(r_ini, rpr.x64 and "vstpath64" or "vstpath")) fx_dir_list = getFxDir(rpr.vstpath) fx_file_list = listArrayDirFiles(fx_dir_list)]] --rpr.aupath = os_is.mac and {"Library/Audio/Plug-Ins/Components", -- "~/Library/Audio/Plug-Ins/Components"} or nil --au_dir_list = os_is.mac and getFxDir(rpr.aupath) or nil --au_file_list = os_is.mac and listArrayDirFiles(au_dir_list) or nil --fx_dir_list = nil --fx_file_list = nil rpr.def_fx_filt = parseIniFxFilt(findContentKey(r_ini, rpr.x64 and (os_is.win and "def_fx_filt64" or "def_fx_filtx64") or os_is.win and "def_fx_filt32" or "def_fx_filtx32")) r_ini = nil if not db.VST2 then if not coVst then coVst = coroutine.create(getVst) end local n = select(2, coroutine.resume(coVst)) get_db_txt = "Scanning VST: " .. (n or #db.VST2 + #db.VST3) elseif not db.JS then if not coJs then coJs = coroutine.create(getJs) end local n = select(2, coroutine.resume(coJs)) get_db_txt = "Scanning JS: " .. (n or #db.JS) elseif os_is.mac and not db.AU then if not coAu then coAu = coroutine.create(getAu) end local n = select(2, coroutine.resume(coAu)) get_db_txt = "Scanning AU: " .. (n or #db.AU) elseif not db.CHAIN then if not coChain then coChain = coroutine.create(getFiles) end local n = select(2, coroutine.resume(coChain, "FXChains", "RfxChain")) get_db_txt = "Scanning FX chains: " .. (n or #db.CHAIN) elseif not db.TEMPLATE then if not coTemplate then coTemplate = coroutine.create(getFiles) end local n = select(2, coroutine.resume(coTemplate, "TrackTemplates", "RTrackTemplate")) get_db_txt = "Scanning track templates: " .. (n or #db.TEMPLATE) elseif config.act_search and not db.ACTION then if not coAction then coAction = coroutine.create(getAction) end local n = select(2, coroutine.resume(coAction)) get_db_txt = "Scanning Actions: " .. (n or 65535) end if not db.VST2 or not db.JS or not db.CHAIN or not db.TEMPLATE or (config.act_search and not db.ACTION) or (os_is.mac and not db.AU) then reaper.defer(dbDefer) else if not scr.db_total then scr.db_total = #db.VST2 + #db.VST3 + #db.JS + #db.CHAIN + #db.TEMPLATE + (db.ACTION and #db.ACTION or 0) + (db.AU and #db.AU or 0) end if not coVst2 or coroutine.status(coVst2) == "suspended" then if not coVst2 then coVst2 = coroutine.create(tableToString) end local str = select(2, coroutine.resume(coVst2, "VST2", db.VST2, true)) --get_db_txt = "Saving VST2 database" get_db_txt = "Finalizing database (" .. scr.db_total .. " entries)" scr.re_search = true if coroutine.status(coVst2) == "dead" then writeFile(scr.plugs, str) end elseif not coVst3 or coroutine.status(coVst3) == "suspended" then if not coVst3 then coVst3 = coroutine.create(tableToString) end local str = select(2, coroutine.resume(coVst3, "VST3", db.VST3, true)) --get_db_txt = "Saving VST3 database" if coroutine.status(coVst3) == "dead" then writeFile(scr.plugs, str, "a") end elseif not coJs or coroutine.status(coJs) == "suspended" then if not coJs then coJs = coroutine.create(tableToString) end local str = select(2, coroutine.resume(coJs, "JS", db.JS, true)) --get_db_txt = "Saving JS database" if coroutine.status(coJs) == "dead" then writeFile(scr.plugs, str, "a") end elseif os_is.mac and (not coAu or coroutine.status(coAu) == "suspended") then if not coAu then coAu = coroutine.create(tableToString) end local str = select(2, coroutine.resume(coAu, "AU", db.AU, true)) --get_db_txt = "Saving JS database" if coroutine.status(coAu) == "dead" then writeFile(scr.plugs, str, "a") end elseif not coChain or coroutine.status(coChain) == "suspended" then if not coChain then coChain = coroutine.create(tableToString) end local str = select(2, coroutine.resume(coChain, "CHAIN", db.CHAIN, true)) --get_db_txt = "Saving chains database" if coroutine.status(coChain) == "dead" then writeFile(scr.plugs, str, "a") end elseif not coTemplate or coroutine.status(coTemplate) == "suspended" then if not coTemplate then coTemplate = coroutine.create(tableToString) end local str = select(2, coroutine.resume(coTemplate, "TEMPLATE", db.TEMPLATE, true)) --get_db_txt = "Saving templates database" if coroutine.status(coTemplate) == "dead" then writeFile(scr.plugs, str, "a") end elseif not coAction or coroutine.status(coAction) == "suspended" then if not coAction then coAction = coroutine.create(tableToString) end local str = select(2, coroutine.resume(coAction, "ACTION", db.ACTION, true)) --get_db_txt = "Saving actions database" if coroutine.status(coAction) == "dead" then if config.act_search then writeFile(scr.plugs, str, "a") end if reaper.GetExtState("Quick Adder", "ACT") == "refresh" then reaper.DeleteExtState("Quick Adder", "ACT", false) if reaper.GetExtState("Quick Adder", "MSG") == "2" then scr.over = true end end get_db_txt = nil get_db = nil coVst2 = nil coVst3 = nil coJs = nil coAu = nil coChain = nil coTemplate = nil coAction = nil fx_folders = nil scr.ellipsis = nil timers.ellipsis = nil if refresh then gui.wnd_h_save = gui.wnd_h gui.reinit = true scr.re_search = true end end end if coVst2 or coVst3 or coJs or coChain or coTemplate or coAction then reaper.defer(dbDefer) end end if reaper.GetExtState("Quick Adder", "MSG") == "2" and get_db_txt then get_db_txt = get_db_txt:gsub("^.", string.lower) reaper.Help_Set("Quick Adder: " .. get_db_txt, 1) end end dbDefer() else db.VST2 = VST2 VST2 = nil db.VST3 = VST3 VST3 = nil db.JS = JS JS = nil db.CHAIN = CHAIN CHAIN = nil db.TEMPLATE = TEMPLATE TEMPLATE = nil db.AU = os_is.mac and AU or os_is.mac and not AU and {} or nil AU = nil db.ACTION = ACTION ACTION = nil end end function isClearFx() if gui.m_cap == mouse_mod.dds + (gfx.mouse_cap&mouse_mod.lmb) then return false end if gui.m_cap&mouse_mod.clear == mouse_mod.clear and not mouse_mod.reverse or gui.m_cap&mouse_mod.clear ~= mouse_mod.clear and mouse_mod.reverse then return true end end local cntSelTrs = function() return reaper.CountSelectedTracks(0) end local cntTrs = function() return reaper.CountTracks(0) end local getTr = function(n) return reaper.GetTrack(0, n) end local getSelTr = function(n) return reaper.GetSelectedTrack(0, n) end function getSelectedTracks() local tbl = {} for i = 0, cntSelTrs() - 1 do local tr = getSelTr(i) table.insert(tbl, tr) end return tbl end function doAdd() local auto_float if reaper.SNM_GetIntConfigVar then auto_float = reaper.SNM_GetIntConfigVar("fxfloat_focus", 0) if auto_float&4 > 0 then reaper.SNM_SetIntConfigVar("fxfloat_focus", auto_float~4) end end if scr.results_list[gui.Results.sel]:match("^(%w+).+") == "ACTION" and (gui.m_cap == 0 or gui.m_cap == mouse_mod.lmb) then --[[ local section = select(3, gui.parseResult(scr.results_list[gui.Results.sel])): match("(.+)/.+") local id = select(6, gui.parseResult(scr.results_list[gui.Results.sel])) --]] local section, _, named_cmd, id = select(3, gui.parseResult(scr.results_list[gui.Results.sel])) section = section:match("(.+)/.+") local id = #named_cmd > 0 and reaper.NamedCommandLookup("_" .. named_cmd) or id if section == "Main" then reaper.Main_OnCommand(id, 0) scr.result_is_action = true elseif section == "MIDI Editor" or section == "MIDI Event List Editor" then local is_list = section == "MIDI Event List Editor" and true or false local ME = reaper.MIDIEditor_GetActive() local ME_mode = reaper.MIDIEditor_GetMode(ME) reaper.MIDIEditor_LastFocused_OnCommand(id, is_list == 1 and true or false) scr.result_is_action = true elseif reaper.JS_Localize and reaper.JS_Window_Find and reaper.JS_Window_OnCommand and section == "Media Explorer" then local ME_name = reaper.JS_Localize("Media Explorer", "common") local ME = reaper.JS_Window_Find(ME_name, true) reaper.JS_Window_OnCommand(ME, id) scr.result_is_action = true end elseif scr.results_list[gui.Results.sel]:match("^(%w+).+") == "TEMPLATE" then if gui.m_cap == 0 or gui.m_cap == mouse_mod.shift + (gfx.mouse_cap&mouse_mod.lmb) or gui.m_cap == mouse_mod.clear + (gfx.mouse_cap&mouse_mod.lmb) or gui.m_cap == mouse_mod.dds + (gfx.mouse_cap&mouse_mod.lmb) or gui.m_cap == mouse_mod.lmb or m_obj then ttAdd(scr.results_list[gui.Results.sel]) end else if gui.m_cap == mouse_mod.dds + (gfx.mouse_cap&mouse_mod.lmb) or gui.m_cap == mouse_mod.alt + mouse_mod.win + (gfx.mouse_cap&mouse_mod.lmb) then scr.create_send = true end local m_obj_is_tr = reaper.ValidatePtr2(0, m_obj, "MediaTrack*") local m_obj_is_tk = reaper.ValidatePtr2(0, m_obj, "MediaItem_Take*") if gui.m_cap&mouse_mod.take == 0 and not m_obj and gui.m_cap&mouse_mod.win == 0 or gui.m_cap == mouse_mod.dds + (gfx.mouse_cap&mouse_mod.lmb) or gui.m_cap == mouse_mod.alt + mouse_mod.win + (gfx.mouse_cap&mouse_mod.lmb) or m_obj_is_tr or type(m_obj) == "string" then reaper.PreventUIRefresh(1) fxTrack() reaper.PreventUIRefresh(-1) elseif gui.m_cap&mouse_mod.take == mouse_mod.take and gui.m_cap&mouse_mod.win == 0 and gui.m_cap&mouse_mod.shift == 0 or m_obj_is_tk then reaper.PreventUIRefresh(1) fxItem() reaper.PreventUIRefresh(-1) end end if auto_float and auto_float&4 > 0 then reaper.SNM_SetIntConfigVar("fxfloat_focus", auto_float) end gui.selected = nil gui.click_ignore = nil gui.loop_start = nil if m_obj then gui.active = nil m_obj = nil end end function clearAddorAdd(s) return gui.m_cap&mouse_mod.clear ~= mouse_mod.clear and "Add " or "Clear FX chain" .. s .." and add " end function waitResult() if wait_result then wait_result = nil if config.pin and (not config.fx_hide or gfx.getchar(65536)&2 ~= 2) and gfx.dock(-1) == 0 then gui.reopen = true gui:init() end end end function createTrackSend(origin_tr, dest_tr) for i = 1, #origin_tr do if origin_tr[i] == reaper.GetMasterTrack(0) then return end reaper.CreateTrackSend(origin_tr[i], dest_tr) end reaper.Main_OnCommand(40293, 0) -- Track: View routing and I/O for current/last touched track if reaper.JS_Window_GetForeground and rpr.ver >= 6 then local wnd = reaper.JS_Window_GetForeground() reaper.JS_Window_SetZOrder(wnd, "TOPMOST") if reaper.NamedCommandLookup("_BR_MOVE_WINDOW_TO_MOUSE_H_M_V_M") > 0 then reaper.Main_OnCommand(reaper.NamedCommandLookup("_BR_MOVE_WINDOW_TO_MOUSE_H_M_V_M"), 0) end scr.bypass_reopen = true end end function fxTrack(sel_tr_count, is_m_sel) local sel_tr_count = sel_tr_count or reaper.CountSelectedTracks(0) local is_m_sel = is_m_sel or type(m_obj) ~= "string" and reaper.IsTrackSelected(m_track) or nil if not scr.create_send and (sel_tr_count > 0 and not m_obj or is_m_sel or m_obj and type(m_obj) == "userdata" and gui.m_cap ~= mouse_mod.take) then reaper.Undo_BeginBlock() local name, undo_name, fx_i for i = 0, m_obj and 0 or sel_tr_count - 1 do local track = m_obj or reaper.GetSelectedTrack(0, i) name, undo_name, fx_i = fxTrack_Add(track) if name == "No FX" then return name end if i == 0 then fxFloat(track, name) end end if is_m_sel and not m_obj then -- if master track is selected name, undo_name, fx_i = fxTrack_Add(m_track) if name == "No FX" then return end if sel_tr_count == 0 then fxFloat(m_track, name) end end ::SKIP:: if fx_i == -1 then return end local t_or_t = (sel_tr_count > 1 or sel_tr_count == 1 and is_m_sel) and "s" or "" local ca_or_a = clearAddorAdd(t_or_t) reaper.Undo_EndBlock(ca_or_a .. (isInput() and "input " or "") .. undo_name .. " to selected track" .. t_or_t, -1) waitResult() else wait_result = scr.results_list[gui.Results.sel] if m_obj or config.no_sel_tracks == 2 or (cntSelTrs() > 0 and (gui.m_cap == mouse_mod.dds + (gfx.mouse_cap&mouse_mod.lmb) or gui.m_cap == mouse_mod.alt + mouse_mod.win + (gfx.mouse_cap&mouse_mod.lmb))) then if scr.create_send then scr.create_send = nil end local sel_tracks if gui.m_cap == mouse_mod.dds + (gfx.mouse_cap&mouse_mod.lmb) and type(m_obj) == "userdata" or not m_obj and cntSelTrs() > 0 then scr.show_routing = true if type(m_obj) == "userdata" and m_obj ~= reaper.GetMasterTrack(0) then sel_tracks = {m_obj} scr.m_obj = true else sel_tracks = getSelectedTracks() end end local idx = ((not m_obj and cntSelTrs() == 0) or type(m_obj) == "string") and cntTrs() or not m_obj and reaper.CSurf_TrackToID(getSelTr(0), false) - 1 or reaper.GetMediaTrackInfo_Value(m_obj, "IP_TRACKNUMBER") == -1 and 0 or reaper.GetMediaTrackInfo_Value(m_obj, "IP_TRACKNUMBER") - 1 reaper.InsertTrackAtIndex(idx, false) local tr = getTr(idx) if m_obj then gui.active = nil m_obj = nil end reaper.GetSetMediaTrackInfo_String(tr, "P_NAME", gui.parseResult(wait_result):match("(.-) ?%(") or gui.parseResult(wait_result):match(".+: (.+)") or gui.parseResult(wait_result), true) if select(3, gui.parseResult(wait_result)) ~= "" then reaper.SetMediaTrackInfo_Value(tr, "I_RECARM", 1) reaper.SetMediaTrackInfo_Value(tr, "I_RECMON", 1) reaper.SetMediaTrackInfo_Value(tr, "I_RECINPUT", 128+63<<5|0) end reaper.SetOnlyTrackSelected(tr) local no_fx = fxTrack(cntSelTrs(), false) if scr.show_routing then if gui.m_cap&mouse_mod.win == 0 then createTrackSend(sel_tracks, tr) end scr.show_routing = nil scr.m_obj = nil end if no_fx then reaper.DeleteTrack(tr) end return elseif cntTrs() == 0 and config.no_sel_tracks == 1 then local answ = reaper.MB("There are no tracks in the project.\n" .. "Do you want to insert tracks to put the FX on?", "REASCRIPT Query", 1) if answ == 1 then reaper.Main_OnCommand(41067, 0) -- Track: Insert multiple new tracks waitTrack() else wait_result = nil end elseif config.no_sel_tracks == 1 then local answ = reaper.MB("Select tracks to put the FX on.", "REASCRIPT Query", 1) if answ == 1 then waitTrack() else wait_result = nil end elseif config.no_sel_tracks == 3 then wait_result = nil end end end function fxFlush(object, kind) if kind < 3 then -- if not take FX if kind < 2 then -- if not track input FX local fx_count = reaper.TrackFX_GetCount(object) for i = 0, fx_count do reaper.TrackFX_SetOffline(object, 0, true) reaper.TrackFX_Delete(object, 0) end else -- if track input FX local fx_count = reaper.TrackFX_GetRecCount(object) for i = 0, fx_count do reaper.TrackFX_SetOffline(object, 0x1000000+0, true) reaper.TrackFX_Delete(object, 0x1000000+0) end end else -- if take FX local fx_count = reaper.TakeFX_GetCount(object) for i = 0, fx_count do reaper.TakeFX_SetOffline(object, 0, true) reaper.TakeFX_Delete(object, 0) end end end function isInput() if gui.m_cap == mouse_mod.dds + (gfx.mouse_cap&mouse_mod.lmb) then return false elseif gui.m_cap&mouse_mod.input == mouse_mod.input then return true else return false end end function parseResultsList(v) local fx_type, name, path, id, undo_name = v:match("(%w-:)(.-)|,|(.-)|,|.-|,|(.-)|,|.*") name = name:gsub("\t.+", "") -- remove folders if fx_type == "JS:" then -- if JS if not name:match("Video processor") then undo_name = name name = fx_type .. path -- fx_type + path else undo_name = path end elseif fx_type == "CHAIN:" then local path = path:gsub(rpr.path .. "/FXChains/", "") undo_name = name name = path .. name .. ".RfxChain" else -- if VST or AU fx_type = fx_type:gsub("i", "") undo_name = name:gsub(" %(.+%)", "") if fx_type == "AU:" or rpr.ver < 6.43 then name = fx_type .. name else name = path .. "<" .. id end end return name, undo_name end function getObjType(obj) if reaper.ValidatePtr2(0, obj, "MediaTrack*") then return "Track" else reaper.ValidatePtr2(0, obj, "MediaItem_Take*") return "Take" end end function isAra(name) local name = name:lower() if name:match("melodyne") or name:match("vocalign") or name:match("revoice pro") or name:match("spectralayers") then return true end end function fxTrack_Add(track) local name, undo_name = parseResultsList(wait_result or scr.results_list[gui.Results.sel]) if isClearFx() then if not isInput() then fxFlush(track, 1) else fxFlush(track, 2) end end local fx_i = reaper.TrackFX_AddByName(track, name, isInput(), -1) if fx_i == -1 then if not isAra(name) then notFound() end close_undo() return "No FX" else return name, undo_name, fx_i end end function fxFloat(obj, name) local obj_type = getObjType(obj) local is_fxc_vis if not name:match("%.RfxChain$") then -- if not chain if isInput() then local chunk = select(2, reaper.GetTrackStateChunk(obj, "", false)) is_fxc_vis = tonumber(chunk:match("