1 /*
2 * Copyright (C) 2018-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 "actions/undo_manager.h"
21 #include "audio/audio_region.h"
22 #include "audio/automation_region.h"
23 #include "audio/chord_region.h"
24 #include "audio/chord_track.h"
25 #include "audio/exporter.h"
26 #include "audio/marker_track.h"
27 #include "gui/backend/arranger_object.h"
28 #include "gui/backend/event.h"
29 #include "gui/backend/event_manager.h"
30 #include "gui/widgets/arranger.h"
31 #include "gui/widgets/arranger_object.h"
32 #include "gui/widgets/center_dock.h"
33 #include "gui/widgets/dialogs/bounce_dialog.h"
34 #include "gui/widgets/dialogs/export_progress_dialog.h"
35 #include "gui/widgets/dialogs/export_midi_file_dialog.h"
36 #include "gui/widgets/main_notebook.h"
37 #include "gui/widgets/ruler.h"
38 #include "gui/widgets/timeline_arranger.h"
39 #include "gui/widgets/timeline_panel.h"
40 #include "gui/widgets/timeline_ruler.h"
41 #include "gui/widgets/track.h"
42 #include "project.h"
43 #include "utils/error.h"
44 #include "utils/flags.h"
45 #include "utils/gtk.h"
46 #include "utils/objects.h"
47 #include "utils/resources.h"
48 #include "utils/symap.h"
49 #include "utils/ui.h"
50 #include "zrythm.h"
51 #include "zrythm_app.h"
52
53 #include <gtk/gtk.h>
54 #include <glib/gi18n.h>
55
56 #define ACTION_IS(x) \
57 (self->action == UI_OVERLAY_ACTION_##x)
58
59 /**
60 * Hides the cut dashed line from hovered regions
61 * and redraws them.
62 *
63 * Used when alt was unpressed.
64 */
65 void
timeline_arranger_widget_set_cut_lines_visible(ArrangerWidget * self)66 timeline_arranger_widget_set_cut_lines_visible (
67 ArrangerWidget * self)
68 {
69 #if 0
70 ArrangerObject * obj =
71 arranger_widget_get_hit_arranger_object (
72 (ArrangerWidget *) self,
73 ARRANGER_OBJECT_TYPE_REGION,
74 self->hover_x,
75 self->hover_y);
76
77 if (obj)
78 {
79 ArrangerObjectWidget * obj_w =
80 Z_ARRANGER_OBJECT_WIDGET (obj->widget);
81 ARRANGER_OBJECT_WIDGET_GET_PRIVATE (obj_w);
82
83 GdkModifierType mask;
84 z_gtk_widget_get_mask (
85 GTK_WIDGET (obj_w),
86 &mask);
87 int alt_pressed =
88 mask & GDK_MOD1_MASK;
89
90 /* if not cutting hide the cut line
91 * from the region immediately */
92 int show_cut =
93 arranger_object_widget_should_show_cut_lines (
94 obj_w,
95 alt_pressed);
96
97 if (show_cut != ao_prv->show_cut)
98 {
99 ao_prv->show_cut = show_cut;
100
101 gtk_widget_queue_draw (
102 GTK_WIDGET (obj_w));
103 }
104 }
105 #endif
106 }
107
108 TrackLane *
timeline_arranger_widget_get_track_lane_at_y(ArrangerWidget * self,double y)109 timeline_arranger_widget_get_track_lane_at_y (
110 ArrangerWidget * self,
111 double y)
112 {
113 Track * track =
114 timeline_arranger_widget_get_track_at_y (
115 self, y);
116 if (!track || !track->lanes_visible)
117 return NULL;
118
119 /* y local to track */
120 int y_local =
121 track_widget_get_local_y (
122 track->widget, self, (int) y);
123
124 TrackLane * lane;
125 for (int j = 0; j < track->num_lanes; j++)
126 {
127 lane = track->lanes[j];
128
129 if (y_local >= lane->y &&
130 y_local < lane->y + lane->height)
131 return lane;
132 }
133
134 return NULL;
135 }
136
137 Track *
timeline_arranger_widget_get_track_at_y(ArrangerWidget * self,double y)138 timeline_arranger_widget_get_track_at_y (
139 ArrangerWidget * self,
140 double y)
141 {
142 Track * track;
143 for (int i = 0; i < TRACKLIST->num_tracks; i++)
144 {
145 track = TRACKLIST->tracks[i];
146
147 if (
148 /* ignore invisible tracks */
149 !track->visible ||
150 /* ignore tracks in the other timeline */
151 self->is_pinned != track_is_pinned (track))
152 continue;
153
154 if (!track_get_should_be_visible (track))
155 continue;
156
157 g_return_val_if_fail (track->widget, NULL);
158
159 if (ui_is_child_hit (
160 GTK_WIDGET (self),
161 GTK_WIDGET (track->widget),
162 0, 1, 0, y, 0, 1))
163 return track;
164 }
165
166 return NULL;
167 }
168
169 /**
170 * Returns the hit AutomationTrack at y.
171 */
172 AutomationTrack *
timeline_arranger_widget_get_at_at_y(ArrangerWidget * self,double y)173 timeline_arranger_widget_get_at_at_y (
174 ArrangerWidget * self,
175 double y)
176 {
177 Track * track =
178 timeline_arranger_widget_get_track_at_y (
179 self, y);
180 if (!track)
181 return NULL;
182
183 AutomationTracklist * atl =
184 track_get_automation_tracklist (track);
185 if (!atl || !track->automation_visible)
186 return NULL;
187
188 /* y local to track */
189 int y_local =
190 track_widget_get_local_y (
191 track->widget, self, (int) y);
192
193 for (int j = 0; j < atl->num_ats; j++)
194 {
195 AutomationTrack * at = atl->ats[j];
196
197 if (!at->created || !at->visible)
198 continue;
199
200 if (y_local >= at->y &&
201 y_local < at->y + at->height)
202 return at;
203 }
204
205 return NULL;
206 }
207
208 void
timeline_arranger_on_export_as_midi_file_clicked(GtkMenuItem * menuitem,ZRegion * r)209 timeline_arranger_on_export_as_midi_file_clicked (
210 GtkMenuItem * menuitem,
211 ZRegion * r)
212 {
213 GtkDialog * dialog =
214 GTK_DIALOG (
215 export_midi_file_dialog_widget_new_for_region (
216 GTK_WINDOW (MAIN_WINDOW),
217 r));
218 int res = gtk_dialog_run (dialog);
219 char * filename;
220 switch (res)
221 {
222 case GTK_RESPONSE_ACCEPT:
223 // do_application_specific_something ();
224 filename =
225 gtk_file_chooser_get_filename (
226 GTK_FILE_CHOOSER (dialog));
227 g_message ("exporting to %s", filename);
228 midi_region_export_to_midi_file (
229 r, filename, 0, 0);
230 g_free (filename);
231 break;
232 default:
233 // do_nothing_since_dialog_was_cancelled ();
234 break;
235 }
236 gtk_widget_destroy (GTK_WIDGET (dialog));
237 }
238
239 void
timeline_arranger_on_quick_bounce_clicked(GtkMenuItem * menuitem,ZRegion * r)240 timeline_arranger_on_quick_bounce_clicked (
241 GtkMenuItem * menuitem,
242 ZRegion * r)
243 {
244 ArrangerSelections * sel =
245 (ArrangerSelections *) TL_SELECTIONS;
246 if (!arranger_selections_has_any (sel))
247 {
248 g_warning ("no selections to bounce");
249 return;
250 }
251
252 ExportSettings settings;
253 settings.mode = EXPORT_MODE_REGIONS;
254 export_settings_set_bounce_defaults (
255 &settings, NULL, r->name);
256 timeline_selections_mark_for_bounce (
257 TL_SELECTIONS, settings.bounce_with_parents);
258
259 /* start exporting in a new thread */
260 GThread * thread =
261 g_thread_new (
262 "bounce_thread",
263 (GThreadFunc) exporter_generic_export_thread,
264 &settings);
265
266 /* create a progress dialog and block */
267 ExportProgressDialogWidget * progress_dialog =
268 export_progress_dialog_widget_new (
269 &settings, true, false, F_CANCELABLE);
270 gtk_window_set_transient_for (
271 GTK_WINDOW (progress_dialog),
272 GTK_WINDOW (MAIN_WINDOW));
273 gtk_dialog_run (GTK_DIALOG (progress_dialog));
274 gtk_widget_destroy (GTK_WIDGET (progress_dialog));
275
276 g_thread_join (thread);
277
278 if (!settings.progress_info.has_error &&
279 !settings.progress_info.cancelled)
280 {
281 /* create audio track with bounced material */
282 Position first_pos;
283 arranger_selections_get_start_pos (
284 (ArrangerSelections *) TL_SELECTIONS,
285 &first_pos, F_GLOBAL);
286 exporter_create_audio_track_after_bounce (
287 &settings, &first_pos);
288 }
289
290 export_settings_free_members (&settings);
291 }
292
293 void
timeline_arranger_on_bounce_clicked(GtkMenuItem * menuitem,ZRegion * r)294 timeline_arranger_on_bounce_clicked (
295 GtkMenuItem * menuitem,
296 ZRegion * r)
297 {
298 ArrangerSelections * sel =
299 (ArrangerSelections *) TL_SELECTIONS;
300 if (!arranger_selections_has_any (sel))
301 {
302 g_warning ("no selections to bounce");
303 return;
304 }
305
306 BounceDialogWidget * dialog =
307 bounce_dialog_widget_new (
308 BOUNCE_DIALOG_REGIONS, r->name);
309 gtk_dialog_run (GTK_DIALOG (dialog));
310 gtk_widget_destroy (GTK_WIDGET (dialog));
311 }
312
313 /**
314 * Create a ZRegion at the given Position in the
315 * given Track's given TrackLane.
316 *
317 * @param type The type of region to create.
318 * @param pos The pre-snapped position.
319 * @param track Track, if non-automation.
320 * @param lane TrackLane, if midi/audio region.
321 * @param at AutomationTrack, if automation Region.
322 */
323 void
timeline_arranger_widget_create_region(ArrangerWidget * self,const RegionType type,Track * track,TrackLane * lane,AutomationTrack * at,const Position * pos)324 timeline_arranger_widget_create_region (
325 ArrangerWidget * self,
326 const RegionType type,
327 Track * track,
328 TrackLane * lane,
329 AutomationTrack * at,
330 const Position * pos)
331 {
332 bool autofilling =
333 self->action == UI_OVERLAY_ACTION_AUTOFILLING;
334
335 /* if autofilling, the action is already set */
336 if (!autofilling)
337 {
338 self->action =
339 UI_OVERLAY_ACTION_CREATING_RESIZING_R;
340 }
341
342 g_message ("creating region");
343
344 Position end_pos;
345 position_set_min_size (
346 pos, &end_pos,
347 self->snap_grid);
348
349 /* create a new region */
350 ZRegion * region = NULL;
351 switch (type)
352 {
353 case REGION_TYPE_MIDI:
354 region =
355 midi_region_new (
356 pos, &end_pos,
357 track_get_name_hash (track),
358 /* create on lane 0 if creating in main
359 * track */
360 lane ? lane->pos : 0,
361 lane ? lane->num_regions :
362 track->lanes[0]->num_regions);
363 break;
364 case REGION_TYPE_AUDIO:
365 break;
366 case REGION_TYPE_CHORD:
367 region =
368 chord_region_new (
369 pos, &end_pos,
370 P_CHORD_TRACK->num_chord_regions);
371 break;
372 case REGION_TYPE_AUTOMATION:
373 region =
374 automation_region_new (
375 pos, &end_pos,
376 track_get_name_hash (track),
377 at->index,
378 at->num_regions);
379 break;
380 }
381
382 ArrangerObject * r_obj =
383 (ArrangerObject *) region;
384 self->start_object = r_obj;
385 /*region_set_end_pos (*/
386 /*region, &end_pos, AO_UPDATE_ALL);*/
387 /*long length =*/
388 /*region_get_full_length_in_ticks (region);*/
389 /*position_from_ticks (*/
390 /*®ion->true_end_pos, length);*/
391 /*region_set_true_end_pos (*/
392 /*region, ®ion->true_end_pos, AO_UPDATE_ALL);*/
393 /*position_init (&tmp);*/
394 /*region_set_clip_start_pos (*/
395 /*region, &tmp, AO_UPDATE_ALL);*/
396 /*region_set_loop_start_pos (*/
397 /*region, &tmp, AO_UPDATE_ALL);*/
398 /*region_set_loop_end_pos (*/
399 /*region, ®ion->true_end_pos, AO_UPDATE_ALL);*/
400
401 switch (type)
402 {
403 case REGION_TYPE_MIDI:
404 track_add_region (
405 track, region, NULL,
406 lane ? lane->pos :
407 (track->num_lanes == 1 ?
408 0 : track->num_lanes - 2), F_GEN_NAME,
409 F_PUBLISH_EVENTS);
410 break;
411 case REGION_TYPE_AUDIO:
412 break;
413 case REGION_TYPE_CHORD:
414 track_add_region (
415 track, region, NULL,
416 -1, F_GEN_NAME, F_PUBLISH_EVENTS);
417 break;
418 case REGION_TYPE_AUTOMATION:
419 track_add_region (
420 track, region, at,
421 -1, F_GEN_NAME, F_PUBLISH_EVENTS);
422 break;
423 }
424
425 /* set visibility */
426 /*arranger_object_gen_widget (r_obj);*/
427 /*arranger_object_set_widget_visibility_and_state (*/
428 /*r_obj, 1);*/
429
430 arranger_object_set_position (
431 r_obj, &r_obj->end_pos,
432 ARRANGER_OBJECT_POSITION_TYPE_END,
433 F_NO_VALIDATE);
434 arranger_object_select (
435 r_obj, F_SELECT,
436 autofilling ? F_APPEND : F_NO_APPEND,
437 F_NO_PUBLISH_EVENTS);
438 }
439
440 /**
441 * Wrapper for
442 * timeline_arranger_widget_create_chord() or
443 * timeline_arranger_widget_create_scale().
444 *
445 * @param y the y relative to the
446 * ArrangerWidget.
447 */
448 void
timeline_arranger_widget_create_chord_or_scale(ArrangerWidget * self,Track * track,double y,const Position * pos)449 timeline_arranger_widget_create_chord_or_scale (
450 ArrangerWidget * self,
451 Track * track,
452 double y,
453 const Position * pos)
454 {
455 int track_height =
456 gtk_widget_get_allocated_height (
457 GTK_WIDGET (track->widget));
458 gint wy;
459 gtk_widget_translate_coordinates (
460 GTK_WIDGET (self),
461 GTK_WIDGET (track->widget),
462 0, (int) y, NULL, &wy);
463
464 if (y >= track_height / 2.0)
465 timeline_arranger_widget_create_scale (
466 self, track, pos);
467 else
468 timeline_arranger_widget_create_region (
469 self, REGION_TYPE_CHORD, track, NULL,
470 NULL, pos);
471 }
472
473 /**
474 * Create a ScaleObject at the given Position in the
475 * given Track.
476 *
477 * @param pos The pre-snapped position.
478 */
479 void
timeline_arranger_widget_create_scale(ArrangerWidget * self,Track * track,const Position * pos)480 timeline_arranger_widget_create_scale (
481 ArrangerWidget * self,
482 Track * track,
483 const Position * pos)
484 {
485 g_warn_if_fail (track->type == TRACK_TYPE_CHORD);
486
487 self->action =
488 UI_OVERLAY_ACTION_CREATING_MOVING;
489
490 /* create a new scale */
491 MusicalScale * descr =
492 musical_scale_new (SCALE_AEOLIAN, NOTE_A);
493 ScaleObject * scale =
494 scale_object_new (descr);
495 ArrangerObject * scale_obj =
496 (ArrangerObject *) scale;
497
498 /* add it to scale track */
499 chord_track_add_scale (track, scale);
500
501 /*arranger_object_gen_widget (scale_obj);*/
502
503 /* set visibility */
504 /*arranger_object_set_widget_visibility_and_state (*/
505 /*scale_obj, 1);*/
506
507 arranger_object_pos_setter (
508 scale_obj, pos);
509
510 EVENTS_PUSH (ET_ARRANGER_OBJECT_CREATED, scale);
511 arranger_object_select (
512 scale_obj, F_SELECT, F_NO_APPEND,
513 F_NO_PUBLISH_EVENTS);
514 }
515
516 /**
517 * Create a Marker at the given Position in the
518 * given Track.
519 *
520 * @param pos The pre-snapped position.
521 */
522 void
timeline_arranger_widget_create_marker(ArrangerWidget * self,Track * track,const Position * pos)523 timeline_arranger_widget_create_marker (
524 ArrangerWidget * self,
525 Track * track,
526 const Position * pos)
527 {
528 g_warn_if_fail (
529 track->type == TRACK_TYPE_MARKER);
530
531 self->action =
532 UI_OVERLAY_ACTION_CREATING_MOVING;
533
534 /* create a new marker */
535 Marker * marker =
536 marker_new (_("Custom Marker"));
537 ArrangerObject * marker_obj =
538 (ArrangerObject *) marker;
539
540 /* add it to marker track */
541 marker_track_add_marker (
542 track, marker);
543
544 /*arranger_object_gen_widget (marker_obj);*/
545
546 /* set visibility */
547 /*arranger_object_set_widget_visibility_and_state (*/
548 /*marker_obj, 1);*/
549
550 arranger_object_pos_setter (
551 marker_obj, pos);
552
553 EVENTS_PUSH (ET_ARRANGER_OBJECT_CREATED, marker);
554 arranger_object_select (
555 marker_obj, F_SELECT, F_NO_APPEND,
556 F_NO_PUBLISH_EVENTS);
557 }
558
559 /**
560 * Determines the selection time (objects/range)
561 * and sets it.
562 */
563 void
timeline_arranger_widget_set_select_type(ArrangerWidget * self,double y)564 timeline_arranger_widget_set_select_type (
565 ArrangerWidget * self,
566 double y)
567 {
568 Track * track =
569 timeline_arranger_widget_get_track_at_y (
570 self, y);
571
572 if (track)
573 {
574 if (track_widget_is_cursor_in_range_select_half (
575 track->widget, y))
576 {
577 /* set resizing range flags */
578 self->resizing_range = true;
579 self->resizing_range_start = true;
580 self->action =
581 UI_OVERLAY_ACTION_RESIZING_R;
582 }
583 else
584 {
585 /* select objects */
586 self->resizing_range = false;
587 }
588 }
589 else
590 {
591 /* TODO something similar as above based on
592 * visible space */
593 self->resizing_range = false;
594 }
595
596 /*arranger_widget_refresh_all_backgrounds ();*/
597 gtk_widget_queue_allocate (
598 GTK_WIDGET (MW_RULER));
599 }
600
601 /**
602 * Snaps the region's start point.
603 *
604 * @param new_start_pos Position to snap to.
605 * @parram dry_run Don't resize notes; just check
606 * if the resize is allowed (check if invalid
607 * resizes will happen)
608 *
609 * @return 0 if the operation was successful,
610 * nonzero otherwise.
611 */
612 static inline int
snap_region_l(ArrangerWidget * self,ZRegion * region,Position * new_pos,int dry_run)613 snap_region_l (
614 ArrangerWidget * self,
615 ZRegion * region,
616 Position * new_pos,
617 int dry_run)
618 {
619 ArrangerObjectResizeType type =
620 ARRANGER_OBJECT_RESIZE_NORMAL;
621 if ACTION_IS (RESIZING_L_LOOP)
622 type = ARRANGER_OBJECT_RESIZE_LOOP;
623 else if ACTION_IS (RESIZING_L_FADE)
624 type = ARRANGER_OBJECT_RESIZE_FADE;
625 else if ACTION_IS (STRETCHING_L)
626 type = ARRANGER_OBJECT_RESIZE_STRETCH;
627
628 /* negative positions not allowed */
629 if (!position_is_positive (new_pos))
630 return -1;
631
632 if (SNAP_GRID_ANY_SNAP (self->snap_grid) &&
633 !self->shift_held &&
634 type != ARRANGER_OBJECT_RESIZE_FADE)
635 {
636 Track * track =
637 arranger_object_get_track (
638 (ArrangerObject *) region);
639 position_snap (
640 &self->earliest_obj_start_pos,
641 new_pos, track,
642 NULL, self->snap_grid);
643 }
644
645 ArrangerObject * r_obj =
646 (ArrangerObject *) region;
647 Position cmp_pos;
648 if (type == ARRANGER_OBJECT_RESIZE_FADE)
649 {
650 cmp_pos = r_obj->fade_out_pos;
651 }
652 else
653 {
654 cmp_pos = r_obj->end_pos;
655 }
656 if (position_is_after_or_equal (
657 new_pos, &cmp_pos))
658 return -1;
659 else if (!dry_run)
660 {
661 int is_valid = 0;
662 double diff = 0;
663
664 if (type == ARRANGER_OBJECT_RESIZE_FADE)
665 {
666 is_valid =
667 arranger_object_validate_pos (
668 r_obj, new_pos,
669 ARRANGER_OBJECT_POSITION_TYPE_FADE_IN);
670 diff =
671 position_to_ticks (new_pos) -
672 position_to_ticks (&r_obj->fade_in_pos);
673 }
674 else
675 {
676 is_valid =
677 arranger_object_validate_pos (
678 r_obj, new_pos,
679 ARRANGER_OBJECT_POSITION_TYPE_START);
680 diff =
681 position_to_ticks (new_pos) -
682 position_to_ticks (&r_obj->pos);
683 }
684
685 if (is_valid)
686 {
687 arranger_object_resize (
688 r_obj, true, type, diff, true);
689 }
690 }
691
692 return 0;
693 }
694
695 /**
696 * Snaps both the transients (to show in the GUI)
697 * and the actual regions.
698 *
699 * @param pos Absolute position in the timeline.
700 * @param dry_run Don't resize notes; just check
701 * if the resize is allowed (check if invalid
702 * resizes will happen).
703 *
704 * @return 0 if the operation was successful,
705 * nonzero otherwise.
706 */
707 int
timeline_arranger_widget_snap_regions_l(ArrangerWidget * self,Position * pos,int dry_run)708 timeline_arranger_widget_snap_regions_l (
709 ArrangerWidget * self,
710 Position * pos,
711 int dry_run)
712 {
713 ArrangerObject * start_r_obj =
714 self->start_object;
715
716 /* get delta with first clicked region's start
717 * pos */
718 double delta;
719 if (ACTION_IS (RESIZING_L_FADE))
720 {
721 delta =
722 position_to_ticks (pos) -
723 (position_to_ticks (
724 &start_r_obj->pos) +
725 position_to_ticks (
726 &start_r_obj->fade_in_pos));
727 }
728 else
729 {
730 delta =
731 position_to_ticks (pos) -
732 position_to_ticks (
733 &start_r_obj->pos);
734 }
735
736 /* new start pos for each region, calculated by
737 * adding delta to the region's original start
738 * pos */
739 Position new_pos;
740
741 ZRegion * region;
742 ArrangerObject * r_obj;
743 int ret;
744 for (int i = 0;
745 i < TL_SELECTIONS->num_regions;
746 i++)
747 {
748 /* main trans region */
749 region =
750 TL_SELECTIONS->regions[i];
751 r_obj = (ArrangerObject *) region;
752
753 /* caclulate new start position */
754 if (ACTION_IS (RESIZING_L_FADE))
755 {
756 position_set_to_pos (
757 &new_pos, &r_obj->fade_in_pos);
758 }
759 else
760 {
761 position_set_to_pos (
762 &new_pos, &r_obj->pos);
763 }
764 position_add_ticks (&new_pos, delta);
765
766 ret =
767 snap_region_l (
768 self, region, &new_pos, dry_run);
769
770 if (ret)
771 return ret;
772 }
773
774 EVENTS_PUSH (
775 ET_ARRANGER_SELECTIONS_CHANGED,
776 TL_SELECTIONS);
777
778 return 0;
779 }
780
781 /**
782 * Snaps the region's end point.
783 *
784 * @param new_end_pos New end position to snap to.
785 * @parram dry_run Don't resize notes; just check
786 * if the resize is allowed (check if invalid
787 * resizes will happen)
788 *
789 * @return 0 if the operation was successful,
790 * nonzero otherwise.
791 */
792 static int
snap_region_r(ArrangerWidget * self,ZRegion * region,Position * new_pos,int dry_run)793 snap_region_r (
794 ArrangerWidget * self,
795 ZRegion * region,
796 Position * new_pos,
797 int dry_run)
798 {
799 ArrangerObjectResizeType type =
800 ARRANGER_OBJECT_RESIZE_NORMAL;
801 if ACTION_IS (RESIZING_R_LOOP)
802 type = ARRANGER_OBJECT_RESIZE_LOOP;
803 else if ACTION_IS (RESIZING_R_FADE)
804 type = ARRANGER_OBJECT_RESIZE_FADE;
805 else if ACTION_IS (STRETCHING_R)
806 type = ARRANGER_OBJECT_RESIZE_STRETCH;
807
808 /* negative positions not allowed */
809 if (!position_is_positive (new_pos))
810 return -1;
811
812 if (SNAP_GRID_ANY_SNAP (self->snap_grid)
813 && !self->shift_held
814 && type != ARRANGER_OBJECT_RESIZE_FADE)
815 {
816 Track * track =
817 arranger_object_get_track (
818 (ArrangerObject *) region);
819 position_snap (
820 &self->earliest_obj_start_pos,
821 new_pos, track,
822 NULL, self->snap_grid);
823 }
824
825 ArrangerObject * r_obj =
826 (ArrangerObject *) region;
827 if (type == ARRANGER_OBJECT_RESIZE_FADE)
828 {
829 Position tmp;
830 position_from_ticks (
831 &tmp,
832 r_obj->end_pos.ticks -
833 r_obj->pos.ticks);
834 if (position_is_before_or_equal (
835 new_pos, &r_obj->fade_in_pos) ||
836 position_is_after (new_pos, &tmp))
837 return -1;
838 }
839 else
840 {
841 if (position_is_before_or_equal (
842 new_pos, &r_obj->pos))
843 return -1;
844 }
845
846 if (!dry_run)
847 {
848 int is_valid = 0;
849 double diff = 0;
850 if (type == ARRANGER_OBJECT_RESIZE_FADE)
851 {
852 is_valid =
853 arranger_object_validate_pos (
854 r_obj, new_pos,
855 ARRANGER_OBJECT_POSITION_TYPE_FADE_OUT);
856 diff =
857 position_to_ticks (new_pos) -
858 position_to_ticks (&r_obj->fade_out_pos);
859 }
860 else
861 {
862 is_valid =
863 arranger_object_validate_pos (
864 r_obj, new_pos,
865 ARRANGER_OBJECT_POSITION_TYPE_END);
866 diff =
867 position_to_ticks (new_pos) -
868 position_to_ticks (&r_obj->end_pos);
869 }
870
871 if (is_valid)
872 {
873 arranger_object_resize (
874 r_obj, false, type, diff, true);
875
876 /* if creating also set the loop points
877 * appropriately */
878 if (self->action ==
879 UI_OVERLAY_ACTION_CREATING_RESIZING_R)
880 {
881 double full_size =
882 arranger_object_get_length_in_ticks (
883 r_obj);
884 Position tmp;
885 position_set_to_pos (
886 &tmp, &r_obj->loop_start_pos);
887 position_add_ticks (&tmp, full_size);
888
889 /* use the setters */
890 arranger_object_loop_end_pos_setter (
891 r_obj, &tmp);
892 }
893 }
894 }
895
896 return 0;
897 }
898
899 /**
900 * Snaps both the transients (to show in the GUI)
901 * and the actual regions.
902 *
903 * @param pos Absolute position in the timeline.
904 * @parram dry_run Don't resize notes; just check
905 * if the resize is allowed (check if invalid
906 * resizes will happen)
907 *
908 * @return 0 if the operation was successful,
909 * nonzero otherwise.
910 */
911 int
timeline_arranger_widget_snap_regions_r(ArrangerWidget * self,Position * pos,int dry_run)912 timeline_arranger_widget_snap_regions_r (
913 ArrangerWidget * self,
914 Position * pos,
915 int dry_run)
916 {
917 ArrangerObject * start_r_obj =
918 self->start_object;
919
920 /* get delta with first clicked region's end
921 * pos */
922 double delta;
923 if (ACTION_IS (RESIZING_R_FADE))
924 {
925 delta =
926 position_to_ticks (pos) -
927 (position_to_ticks (
928 &start_r_obj->pos) +
929 position_to_ticks (
930 &start_r_obj->fade_out_pos));
931 }
932 else
933 {
934 delta =
935 position_to_ticks (pos) -
936 position_to_ticks (
937 &start_r_obj->end_pos);
938 }
939
940 /* new end pos for each region, calculated by
941 * adding delta to the region's original end
942 * pos */
943 Position new_pos;
944
945 ZRegion * region;
946 ArrangerObject * r_obj;
947 int ret;
948 for (int i = 0;
949 i < TL_SELECTIONS->num_regions;
950 i++)
951 {
952 region =
953 TL_SELECTIONS->regions[i];
954 r_obj = (ArrangerObject *) region;
955
956 if (ACTION_IS (RESIZING_R_FADE))
957 {
958 position_set_to_pos (
959 &new_pos, &r_obj->fade_out_pos);
960 }
961 else
962 {
963 position_set_to_pos (
964 &new_pos, &r_obj->end_pos);
965 }
966 position_add_ticks (&new_pos, delta);
967
968 ret =
969 snap_region_r (
970 self, region, &new_pos, dry_run);
971
972 if (ret)
973 return ret;
974 }
975
976 EVENTS_PUSH (
977 ET_ARRANGER_SELECTIONS_CHANGED,
978 (ArrangerSelections *) TL_SELECTIONS);
979
980 return 0;
981 }
982
983 void
timeline_arranger_widget_snap_range_r(ArrangerWidget * self,Position * pos)984 timeline_arranger_widget_snap_range_r (
985 ArrangerWidget * self,
986 Position * pos)
987 {
988 if (self->resizing_range_start)
989 {
990 /* set range 1 at current point */
991 ui_px_to_pos_timeline (
992 self->start_x,
993 &TRANSPORT->range_1,
994 1);
995 if (SNAP_GRID_ANY_SNAP (
996 self->snap_grid) &&
997 !self->shift_held)
998 {
999 position_snap_simple (
1000 &TRANSPORT->range_1,
1001 SNAP_GRID_TIMELINE);
1002 }
1003 position_set_to_pos (
1004 &TRANSPORT->range_2,
1005 &TRANSPORT->range_1);
1006
1007 MW_TIMELINE->resizing_range_start = 0;
1008 }
1009
1010 /* set range */
1011 if (SNAP_GRID_ANY_SNAP (self->snap_grid) &&
1012 !self->shift_held)
1013 position_snap_simple (
1014 pos,
1015 SNAP_GRID_TIMELINE);
1016 position_set_to_pos (
1017 &TRANSPORT->range_2, pos);
1018 transport_set_has_range (TRANSPORT, true);
1019 }
1020
1021 /** Used when activating fade presets. */
1022 typedef struct CurveOptionInfo
1023 {
1024 CurveOptions opts;
1025 ArrangerObject * obj;
1026
1027 /** 1 for in, 0 for out. */
1028 int fade_in;
1029 } CurveOptionInfo;
1030
1031 static void
on_fade_preset_selected(GtkMenuItem * menu_item,CurveOptionInfo * info)1032 on_fade_preset_selected (
1033 GtkMenuItem * menu_item,
1034 CurveOptionInfo * info)
1035 {
1036 ArrangerSelections * sel_before =
1037 arranger_selections_clone (
1038 (ArrangerSelections *) TL_SELECTIONS);
1039 if (info->fade_in)
1040 {
1041 info->obj->fade_in_opts.algo =
1042 info->opts.algo;
1043 info->obj->fade_in_opts.curviness =
1044 info->opts.curviness;
1045 }
1046 else
1047 {
1048 info->obj->fade_out_opts.algo =
1049 info->opts.algo;
1050 info->obj->fade_out_opts.curviness =
1051 info->opts.curviness;
1052 }
1053
1054 g_warn_if_fail (
1055 arranger_object_is_selected (info->obj));
1056
1057 GError * err = NULL;
1058 bool ret =
1059 arranger_selections_action_perform_edit (
1060 sel_before,
1061 (ArrangerSelections *) TL_SELECTIONS,
1062 ARRANGER_SELECTIONS_ACTION_EDIT_FADES,
1063 true, &err);
1064 if (!ret)
1065 {
1066 HANDLE_ERROR (
1067 err, "%s", _("Failed to edit fades"));
1068 }
1069
1070 g_warn_if_fail (IS_ARRANGER_OBJECT (info->obj));
1071 EVENTS_PUSH (
1072 ET_ARRANGER_OBJECT_CHANGED, info->obj);
1073
1074 object_zero_and_free (info);
1075 }
1076
1077 /**
1078 * @param fade_in 1 for in, 0 for out.
1079 */
1080 static void
create_fade_preset_menu(ArrangerWidget * self,GtkWidget * menu,ArrangerObject * obj,int fade_in)1081 create_fade_preset_menu (
1082 ArrangerWidget * self,
1083 GtkWidget * menu,
1084 ArrangerObject * obj,
1085 int fade_in)
1086 {
1087 GtkWidget * menuitem =
1088 gtk_menu_item_new_with_label (_("Fade preset"));
1089 gtk_menu_shell_append (
1090 GTK_MENU_SHELL (menu), menuitem);
1091 GtkMenu * submenu = GTK_MENU (gtk_menu_new ());
1092 gtk_widget_set_visible (GTK_WIDGET (submenu), 1);
1093 GtkMenuItem * submenu_item;
1094 CurveOptionInfo * opts;
1095
1096 #define CREATE_ITEM(name,xalgo,curve) \
1097 submenu_item = \
1098 GTK_MENU_ITEM ( \
1099 gtk_menu_item_new_with_label (name)); \
1100 opts = object_new (CurveOptionInfo); \
1101 opts->opts.algo = CURVE_ALGORITHM_##xalgo; \
1102 opts->opts.curviness = curve; \
1103 opts->fade_in = fade_in; \
1104 opts->obj = obj; \
1105 g_signal_connect ( \
1106 G_OBJECT (submenu_item), "activate", \
1107 G_CALLBACK (on_fade_preset_selected), opts); \
1108 gtk_menu_shell_append ( \
1109 GTK_MENU_SHELL (submenu), \
1110 GTK_WIDGET (submenu_item)); \
1111 gtk_widget_set_visible ( \
1112 GTK_WIDGET (submenu_item), 1)
1113
1114 CREATE_ITEM (_("Linear"), SUPERELLIPSE, 0);
1115 CREATE_ITEM (_("Exponential"), EXPONENT, - 0.6);
1116 CREATE_ITEM (_("Elliptic"), SUPERELLIPSE, - 0.5);
1117 CREATE_ITEM (
1118 _("Logarithmic"), LOGARITHMIC, - 0.5);
1119 CREATE_ITEM (_("Vital"), VITAL, - 0.5);
1120
1121 #undef CREATE_ITEM
1122
1123 gtk_menu_item_set_submenu (
1124 GTK_MENU_ITEM (menuitem),
1125 GTK_WIDGET (submenu));
1126 gtk_widget_set_visible (
1127 GTK_WIDGET (menuitem), 1);
1128 }
1129
1130 /** Used when selecting a musical mode. */
1131 typedef struct MusicalModeInfo
1132 {
1133 RegionMusicalMode mode;
1134 ArrangerObject * obj;
1135 } MusicalModeInfo;
1136
1137 static void
on_musical_mode_toggled(GtkCheckMenuItem * menu_item,MusicalModeInfo * info)1138 on_musical_mode_toggled (
1139 GtkCheckMenuItem * menu_item,
1140 MusicalModeInfo * info)
1141 {
1142 if (!gtk_check_menu_item_get_active (menu_item))
1143 {
1144 return;
1145 }
1146
1147 ArrangerSelections * sel_before =
1148 arranger_selections_clone (
1149 (ArrangerSelections *) TL_SELECTIONS);
1150
1151 /* make the change */
1152 ZRegion * region = (ZRegion *) info->obj;
1153 region->musical_mode = info->mode;
1154
1155 g_warn_if_fail (
1156 arranger_object_is_selected (info->obj));
1157
1158 GError * err = NULL;
1159 bool ret =
1160 arranger_selections_action_perform_edit (
1161 sel_before,
1162 (ArrangerSelections *) TL_SELECTIONS,
1163 ARRANGER_SELECTIONS_ACTION_EDIT_PRIMITIVE,
1164 true, &err);
1165 if (!ret)
1166 {
1167 HANDLE_ERROR (
1168 err, "%s",
1169 _("Failed to edit selections"));
1170 }
1171
1172 g_warn_if_fail (IS_ARRANGER_OBJECT (info->obj));
1173 EVENTS_PUSH (
1174 ET_ARRANGER_OBJECT_CHANGED, info->obj);
1175
1176 object_zero_and_free (info);
1177 }
1178
1179 /**
1180 * @param fade_in 1 for in, 0 for out.
1181 */
1182 static void
create_musical_mode_pset_menu(ArrangerWidget * self,GtkWidget * menu,ArrangerObject * obj)1183 create_musical_mode_pset_menu (
1184 ArrangerWidget * self,
1185 GtkWidget * menu,
1186 ArrangerObject * obj)
1187 {
1188 GtkWidget * menuitem =
1189 gtk_menu_item_new_with_label (
1190 _("Musical Mode"));
1191 gtk_menu_shell_append (
1192 GTK_MENU_SHELL (menu), menuitem);
1193
1194 GtkMenu * submenu = GTK_MENU (gtk_menu_new ());
1195 gtk_widget_set_visible (GTK_WIDGET (submenu), 1);
1196 GtkMenuItem * submenu_item[
1197 REGION_MUSICAL_MODE_ON + 2];
1198 MusicalModeInfo * nfo;
1199 GSList * group = NULL;
1200 ZRegion * region = (ZRegion *) obj;
1201 for (int i = 0; i <= REGION_MUSICAL_MODE_ON; i++)
1202 {
1203 submenu_item[i] =
1204 GTK_MENU_ITEM (
1205 gtk_radio_menu_item_new_with_label (
1206 group,
1207 _(region_musical_mode_strings[i].str)));
1208 if ((RegionMusicalMode) i ==
1209 region->musical_mode)
1210 {
1211 gtk_check_menu_item_set_active (
1212 GTK_CHECK_MENU_ITEM (
1213 submenu_item[i]), true);
1214 }
1215 gtk_menu_shell_append (
1216 GTK_MENU_SHELL (submenu),
1217 GTK_WIDGET (submenu_item[i]));
1218 gtk_widget_set_visible (
1219 GTK_WIDGET (submenu_item[i]), true);
1220
1221 group =
1222 gtk_radio_menu_item_get_group (
1223 GTK_RADIO_MENU_ITEM (submenu_item[i]));
1224 }
1225
1226 gtk_menu_item_set_submenu (
1227 GTK_MENU_ITEM (menuitem),
1228 GTK_WIDGET (submenu));
1229 gtk_widget_set_visible (
1230 GTK_WIDGET (menuitem), 1);
1231
1232 for (int i = 0; i <= REGION_MUSICAL_MODE_ON; i++)
1233 {
1234 nfo = object_new (MusicalModeInfo);
1235 nfo->mode = i;
1236 nfo->obj = obj;
1237 g_signal_connect (
1238 G_OBJECT (submenu_item[i]), "toggled",
1239 G_CALLBACK (on_musical_mode_toggled), nfo);
1240 }
1241 }
1242
1243 typedef struct ContextMenuData
1244 {
1245 AudioFunctionType audio_func;
1246 } ContextMenuData;
1247
1248 static void
free_context_menu_data(ContextMenuData * data,GClosure * closure)1249 free_context_menu_data (
1250 ContextMenuData * data,
1251 GClosure * closure)
1252 {
1253 free (data);
1254 }
1255
1256 static void
on_audio_func_activate(GtkCheckMenuItem * item,ContextMenuData * data)1257 on_audio_func_activate (
1258 GtkCheckMenuItem * item,
1259 ContextMenuData * data)
1260 {
1261 for (int i = 0; i < TL_SELECTIONS->num_regions;
1262 i++)
1263 {
1264 ZRegion * r = TL_SELECTIONS->regions[i];
1265 AudioSelections * sel =
1266 (AudioSelections *)
1267 arranger_selections_new (
1268 ARRANGER_SELECTIONS_TYPE_AUDIO);
1269 region_identifier_copy (
1270 &sel->region_id, &r->id);
1271 sel->has_selection = true;
1272 ArrangerObject * r_obj =
1273 (ArrangerObject *) r;
1274
1275 /* timeline start pos */
1276 position_set_to_pos (
1277 &sel->sel_start, &r_obj->clip_start_pos);
1278 position_add_ticks (
1279 &sel->sel_start, r_obj->pos.ticks);
1280
1281 /* timeline end pos */
1282 position_set_to_pos (
1283 &sel->sel_end, &r_obj->loop_end_pos);
1284 position_add_ticks (
1285 &sel->sel_end, r_obj->pos.ticks);
1286 if (position_is_after (
1287 &sel->sel_end, &r_obj->end_pos))
1288 {
1289 position_set_to_pos (
1290 &sel->sel_end, &r_obj->end_pos);
1291 }
1292
1293 GError * err = NULL;
1294 bool ret =
1295 arranger_selections_action_perform_edit_audio_function (
1296 (ArrangerSelections *) sel,
1297 data->audio_func, NULL, &err);
1298 if (!ret)
1299 {
1300 HANDLE_ERROR (
1301 err, "%s",
1302 _("Failed to apply audio function"));
1303 break;
1304 }
1305 else
1306 {
1307 UndoableAction * ua =
1308 undo_manager_get_last_action (
1309 UNDO_MANAGER);
1310 ua->num_actions = i + 1;
1311 }
1312 }
1313 }
1314
1315 static void
on_detect_bpm_activate(GtkMenuItem * item,void * user_data)1316 on_detect_bpm_activate (
1317 GtkMenuItem * item,
1318 void * user_data)
1319 {
1320 ZRegion * r = (ZRegion *) user_data;
1321 g_return_if_fail (IS_REGION_AND_NONNULL (r));
1322
1323 GArray * candidates =
1324 g_array_new (false, true, sizeof (float));
1325 bpm_t bpm =
1326 audio_region_detect_bpm (r, candidates);
1327
1328 GString * gstr = g_string_new (NULL);
1329 g_string_append_printf (
1330 gstr, _("Detected BPM: %.2f"), bpm);
1331 g_string_append (gstr, "\n\n");
1332 g_string_append_printf (
1333 gstr, _("Candidates:"));
1334 for (size_t i = 0; i < candidates->len; i++)
1335 {
1336 float candidate =
1337 g_array_index (candidates, float, i);
1338 g_string_append_printf (
1339 gstr, " %.2f", candidate);
1340 }
1341 char * str = g_string_free (gstr, false);
1342 ui_show_message_printf (
1343 MAIN_WINDOW, GTK_MESSAGE_INFO,
1344 "%s", str);
1345 g_free (str);
1346 }
1347
1348 /**
1349 * Show context menu at x, y.
1350 */
1351 void
timeline_arranger_widget_show_context_menu(ArrangerWidget * self,double x,double y)1352 timeline_arranger_widget_show_context_menu (
1353 ArrangerWidget * self,
1354 double x,
1355 double y)
1356 {
1357 GtkWidget *menu, *menuitem;
1358 menu = gtk_menu_new();
1359
1360 #define APPEND_TO_MENU \
1361 gtk_menu_shell_append ( \
1362 GTK_MENU_SHELL (menu), menuitem)
1363
1364 ArrangerObject * obj =
1365 arranger_widget_get_hit_arranger_object (
1366 (ArrangerWidget *) self,
1367 ARRANGER_OBJECT_TYPE_ALL, x, y);
1368
1369 if (obj)
1370 {
1371 int local_x = (int) (x - obj->full_rect.x);
1372 int local_y = (int) (y - obj->full_rect.y);
1373
1374 /* create cut, copy, duplicate, delete */
1375 menuitem =
1376 GTK_WIDGET (
1377 CREATE_CUT_MENU_ITEM ("app.cut"));
1378 APPEND_TO_MENU;
1379 menuitem =
1380 GTK_WIDGET (
1381 CREATE_COPY_MENU_ITEM ("app.copy"));
1382 APPEND_TO_MENU;
1383 menuitem =
1384 GTK_WIDGET (
1385 CREATE_DUPLICATE_MENU_ITEM (
1386 "app.duplicate"));
1387 APPEND_TO_MENU;
1388 menuitem =
1389 GTK_WIDGET (
1390 CREATE_DELETE_MENU_ITEM ("app.delete"));
1391 APPEND_TO_MENU;
1392 menuitem =
1393 gtk_separator_menu_item_new ();
1394 APPEND_TO_MENU;
1395
1396 if (timeline_selections_contains_only_regions (TL_SELECTIONS))
1397 {
1398 ZRegion * r = (ZRegion *) obj;
1399
1400 if (timeline_selections_contains_only_region_types (TL_SELECTIONS, REGION_TYPE_AUDIO))
1401 {
1402 if (TL_SELECTIONS->num_regions == 1)
1403 {
1404 menuitem =
1405 GTK_WIDGET (
1406 z_gtk_create_menu_item (
1407 _("Detect BPM"), NULL,
1408 F_NO_TOGGLE, NULL));
1409 gtk_widget_set_visible (
1410 GTK_WIDGET (menuitem), true);
1411 g_signal_connect (
1412 G_OBJECT (menuitem),
1413 "activate",
1414 G_CALLBACK (
1415 on_detect_bpm_activate),
1416 TL_SELECTIONS->regions[0]);
1417 gtk_menu_shell_append (
1418 GTK_MENU_SHELL (menu),
1419 menuitem);
1420 }
1421
1422 menuitem =
1423 GTK_WIDGET (
1424 z_gtk_create_menu_item (
1425 _("Apply Function"),
1426 "modulator", F_NO_TOGGLE,
1427 NULL));
1428 gtk_widget_set_visible (
1429 GTK_WIDGET (menuitem), true);
1430
1431 GtkMenu * submenu =
1432 GTK_MENU (gtk_menu_new ());
1433 gtk_widget_set_visible (
1434 GTK_WIDGET (submenu), true);
1435 for (int i = AUDIO_FUNCTION_INVERT;
1436 i < AUDIO_FUNCTION_CUSTOM_PLUGIN;
1437 i++)
1438 {
1439 if (i == AUDIO_FUNCTION_NORMALIZE_RMS
1440 || i == AUDIO_FUNCTION_NORMALIZE_LUFS)
1441 continue;
1442
1443 GtkWidget * submenu_item =
1444 GTK_WIDGET (
1445 z_gtk_create_menu_item (
1446 _(audio_function_type_to_string (i)),
1447 NULL, F_NO_TOGGLE, NULL));
1448 ContextMenuData * data =
1449 object_new (ContextMenuData);
1450 data->audio_func = i;
1451 g_signal_connect_data (
1452 G_OBJECT (submenu_item),
1453 "activate",
1454 G_CALLBACK (
1455 on_audio_func_activate),
1456 data,
1457 (GClosureNotify)
1458 free_context_menu_data,
1459 0);
1460 gtk_widget_set_visible (
1461 GTK_WIDGET (submenu_item),
1462 true);
1463 gtk_menu_shell_append (
1464 GTK_MENU_SHELL (submenu),
1465 submenu_item);
1466 }
1467 gtk_menu_item_set_submenu (
1468 GTK_MENU_ITEM (menuitem),
1469 GTK_WIDGET (submenu));
1470 gtk_menu_shell_append (
1471 GTK_MENU_SHELL (menu), menuitem);
1472 }
1473
1474 if (arranger_object_get_muted (obj))
1475 {
1476 menuitem =
1477 GTK_WIDGET (
1478 CREATE_UNMUTE_MENU_ITEM (
1479 "app.mute-selection"));
1480 gtk_actionable_set_action_target (
1481 GTK_ACTIONABLE (menuitem),
1482 "s", "timeline");
1483 }
1484 else
1485 {
1486 menuitem =
1487 GTK_WIDGET (
1488 CREATE_MUTE_MENU_ITEM (
1489 "app.mute-selection"));
1490 gtk_actionable_set_action_target (
1491 GTK_ACTIONABLE (menuitem),
1492 "s", "timeline");
1493 }
1494 gtk_menu_shell_append (
1495 GTK_MENU_SHELL(menu), menuitem);
1496
1497 if (timeline_selections_contains_only_region_types (TL_SELECTIONS, REGION_TYPE_MIDI))
1498 {
1499 menuitem =
1500 gtk_menu_item_new_with_label (
1501 _("Export as MIDI file"));
1502 gtk_menu_shell_append (
1503 GTK_MENU_SHELL(menu), menuitem);
1504 g_signal_connect (
1505 menuitem, "activate",
1506 G_CALLBACK (
1507 timeline_arranger_on_export_as_midi_file_clicked),
1508 r);
1509 }
1510
1511 if (r->id.type == REGION_TYPE_AUDIO)
1512 {
1513 /* create fade menus */
1514 if (arranger_object_is_fade_in (
1515 obj, local_x, local_y, 0, 0))
1516 {
1517 create_fade_preset_menu (
1518 self, menu, obj, 1);
1519 }
1520 if (arranger_object_is_fade_out (
1521 obj, local_x, local_y, 0, 0))
1522 {
1523 create_fade_preset_menu (
1524 self, menu, obj, 0);
1525 }
1526
1527 /* create musical mode menu */
1528 create_musical_mode_pset_menu (
1529 self, menu, obj);
1530 }
1531
1532 menuitem =
1533 gtk_menu_item_new_with_label (
1534 _("Quick bounce"));
1535 gtk_menu_shell_append (
1536 GTK_MENU_SHELL(menu), menuitem);
1537 g_signal_connect (
1538 menuitem, "activate",
1539 G_CALLBACK (
1540 timeline_arranger_on_quick_bounce_clicked),
1541 r);
1542
1543 menuitem =
1544 gtk_menu_item_new_with_label (
1545 _("Bounce..."));
1546 gtk_menu_shell_append (
1547 GTK_MENU_SHELL(menu), menuitem);
1548 g_signal_connect (
1549 menuitem, "activate",
1550 G_CALLBACK (
1551 timeline_arranger_on_bounce_clicked),
1552 r);
1553 }
1554 }
1555 else
1556 {
1557 menuitem =
1558 GTK_WIDGET (
1559 CREATE_PASTE_MENU_ITEM ("app.paste"));
1560 APPEND_TO_MENU;
1561 }
1562
1563 #undef APPEND_TO_MENU
1564
1565 gtk_menu_attach_to_widget (
1566 GTK_MENU (menu), GTK_WIDGET (self), NULL);
1567 gtk_widget_show_all (menu);
1568 gtk_menu_popup_at_pointer (
1569 GTK_MENU (menu), NULL);
1570 }
1571
1572 /**
1573 * Fade up/down.
1574 *
1575 * @param fade_in 1 for in, 0 for out.
1576 */
1577 void
timeline_arranger_widget_fade_up(ArrangerWidget * self,double offset_y,int fade_in)1578 timeline_arranger_widget_fade_up (
1579 ArrangerWidget * self,
1580 double offset_y,
1581 int fade_in)
1582 {
1583 for (int i = 0; i < TL_SELECTIONS->num_regions; i++)
1584 {
1585 ZRegion * r = TL_SELECTIONS->regions[i];
1586 ArrangerObject * obj =
1587 (ArrangerObject *) r;
1588
1589 CurveOptions * opts =
1590 fade_in ?
1591 &obj->fade_in_opts : &obj->fade_out_opts;
1592 double delta =
1593 (self->last_offset_y - offset_y) * 0.008;
1594 opts->curviness =
1595 CLAMP (opts->curviness + delta, - 1.0, 1.0);
1596 }
1597 }
1598
1599 static void
highlight_timeline(ArrangerWidget * self,GdkModifierType mask,int x,int y,Track * track,TrackLane * lane)1600 highlight_timeline (
1601 ArrangerWidget * self,
1602 GdkModifierType mask,
1603 int x,
1604 int y,
1605 Track * track,
1606 TrackLane * lane)
1607 {
1608 /* get default size */
1609 int ticks =
1610 snap_grid_get_default_ticks (
1611 SNAP_GRID_TIMELINE);
1612 Position length_pos;
1613 position_from_ticks (&length_pos, ticks);
1614 int width_px =
1615 ui_pos_to_px_timeline (&length_pos, false);
1616
1617 /* get snapped x */
1618 Position pos;
1619 ui_px_to_pos_timeline (x, &pos, true);
1620 if (!(mask & GDK_SHIFT_MASK))
1621 {
1622 position_snap_simple (
1623 &pos, SNAP_GRID_TIMELINE);
1624 x = ui_pos_to_px_timeline (&pos, true);
1625 }
1626
1627 int height = TRACK_DEF_HEIGHT;
1628 /* if track, get y/height inside track */
1629 if (track)
1630 {
1631 height = (int) track->main_height;
1632 int track_y_local =
1633 track_widget_get_local_y (
1634 track->widget, self, (int) y);
1635 if (lane)
1636 {
1637 y -= track_y_local - lane->y;
1638 height = (int) lane->height;
1639 }
1640 else
1641 {
1642 y -= track_y_local;
1643 }
1644 }
1645 /* else if no track, get y/height under last
1646 * visible track */
1647 else
1648 {
1649 /* get y below the track */
1650 int y_after_last_track = 0;
1651 for (int i = 0; i < TRACKLIST->num_tracks;
1652 i++)
1653 {
1654 Track * t = TRACKLIST->tracks[i];
1655 if (track_get_should_be_visible (t)
1656 &&
1657 track_is_pinned (t) ==
1658 self->is_pinned)
1659 {
1660 y_after_last_track +=
1661 (int)
1662 track_get_full_visible_height (t);
1663 }
1664 }
1665 y = y_after_last_track;
1666 }
1667
1668 GdkRectangle highlight_rect = {
1669 x, y, width_px, height };
1670 arranger_widget_set_highlight_rect (
1671 self, &highlight_rect);
1672 }
1673
1674 static void
on_dnd_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time,ArrangerWidget * self)1675 on_dnd_data_received (
1676 GtkWidget * widget,
1677 GdkDragContext * context,
1678 gint x,
1679 gint y,
1680 GtkSelectionData * data,
1681 guint info,
1682 guint time,
1683 ArrangerWidget * self)
1684 {
1685 GdkAtom target =
1686 gtk_selection_data_get_target (data);
1687 char * target_name = gdk_atom_name (target);
1688
1689 Track * track =
1690 timeline_arranger_widget_get_track_at_y (
1691 self, y);
1692 TrackLane * lane =
1693 timeline_arranger_widget_get_track_lane_at_y (
1694 self, y);
1695 AutomationTrack * at =
1696 timeline_arranger_widget_get_at_at_y (
1697 self, y);
1698
1699 GdkModifierType mask;
1700 z_gtk_widget_get_mask (widget, &mask);
1701
1702 highlight_timeline (
1703 self, mask, x, y, track, lane);
1704
1705 g_message (
1706 "(%u) dnd data received (timeline - is "
1707 "highlighted %d): %s",
1708 time, self->is_highlighted, target_name);
1709
1710 /* determine if moving or copying */
1711 GdkDragAction action =
1712 gdk_drag_context_get_selected_action (
1713 context);
1714
1715 if (target ==
1716 GET_ATOM (TARGET_ENTRY_CHORD_DESCR) &&
1717 self->is_highlighted && track &&
1718 track_type_has_piano_roll (track->type))
1719 {
1720 ChordDescriptor * descr = NULL;
1721 const guchar * my_data =
1722 gtk_selection_data_get_data (data);
1723 memcpy (&descr, my_data, sizeof (descr));
1724
1725 /* create chord region */
1726 Position pos, end_pos;
1727 ui_px_to_pos_timeline (
1728 self->highlight_rect.x, &pos, true);
1729 ui_px_to_pos_timeline (
1730 self->highlight_rect.x +
1731 self->highlight_rect.width,
1732 &end_pos, true);
1733 int lane_pos =
1734 lane ? lane->pos :
1735 (track->num_lanes == 1 ?
1736 0 : track->num_lanes - 2);
1737 int idx_in_lane =
1738 track->lanes[lane_pos]->num_regions;
1739 ZRegion * region =
1740 midi_region_new_from_chord_descr (
1741 &pos, descr,
1742 track_get_name_hash (track),
1743 lane_pos, idx_in_lane);
1744 track_add_region (
1745 track, region, NULL, lane_pos,
1746 F_GEN_NAME, F_PUBLISH_EVENTS);
1747 arranger_object_select (
1748 (ArrangerObject *) region, F_SELECT,
1749 F_NO_APPEND,
1750 F_NO_PUBLISH_EVENTS);
1751
1752 GError * err = NULL;
1753 bool ret =
1754 arranger_selections_action_perform_create (
1755 TL_SELECTIONS, &err);
1756 if (!ret)
1757 {
1758 HANDLE_ERROR (
1759 err, "%s",
1760 _("Failed to create selections"));
1761 }
1762 }
1763 else if (target ==
1764 GET_ATOM (TARGET_ENTRY_URI_LIST) ||
1765 target ==
1766 GET_ATOM (TARGET_ENTRY_SUPPORTED_FILE))
1767 {
1768 if (at)
1769 {
1770 /* nothing to do */
1771 goto finish_data_received;
1772 }
1773
1774 SupportedFile * file = NULL;
1775 char ** uris = NULL;
1776 if (target ==
1777 GET_ATOM (TARGET_ENTRY_SUPPORTED_FILE))
1778 {
1779 const guchar *my_data =
1780 gtk_selection_data_get_data (data);
1781 memcpy (&file, my_data, sizeof (file));
1782 }
1783 else
1784 {
1785 uris = gtk_selection_data_get_uris (data);
1786 }
1787
1788 Position pos;
1789 ui_px_to_pos_timeline (
1790 self->highlight_rect.x, &pos, true);
1791 tracklist_handle_file_drop (
1792 TRACKLIST, uris, file, track, lane, &pos,
1793 true);
1794 }
1795
1796 if (action == GDK_ACTION_COPY)
1797 {
1798 }
1799 else if (action == GDK_ACTION_MOVE)
1800 {
1801 }
1802
1803 finish_data_received:
1804 arranger_widget_set_highlight_rect (self, NULL);
1805 }
1806
1807 static gboolean
on_dnd_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,ArrangerWidget * self)1808 on_dnd_motion (
1809 GtkWidget * widget,
1810 GdkDragContext * context,
1811 gint x,
1812 gint y,
1813 guint time,
1814 ArrangerWidget * self)
1815 {
1816 AutomationTrack * at =
1817 timeline_arranger_widget_get_at_at_y (self, y);
1818 TrackLane * lane =
1819 timeline_arranger_widget_get_track_lane_at_y (
1820 self, y);
1821 (void) lane;
1822 Track * track =
1823 timeline_arranger_widget_get_track_at_y (
1824 self, y);
1825
1826 GdkModifierType mask;
1827 z_gtk_widget_get_mask (widget, &mask);
1828
1829 arranger_widget_set_highlight_rect (self, NULL);
1830
1831 GdkAtom target =
1832 gtk_drag_dest_find_target (
1833 widget, context, NULL);
1834 if (target == GDK_NONE)
1835 {
1836 g_message ("target is none");
1837 gdk_drag_status (context, 0, time);
1838 }
1839 else if (target ==
1840 GET_ATOM (TARGET_ENTRY_CHORD_DESCR))
1841 {
1842 if (at || !track ||
1843 !track_type_has_piano_roll (track->type))
1844 {
1845 /* nothing to do */
1846 return false;
1847 }
1848
1849 /* highlight track */
1850 highlight_timeline (
1851 self, mask, x, y, track, lane);
1852
1853 return true;
1854 }
1855 else if (target ==
1856 GET_ATOM (TARGET_ENTRY_URI_LIST) ||
1857 target ==
1858 GET_ATOM (TARGET_ENTRY_SUPPORTED_FILE))
1859 {
1860 /* if current track exists and current track
1861 * supports dnd highlight */
1862 if (track)
1863 {
1864 if (track->type != TRACK_TYPE_MIDI &&
1865 track->type !=
1866 TRACK_TYPE_INSTRUMENT &&
1867 track->type != TRACK_TYPE_AUDIO)
1868 {
1869 return false;
1870 }
1871
1872 /* track is compatible, highlight */
1873 highlight_timeline (
1874 self, mask, x, y, track, lane);
1875 g_message ("highlighting track");
1876
1877 return true;
1878 }
1879 /* else if no track, highlight below the
1880 * last track TODO */
1881 else
1882 {
1883 highlight_timeline (
1884 self, mask, x, y, NULL, NULL);
1885 }
1886 }
1887 else
1888 {
1889 }
1890
1891 return true;
1892 }
1893
1894 static void
on_dnd_leave(GtkWidget * widget,GdkDragContext * context,guint time,ArrangerWidget * self)1895 on_dnd_leave (
1896 GtkWidget * widget,
1897 GdkDragContext * context,
1898 guint time,
1899 ArrangerWidget * self)
1900 {
1901 g_message (
1902 "(%u) dnd leaving timeline, unhighlighting "
1903 "rect",
1904 time);
1905
1906 arranger_widget_set_highlight_rect (self, NULL);
1907 }
1908
1909 /**
1910 * Sets up the timeline arranger as a drag dest.
1911 */
1912 void
timeline_arranger_setup_drag_dest(ArrangerWidget * self)1913 timeline_arranger_setup_drag_dest (
1914 ArrangerWidget * self)
1915 {
1916 /* set as drag dest */
1917 GtkTargetEntry entries[] = {
1918 {
1919 (char *) TARGET_ENTRY_CHORD_DESCR,
1920 GTK_TARGET_SAME_APP,
1921 symap_map (ZSYMAP, TARGET_ENTRY_CHORD_DESCR),
1922 },
1923 {
1924 (char *) TARGET_ENTRY_SUPPORTED_FILE,
1925 GTK_TARGET_SAME_APP,
1926 symap_map (
1927 ZSYMAP, TARGET_ENTRY_SUPPORTED_FILE),
1928 },
1929 {
1930 (char *) TARGET_ENTRY_URI_LIST,
1931 GTK_TARGET_SAME_APP,
1932 symap_map (ZSYMAP, TARGET_ENTRY_URI_LIST),
1933 },
1934 {
1935 (char *) TARGET_ENTRY_URI_LIST,
1936 GTK_TARGET_OTHER_APP,
1937 symap_map (ZSYMAP, TARGET_ENTRY_URI_LIST),
1938 },
1939 };
1940 gtk_drag_dest_set (
1941 GTK_WIDGET (self),
1942 GTK_DEST_DEFAULT_MOTION |
1943 GTK_DEST_DEFAULT_DROP,
1944 entries, G_N_ELEMENTS (entries),
1945 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1946
1947 g_signal_connect (
1948 GTK_WIDGET (self), "drag-data-received",
1949 G_CALLBACK (on_dnd_data_received), self);
1950 g_signal_connect (
1951 GTK_WIDGET (self), "drag-motion",
1952 G_CALLBACK (on_dnd_motion), self);
1953 g_signal_connect (
1954 GTK_WIDGET (self), "drag-leave",
1955 G_CALLBACK (on_dnd_leave), self);
1956 }
1957