1 /*
2 * Copyright (C) 2020-2021 Alexandros Theodotou <alex at zrythm dot org>
3 *
4 * This file is part of Zrythm
5 *
6 * Zrythm is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Zrythm is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "audio/fader.h"
21 #include "audio/graph.h"
22 #include "audio/graph_node.h"
23 #include "audio/graph_export.h"
24 #include "audio/port.h"
25 #include "audio/router.h"
26 #include "audio/track.h"
27 #include "plugins/plugin.h"
28 #include "project.h"
29 #include "utils/flags.h"
30 #include "utils/objects.h"
31
32 #ifdef HAVE_CGRAPH
33 #include <graphviz/cgraph.h>
34 #include <graphviz/gvc.h>
35 #endif
36
37 #ifdef HAVE_CGRAPH
38
39 typedef struct ANode
40 {
41 GraphNode * node;
42
43 /** Subgraph the node is a part of. */
44 Agraph_t * graph;
45 } ANode;
46
47 static ANode *
anode_new(void)48 anode_new (void)
49 {
50 return object_new (ANode);
51 }
52
53 static void
anode_free(ANode * anode)54 anode_free (
55 ANode * anode)
56 {
57 object_zero_and_free (anode);
58 }
59
60 static Agraph_t *
get_graph_from_node(GHashTable * anodes,GraphNode * node)61 get_graph_from_node (
62 GHashTable * anodes,
63 GraphNode * node)
64 {
65 ANode * anode =
66 (ANode *)
67 g_hash_table_lookup (anodes, node);
68 if (anode && anode->node->id == node->id)
69 return anode->graph;
70 g_warning (
71 "%p %s", node, graph_node_get_name (node));
72 g_return_val_if_reached (NULL);
73 }
74
75 static Agraph_t *
get_parent_graph(GHashTable * anodes,GraphNode * node)76 get_parent_graph (
77 GHashTable * anodes,
78 GraphNode * node)
79 {
80 GraphNode * parent_node = NULL;
81 switch (node->type)
82 {
83 case ROUTE_NODE_TYPE_TRACK:
84 case ROUTE_NODE_TYPE_PLUGIN:
85 case ROUTE_NODE_TYPE_FADER:
86 case ROUTE_NODE_TYPE_PREFADER:
87 case ROUTE_NODE_TYPE_MODULATOR_MACRO_PROCESOR:
88 case ROUTE_NODE_TYPE_CHANNEL_SEND:
89 parent_node = node;
90 break;
91 case ROUTE_NODE_TYPE_PORT:
92 {
93 switch (node->port->id.owner_type)
94 {
95 case PORT_OWNER_TYPE_PLUGIN:
96 {
97 Plugin * pl =
98 port_get_plugin (node->port, true);
99 parent_node =
100 graph_find_node_from_plugin (
101 node->graph, pl);
102 }
103 break;
104 case PORT_OWNER_TYPE_TRACK:
105 {
106 Track * tr =
107 port_get_track (node->port, true);
108 if (node->port->id.flags &
109 PORT_FLAG_MODULATOR_MACRO)
110 {
111 parent_node =
112 graph_find_node_from_modulator_macro_processor (
113 node->graph,
114 tr->modulator_macros[
115 node->port->id.port_index]);
116 }
117 else
118 {
119 parent_node =
120 graph_find_node_from_track (
121 node->graph, tr, true);
122 }
123 }
124 break;
125 case PORT_OWNER_TYPE_CHANNEL_SEND:
126 {
127 Track * tr =
128 port_get_track (
129 node->port, true);
130 g_return_val_if_fail (
131 IS_TRACK_AND_NONNULL (tr), NULL);
132 g_return_val_if_fail (
133 tr->channel, NULL);
134 ChannelSend * send =
135 tr->channel->sends[
136 node->port->id.port_index];
137 g_return_val_if_fail (send, NULL);
138 parent_node =
139 graph_find_node_from_channel_send (
140 node->graph, send);
141 }
142 break;
143 case PORT_OWNER_TYPE_FADER:
144 {
145 if (node->port->id.flags2 &
146 PORT_FLAG2_MONITOR_FADER)
147 {
148 parent_node =
149 graph_find_node_from_fader (
150 node->graph,
151 MONITOR_FADER);
152 }
153 else if (
154 node->port->id.flags2 &
155 PORT_FLAG2_SAMPLE_PROCESSOR_FADER)
156 {
157 parent_node =
158 graph_find_node_from_fader (
159 node->graph,
160 SAMPLE_PROCESSOR->fader);
161 }
162 else
163 {
164 Track * tr =
165 port_get_track (
166 node->port, true);
167 if (node->port->id.flags2 &
168 PORT_FLAG2_PREFADER)
169 parent_node =
170 graph_find_node_from_prefader (
171 node->graph,
172 tr->channel->prefader);
173 else
174 parent_node =
175 graph_find_node_from_fader (
176 node->graph,
177 tr->channel->fader);
178 }
179 }
180 break;
181 case PORT_OWNER_TYPE_TRACK_PROCESSOR:
182 {
183 Track * tr =
184 port_get_track (node->port, true);
185 parent_node =
186 graph_find_node_from_track (
187 node->graph, tr, true);
188 }
189 break;
190 default:
191 break;
192 }
193 }
194 break;
195 default:
196 break;
197 }
198 if (!parent_node)
199 return NULL;
200
201 return
202 get_graph_from_node (
203 anodes, parent_node);
204 }
205
206 static Agnode_t *
create_anode(Agraph_t * aroot_graph,GraphNode * node,GHashTable * anodes)207 create_anode (
208 Agraph_t * aroot_graph,
209 GraphNode * node,
210 GHashTable * anodes)
211 {
212 Agraph_t * aparent_graph =
213 get_parent_graph (anodes, node);
214 if (!aparent_graph)
215 aparent_graph = aroot_graph;
216
217 char * plain_node_name = graph_node_get_name (node);
218 char * node_name =
219 g_strdup_printf (
220 "%s\np:%d (%d) c:%d",
221 plain_node_name, node->playback_latency,
222 node->route_playback_latency, 0);
223 /*g_strdup_printf (*/
224 /*"%s i:%d t:%d init refcount: %d",*/
225 /*plain_node_name,*/
226 /*node->initial, node->terminal,*/
227 /*node->init_refcount);*/
228 g_free (plain_node_name);
229 Agnode_t * anode =
230 agnode (aparent_graph, node_name, true);
231 switch (node->type)
232 {
233 case ROUTE_NODE_TYPE_PORT:
234 switch (node->port->id.type)
235 {
236 case TYPE_AUDIO:
237 agsafeset (
238 anode, (char *) "color",
239 (char *) "crimson",
240 (char *) "black");
241 break;
242 case TYPE_EVENT:
243 agsafeset (
244 anode, (char *) "color",
245 (char *) "navy",
246 (char *) "black");
247 break;
248 case TYPE_CONTROL:
249 agsafeset (
250 anode, (char *) "color",
251 (char *) "darkviolet",
252 (char *) "black");
253 break;
254 case TYPE_CV:
255 agsafeset (
256 anode, (char *) "color",
257 (char *) "darkgreen",
258 (char *) "black");
259 break;
260 default:
261 break;
262 }
263 break;
264 default:
265 agsafeset (
266 anode, (char *) "shape", (char *) "record",
267 (char *) "ellipse");
268 break;
269 }
270 g_free (node_name);
271
272 return anode;
273 }
274
275 static void
fill_anodes(Graph * graph,Agraph_t * aroot_graph,GHashTable * anodes)276 fill_anodes (
277 Graph * graph,
278 Agraph_t * aroot_graph,
279 GHashTable * anodes)
280 {
281 char cluster_name[600];
282
283 /* fill nodes */
284 GHashTableIter iter;
285 gpointer key, value;
286 g_hash_table_iter_init (
287 &iter, graph->setup_graph_nodes);
288 while (g_hash_table_iter_next (
289 &iter, &key, &value))
290 {
291 GraphNode * node = (GraphNode *) value;
292 ANode * anode = anode_new ();
293 anode->node = node;
294 g_hash_table_insert (anodes, node, anode);
295 get_graph_from_node (anodes, node);
296 }
297
298 /* create top clusters (tracks, sample processor,
299 * monitor fader) */
300 g_hash_table_iter_init (&iter, anodes);
301 while (g_hash_table_iter_next (
302 &iter, &key, &value))
303 {
304 GraphNode * node = (GraphNode *) key;
305 ANode * anode = (ANode *) value;
306
307 if (node->type != ROUTE_NODE_TYPE_TRACK &&
308 node->type !=
309 ROUTE_NODE_TYPE_SAMPLE_PROCESSOR &&
310 node->type !=
311 ROUTE_NODE_TYPE_MONITOR_FADER)
312 {
313 continue;
314 }
315
316 char * node_name = graph_node_get_name (node);
317 sprintf (
318 cluster_name, "cluster_%s", node_name);
319 anode->graph =
320 agsubg (aroot_graph, cluster_name, true);
321 agsafeset (
322 anode->graph, (char *) "label", node_name,
323 (char *) "");
324 }
325
326 /* create track subclusters */
327 g_hash_table_iter_init (&iter, anodes);
328 while (g_hash_table_iter_next (
329 &iter, &key, &value))
330 {
331 GraphNode * node = (GraphNode *) key;
332 ANode * anode = (ANode *) value;
333
334 if (node->type != ROUTE_NODE_TYPE_PLUGIN &&
335 node->type != ROUTE_NODE_TYPE_FADER &&
336 node->type != ROUTE_NODE_TYPE_PREFADER &&
337 node->type != ROUTE_NODE_TYPE_CHANNEL_SEND &&
338 node->type !=
339 ROUTE_NODE_TYPE_MODULATOR_MACRO_PROCESOR)
340 continue;
341
342 GraphNode * parent_node;
343 switch (node->type)
344 {
345 case ROUTE_NODE_TYPE_PLUGIN:
346 {
347 Plugin * pl = node->pl;
348 Track * tr = plugin_get_track (pl);
349 parent_node =
350 graph_find_node_from_track (
351 node->graph, tr, true);
352 }
353 break;
354 case ROUTE_NODE_TYPE_FADER:
355 {
356 Fader * fader = node->fader;
357 Track * tr = fader_get_track (fader);
358 parent_node =
359 graph_find_node_from_track (
360 node->graph, tr, true);
361 }
362 break;
363 case ROUTE_NODE_TYPE_PREFADER:
364 {
365 Fader * prefader = node->prefader;
366 Track * tr =
367 fader_get_track (prefader);
368 parent_node =
369 graph_find_node_from_track (
370 node->graph, tr, true);
371 }
372 break;
373 case ROUTE_NODE_TYPE_CHANNEL_SEND:
374 {
375 ChannelSend * send = node->send;
376 g_return_if_fail (send);
377 Track * tr =
378 channel_send_get_track (send);
379 g_return_if_fail (
380 IS_TRACK_AND_NONNULL (tr));
381 parent_node =
382 graph_find_node_from_track (
383 node->graph, tr, true);
384 }
385 break;
386 case ROUTE_NODE_TYPE_MODULATOR_MACRO_PROCESOR:
387 {
388 ModulatorMacroProcessor * mmp =
389 node->modulator_macro_processor;
390 Track * tr =
391 modulator_macro_processor_get_track (
392 mmp);
393 parent_node =
394 graph_find_node_from_track (
395 node->graph, tr, true);
396 }
397 break;
398 default:
399 continue;
400 }
401
402 Agraph_t * aparent_graph =
403 get_parent_graph (
404 anodes, parent_node);
405 g_warn_if_fail (aparent_graph);
406 char * node_name =
407 graph_node_get_name (node);
408 sprintf (
409 cluster_name, "cluster_%s", node_name);
410 anode->graph =
411 agsubg (aparent_graph, cluster_name, true);
412 agsafeset (
413 anode->graph, (char *) "label", node_name,
414 (char *) "");
415 }
416 }
417
418 static void
export_as_graphviz_type(Graph * graph,const char * export_path,const char * type)419 export_as_graphviz_type (
420 Graph * graph,
421 const char * export_path,
422 const char * type)
423 {
424 GVC_t * gvc = gvContext();
425 Agraph_t * agraph =
426 agopen (
427 (char *) "routing_graph", Agstrictdirected,
428 NULL);
429
430 /* fill anodes with subgraphs */
431 /* Hash table of
432 * key: (GraphNode *), value: (ANode *) */
433 GHashTable * anodes =
434 g_hash_table_new_full (
435 g_direct_hash, g_direct_equal, NULL,
436 (GDestroyNotify) anode_free);
437 fill_anodes (graph, agraph, anodes);
438
439 /* create graph */
440 GHashTableIter iter;
441 gpointer key, value;
442 g_hash_table_iter_init (&iter, anodes);
443 while (g_hash_table_iter_next (
444 &iter, &key, &value))
445 {
446 GraphNode * node = (GraphNode *) key;
447
448 Agnode_t * anode =
449 create_anode (
450 agraph, node, anodes);
451 for (int j = 0; j < node->n_childnodes; j++)
452 {
453 GraphNode * child = node->childnodes[j];
454 Agnode_t * achildnode =
455 create_anode (
456 agraph, child, anodes);
457
458 /* create edge */
459 Agedge_t * edge =
460 agedge (
461 agraph, anode, achildnode, NULL, true);
462 if (node->type == ROUTE_NODE_TYPE_PORT)
463 {
464 char * color =
465 agget (anode, (char *) "color");
466 agsafeset (
467 edge, (char *) "color", color,
468 (char *) "black");
469 }
470 else if (child->type ==
471 ROUTE_NODE_TYPE_PORT)
472 {
473 char * color =
474 agget (achildnode, (char *) "color");
475 agsafeset (
476 edge, (char *) "color",
477 (char *) color,
478 (char *) "black");
479 }
480 }
481 }
482
483 gvLayout (gvc, agraph, "dot");
484 gvRenderFilename (gvc, agraph, type, export_path);
485 gvFreeLayout(gvc, agraph);
486 agclose (agraph);
487 gvFreeContext(gvc);
488 object_free_w_func_and_null (
489 g_hash_table_unref, anodes);
490 }
491 #endif
492
493 void
graph_export_as_simple(GraphExportType type,const char * export_path)494 graph_export_as_simple (
495 GraphExportType type,
496 const char * export_path)
497 {
498 /* pause engine */
499 EngineState state;
500 engine_wait_for_pause (
501 AUDIO_ENGINE, &state, F_FORCE);
502
503 Graph * graph = graph_new (ROUTER);
504 graph_setup (graph, false, false);
505
506 graph_export_as (graph, type, export_path);
507
508 graph_free (graph);
509
510 /* continue engine */
511 engine_resume (AUDIO_ENGINE, &state);
512 }
513
514 /**
515 * Exports the graph at the given path.
516 *
517 * Engine must be paused before calling this.
518 */
519 void
graph_export_as(Graph * graph,GraphExportType type,const char * export_path)520 graph_export_as (
521 Graph * graph,
522 GraphExportType type,
523 const char * export_path)
524 {
525 g_message (
526 "exporting graph to %s...", export_path);
527
528 switch (type)
529 {
530 #ifdef HAVE_CGRAPH
531 case GRAPH_EXPORT_PNG:
532 export_as_graphviz_type (
533 graph, export_path, "png");
534 break;
535 case GRAPH_EXPORT_DOT:
536 export_as_graphviz_type (
537 graph, export_path, "dot");
538 break;
539 case GRAPH_EXPORT_PS:
540 export_as_graphviz_type (
541 graph, export_path, "ps");
542 break;
543 case GRAPH_EXPORT_SVG:
544 export_as_graphviz_type (
545 graph, export_path, "svg");
546 break;
547 #endif
548 default:
549 g_warn_if_reached ();
550 break;
551 }
552
553 g_message ("graph exported");
554 }
555