-- Version 1.0
--
-- This script toggles visibility of all tracks that have a VSTi FX.
-- It requires that the FX be prefixed with "VSTi:" (which Reaper
-- does do by default).  Care is taken to preserve existing TCP/MCP
-- visibility when the filter is toggled off.
--
-- SWS is required (http://www.sws-extension.org/)
--
-- Released to the public domain.

SCRIPT_NAME = "toggle_disabled_instrument_tracks"
-- If true, ensures all parents (and ancestors) of unfiltered child tracks
-- are not filtered.  Set this to false if you want to hide parent tracks
-- with no MIDI items.
PRESERVE_PARENT_FOLDERS = true

function isInstrumentTrack(track)
    for fxIdx = 0, reaper.TrackFX_GetCount(track) - 1 do  
      r, name = reaper.TrackFX_GetFXName(track, fxIdx, "")
      if string.sub(name, 0, 5) == "VSTi:" then
          return true
      end
    end
    return false
end

function setTrackVisibility(track, tcp, mcp)
    reaper.SetMediaTrackInfo_Value(track, "B_SHOWINTCP", tcp)
    reaper.SetMediaTrackInfo_Value(track, "B_SHOWINMIXER", mcp)
end

function setTrackFiltering(track, filtered)
    local guid = reaper.GetTrackGUID(track)
    local tcp, mcp
    
    if filterOn then
        -- Filter is being enabled.  Store current track setting.       
        tcp = reaper.GetMediaTrackInfo_Value(track, "B_SHOWINTCP")
        mcp = reaper.GetMediaTrackInfo_Value(track, "B_SHOWINMIXER")
        reaper.SetProjExtState(0, SCRIPT_NAME, guid, math.floor(tcp) .. math.floor(mcp))
        if filtered then
            setTrackVisibility(track, 0, 0)
        end
    else
        -- Filter is being turned off.  Restore tcp/mcp setting.
        exists, state = reaper.GetProjExtState(0, SCRIPT_NAME, guid)
        --reaper.ShowConsoleMsg(guid .. exists .. " " .. state .. "\n")        
        reaper.SetProjExtState(0, SCRIPT_NAME, guid, "")
        if exists == 1 then
            tcp = tonumber(string.sub(state, 1, 1))
            mcp = tonumber(string.sub(state, 2, 2))
            setTrackVisibility(track, tcp, mcp)
        else
            setTrackVisibility(track, 1, 1)
        end
    end
    
end

function markParents(parents, track, hasVisibleChildren)
    local parent = reaper.GetParentTrack(track)
    while parent ~= nil do
        -- Small optimization: if parent is already known to have
        -- visible children then there's no need to traverse up the
        -- parent hierarchy.
        if parents[parent] == 1 then
            break
        end
        parents[parent] = (parents[parent] or 0) | hasVisibleChildren
        parent = reaper.GetParentTrack(parent)
    end
end


function main()
    local parents = {}
    _, _, section, cmd, _, _, _ = reaper.get_action_context()
    exists, filterOn = reaper.GetProjExtState(0, SCRIPT_NAME, "filter_on")
    
    -- Toggle filter
    if filterOn == "1" then
        filterOn = false
    else
        filterOn = true
    end

    reaper.Undo_BeginBlock2(0)
    for trackIdx = 0, reaper.CountTracks(0) - 1 do
        track = reaper.GetTrack(0, trackIdx)
        if isInstrumentTrack(track) then
            if not filterOn then
                -- disabling filter
                setTrackFiltering(track, false)
                markParents(parents, track, 1)
            else
                if reaper.GetMediaTrackInfo_Value(track, "I_FXEN") > 0 then
                    setTrackFiltering(track, false)
                    markParents(parents, track, 1)
                else
                    markParents(parents, track, 0)
                    setTrackFiltering(track, true)
                end
            end
        end
    end

    for track, hasVisibleChildren in pairs(parents) do
        if hasVisibleChildren == 1 and PRESERVE_PARENT_FOLDERS then
            setTrackFiltering(track, false)
        else
            setTrackFiltering(track, true)
        end
    end

    filterOn = filterOn and "1" or "0"
    reaper.SetProjExtState(0, SCRIPT_NAME, "filter_on", filterOn)
    reaper.SetToggleCommandState(section, cmd, filterOn)
    reaper.RefreshToolbar2(section, cmd)
    
    reaper.TrackList_AdjustWindows(0)
    reaper.UpdateArrange()
    
    -- Terrible kludge, but seems necessary to force the arrange
    -- scrollbar to update.
    a, b = reaper.BR_GetArrangeView(0)
    reaper.BR_SetArrangeView(0, a, b-0.01)
    reaper.BR_SetArrangeView(0, a, b)
    
    reaper.Undo_EndBlock2(0, "Toggle visibility of disabled instrument tracks", -1)
end

reaper.defer(main)
