Last active
July 8, 2024 17:55
-
-
Save pmache/47219ae5d6b30169c436e61f6eb91cad to your computer and use it in GitHub Desktop.
Reaper Alternative Media Browser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Media Browser for REAPER using ReaImGui | |
-- Version 1.2.0 (Orange and Gray Theme with Plugin Enhancements) | |
local r = reaper | |
local ctx = r.ImGui_CreateContext('Media Browser') | |
-- Set standard fonts | |
local font_size = 16 | |
local font = r.ImGui_CreateFont("Segoe UI", font_size) | |
local font = r.ImGui_CreateFont("Segoe UI", font_size) | |
r.ImGui_Attach(ctx, font) | |
-- Wingdings icon constants | |
--local ICON_FOLDER = "\\xFC" -- Closed folder icon | |
--local ICON_FOLDER_OPEN = "\\xFD" -- Open folder icon | |
--local ICON_FILE = "\\x9E" -- Document icon | |
--local ICON_AUDIO = "\\xB3" -- Note icon for audio files | |
--local ICON_FAVORITE = "\\xAB" -- Star icon for favorites | |
--local ICON_SEARCH = "\\xF0" -- Magnifying glass icon | |
--local ICON_REFRESH = "\\xF3" -- Circular arrow icon | |
--local ICON_EXPAND = "\\xE9" -- Down arrow icon | |
--local ICON_COLLAPSE = "\\xEA" -- Up arrow icon | |
--local ICON_PLAY = "\\xB2" -- Play icon | |
--local ICON_STOP = "\\xA4" -- Stop icon | |
--local ICON_ADD = "\\xEB" -- Plus icon | |
-- Main content directory | |
local MAIN_DIR = "E:\\Samples and Music\\" | |
local PROJECTS_DIR = "E:\\Samples and Music\\REAPER Media\\" | |
local projects = {} | |
local project_search_term = "" | |
local filtered_projects = {} | |
-- Global variables | |
local tree = {} | |
local selected_node = nil | |
local selected_files = {} | |
local favorites = {} | |
local is_dragging = false | |
local drag_data = nil | |
local search_term = "" | |
local preview_track = nil | |
local plugins = {} | |
local filtered_plugins = {} | |
local plugin_search_term = "" | |
local plugin_favorites = {} | |
local project_notes = {} | |
local project_checked = {} | |
-- New global variables for custom categories | |
local categories = {} | |
local plugin_categories = {} | |
-- New global variables for mini player | |
local is_playing = false | |
local play_position = 0 | |
local current_preview_file = nil | |
-- Helper functions | |
local function table_contains(tbl, item) | |
for _, value in pairs(tbl) do | |
if value == item then return true end | |
end | |
return false | |
end | |
local function table_index_of(tbl, item) | |
for i, value in ipairs(tbl) do | |
if value == item then return i end | |
end | |
return nil | |
end | |
local function table_count(tbl) | |
local count = 0 | |
for _ in pairs(tbl) do count = count + 1 end | |
return count | |
end | |
-- Helper function to check if a file has a specific extension | |
local function has_extension(file, ext) | |
return file:match("%." .. ext .. "$") ~= nil | |
end | |
-- Function to load project notes | |
local function load_project_notes() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_project_notes.txt", "r") | |
if file then | |
for line in file:lines() do | |
local project_path, note = line:match("([^|]+)|(.+)") | |
if project_path and note then | |
project_notes[project_path] = note | |
end | |
end | |
file:close() | |
end | |
end | |
-- Function to save project notes | |
local function save_project_notes() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_project_notes.txt", "w") | |
if file then | |
for project_path, note in pairs(project_notes) do | |
file:write(project_path .. "|" .. note .. "\n") | |
end | |
file:close() | |
end | |
end | |
-- Function to load project checked states | |
local function load_project_checked() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_project_checked.txt", "r") | |
if file then | |
for line in file:lines() do | |
project_checked[line] = true | |
end | |
file:close() | |
end | |
end | |
-- Function to save project checked states | |
local function save_project_checked() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_project_checked.txt", "w") | |
if file then | |
for project_path, _ in pairs(project_checked) do | |
file:write(project_path .. "\n") | |
end | |
file:close() | |
end | |
end | |
-- Function to get all projects | |
local function get_all_projects() | |
local projects = {} | |
local stack = {{path = PROJECTS_DIR, depth = 0}} | |
local max_depth = 2 -- Increase this value to search deeper | |
while #stack > 0 do | |
local current = table.remove(stack) | |
local path, depth = current.path, current.depth | |
reaper.ShowConsoleMsg("Scanning directory: " .. path .. "\n") | |
local i = 0 | |
repeat | |
local file = r.EnumerateFiles(path, i) | |
if file then | |
local full_path = path .. file | |
if file:match("%.rpp$") then | |
local project_name = path:match("([^/\\]+)\\$") or "Unknown" | |
table.insert(projects, { | |
name = project_name .. " - " .. file:gsub("%.rpp$", ""), | |
path = full_path | |
}) | |
reaper.ShowConsoleMsg("Found project: " .. full_path .. "\n") | |
end | |
end | |
i = i + 1 | |
until not file | |
-- Now enumerate subdirectories | |
if depth < max_depth then | |
i = 0 | |
repeat | |
local subdir = r.EnumerateSubdirectories(path, i) | |
if subdir then | |
table.insert(stack, {path = path .. subdir .. "\\", depth = depth + 1}) | |
end | |
i = i + 1 | |
until not subdir | |
end | |
end | |
reaper.ShowConsoleMsg("Total projects found: " .. #projects .. "\n") | |
table.sort(projects, function(a, b) return a.name:lower() < b.name:lower() end) | |
return projects | |
end | |
-- Function to filter projects based on search term | |
local function filter_projects(projects, search_term) | |
local filtered = {} | |
for _, project in ipairs(projects) do | |
if string.find(string.lower(project.name), string.lower(search_term)) then | |
table.insert(filtered, project) | |
end | |
end | |
return filtered | |
end | |
-- Function to open a project | |
local function open_project(project_path) | |
r.Main_openProject(project_path) | |
end | |
-- Function to draw project launcher | |
local function draw_project_launcher() | |
local window_width, window_height = r.ImGui_GetWindowSize(ctx) | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, ICON_SEARCH) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
local changed, new_project_search_term = r.ImGui_InputText(ctx, "Search Projects", project_search_term) | |
if changed then | |
project_search_term = new_project_search_term | |
filtered_projects = filter_projects(projects, project_search_term) | |
end | |
if r.ImGui_BeginChild(ctx, "ProjectList", window_width, window_height - 100) then | |
for _, project in ipairs(filtered_projects) do | |
if r.ImGui_Selectable(ctx, project.name) then | |
open_project(project.path) | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if r.ImGui_MenuItem(ctx, "Open Project") then | |
open_project(project.path) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
end | |
r.ImGui_EndChild(ctx) | |
end | |
end | |
-- Function to filter projects based on search term | |
local function filter_projects(projects, search_term) | |
local filtered = {} | |
for _, project in ipairs(projects) do | |
if string.find(string.lower(project.name), string.lower(search_term)) then | |
table.insert(filtered, project) | |
end | |
end | |
return filtered | |
end | |
-- Function to open a project | |
local function open_project(project_path) | |
r.Main_openProject(project_path) | |
end | |
function draw_project_launcher() | |
local window_width, window_height = r.ImGui_GetWindowSize(ctx) | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, ICON_SEARCH) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
local changed, new_project_search_term = r.ImGui_InputText(ctx, "Search Projects", project_search_term) | |
if changed then | |
project_search_term = new_project_search_term | |
filtered_projects = filter_projects(projects, project_search_term) | |
end | |
if r.ImGui_BeginChild(ctx, "ProjectList", window_width, window_height - 100) then | |
for _, project in ipairs(filtered_projects) do | |
local checked = project_checked[project.path] or false | |
local checkbox_changed, new_checked = r.ImGui_Checkbox(ctx, "##" .. project.path, checked) | |
if checkbox_changed then | |
project_checked[project.path] = new_checked | |
save_project_checked() | |
end | |
r.ImGui_SameLine(ctx) | |
if r.ImGui_Selectable(ctx, project.name, false, r.ImGui_SelectableFlags_SpanAllColumns()) then | |
open_project(project.path) | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if r.ImGui_MenuItem(ctx, "Open Project") then | |
open_project(project.path) | |
end | |
if r.ImGui_MenuItem(ctx, "Add/Edit Note") then | |
r.ImGui_OpenPopup(ctx, "ProjectNote_" .. project.path) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
if r.ImGui_BeginPopupModal(ctx, "ProjectNote_" .. project.path, nil, r.ImGui_WindowFlags_AlwaysAutoResize()) then | |
local current_note = project_notes[project.path] or "" | |
local note_changed, new_note = r.ImGui_InputTextMultiline(ctx, "Note", current_note, 300, 100) | |
if note_changed then | |
project_notes[project.path] = new_note | |
save_project_notes() | |
end | |
if r.ImGui_Button(ctx, "Close") then | |
r.ImGui_CloseCurrentPopup(ctx) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
if project_notes[project.path] then | |
r.ImGui_SameLine(ctx) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, "Note") | |
if r.ImGui_IsItemHovered(ctx) then | |
r.ImGui_SetTooltip(ctx, project_notes[project.path]) | |
end | |
end | |
end | |
r.ImGui_EndChild(ctx) | |
end | |
end | |
-- Function to filter projects based on search term | |
local function filter_projects(projects, search_term) | |
local filtered = {} | |
for _, project in ipairs(projects) do | |
if string.find(string.lower(project.name), string.lower(search_term)) then | |
table.insert(filtered, project) | |
end | |
end | |
return filtered | |
end | |
-- Function to open a project | |
local function open_project(project_path) | |
r.Main_openProject(project_path) | |
end | |
function draw_project_launcher() | |
local window_width, window_height = r.ImGui_GetWindowSize(ctx) | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, ICON_SEARCH) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
local changed, new_project_search_term = r.ImGui_InputText(ctx, "Search Projects", project_search_term) | |
if changed then | |
project_search_term = new_project_search_term | |
filtered_projects = filter_projects(projects, project_search_term) | |
end | |
if r.ImGui_BeginChild(ctx, "ProjectList", window_width, window_height - 100) then | |
for _, project in ipairs(filtered_projects) do | |
local checked = project_checked[project.path] or false | |
local checkbox_changed, new_checked = r.ImGui_Checkbox(ctx, "##" .. project.path, checked) | |
if checkbox_changed then | |
project_checked[project.path] = new_checked | |
save_project_checked() | |
end | |
r.ImGui_SameLine(ctx) | |
if r.ImGui_Selectable(ctx, project.name, false, r.ImGui_SelectableFlags_SpanAllColumns()) then | |
open_project(project.path) | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if r.ImGui_MenuItem(ctx, "Open Project") then | |
open_project(project.path) | |
end | |
if r.ImGui_MenuItem(ctx, "Add/Edit Note") then | |
r.ImGui_OpenPopup(ctx, "ProjectNote_" .. project.path) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
if r.ImGui_BeginPopupModal(ctx, "ProjectNote_" .. project.path, nil, r.ImGui_WindowFlags_AlwaysAutoResize()) then | |
local current_note = project_notes[project.path] or "" | |
local note_changed, new_note = r.ImGui_InputTextMultiline(ctx, "Note", current_note, 300, 100) | |
if note_changed then | |
project_notes[project.path] = new_note | |
save_project_notes() | |
end | |
if r.ImGui_Button(ctx, "Close") then | |
r.ImGui_CloseCurrentPopup(ctx) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
if project_notes[project.path] then | |
r.ImGui_SameLine(ctx) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, "Note") | |
if r.ImGui_IsItemHovered(ctx) then | |
r.ImGui_SetTooltip(ctx, project_notes[project.path]) | |
end | |
end | |
end | |
r.ImGui_EndChild(ctx) | |
end | |
end | |
-- Function to get plugin information | |
local function get_plugin_info(file_path) | |
local filename = file_path:match("([^/\\]+)$") -- Get the filename from the path | |
local name = filename:gsub("%.%w+$", "") -- Remove file extension | |
local manufacturer = "Unknown" | |
-- Try to extract manufacturer from the file path | |
local path_parts = {} | |
for part in file_path:gmatch("[^/\\]+") do | |
table.insert(path_parts, part) | |
end | |
for i = #path_parts - 1, 1, -1 do | |
if path_parts[i] ~= "Plugins" and path_parts[i] ~= "VST" and path_parts[i] ~= "VST3" then | |
manufacturer = path_parts[i] | |
break | |
end | |
end | |
return { | |
name = name, | |
manufacturer = manufacturer, | |
filename = filename, | |
path = file_path | |
} | |
end | |
-- Function to recursively scan a directory for plugins | |
local function scan_directory(path, plugins) | |
local i = 0 | |
repeat | |
local file = r.EnumerateFiles(path, i) | |
if file then | |
local full_path = path .. "/" .. file | |
if has_extension(file, "dll") or has_extension(file, "vst") or has_extension(file, "vst3") or has_extension(file, "component") then | |
table.insert(plugins, get_plugin_info(full_path)) | |
elseif r.EnumerateSubdirectories(path, i) ~= nil then | |
scan_directory(full_path, plugins) | |
end | |
end | |
i = i + 1 | |
until not file | |
end | |
-- Function to get all plugins | |
local function get_all_plugins() | |
local plugins = {} | |
local common_paths = { | |
r.GetResourcePath() .. "/Plugins", | |
"C:/Program Files/Common Files/VST3", | |
"C:/Program Files/Steinberg/VSTPlugins", | |
"C:/Program Files/VSTPlugins", | |
"C:/Program Files/Common Files/VST2", | |
"C:/Program Files/Common Files/Steinberg/VST2", | |
"%LOCALAPPDATA%/Programs/Common/VST", | |
"%LOCALAPPDATA%/Programs/Common/VST3", | |
"C:/Program Files (x86)/VSTPlugIns", | |
"C:/Program Files/Common Files/VST2", | |
"C:/Program Files/Common Files/VST3", | |
"C:/Users/athema/AppData/Local/Programs/Common/VST3", | |
"E:/Samples and Music/VST Files", | |
"E:/Samples and Music/VST Files/Cakewalk VST", | |
"E:/Samples and Music/VST Files/FL Studio VSTi", | |
"E:/Samples and Music/VST Files/NI Library", | |
"E:/Samples and Music/VST Files/VST 2", | |
"E:/Samples and Music/VST Files/VST 3", | |
"E:/Samples and Music/VST Files/VST Data", | |
} | |
for _, path in ipairs(common_paths) do | |
scan_directory(path, plugins) | |
end | |
-- On macOS, also scan AU plugins | |
if r.GetOS() == "OSX32" or r.GetOS() == "OSX64" then | |
scan_directory("/Library/Audio/Plug-Ins/Components", plugins) | |
end | |
-- Sort plugins alphabetically by name | |
table.sort(plugins, function(a, b) return a.name:lower() < b.name:lower() end) | |
return plugins | |
end | |
-- Function to filter plugins based on search term | |
local function filter_plugins(plugins, search_term) | |
local filtered = {} | |
for _, plugin in ipairs(plugins) do | |
if string.find(string.lower(plugin.name), string.lower(search_term)) or | |
string.find(string.lower(plugin.manufacturer), string.lower(search_term)) or | |
string.find(string.lower(plugin.filename), string.lower(search_term)) then | |
table.insert(filtered, plugin) | |
end | |
end | |
return filtered | |
end | |
-- Function to insert plugin into selected track | |
local function insert_plugin(plugin_path) | |
local track = r.GetSelectedTrack(0, 0) | |
if track then | |
local index = r.TrackFX_AddByName(track, plugin_path, false, -1) | |
if index < 0 then | |
r.ShowMessageBox("Failed to add plugin.", "Error", 0) | |
end | |
else | |
r.ShowMessageBox("Please select a track first.", "No Track Selected", 0) | |
end | |
end | |
-- Function to load categories from file | |
local function load_categories() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_categories.txt", "r") | |
if file then | |
for line in file:lines() do | |
table.insert(categories, line) | |
end | |
file:close() | |
end | |
end | |
-- Function to save categories to file | |
local function save_categories() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_categories.txt", "w") | |
if file then | |
for _, category in ipairs(categories) do | |
file:write(category .. "\n") | |
end | |
file:close() | |
end | |
end | |
-- Function to load plugin categories from file | |
local function load_plugin_categories() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_plugin_categories.txt", "r") | |
if file then | |
for line in file:lines() do | |
local plugin_path, categories_str = line:match("([^|]+)|(.+)") | |
if plugin_path and categories_str then | |
plugin_categories[plugin_path] = {} | |
for category in categories_str:gmatch("[^,]+") do | |
table.insert(plugin_categories[plugin_path], category) | |
end | |
end | |
end | |
file:close() | |
end | |
end | |
-- Function to save plugin categories to file | |
local function save_plugin_categories() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_plugin_categories.txt", "w") | |
if file then | |
for plugin_path, plugin_cats in pairs(plugin_categories) do | |
file:write(plugin_path .. "|" .. table.concat(plugin_cats, ",") .. "\n") | |
end | |
file:close() | |
end | |
end | |
-- Function to add a new category | |
local function add_category(category_name) | |
if not table_contains(categories, category_name) then | |
table.insert(categories, category_name) | |
save_categories() | |
end | |
end | |
-- Function to remove a category | |
local function remove_category(category_name) | |
local index = table_index_of(categories, category_name) | |
if index then | |
table.remove(categories, index) | |
save_categories() | |
-- Remove the category from all plugins | |
for plugin_path, plugin_cats in pairs(plugin_categories) do | |
local cat_index = table_index_of(plugin_cats, category_name) | |
if cat_index then | |
table.remove(plugin_cats, cat_index) | |
end | |
end | |
save_plugin_categories() | |
end | |
end | |
-- Function to assign a plugin to a category | |
local function assign_plugin_to_category(plugin_path, category_name) | |
if not plugin_categories[plugin_path] then | |
plugin_categories[plugin_path] = {} | |
end | |
if not table_contains(plugin_categories[plugin_path], category_name) then | |
table.insert(plugin_categories[plugin_path], category_name) | |
save_plugin_categories() | |
end | |
end | |
-- Function to remove a plugin from a category | |
local function remove_plugin_from_category(plugin_path, category_name) | |
if plugin_categories[plugin_path] then | |
local index = table_index_of(plugin_categories[plugin_path], category_name) | |
if index then | |
table.remove(plugin_categories[plugin_path], index) | |
save_plugin_categories() | |
end | |
end | |
end | |
-- Function to get file name from full path | |
local function get_filename_from_path(path) | |
return path:match("([^/\\]+)$") or path | |
end | |
-- File system integration | |
local function get_subdirectories(path) | |
local dirs = {} | |
local i = 0 | |
repeat | |
local dir = r.EnumerateSubdirectories(path, i) | |
if dir then | |
local full_path = path .. dir .. "\\" | |
table.insert(dirs, {name = dir, path = full_path}) | |
end | |
i = i + 1 | |
until not dir | |
return dirs | |
end | |
local function get_files_in_directory(path) | |
local files = {} | |
local i = 0 | |
repeat | |
local file = r.EnumerateFiles(path, i) | |
if file then | |
table.insert(files, file) | |
end | |
i = i + 1 | |
until not file | |
return files | |
end | |
local function build_tree(path, depth, max_depth) | |
if depth > max_depth then return {} end | |
local tree = {} | |
local dirs = get_subdirectories(path) | |
for _, dir in ipairs(dirs) do | |
local children = build_tree(dir.path, depth + 1, max_depth) | |
table.insert(tree, { | |
name = dir.name, | |
path = dir.path, | |
children = children, | |
expanded = false, | |
is_directory = true | |
}) | |
end | |
local files = get_files_in_directory(path) | |
for _, file in ipairs(files) do | |
if file:match("%.wav$") or file:match("%.mp3$") or file:match("%.aiff?$") then | |
table.insert(tree, { | |
name = file, | |
path = path .. file, | |
is_directory = false | |
}) | |
end | |
end | |
return tree | |
end | |
-- Load favorites from file | |
local function load_favorites() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_favorites.txt", "r") | |
if file then | |
for line in file:lines() do | |
table.insert(favorites, line) | |
end | |
file:close() | |
end | |
end | |
-- Save favorites to file | |
local function save_favorites() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/media_browser_favorites.txt", "w") | |
if file then | |
for _, fav in ipairs(favorites) do | |
file:write(fav .. "\n") | |
end | |
file:close() | |
end | |
end | |
-- Load plugin favorites from file | |
local function load_plugin_favorites() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/plugin_favorites.txt", "r") | |
if file then | |
for line in file:lines() do | |
table.insert(plugin_favorites, line) | |
end | |
file:close() | |
end | |
end | |
-- Save plugin favorites to file | |
local function save_plugin_favorites() | |
local file = io.open(r.GetResourcePath() .. "/Scripts/plugin_favorites.txt", "w") | |
if file then | |
for _, fav in ipairs(plugin_favorites) do | |
file:write(fav .. "\n") | |
end | |
file:close() | |
end | |
end | |
-- Preview track management | |
local function create_preview_track() | |
if not preview_track then | |
r.InsertTrackAtIndex(0, false) | |
preview_track = r.GetTrack(0, 0) | |
if preview_track then | |
r.GetSetMediaTrackInfo_String(preview_track, "P_NAME", "Preview", true) | |
r.SetMediaTrackInfo_Value(preview_track, "I_SOLO", 1) | |
else | |
-- debug_print("Failed to create preview track") | |
end | |
end | |
end | |
local function remove_preview_track() | |
if preview_track then | |
local track_index = r.GetMediaTrackInfo_Value(preview_track, "IP_TRACKNUMBER") - 1 | |
r.DeleteTrack(preview_track) | |
preview_track = nil | |
end | |
end | |
-- Function to format time (seconds to MM:SS) | |
local function format_time(seconds) | |
local minutes = math.floor(seconds / 60) | |
local remaining_seconds = math.floor(seconds % 60) | |
return string.format("%02d:%02d", minutes, remaining_seconds) | |
end | |
-- Updated preview_audio function | |
local function preview_audio(file_path) | |
create_preview_track() | |
if preview_track then | |
r.SetOnlyTrackSelected(preview_track) | |
r.InsertMedia(file_path, 0) -- Insert at edit cursor | |
r.SetEditCurPos(0, false, false) -- Move edit cursor to start | |
r.Main_OnCommand(40044, 0) -- Transport: Play/stop | |
is_playing = true | |
play_position = 0 | |
current_preview_file = file_path | |
else | |
-- debug_print("Failed to preview audio: No preview track") | |
end | |
end | |
-- Updated stop_preview function | |
local function stop_preview() | |
r.Main_OnCommand(40044, 0) -- Transport: Play/stop | |
is_playing = false | |
play_position = 0 | |
current_preview_file = nil | |
remove_preview_track() | |
end | |
local function insert_media_new_track(file_path) | |
local track_index = r.GetNumTracks() | |
r.InsertTrackAtIndex(track_index, false) | |
local new_track = r.GetTrack(0, track_index) | |
r.SetOnlyTrackSelected(new_track) | |
local cursor_position = r.GetCursorPosition() | |
r.InsertMedia(file_path, 3, cursor_position) -- 3 means insert at edit cursor | |
return new_track | |
end | |
-- Function to check if an item should be displayed based on the search term | |
local function should_display(item) | |
if search_term == "" then return true end | |
return string.find(string.lower(item.name), string.lower(search_term)) ~= nil | |
end | |
-- Function to get audio file information | |
local function get_audio_info(file_path) | |
local source = r.PCM_Source_CreateFromFile(file_path) | |
if source then | |
local channels = r.GetMediaSourceNumChannels(source) | |
local sample_rate = r.GetMediaSourceSampleRate(source) | |
local length = r.GetMediaSourceLength(source) | |
r.PCM_Source_Destroy(source) | |
return { | |
channels = channels, | |
sample_rate = sample_rate, | |
length = length | |
} | |
end | |
return nil | |
end | |
-- Function to draw mini player | |
local function draw_mini_player(window_width, preview_height) | |
if current_preview_file then | |
local info = get_audio_info(current_preview_file) | |
if info then | |
local file_name = get_filename_from_path(current_preview_file) | |
r.ImGui_Text(ctx, "Now playing: " .. file_name) | |
r.ImGui_Text(ctx, string.format("Channels: %d, Sample Rate: %d Hz, Length: %.2f s", | |
info.channels, info.sample_rate, info.length)) | |
-- Play/Pause button | |
r.ImGui_PushFont(ctx, font) | |
if r.ImGui_Button(ctx, is_playing and ICON_STOP or ICON_PLAY) then | |
if is_playing then | |
stop_preview() | |
else | |
preview_audio(current_preview_file) | |
end | |
end | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
-- Progress bar | |
local progress = play_position / info.length | |
r.ImGui_ProgressBar(ctx, progress, -1, 20, format_time(play_position) .. " / " .. format_time(info.length)) | |
-- Update play position | |
if is_playing then | |
play_position = r.GetPlayPosition() | |
if play_position >= info.length then | |
stop_preview() | |
end | |
end | |
end | |
else | |
r.ImGui_Text(ctx, "No file selected for preview") | |
end | |
end | |
local function handle_drag_and_drop() | |
if r.ImGui_BeginDragDropTarget(ctx) then | |
local payload = r.ImGui_AcceptDragDropPayload(ctx, "REAPER_MEDIABROWSER_ITEM") | |
if payload then | |
local file_path = payload | |
insert_media_new_track(file_path) | |
end | |
r.ImGui_EndDragDropTarget(ctx) | |
end | |
end | |
-- Updated draw_tree_and_files function with orange and gray theme colors | |
function draw_tree_and_files(node, depth) | |
depth = depth or 0 | |
for i, item in ipairs(node) do | |
if should_display(item) then | |
local display_name = item.name or "Unnamed" | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_Header(), 0x3d3d3dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_HeaderHovered(), 0x4d4d4dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_HeaderActive(), 0xff8c00ff) | |
local is_leaf = not item.is_directory | |
local flags = r.ImGui_TreeNodeFlags_OpenOnArrow() | r.ImGui_TreeNodeFlags_OpenOnDoubleClick() | |
if is_leaf then flags = flags | r.ImGui_TreeNodeFlags_Leaf() end | |
if selected_node == item then flags = flags | r.ImGui_TreeNodeFlags_Selected() end | |
local node_id = item.path .. "_" .. i | |
if item.is_directory and item.expanded == nil then item.expanded = false end | |
if item.is_directory then r.ImGui_SetNextItemOpen(ctx, item.expanded) end | |
-- Display appropriate icon | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, item.is_directory and (item.expanded and ICON_FOLDER_OPEN or ICON_FOLDER) or ICON_AUDIO) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
local expanded = r.ImGui_TreeNodeEx(ctx, node_id, display_name, flags) | |
if item.is_directory then item.expanded = expanded end | |
if r.ImGui_IsItemClicked(ctx) then | |
if selected_node ~= item then | |
if selected_node and not selected_node.is_directory then | |
stop_preview() | |
end | |
selected_node = item | |
if not item.is_directory then | |
preview_audio(item.path) | |
end | |
end | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if not item.is_directory then | |
if r.ImGui_MenuItem(ctx, "Add to Favorites") then | |
if not table_contains(favorites, item.path) then | |
table.insert(favorites, item.path) | |
save_favorites() | |
end | |
end | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
if r.ImGui_BeginDragDropSource(ctx, r.ImGui_DragDropFlags_SourceAllowNullID()) then | |
r.ImGui_SetDragDropPayload(ctx, "REAPER_MEDIABROWSER_ITEM", item.path) | |
r.ImGui_Text(ctx, "Dragging: " .. display_name) | |
r.ImGui_EndDragDropSource(ctx) | |
if not is_dragging then | |
r.Main_OnCommand(40914, 0) -- View: Show region/marker lane | |
r.Main_OnCommand(41195, 0) -- Markers: Insert marker at play position | |
local retval, num_markers, num_regions = r.CountProjectMarkers(0) | |
if retval > 0 then | |
local _, _, _, _, _, marker_guid = r.EnumProjectMarkers(num_markers - 1) | |
r.SetExtState("MediaBrowserDrag", "GUID", marker_guid, false) | |
r.SetExtState("MediaBrowserDrag", "FilePath", item.path, false) | |
r.Main_OnCommand(40422, 0) -- Item: Select all items in track | |
r.Main_OnCommand(40698, 0) -- Edit: Copy items | |
r.Main_OnCommand(40006, 0) -- Item: Remove items | |
is_dragging = true | |
end | |
end | |
end | |
if is_dragging and not r.ImGui_IsMouseDown(ctx, 0) then | |
r.DeleteExtState("MediaBrowserDrag", "GUID", false) | |
r.DeleteExtState("MediaBrowserDrag", "FilePath", false) | |
r.Main_OnCommand(40421, 0) -- Item: Select all items | |
r.Main_OnCommand(40006, 0) -- Item: Remove items | |
r.Main_OnCommand(40915, 0) -- View: Hide region/marker lane | |
is_dragging = false | |
end | |
if expanded then | |
if item.is_directory and item.children then | |
draw_tree_and_files(item.children, depth + 1) | |
end | |
end | |
if expanded or is_leaf then | |
r.ImGui_TreePop(ctx) | |
end | |
r.ImGui_PopStyleColor(ctx, 3) | |
end | |
end | |
end | |
local function handle_drop() | |
local drag_guid = r.GetExtState("MediaBrowserDrag", "GUID") | |
if drag_guid ~= "" then | |
local file_path = r.GetExtState("MediaBrowserDrag", "FilePath") | |
if file_path ~= "" then | |
local retval, num_markers, num_regions = r.CountProjectMarkers(0) | |
if retval > 0 then | |
for i = 0, num_markers - 1 do | |
local _, _, pos, _, _, guid = r.EnumProjectMarkers(i) | |
if guid == drag_guid then | |
r.DeleteProjectMarker(0, i, false) | |
r.SetEditCurPos(pos, false, false) | |
insert_media_new_track(file_path) | |
r.DeleteExtState("MediaBrowserDrag", "GUID", false) | |
r.DeleteExtState("MediaBrowserDrag", "FilePath", false) | |
break | |
end | |
end | |
end | |
end | |
end | |
end | |
-- Function to draw favorites with orange and gray theme colors | |
local function draw_favorites() | |
for i, fav in ipairs(favorites) do | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, ICON_FAVORITE) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
if r.ImGui_Selectable(ctx, get_filename_from_path(fav)) then | |
preview_audio(fav) | |
end | |
if r.ImGui_IsItemHovered(ctx) and r.ImGui_IsMouseDoubleClicked(ctx, 0) then | |
insert_media_new_track(fav) | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if r.ImGui_MenuItem(ctx, "Remove from Favorites") then | |
table.remove(favorites, i) | |
save_favorites() | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
end | |
end | |
-- Updated draw_plugin_browser function to include categories | |
function draw_plugin_browser() | |
local window_width, window_height = r.ImGui_GetWindowSize(ctx) | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, ICON_SEARCH) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
local changed, new_plugin_search_term = r.ImGui_InputText(ctx, "Search Plugins", plugin_search_term) | |
if changed then | |
plugin_search_term = new_plugin_search_term | |
filtered_plugins = filter_plugins(plugins, plugin_search_term) | |
end | |
if r.ImGui_BeginChild(ctx, "PluginList", window_width, window_height - 100) then | |
-- Draw plugin favorites | |
if r.ImGui_CollapsingHeader(ctx, "Favorites") then | |
for i, fav in ipairs(plugin_favorites) do | |
local plugin = get_plugin_info(fav) | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, ICON_FAVORITE) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
if r.ImGui_Selectable(ctx, plugin.name .. " - " .. plugin.manufacturer .. " (" .. plugin.filename .. ")") then | |
insert_plugin(plugin.path) | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if r.ImGui_MenuItem(ctx, "Remove from Favorites") then | |
table.remove(plugin_favorites, i) | |
save_plugin_favorites() | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
end | |
end | |
-- Draw plugins by category | |
for _, category in ipairs(categories) do | |
if r.ImGui_CollapsingHeader(ctx, category) then | |
for _, plugin in ipairs(filtered_plugins) do | |
if plugin_categories[plugin.path] and table_contains(plugin_categories[plugin.path], category) then | |
if r.ImGui_Selectable(ctx, plugin.name .. " - " .. plugin.manufacturer .. " (" .. plugin.filename .. ")") then | |
insert_plugin(plugin.path) | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if r.ImGui_MenuItem(ctx, "Insert Plugin") then | |
insert_plugin(plugin.path) | |
end | |
if not table_contains(plugin_favorites, plugin.path) then | |
if r.ImGui_MenuItem(ctx, "Add to Favorites") then | |
table.insert(plugin_favorites, plugin.path) | |
save_plugin_favorites() | |
end | |
end | |
if r.ImGui_BeginMenu(ctx, "Categories") then | |
for _, cat in ipairs(categories) do | |
local is_assigned = table_contains(plugin_categories[plugin.path], cat) | |
if r.ImGui_MenuItem(ctx, cat, nil, is_assigned) then | |
if is_assigned then | |
remove_plugin_from_category(plugin.path, cat) | |
else | |
assign_plugin_to_category(plugin.path, cat) | |
end | |
end | |
end | |
r.ImGui_EndMenu(ctx) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
end | |
end | |
end | |
end | |
-- Draw uncategorized plugins | |
if r.ImGui_CollapsingHeader(ctx, "Uncategorized") then | |
for _, plugin in ipairs(filtered_plugins) do | |
if not plugin_categories[plugin.path] or #plugin_categories[plugin.path] == 0 then | |
if r.ImGui_Selectable(ctx, plugin.name .. " - " .. plugin.manufacturer .. " (" .. plugin.filename .. ")") then | |
insert_plugin(plugin.path) | |
end | |
if r.ImGui_BeginPopupContextItem(ctx) then | |
if r.ImGui_MenuItem(ctx, "Insert Plugin") then | |
insert_plugin(plugin.path) | |
end | |
if not table_contains(plugin_favorites, plugin.path) then | |
if r.ImGui_MenuItem(ctx, "Add to Favorites") then | |
table.insert(plugin_favorites, plugin.path) | |
save_plugin_favorites() | |
end | |
end | |
if r.ImGui_BeginMenu(ctx, "Assign to Category") then | |
for _, category in ipairs(categories) do | |
if r.ImGui_MenuItem(ctx, category) then | |
assign_plugin_to_category(plugin.path, category) | |
end | |
end | |
r.ImGui_EndMenu(ctx) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
end | |
end | |
end | |
r.ImGui_EndChild(ctx) | |
end | |
-- Category management | |
if r.ImGui_Button(ctx, "Manage Categories") then | |
r.ImGui_OpenPopup(ctx, "CategoryManagement") | |
end | |
if r.ImGui_BeginPopupModal(ctx, "CategoryManagement", nil, r.ImGui_WindowFlags_AlwaysAutoResize()) then | |
r.ImGui_Text(ctx, "Manage Categories") | |
r.ImGui_Separator(ctx) | |
-- List existing categories | |
for i, category in ipairs(categories) do | |
if r.ImGui_Selectable(ctx, category) then | |
-- Allow editing the category name | |
end | |
r.ImGui_SameLine(ctx) | |
if r.ImGui_Button(ctx, "X##" .. i) then | |
remove_category(category) | |
end | |
end | |
-- Add new category | |
local new_category = r.ImGui_InputText(ctx, "New Category", "") | |
r.ImGui_SameLine(ctx) | |
if r.ImGui_Button(ctx, "Add") and new_category ~= "" then | |
add_category(new_category) | |
end | |
if r.ImGui_Button(ctx, "Close") then | |
r.ImGui_CloseCurrentPopup(ctx) | |
end | |
r.ImGui_EndPopup(ctx) | |
end | |
end | |
-- Main function with orange and gray theme colors | |
function main() | |
r.ImGui_SetNextWindowSize(ctx, 600, 400, r.ImGui_Cond_FirstUseEver()) | |
-- Set orange and gray theme colors | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_WindowBg(), 0x2d2d2dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_Text(), 0xe0e0e0ff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_Border(), 0x444444ff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_BorderShadow(), 0x000000ff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_FrameBg(), 0x3d3d3dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_FrameBgHovered(), 0x4d4d4dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_FrameBgActive(), 0xff8c00ff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_TitleBg(), 0x2d2d2dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_TitleBgActive(), 0x3d3d3dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_Tab(), 0x2d2d2dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_TabHovered(), 0x3d3d3dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_TabActive(), 0xff8c00ff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_Button(), 0x3d3d3dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_ButtonHovered(), 0x4d4d4dff) | |
r.ImGui_PushStyleColor(ctx, r.ImGui_Col_ButtonActive(), 0xff8c00ff) | |
local visible, open = r.ImGui_Begin(ctx, 'Media Browser', true) | |
if visible then | |
local window_width, window_height = r.ImGui_GetWindowSize(ctx) | |
if r.ImGui_BeginTabBar(ctx, "MediaBrowserTabs") then | |
if r.ImGui_BeginTabItem(ctx, "File Browser") then | |
if r.ImGui_CollapsingHeader(ctx, "Favorites") then | |
draw_favorites() | |
end | |
r.ImGui_PushFont(ctx, font) | |
r.ImGui_TextColored(ctx, 0xff8c00ff, ICON_SEARCH) | |
r.ImGui_PopFont(ctx) | |
r.ImGui_SameLine(ctx) | |
local changed, new_search_term = r.ImGui_InputText(ctx, "Search", search_term) | |
if changed then | |
search_term = new_search_term | |
end | |
r.ImGui_SameLine(ctx) | |
r.ImGui_PushFont(ctx, font) | |
if r.ImGui_Button(ctx, ICON_REFRESH) then | |
tree = build_tree(MAIN_DIR, 0) | |
end | |
r.ImGui_PopFont(ctx) | |
local browser_height = window_height - 150 | |
if r.ImGui_BeginChild(ctx, "FileBrowser", window_width - 20, browser_height) then | |
draw_tree_and_files(tree) | |
r.ImGui_EndChild(ctx) | |
end | |
if r.ImGui_IsWindowHovered(ctx) and r.ImGui_IsMouseClicked(ctx, 0) then | |
local is_tree_clicked = false | |
r.ImGui_TreeNodeEx(ctx, "##dummy", "", r.ImGui_TreeNodeFlags_Leaf()) | |
if r.ImGui_IsItemHovered(ctx) then | |
is_tree_clicked = true | |
end | |
r.ImGui_TreePop(ctx) | |
if not is_tree_clicked then | |
if selected_node and not selected_node.is_directory then | |
stop_preview() | |
end | |
selected_node = nil | |
end | |
end | |
draw_mini_player(window_width, 80) | |
r.ImGui_EndTabItem(ctx) | |
end | |
if r.ImGui_BeginTabItem(ctx, "Plugin Browser") then | |
draw_plugin_browser() | |
r.ImGui_EndTabItem(ctx) | |
end | |
if r.ImGui_BeginTabItem(ctx, "Project Launcher") then | |
draw_project_launcher() | |
r.ImGui_EndTabItem(ctx) | |
end | |
r.ImGui_EndTabBar(ctx) | |
end | |
handle_drag_and_drop() | |
handle_drop() | |
r.ImGui_End(ctx) | |
end | |
r.ImGui_PopStyleColor(ctx, 15) | |
if open then | |
r.defer(main) | |
else | |
remove_preview_track() | |
end | |
end | |
-- Initialize | |
tree = build_tree(MAIN_DIR, 0, 5) | |
load_favorites() | |
load_plugin_favorites() | |
load_categories() | |
load_plugin_categories() | |
plugins = get_all_plugins() | |
filtered_plugins = plugins | |
projects = get_all_projects() | |
filtered_projects = projects | |
project_search_term = "" | |
load_project_notes() | |
load_project_checked() | |
r.defer(main) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment