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