Last active
March 6, 2024 11:01
-
-
Save X-Raym/138141d6cdfad742f7a8c7b55e1ee849 to your computer and use it in GitHub Desktop.
REAPER Track Routing to Graphviz by Fabian mod by X-Raym https://forums.cockos.com/showthread.php?t=239250
This file contains hidden or 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
-- TrackRouting2dot, v0.1, M Fabian (Mod by X-Raym | |
-- Goes through the current project and generates a file (png or gif or jpg or...) | |
-- that graphically displays the track routing within the project | |
-- Uses the dot layout engine, available in the Graphviz package from http://graphviz.org/ | |
-- TODO: | |
-- * DONE! - Add send and receive channels at the respective ends of the arrows | |
-- * DONE! - Add "implicit" sends from children to parents, hatched lines | |
-- * Make edges orthogonal (splines=ortho should fix that but doesn't, probably not possible) | |
-- * DONE! - Make the track colors more like what the colors actually look in Reaper | |
-- * DONE! - Distinhguish MIDI sends | |
-- * Allow to graph only selected tracks | |
-- * Automagically load the generated gif/png/jpg/whatever into Reaper (possible?) | |
local DOT_PATH="C:\\Program Files\\Graphviz\\bin\\" -- where to find dot, make sure it ends with "\\" | |
local DOT_OUT = "E:\\Bureau\\graph" -- the name of the file used in the DOT_CMD | |
local DOT_TYPE = "svg" -- can be gif, png, pdf, bmp, jpg, svg, and more | |
local DOT_SPLINES = "spline" -- can be spline, line, polyline, ortho | |
local DOT_TSHAPE = "node [shape=box style=filled]" | |
local DOT_EXT = ".dot" | |
local DOT_CMD = "dot.exe -T "..DOT_TYPE.." -o "..DOT_OUT.."."..DOT_TYPE.." "..DOT_OUT..DOT_EXT | |
local DOT_GRAPH = "graph [fontsize=24 labelloc=\"t\" label=\"_\" splines="..DOT_SPLINES.. | |
" overlap=false rankdir=\"LR\"];" | |
local rpr = reaper | |
local GET_SENDS = 0 | |
local MASTER_TRACK = -1 | |
----------------------------------------- | |
local function formatChanLabel(chanvalue) | |
-- For src and dest channels: | |
-- 0 means stereo 1/2, 1 means stereo 2/3, 2 means stereo 3/4, etc. So: (srcchan+1)/(srcchan+2) | |
-- 1024 means mono 1, 1025 means mono 2, 1026 means mono 3, etc. So: srcchan-1023 | |
-- negative values represent midi channels -17, | |
if 0 <= chanvalue then | |
if chanvalue < 1024 then -- stero | |
return (chanvalue+1).."/"..(chanvalue+2) | |
else -- mono | |
return ""..(chanvalue-1023) | |
end | |
else -- negative value means MIDI | |
return "MIDI" -- simply this for now! | |
end | |
end -- formatChanLabel | |
------------------------------------------ | |
local function formatChanLabels(src, dest) | |
local label = "" | |
if src < 0 then -- this is MIDI | |
assert(dest < 0, "Something seriuoly wrong here!") | |
local midisrc = src + 17 | |
local mididest = dest + 17 | |
if midisrc == mididest then | |
label = "label=\"MIDI "..midisrc.."\" style=dotted" | |
else | |
label = "label=\"MIDI "..midisrc.." > "..mididest.."\" style=dotted" | |
end | |
elseif src == dest then | |
label = "label=\""..formatChanLabel(src).."\"" | |
else | |
label ="taillabel=\""..formatChanLabel(src).. | |
"\" headlabel=\""..formatChanLabel(dest).."\"".." labeldistance=2.0" | |
end | |
--Msg(label) | |
return label | |
end -- formatChanLabels | |
---------------------------------------- | |
local function getTrackColor(mediatrack) | |
local color = math.floor(rpr.GetTrackColor(mediatrack)) | |
if color == 0 then | |
return "" | |
end | |
local R, G, B = rpr.ColorFromNative(color) | |
-- dot takes colors in rgb hex as "#FF0000" for red, "#00FF00" green, "#0000FF" blue. and all in-between | |
-- The fourth hex "88" at the end is the alpha channel, without it the colors are a tad too strong | |
local outstr = string.format("#%02x%02x%02x88", R, G, B) | |
return outstr | |
end | |
----------------------------- | |
local function getTrackInfo() | |
local tracks = {} -- collects all the tracks | |
local numtracks = rpr.GetNumTracks() | |
if numtracks == 0 then return tracks end | |
for i = 0, numtracks-1 do | |
local trackinfo = {} -- mediatrack, num, name, color | |
local mediatrack = rpr.GetTrack(0, i) | |
trackinfo.mediatrack = mediatrack | |
trackinfo.num = math.floor(rpr.GetMediaTrackInfo_Value(mediatrack, "IP_TRACKNUMBER")) | |
_, trackinfo.name = rpr.GetTrackName(mediatrack, "") | |
trackinfo.color = getTrackColor(mediatrack) | |
table.insert(tracks, trackinfo) | |
end | |
return tracks | |
end -- getTrackInfo | |
---------------------------------- | |
local function getSends(mediatrack) | |
local sendstable = {} | |
-- First handle the explicit sends | |
local numsends = rpr.GetTrackNumSends(mediatrack, GET_SENDS) | |
if numsends > 0 then | |
for i = 0, numsends-1 do | |
local sendstruct = {} | |
local targettrack = rpr.GetTrackSendInfo_Value(mediatrack, GET_SENDS, i, "P_DESTTRACK") | |
local targetnum = rpr.GetMediaTrackInfo_Value(targettrack, "IP_TRACKNUMBER") | |
local srcchan = rpr.GetTrackSendInfo_Value(mediatrack, GET_SENDS, i, "I_SRCCHAN") | |
local destchan = rpr.GetTrackSendInfo_Value(mediatrack, GET_SENDS, i, "I_DSTCHAN") | |
sendstruct.targetnum = math.floor(targetnum) | |
sendstruct.srcchan = math.floor(srcchan) | |
sendstruct.destchan = math.floor(destchan) | |
-- For src and dest channels: | |
-- 0 means stereo 1/2, 1 means stereo 2/3, 2 means stereo 3/4, etc. So: (srcchan+1)/(srcchan+2) | |
-- 1024 means mono 1, 1025 means mono 2, 1026 means mono 3, etc. So: srcchan-1023 | |
-- -1 for src (dest is 0) means none (so there is a send, but not audio, thus MIDI, check "I_MIDIFLAGS") | |
if sendstruct.srcchan == -1 then | |
local midichannels = rpr.GetTrackSendInfo_Value(mediatrack, GET_SENDS, i, "I_MIDIFLAGS") | |
local midisrc = midichannels&0x1F -- lo 5 bits is src channel, 0=all, else 1-16 | |
local mididest = (midichannels&0x3E0)>>5 -- next 5 bits are dest chan, 0=orig, else 1-16 | |
-- These are stored as negative numbers by subtracting 17, which makes -17 mean "all/orig" | |
-- -16 mean midi channel 1, -15 midi channel 2 etc | |
sendstruct.srcchan = midisrc - 17 | |
sendstruct.destchan = mididest - 17 | |
end | |
table.insert(sendstable, sendstruct) | |
end | |
end | |
-- Now handle the implicit sends, to parents and the master | |
-- The implicit sends have their targettrack num negated to be able to make them hatched | |
local parentsend = rpr.GetMediaTrackInfo_Value(mediatrack, "B_MAINSEND") -- returns 0.0 for false, 1.0 for true | |
if parentsend > 0 then -- this track sends either to its parent or to the Master | |
local sendstruct = {} | |
local parenttrack = rpr.GetParentTrack(mediatrack) | |
if parenttrack ~= nil then -- this track sends to parent, not the Master | |
local targetnum = math.floor(rpr.GetMediaTrackInfo_Value(parenttrack, "IP_TRACKNUMBER")) | |
sendstruct.targetnum = -targetnum-1 -- since the Master is num -1, we need to make these start 1 step lower | |
else -- this track sends to the master | |
sendstruct.targetnum = MASTER_TRACK | |
end | |
sendstruct.srcchan = 0 -- Don't know yet how to get these | |
sendstruct.destchan = 0 | |
table.insert(sendstable, sendstruct) | |
end | |
return sendstable | |
end -- getSends | |
----------------------------------- | |
local function writePreamble(fileh) | |
fileh:write("digraph ") | |
local pname = rpr.GetProjectName(0, "") | |
if pname == "" then pname = "Unnamed project" end | |
DOT_GRAPH = DOT_GRAPH:gsub("_", pname) | |
fileh:write("\""..pname.."\" {\n\t"..DOT_GRAPH.."\n\t"..DOT_TSHAPE.."\n\t") | |
end -- writePreamble | |
---------------------------------------- | |
local function writeNodes(fileh, tracks) | |
for i, track in ipairs( tracks ) do | |
fileh:write("track"..tracks[i].num.." [fontname=\"Arial\" label=\""..tracks[i].name.."\" fillcolor=\""..tracks[i].color.."\"]\n\t") | |
end | |
fileh:write("Master\n\t") -- Last in the line | |
end -- writeNodes | |
------------------------------------------ | |
local function writeRouting(fileh, tracks) | |
for t = 1, #tracks do | |
local mediatrack = tracks[t].mediatrack -- rpr.GetTrack(0, i) | |
local tracknum = tracks[t].num | |
local sends = getSends(mediatrack) | |
for i = 1, #sends do | |
local sendstruct = sends[i] | |
local sendtrack = "Master" | |
local label = "" | |
if sendstruct.targetnum < 0 then -- implicit send, either to Master or parent | |
label = "[style=dashed]" -- implicit sends are denoted by dashed lines, no channels | |
if sendstruct.targetnum < -1 then -- handle parent sends that are not to Master | |
sendtrack = "track"..-(sendstruct.targetnum+1) | |
end | |
else -- explicit send, include channels label(s) | |
sendtrack = "track"..sendstruct.targetnum | |
label = "["..formatChanLabels(sendstruct.srcchan, sendstruct.destchan).."]" | |
end | |
fileh:write("track"..tracknum.." -> "..sendtrack.." "..label.."\n\t") | |
end | |
end | |
end -- writeRouting | |
------------------------------------------------------------------------- | |
--------------------------------------------------- Open code starts here | |
local tracks = getTrackInfo() | |
local fileh = assert(io.open(DOT_OUT..DOT_EXT, "w"), "Error opening file "..DOT_OUT.." for writing") | |
writePreamble(fileh) | |
writeNodes(fileh, tracks) | |
writeRouting(fileh, tracks) | |
fileh:write("}") | |
fileh:flush() | |
fileh:close() | |
-- "C:\Program Files\Graphviz\bin\dot.exe -Tsvg -o E:\Bureau\graph.svg E:\Bureau\graph.dot" | |
-- "C:\Program Files\Graphviz\bin\dot.exe" -Tsvg "E:\Bureau\graph.dot" -o "E:\Bureau\graph.svg" | |
os.execute([["C:\Program Files\Graphviz\bin\dot.exe" -Tsvg E:\Bureau\graph.dot -o E:\Bureau\graph.svg]]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment