1 /*
2 * Copyright (C) 2019-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 this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "gui/backend/event.h"
21 #include "gui/backend/event_manager.h"
22 #include "gui/backend/mixer_selections.h"
23 #include "gui/widgets/center_dock.h"
24 #include "gui/widgets/left_dock_edge.h"
25 #include "gui/widgets/main_window.h"
26 #include "project.h"
27 #include "utils/arrays.h"
28 #include "utils/error.h"
29 #include "utils/flags.h"
30 #include "utils/objects.h"
31 #include "utils/yaml.h"
32 #include "zrythm_app.h"
33
34 #include <gtk/gtk.h>
35 #include <glib/gi18n.h>
36
37 void
mixer_selections_init_loaded(MixerSelections * self,bool is_project)38 mixer_selections_init_loaded (
39 MixerSelections * self,
40 bool is_project)
41 {
42 if (!is_project)
43 {
44 for (int i = 0; i < self->num_slots; i++)
45 {
46 plugin_init_loaded (
47 self->plugins[i], NULL, self);
48 }
49 }
50
51 /* sort the selections */
52 mixer_selections_sort (self, F_ASCENDING);
53 }
54
55 void
mixer_selections_init(MixerSelections * self)56 mixer_selections_init (
57 MixerSelections * self)
58 {
59 self->schema_version =
60 MIXER_SELECTIONS_SCHEMA_VERSION;
61 }
62
63 MixerSelections *
mixer_selections_new(void)64 mixer_selections_new (void)
65 {
66 MixerSelections * self =
67 object_new (MixerSelections);
68
69 mixer_selections_init (self);
70
71 return self;
72 }
73
74 /**
75 * Returns if there are any selections.
76 */
77 int
mixer_selections_has_any(MixerSelections * self)78 mixer_selections_has_any (
79 MixerSelections * self)
80 {
81 return self->has_any;
82 }
83
84 static int
sort_plugins_asc_func(const void * _a,const void * _b)85 sort_plugins_asc_func (
86 const void * _a,
87 const void * _b)
88 {
89 Plugin * a =
90 *(Plugin * const *) _a;
91 Plugin * b =
92 *(Plugin * const *)_b;
93 return a->id.slot - b->id.slot;
94 }
95
96 static int
sort_plugins_desc_func(const void * _a,const void * _b)97 sort_plugins_desc_func (
98 const void * _a,
99 const void * _b)
100 {
101 return - sort_plugins_asc_func (_a, _b);
102 }
103
104 static int
sort_slots_asc_func(const void * _a,const void * _b)105 sort_slots_asc_func (
106 const void * _a,
107 const void * _b)
108 {
109 int a = *(int const *) _a;
110 int b = *(int const *) _b;
111 return a - b;
112 }
113
114 static int
sort_slots_desc_func(const void * _a,const void * _b)115 sort_slots_desc_func (
116 const void * _a,
117 const void * _b)
118 {
119 return - sort_slots_asc_func (_a, _b);
120 }
121
122 /**
123 * Sorts the selections by slot index.
124 *
125 * @param asc Ascending or not.
126 */
127 void
mixer_selections_sort(MixerSelections * self,bool asc)128 mixer_selections_sort (
129 MixerSelections * self,
130 bool asc)
131 {
132 if (!self->has_any)
133 {
134 return;
135 }
136
137 qsort (
138 self->slots, (size_t) self->num_slots,
139 sizeof (int),
140 asc ?
141 sort_slots_asc_func : sort_slots_desc_func);
142 qsort (
143 self->plugins, (size_t) self->num_slots,
144 sizeof (Plugin *),
145 asc ?
146 sort_plugins_asc_func :
147 sort_plugins_desc_func);
148 }
149
150 /**
151 * Gets highest slot in the selections.
152 */
153 int
mixer_selections_get_highest_slot(MixerSelections * ms)154 mixer_selections_get_highest_slot (
155 MixerSelections * ms)
156 {
157 int min = STRIP_SIZE;
158
159 for (int i = 0; i < ms->num_slots; i++)
160 {
161 if (ms->slots[i] < min)
162 min = ms->slots[i];
163 }
164
165 g_warn_if_fail (min < STRIP_SIZE);
166
167 return min;
168 }
169
170 /**
171 * Gets lowest slot in the selections.
172 */
173 int
mixer_selections_get_lowest_slot(MixerSelections * ms)174 mixer_selections_get_lowest_slot (
175 MixerSelections * ms)
176 {
177 int max = -1;
178
179 for (int i = 0; i < ms->num_slots; i++)
180 {
181 if (ms->slots[i] > max)
182 max = ms->slots[i];
183 }
184
185 g_warn_if_fail (max > -1);
186
187 return max;
188 }
189
190 /**
191 * Get current Track.
192 */
193 Track *
mixer_selections_get_track(MixerSelections * self)194 mixer_selections_get_track (
195 MixerSelections * self)
196 {
197 if (!self->has_any)
198 return NULL;
199
200 Track * track =
201 tracklist_find_track_by_name_hash (
202 TRACKLIST, self->track_name_hash);
203 g_return_val_if_fail (track, NULL);
204 return track;
205 }
206
207 /**
208 * Adds a slot to the selections.
209 *
210 * The selections can only be from one channel.
211 *
212 * @param track The track.
213 * @param slot The slot to add to the selections.
214 * @param clone_pl Whether to clone the plugin
215 * when storing it in \ref
216 * MixerSelections.plugins. Used in some actions.
217 */
218 void
mixer_selections_add_slot(MixerSelections * ms,Track * track,PluginSlotType type,int slot,bool clone_pl)219 mixer_selections_add_slot (
220 MixerSelections * ms,
221 Track * track,
222 PluginSlotType type,
223 int slot,
224 bool clone_pl)
225 {
226 unsigned int name_hash =
227 track_get_name_hash (track);
228
229 if (!ms->has_any
230 || name_hash != ms->track_name_hash
231 || type != ms->type)
232 {
233 mixer_selections_clear (
234 ms, F_NO_PUBLISH_EVENTS);
235 }
236 ms->track_name_hash = name_hash;
237 ms->type = type;
238 ms->has_any = true;
239
240 g_debug ("adding slot %s:%s:%d",
241 track->name,
242 plugin_slot_type_strings[type].str, slot);
243
244 Plugin * pl = NULL;
245 if (!array_contains_int (
246 ms->slots, ms->num_slots, slot))
247 {
248 switch (type)
249 {
250 case PLUGIN_SLOT_MIDI_FX:
251 pl = track->channel->midi_fx[slot];
252 break;
253 case PLUGIN_SLOT_INSERT:
254 pl = track->channel->inserts[slot];
255 break;
256 case PLUGIN_SLOT_MODULATOR:
257 pl = track->modulators[slot];
258 break;
259 case PLUGIN_SLOT_INSTRUMENT:
260 pl = track->channel->instrument;
261 break;
262 default:
263 break;
264 }
265 g_return_if_fail (pl);
266
267 Plugin * pl_to_append = pl;
268 if (clone_pl)
269 {
270 GError * err = NULL;
271 pl_to_append =
272 plugin_clone (pl, &err);
273 if (!pl_to_append)
274 {
275 /* FIXME handle properly */
276 g_critical (
277 "failed to clone plugin: %s",
278 err->message);
279 return;
280 }
281 }
282
283 array_double_append (
284 ms->slots, ms->plugins, ms->num_slots,
285 slot, pl_to_append);
286 }
287
288 if (pl && plugin_is_in_active_project (pl))
289 {
290 EVENTS_PUSH (
291 ET_MIXER_SELECTIONS_CHANGED, pl);
292 }
293 }
294
295 /**
296 * Removes a slot from the selections.
297 *
298 * Assumes that the channel is the one already
299 * selected.
300 */
301 void
mixer_selections_remove_slot(MixerSelections * ms,int slot,PluginSlotType type,bool publish_events)302 mixer_selections_remove_slot (
303 MixerSelections * ms,
304 int slot,
305 PluginSlotType type,
306 bool publish_events)
307 {
308 g_message ("removing slot %d", slot);
309 array_delete_primitive (
310 ms->slots, ms->num_slots, slot);
311
312 if (ms->num_slots == 0 ||
313 ms->type == PLUGIN_SLOT_INSTRUMENT)
314 {
315 ms->has_any = 0;
316 ms->track_name_hash = 0;
317 }
318
319 if (ZRYTHM_HAVE_UI && publish_events)
320 {
321 EVENTS_PUSH (ET_MIXER_SELECTIONS_CHANGED,
322 NULL);
323 }
324 }
325
326
327 /**
328 * Returns if the slot is selected or not.
329 */
330 bool
mixer_selections_contains_slot(MixerSelections * self,PluginSlotType type,int slot)331 mixer_selections_contains_slot (
332 MixerSelections * self,
333 PluginSlotType type,
334 int slot)
335 {
336 if (type != self->type)
337 return false;
338
339 if (type == PLUGIN_SLOT_INSTRUMENT)
340 {
341 return self->has_any;
342 }
343 else
344 {
345 for (int i = 0; i < self->num_slots; i++)
346 {
347 if (self->slots[i] == slot)
348 {
349 return true;
350 }
351 }
352 }
353
354 return false;
355 }
356
357 /**
358 * Returns if the plugin is selected or not.
359 */
360 bool
mixer_selections_contains_plugin(MixerSelections * ms,Plugin * pl)361 mixer_selections_contains_plugin (
362 MixerSelections * ms,
363 Plugin * pl)
364 {
365 g_return_val_if_fail (ms && IS_PLUGIN (pl), false);
366
367 if (ms->track_name_hash != pl->id.track_name_hash)
368 return false;
369
370 if (ms->type == PLUGIN_SLOT_INSTRUMENT)
371 {
372 if (pl->id.slot_type ==
373 PLUGIN_SLOT_INSTRUMENT &&
374 pl->id.track_name_hash ==
375 ms->track_name_hash)
376 {
377 return true;
378 }
379 }
380 else
381 {
382 for (int i = 0; i < ms->num_slots; i++)
383 {
384 if (ms->slots[i] == pl->id.slot &&
385 ms->type == pl->id.slot_type)
386 return true;
387 }
388 }
389
390 return false;
391 }
392
393 /**
394 * Returns the first selected plugin if any is
395 * selected, otherwise NULL.
396 */
397 Plugin *
mixer_selections_get_first_plugin(MixerSelections * self)398 mixer_selections_get_first_plugin (
399 MixerSelections * self)
400 {
401 if (self->has_any)
402 {
403 Track * track =
404 mixer_selections_get_track (self);
405 g_return_val_if_fail (track, NULL);
406 switch (self->type)
407 {
408 case PLUGIN_SLOT_INSTRUMENT:
409 return track->channel->instrument;
410 case PLUGIN_SLOT_INSERT:
411 return
412 track->channel->inserts[
413 self->slots[0]];
414 case PLUGIN_SLOT_MIDI_FX:
415 return
416 track->channel->midi_fx[
417 self->slots[0]];
418 case PLUGIN_SLOT_MODULATOR:
419 return
420 track->modulators[self->slots[0]];
421 default:
422 g_return_val_if_reached (NULL);
423 break;
424 }
425 }
426
427 return NULL;
428 }
429
430 bool
mixer_selections_validate(MixerSelections * self)431 mixer_selections_validate (
432 MixerSelections * self)
433 {
434 if (!self->has_any)
435 return true;
436
437 Track * track =
438 mixer_selections_get_track (self);
439 g_return_val_if_fail (
440 IS_TRACK_AND_NONNULL (track), false);
441
442 for (int i = 0; i < self->num_slots; i++)
443 {
444 Plugin * pl = NULL;
445 switch (self->type)
446 {
447 case PLUGIN_SLOT_INSTRUMENT:
448 pl = track->channel->instrument;
449 break;
450 case PLUGIN_SLOT_INSERT:
451 pl =
452 track->channel->inserts[
453 self->slots[i]];
454 break;
455 case PLUGIN_SLOT_MIDI_FX:
456 pl =
457 track->channel->midi_fx[
458 self->slots[i]];
459 break;
460 case PLUGIN_SLOT_MODULATOR:
461 pl = track->modulators[self->slots[i]];
462 break;
463 default:
464 g_return_val_if_reached (false);
465 break;
466 }
467
468 g_return_val_if_fail (
469 IS_PLUGIN_AND_NONNULL (pl), false);
470 }
471
472 return true;
473 }
474
475 /**
476 * Clears selections.
477 */
478 void
mixer_selections_clear(MixerSelections * self,const int pub_events)479 mixer_selections_clear (
480 MixerSelections * self,
481 const int pub_events)
482 {
483 int i;
484
485 for (i = self->num_slots - 1; i >= 0; i--)
486 {
487 mixer_selections_remove_slot (
488 self, self->slots[i], self->type, 0);
489 }
490
491 self->has_any = false;
492 self->track_name_hash = 0;
493
494 if (pub_events)
495 {
496 EVENTS_PUSH (ET_MIXER_SELECTIONS_CHANGED,
497 NULL);
498 }
499
500 g_message ("cleared mixer selections");
501 }
502
503 /**
504 * Clone the struct for copying, undoing, etc.
505 *
506 * @bool src_is_project Whether \ref src are the
507 * project selections.
508 */
509 MixerSelections *
mixer_selections_clone(MixerSelections * src,bool src_is_project)510 mixer_selections_clone (
511 MixerSelections * src,
512 bool src_is_project)
513 {
514 MixerSelections * ms =
515 mixer_selections_new ();
516
517 int i;
518
519 for (i = 0; i < src->num_slots; i++)
520 {
521 Plugin * pl = NULL;
522 if (src_is_project)
523 {
524 Track * track =
525 tracklist_find_track_by_name_hash (
526 TRACKLIST, src->track_name_hash);
527 pl =
528 track_get_plugin_at_slot (
529 track,
530 src->type, src->slots[i]);
531 g_return_val_if_fail (
532 IS_PLUGIN (pl), NULL);
533 }
534 else
535 {
536 pl = src->plugins[i];
537 }
538
539 GError * err = NULL;
540 ms->plugins[i] =
541 plugin_clone (pl, &err);
542 if (!ms->plugins[i])
543 {
544 g_warning (
545 "failed to clone plugin %s: %s",
546 pl->setting->descr->name, err->message);
547 g_error_free_and_null (err);
548 return NULL;
549 }
550 ms->slots[i] = src->slots[i];
551 }
552
553 ms->type = src->type;
554 ms->num_slots = src->num_slots;
555 ms->has_any = src->has_any;
556 ms->track_name_hash = src->track_name_hash;
557
558 return ms;
559 }
560
561 void
mixer_selections_post_deserialize(MixerSelections * self)562 mixer_selections_post_deserialize (
563 MixerSelections * self)
564 {
565 for (int i = 0; i < self->num_slots; i++)
566 {
567 plugin_init_loaded (
568 self->plugins[i], NULL, self);
569 }
570
571 /* sort the selections */
572 mixer_selections_sort (self, F_ASCENDING);
573 }
574
575 /**
576 * Returns whether the selections can be pasted to
577 * MixerWidget.paste_slot.
578 */
579 bool
mixer_selections_can_be_pasted(MixerSelections * self,Channel * ch,PluginSlotType type,int slot)580 mixer_selections_can_be_pasted (
581 MixerSelections * self,
582 Channel * ch,
583 PluginSlotType type,
584 int slot)
585 {
586 int lowest =
587 mixer_selections_get_lowest_slot (self);
588 int highest =
589 mixer_selections_get_highest_slot (self);
590 int delta = lowest - highest;
591
592 return slot + delta < STRIP_SIZE;
593 }
594
595 /**
596 * Paste the selections starting at the slot in the
597 * given channel.
598 */
599 void
mixer_selections_paste_to_slot(MixerSelections * ms,Channel * ch,PluginSlotType type,int slot)600 mixer_selections_paste_to_slot (
601 MixerSelections * ms,
602 Channel * ch,
603 PluginSlotType type,
604 int slot)
605 {
606 GError * err = NULL;
607 bool ret =
608 mixer_selections_action_perform_paste (
609 ms, PORT_CONNECTIONS_MGR,
610 type,
611 track_get_name_hash (ch->track),
612 slot, &err);
613 if (!ret)
614 {
615 HANDLE_ERROR (
616 err, "%s",
617 _("Failed to paste plugins"));
618 }
619 }
620
621 void
mixer_selections_free(MixerSelections * self)622 mixer_selections_free (MixerSelections * self)
623 {
624 free (self);
625 }
626