From 599dd8d4ede7446a597b087d72843f8b3c57b20d Mon Sep 17 00:00:00 2001 From: Christian Fillion Date: Sat, 12 Oct 2024 23:21:18 -0400 Subject: [PATCH] Release Song switcher (for live use) v1.7 (#1441) run action markers from take markers in the current song's primary track (REAPER v6+) --- Various/cfillion_Song switcher.lua | 105 +++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 21 deletions(-) diff --git a/Various/cfillion_Song switcher.lua b/Various/cfillion_Song switcher.lua index 01aa73ebb..c6af93875 100644 --- a/Various/cfillion_Song switcher.lua +++ b/Various/cfillion_Song switcher.lua @@ -1,9 +1,7 @@ -- @description Song switcher (for live use) -- @author cfillion --- @version 1.6.1 --- @changelog --- disable keyboard input when the filter text box or a context menu has focus --- fix the toolbar buttons showing 1 frame early, before the filter text box is hidden +-- @version 1.7 +-- @changelog run action markers from take markers in the current song's folder track (REAPER v6+) -- @provides -- [main] cfillion_Song switcher/cfillion_Song switcher - Send signal.lua > cfillion_Song switcher/cfillion_Song switcher - Switch to next song.lua -- [main] cfillion_Song switcher/cfillion_Song switcher - Send signal.lua > cfillion_Song switcher/cfillion_Song switcher - Switch to previous song.lua @@ -48,6 +46,8 @@ -- - cfillion_Song switcher - Switch to queued song.lua -- - cfillion_Song switcher - Reset data.lua -- +-- Take markers starting with a `!` within the current song's folder track are treated as action markers. +-- -- A web browser interface is also installed as **song_switcher.html** for -- remote use (this feature requires REAPER v5.30+ and ReaPack v1.1+). -- Note that the timecode displayed in the web interface always starts at 00:00 for convenience. @@ -59,7 +59,7 @@ if not reaper.ImGui_GetBuiltinPath then return reaper.MB('ReaImGui is not installed or too old.', SCRIPT_NAME, 0) end package.path = reaper.ImGui_GetBuiltinPath() .. '/?.lua' -local ImGui = require 'imgui' '0.9' +local ImGui = require 'imgui' '0.9.3' local EXT_SECTION = 'cfillion_song_switcher' local EXT_SWITCH_MODE = 'onswitch' @@ -78,6 +78,7 @@ local scrollTo, setDock -- initialized in reset() local currentIndex, nextIndex, invalid, filterPrompt local signals = {} +local prevPlayPos local fonts = { small = ImGui.CreateFont('sans-serif', 13), @@ -194,7 +195,7 @@ end local function updateState() local song = songs[currentIndex] or {name='', startTime=0, endTime=0} - local state = string.format("%d\t%d\t%s\t%f\t%f\t%s", + local state = ("%d\t%d\t%s\t%f\t%f\t%s"):format( currentIndex, #songs, song.name, song.startTime, song.endTime, tostring(invalid) ) @@ -352,6 +353,7 @@ local function reset() filterPrompt, invalid = false, false currentIndex, nextIndex, scrollTo = 0, 0, 0 highlightTime = ImGui.GetTime(ctx) + prevPlayPos = nil -- clear previous pending external commands for signal, _ in pairs(signals) do @@ -383,6 +385,68 @@ local function execRemoteActions() end end +local function getParentProject(track) + local search = reaper.GetMediaTrackInfo_Value(track, 'P_PROJECT') + + if reaper.JS_Window_HandleFromAddress then + return reaper.JS_Window_HandleFromAddress(search) + end + + for i = 0, math.huge do + local project = reaper.EnumProjects(i) + if not project then break end + + local master = reaper.GetMasterTrack(project) + if search == reaper.GetMediaTrackInfo_Value(master, 'P_PROJECT') then + return project + end + end +end + +local function execTakeMarkers() + if not reaper.GetNumTakeMarkers then return end -- REAPER v5 + + local song = songs[currentIndex] + local track = song and song.tracks[1] + local valid, numItems = pcall(reaper.GetTrackNumMediaItems, track) + if not valid then return end -- validates track across all tabs + + local proj = getParentProject(track) + if reaper.GetPlayStateEx(proj) & 3 ~= 1 then return end -- not playing or paused + + local playPos = reaper.GetPlayPositionEx(proj) + if playPos == prevPlayPos then return end + + local minPos, maxPos = playPos, playPos + if prevPlayPos and playPos > prevPlayPos and (playPos - prevPlayPos) < 0.1 then + minPos = prevPlayPos + end + prevPlayPos = playPos + + for ii = 0, numItems - 1 do + local item = reaper.GetTrackMediaItem(track, ii) + local mute = reaper.GetMediaItemInfo_Value(item, 'B_MUTE') + local pos = reaper.GetMediaItemInfo_Value(item, 'D_POSITION') + local len = reaper.GetMediaItemInfo_Value(item, 'D_LENGTH') + local take = reaper.GetActiveTake(item) + + if take and mute == 0 and pos <= minPos and pos + len > maxPos then + local offs = reaper.GetMediaItemTakeInfo_Value(take, 'D_STARTOFFS') + + for mi = 0, reaper.GetNumTakeMarkers(take) - 1 do + local time, name = reaper.GetTakeMarker(take, mi) + time = time + pos - offs + if time >= minPos and time <= maxPos and name:sub(1, 1) == '!' then + for action in name:sub(2):gmatch('%S+') do + local action = reaper.NamedCommandLookup(action) + if action ~= 0 then reaper.Main_OnCommandEx(action, 0, proj) end + end + end + end + end + end +end + function drawName(song) local name = song and song.name or 'No song selected' @@ -600,27 +664,25 @@ local function navButtons() end local function keyInput(input) - if not ImGui.IsWindowFocused(ctx, - ImGui.FocusedFlags_ChildWindows | ImGui.FocusedFlags_NoPopupHierarchy) or - ImGui.IsAnyItemActive(ctx) then return end + if ImGui.IsAnyItemActive(ctx) then return end - if ImGui.IsKeyPressed(ctx, ImGui.Key_UpArrow) or - ImGui.IsKeyPressed(ctx, ImGui.Key_LeftArrow) then + if ImGui.Shortcut(ctx, ImGui.Key_UpArrow, ImGui.InputFlags_Repeat) or + ImGui.Shortcut(ctx, ImGui.Key_LeftArrow, ImGui.InputFlags_Repeat) then setNextIndex(nextIndex - 1) - elseif ImGui.IsKeyPressed(ctx, ImGui.Key_DownArrow) or - ImGui.IsKeyPressed(ctx, ImGui.Key_RightArrow) then + elseif ImGui.Shortcut(ctx, ImGui.Key_DownArrow, ImGui.InputFlags_Repeat) or + ImGui.Shortcut(ctx, ImGui.Key_RightArrow, ImGui.InputFlags_Repeat) then setNextIndex(nextIndex + 1) - elseif ImGui.IsKeyPressed(ctx, ImGui.Key_PageUp, false) or - ImGui.IsKeyPressed(ctx, ImGui.Key_KeypadSubtract, false) then + elseif ImGui.Shortcut(ctx, ImGui.Key_PageUp) or + ImGui.Shortcut(ctx, ImGui.Key_KeypadSubtract) then trySetCurrentIndex(currentIndex - 1) - elseif ImGui.IsKeyPressed(ctx, ImGui.Key_PageDown, false) or - ImGui.IsKeyPressed(ctx, ImGui.Key_KeypadAdd, false) then + elseif ImGui.Shortcut(ctx, ImGui.Key_PageDown) or + ImGui.Shortcut(ctx, ImGui.Key_KeypadAdd) then trySetCurrentIndex(currentIndex + 1) - elseif ImGui.IsKeyPressed(ctx, ImGui.Key_Insert, false) or - ImGui.IsKeyPressed(ctx, ImGui.Key_NumLock, false) then + elseif ImGui.Shortcut(ctx, ImGui.Key_Insert) or + ImGui.Shortcut(ctx, ImGui.Key_NumLock) then reset() - elseif ImGui.IsKeyPressed(ctx, ImGui.Key_Enter, false) or - ImGui.IsKeyPressed(ctx, ImGui.Key_KeypadEnter, false) then + elseif ImGui.Shortcut(ctx, ImGui.Key_Enter) or + ImGui.Shortcut(ctx, ImGui.Key_KeypadEnter) then if nextIndex == currentIndex then filterPrompt = true else @@ -703,6 +765,7 @@ end local function loop() execRemoteActions() + execTakeMarkers() ImGui.PushFont(ctx, fonts.small) ImGui.SetNextWindowSize(ctx, 500, 300, setDock and ImGui.Cond_Always or ImGui.Cond_FirstUseEver)