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