1 /*
2  * Copyright (C) 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/port_connections_manager.h"
21 #include "project.h"
22 #include "utils/arrays.h"
23 #include "utils/objects.h"
24 #include "utils/string.h"
25 #include "utils/terminal.h"
26 #include "zrythm_app.h"
27 
28 static void
free_connections(void * data)29 free_connections (
30   void * data)
31 {
32   GPtrArray * arr = (GPtrArray *) data;
33   g_ptr_array_unref (arr);
34 }
35 
36 static void
add_or_replace_connection(GHashTable * ht,const PortIdentifier * pi,PortConnection * conn)37 add_or_replace_connection (
38   GHashTable *           ht,
39   const PortIdentifier * pi,
40   PortConnection *       conn)
41 {
42   /* prepare the connections */
43   GPtrArray * connections =
44     g_hash_table_lookup (ht, pi);
45   bool replacing = false;
46   if (connections)
47     {
48       replacing = true;
49       GPtrArray * prev_connections = connections;
50       connections = g_ptr_array_new ();
51       g_ptr_array_extend (
52         connections, prev_connections, NULL,
53         NULL);
54       /* the old array will be freed automatically
55        * by the hash table when replacing */
56     }
57   else
58     {
59       connections = g_ptr_array_new ();
60     }
61   g_ptr_array_add (connections, conn);
62 
63   if (replacing)
64     {
65       PortIdentifier * pi_clone =
66         port_identifier_clone (pi);
67       g_hash_table_replace (
68         ht, pi_clone, connections);
69     }
70   else
71     {
72       PortIdentifier * pi_clone =
73         port_identifier_clone (pi);
74       g_hash_table_insert (
75         ht, pi_clone, connections);
76     }
77 }
78 
79 /**
80  * Regenerates the hash tables.
81  *
82  * Must be called when a change is made in the
83  * connections.
84  */
85 void
port_connections_manager_regenerate_hashtables(PortConnectionsManager * self)86 port_connections_manager_regenerate_hashtables (
87   PortConnectionsManager * self)
88 {
89 #if 0
90   g_debug (
91     "regenerating hashtables for port connections "
92     "manager...");
93 #endif
94 
95   object_free_w_func_and_null (
96     g_hash_table_destroy, self->src_ht);
97   object_free_w_func_and_null (
98     g_hash_table_destroy, self->dest_ht);
99 
100   self->src_ht =
101     g_hash_table_new_full (
102       port_identifier_get_hash,
103       port_identifier_is_equal_func,
104       port_identifier_free_func,
105       free_connections);
106   self->dest_ht =
107     g_hash_table_new_full (
108       port_identifier_get_hash,
109       port_identifier_is_equal_func,
110       port_identifier_free_func,
111       free_connections);
112 
113   for (int i = 0; i < self->num_connections; i++)
114     {
115       PortConnection * conn = self->connections[i];
116       add_or_replace_connection (
117         self->src_ht, conn->src_id, conn);
118       add_or_replace_connection (
119         self->dest_ht, conn->dest_id, conn);
120     }
121 
122 #if 0
123   unsigned int srcs_size =
124     g_hash_table_size (self->src_ht);
125   unsigned int dests_size =
126     g_hash_table_size (self->dest_ht);
127   g_debug (
128     "Sources hashtable: %u elements | "
129     "Destinations hashtable: %u elements",
130     srcs_size, dests_size);
131 #endif
132 }
133 
134 void
port_connections_manager_init_loaded(PortConnectionsManager * self)135 port_connections_manager_init_loaded (
136   PortConnectionsManager * self)
137 {
138   self->connections_size =
139     (size_t) self->num_connections;
140   port_connections_manager_regenerate_hashtables (self);
141 }
142 
143 PortConnectionsManager *
port_connections_manager_new(void)144 port_connections_manager_new (void)
145 {
146   PortConnectionsManager * self =
147     object_new (PortConnectionsManager);
148   self->schema_version =
149     PORT_CONNECTIONS_MANAGER_SCHEMA_VERSION;
150 
151   self->connections_size = 64;
152   self->connections =
153     object_new_n (
154       self->connections_size, PortConnection *);
155 
156   port_connections_manager_regenerate_hashtables (self);
157 
158   return self;
159 }
160 
161 /**
162  * Returns whether the given connection is for the
163  * given send.
164  */
165 bool
port_connections_manager_predicate_is_send_of(const void * obj,const void * user_data)166 port_connections_manager_predicate_is_send_of (
167   const void * obj,
168   const void * user_data)
169 {
170   const PortConnection * conn =
171     (const PortConnection *) obj;
172   const ChannelSend * send =
173     (const ChannelSend *) user_data;
174 
175   Track * track = channel_send_get_track (send);
176   g_return_val_if_fail (
177     IS_TRACK_AND_NONNULL (track), false);
178 
179   if (track->out_signal_type == TYPE_AUDIO)
180     {
181       StereoPorts * self_stereo =
182         channel_send_is_prefader (send) ?
183           track->channel->prefader->stereo_out :
184           track->channel->fader->stereo_out;
185 
186       return
187         port_identifier_is_equal (
188           conn->src_id, &self_stereo->l->id)
189         ||
190         port_identifier_is_equal (
191           conn->src_id, &self_stereo->r->id);
192     }
193   else if (track->out_signal_type == TYPE_EVENT)
194     {
195       Port * self_port =
196         channel_send_is_prefader (send) ?
197           track->channel->prefader->midi_out :
198           track->channel->fader->midi_out;
199 
200       return
201         port_identifier_is_equal (
202           conn->src_id, &self_port->id);
203     }
204 
205   g_return_val_if_reached (false);
206 }
207 
208 /**
209  * Adds the sources/destinations of @ref id in the
210  * given array.
211  *
212  * @param id The identifier of the port to look for.
213  * @param arr Optional array to fill.
214  * @param sources True to look for sources, false for
215  *   destinations.
216  *
217  * @return The number of ports found.
218  */
219 int
port_connections_manager_get_sources_or_dests(const PortConnectionsManager * self,GPtrArray * arr,const PortIdentifier * id,bool sources)220 port_connections_manager_get_sources_or_dests (
221   const PortConnectionsManager * self,
222   GPtrArray *                    arr,
223   const PortIdentifier *         id,
224   bool                           sources)
225 {
226   g_return_val_if_fail (
227     self->dest_ht && self->src_ht, 0);
228   g_return_val_if_fail (
229     ZRYTHM_APP_IS_GTK_THREAD, 0);
230   GPtrArray * res =
231     g_hash_table_lookup (
232       /* note: we look at the opposite hashtable */
233       (sources ? self->dest_ht : self->src_ht), id);
234 
235   if (!res)
236     return 0;
237 
238   /* append to the given array */
239   if (arr)
240     {
241       g_ptr_array_extend (arr, res, NULL, NULL);
242     }
243 
244   /* return number of connections found */
245   return (int) res->len;
246 }
247 
248 /**
249  * Wrapper over
250  * port_connections_manager_get_sources_or_dests()
251  * that returns the first connection.
252  *
253  * It is a programming error to call this for ports
254  * that are not expected to have exactly 1  matching
255  * connection.
256  */
257 PortConnection *
port_connections_manager_get_source_or_dest(const PortConnectionsManager * self,const PortIdentifier * id,bool sources)258 port_connections_manager_get_source_or_dest (
259   const PortConnectionsManager * self,
260   const PortIdentifier *         id,
261   bool                           sources)
262 {
263   GPtrArray * conns = g_ptr_array_new ();
264   int num_conns =
265     port_connections_manager_get_sources_or_dests (
266       self, conns, id, sources);
267   if (num_conns != 1)
268     {
269       size_t sz = 2000;
270       char buf[sz];
271       port_identifier_print_to_str (id, buf, sz);
272       g_critical (
273         "expected 1 %s, found %d "
274         "connections for\n%s",
275         sources ? "source" : "destination",
276         num_conns, buf);
277       return NULL;
278     }
279 
280   PortConnection * conn =
281     g_ptr_array_index (conns, 0);
282   g_ptr_array_unref (conns);
283 
284   return conn;
285 }
286 
287 PortConnection *
port_connections_manager_find_connection(const PortConnectionsManager * self,const PortIdentifier * src,const PortIdentifier * dest)288 port_connections_manager_find_connection (
289   const PortConnectionsManager * self,
290   const PortIdentifier *         src,
291   const PortIdentifier *         dest)
292 {
293   for (int i = 0; i < self->num_connections; i++)
294     {
295       PortConnection * conn =
296         self->connections[i];
297       if (port_identifier_is_equal (
298             conn->src_id, src)
299           &&
300           port_identifier_is_equal (
301             conn->dest_id, dest))
302         {
303           return conn;
304         }
305     }
306 
307   return NULL;
308 }
309 
310 /**
311  * Stores the connection for the given ports if
312  * it doesn't exist, otherwise updates the existing
313  * connection.
314  *
315  * @return Whether a new connection was made.
316  */
317 const PortConnection *
port_connections_manager_ensure_connect(PortConnectionsManager * self,const PortIdentifier * src,const PortIdentifier * dest,float multiplier,bool locked,bool enabled)318 port_connections_manager_ensure_connect (
319   PortConnectionsManager * self,
320   const PortIdentifier *   src,
321   const PortIdentifier *   dest,
322   float                    multiplier,
323   bool                     locked,
324   bool                     enabled)
325 {
326   g_return_val_if_fail (
327     ZRYTHM_APP_IS_GTK_THREAD, NULL);
328 
329   for (int i = 0; i < self->num_connections; i++)
330     {
331       PortConnection * conn =
332         self->connections[i];
333       if (port_identifier_is_equal (
334             conn->src_id, src)
335           &&
336           port_identifier_is_equal (
337             conn->dest_id, dest))
338         {
339           port_connection_update (
340             conn, multiplier, locked, enabled);
341           port_connections_manager_regenerate_hashtables (self);
342           return conn;
343         }
344     }
345 
346   array_double_size_if_full (
347     self->connections, self->num_connections,
348     self->connections_size, PortConnection *);
349   PortConnection * conn =
350     port_connection_new (
351       src, dest, multiplier, locked, enabled);
352   self->connections[self->num_connections++] = conn;
353 
354   if (self == PORT_CONNECTIONS_MGR)
355     {
356       size_t sz = 800;
357       char buf[sz];
358       port_connection_print_to_str (
359         conn, buf, sz);
360       g_debug (
361         "New connection: <%s>; "
362         "have %d connections",
363         buf, self->num_connections);
364     }
365 
366   port_connections_manager_regenerate_hashtables (
367     self);
368 
369   return conn;
370 }
371 
372 static void
remove_connection(PortConnectionsManager * self,const int idx)373 remove_connection (
374   PortConnectionsManager * self,
375   const int                idx)
376 {
377   PortConnection * conn = self->connections[idx];
378 
379   for (int i = idx; i < self->num_connections - 1;
380        i++)
381     {
382       self->connections[i] = self->connections[i + 1];
383     }
384   self->num_connections--;
385 
386   if (self == PORT_CONNECTIONS_MGR)
387     {
388       size_t sz = 800;
389       char buf[sz];
390       port_connection_print_to_str (
391         conn, buf, sz);
392       g_debug (
393         "Disconnected <%s>; "
394         "have %d connections",
395         buf, self->num_connections);
396     }
397 
398   port_connections_manager_regenerate_hashtables (self);
399 
400   object_free_w_func_and_null (
401     port_connection_free, conn);
402 }
403 
404 /**
405  * Removes the connection for the given ports if
406  * it exists.
407  *
408  * @return Whether a connection was removed.
409  */
410 bool
port_connections_manager_ensure_disconnect(PortConnectionsManager * self,const PortIdentifier * src,const PortIdentifier * dest)411 port_connections_manager_ensure_disconnect (
412   PortConnectionsManager * self,
413   const PortIdentifier *   src,
414   const PortIdentifier *   dest)
415 {
416   g_return_val_if_fail (
417     ZRYTHM_APP_IS_GTK_THREAD, NULL);
418 
419   for (int i = 0; i < self->num_connections; i++)
420     {
421       PortConnection * conn =
422         self->connections[i];
423       if (port_identifier_is_equal (
424             conn->src_id, src)
425           &&
426           port_identifier_is_equal (
427             conn->dest_id, dest))
428         {
429           remove_connection (self, i);
430           return true;
431         }
432     }
433 
434   return false;
435 }
436 
437 /**
438  * Disconnect all sources and dests of the given
439  * port identifier.
440  */
441 void
port_connections_manager_ensure_disconnect_all(PortConnectionsManager * self,const PortIdentifier * pi)442 port_connections_manager_ensure_disconnect_all (
443   PortConnectionsManager * self,
444   const PortIdentifier *   pi)
445 {
446   g_return_if_fail (ZRYTHM_APP_IS_GTK_THREAD);
447 
448   for (int i = 0; i < self->num_connections; i++)
449     {
450       PortConnection * conn =
451         self->connections[i];
452       if (port_identifier_is_equal (conn->src_id, pi)
453           ||
454           port_identifier_is_equal (conn->dest_id, pi))
455         {
456           remove_connection (self, i);
457         }
458     }
459 }
460 
461 bool
port_connections_manager_contains_connection(const PortConnectionsManager * self,const PortConnection * const conn)462 port_connections_manager_contains_connection (
463   const PortConnectionsManager * self,
464   const PortConnection * const   conn)
465 {
466   for (int i = 0; i < self->num_connections; i++)
467     {
468       if (self->connections[i] == conn)
469         return true;
470     }
471   return false;
472 }
473 
474 static void
clear(PortConnectionsManager * self)475 clear (
476   PortConnectionsManager * self)
477 {
478   for (int i = 0; i < self->num_connections; i++)
479     {
480       object_free_w_func_and_null (
481         port_connection_free, self->connections[i]);
482     }
483   self->num_connections = 0;
484 }
485 
486 /**
487  * Removes all connections from @ref self.
488  *
489  * @param src If non-NULL, the connections are copied
490  *   from this to @ref self.
491  */
492 void
port_connections_manager_reset(PortConnectionsManager * self,const PortConnectionsManager * src)493 port_connections_manager_reset (
494   PortConnectionsManager *       self,
495   const PortConnectionsManager * src)
496 {
497   clear (self);
498 
499   self->connections =
500     g_realloc_n (
501       self->connections,
502       (size_t) src->num_connections,
503       sizeof (PortConnection *));
504   self->connections_size =
505     (size_t) src->num_connections;
506   for (int i = 0; i < src->num_connections; i++)
507     {
508       self->connections[i] =
509         port_connection_clone (src->connections[i]);
510     }
511   self->num_connections = src->num_connections;
512 
513   port_connections_manager_regenerate_hashtables (self);
514 }
515 
516 void
port_connections_manager_print_ht(GHashTable * ht)517 port_connections_manager_print_ht (
518   GHashTable * ht)
519 {
520   GHashTableIter iter;
521   gpointer key, value;
522   g_hash_table_iter_init (&iter, ht);
523   GString * gstr = g_string_new (NULL);
524   size_t sz = 800;
525   char buf[sz];
526   while (g_hash_table_iter_next (&iter, &key, &value))
527     {
528       const PortIdentifier * pi =
529         (const PortIdentifier *) key;
530       g_string_append_printf (
531         gstr, "%s:\n", pi->label);
532       GPtrArray * conns = (GPtrArray *) value;
533       for (size_t i = 0; i < conns->len; i++)
534         {
535           PortConnection * conn =
536             g_ptr_array_index (conns, i);
537           port_connection_print_to_str (conn, buf, sz);
538           g_string_append_printf (
539             gstr, "  %s\n", buf);
540         }
541     }
542   char * str = g_string_free (gstr, false);
543   g_message ("%s", str);
544   g_free (str);
545 }
546 
547 void
port_connections_manager_print(const PortConnectionsManager * self)548 port_connections_manager_print (
549   const PortConnectionsManager * self)
550 {
551   GString * gstr = g_string_new (NULL);
552   g_string_append_printf (
553     gstr, "Port connections manager (%p):\n", self);
554   for (int i = 0; i < self->num_connections; i++)
555     {
556       PortConnection * conn = self->connections[i];
557       size_t sz = 400;
558       char buf[sz];
559       port_connection_print_to_str (conn, buf, sz);
560       g_string_append_printf (
561         gstr, "[%d] %s\n", i, buf);
562     }
563   char * str = g_string_free (gstr, false);
564   g_message ("%s", str);
565   g_free (str);
566 }
567 
568 /**
569  * To be used during serialization.
570  */
571 PortConnectionsManager *
port_connections_manager_clone(const PortConnectionsManager * src)572 port_connections_manager_clone (
573   const PortConnectionsManager * src)
574 {
575   PortConnectionsManager * self =
576     port_connections_manager_new ();
577 
578   self->connections =
579     g_realloc_n (
580       self->connections,
581       (size_t) src->num_connections,
582       sizeof (PortConnection *));
583   self->connections_size =
584     (size_t) src->num_connections;
585   for (int i = 0; i < src->num_connections; i++)
586     {
587       self->connections[i] =
588         port_connection_clone (src->connections[i]);
589     }
590   self->num_connections = src->num_connections;
591 
592   port_connections_manager_regenerate_hashtables (self);
593 
594   return self;
595 }
596 
597 /**
598  * Deletes port, doing required cleanup and updating counters.
599  */
600 void
port_connections_manager_free(PortConnectionsManager * self)601 port_connections_manager_free (
602   PortConnectionsManager * self)
603 {
604   for (int i = 0; i < self->num_connections; i++)
605     {
606       object_free_w_func_and_null (
607         port_connection_free, self->connections[i]);
608     }
609 
610   object_free_w_func_and_null (
611     g_hash_table_destroy, self->src_ht);
612   object_free_w_func_and_null (
613     g_hash_table_destroy, self->dest_ht);
614 
615   object_zero_and_free (self);
616 }
617