Skip to content

Instantly share code, notes, and snippets.

@pmache
Last active July 8, 2024 17:55
Show Gist options
  • Save pmache/47219ae5d6b30169c436e61f6eb91cad to your computer and use it in GitHub Desktop.
Save pmache/47219ae5d6b30169c436e61f6eb91cad to your computer and use it in GitHub Desktop.
Reaper Alternative Media Browser
-- 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