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 /**
21  * \file
22  *
23  * The automation containing regions and other
24  * objects.
25  */
26 
27 #include "actions/arranger_selections.h"
28 #include "actions/undoable_action.h"
29 #include "actions/undo_manager.h"
30 #include "audio/automation_region.h"
31 #include "audio/automation_track.h"
32 #include "audio/automation_tracklist.h"
33 #include "audio/audio_track.h"
34 #include "audio/audio_bus_track.h"
35 #include "audio/channel.h"
36 #include "audio/chord_object.h"
37 #include "audio/chord_track.h"
38 #include "audio/control_port.h"
39 #include "audio/curve.h"
40 #include "audio/instrument_track.h"
41 #include "audio/marker_track.h"
42 #include "audio/master_track.h"
43 #include "audio/midi_region.h"
44 #include "audio/scale_object.h"
45 #include "audio/track.h"
46 #include "audio/tracklist.h"
47 #include "audio/transport.h"
48 #include "gui/backend/event.h"
49 #include "gui/backend/event_manager.h"
50 #include "gui/backend/automation_selections.h"
51 #include "gui/widgets/arranger.h"
52 #include "gui/widgets/automation_arranger.h"
53 #include "gui/widgets/automation_editor_space.h"
54 #include "gui/widgets/automation_point.h"
55 #include "gui/widgets/bot_dock_edge.h"
56 #include "gui/widgets/center_dock.h"
57 #include "gui/widgets/chord_object.h"
58 #include "gui/widgets/clip_editor.h"
59 #include "gui/widgets/clip_editor_inner.h"
60 #include "gui/widgets/color_area.h"
61 #include "gui/widgets/editor_ruler.h"
62 #include "gui/widgets/main_window.h"
63 #include "gui/widgets/marker.h"
64 #include "gui/widgets/midi_arranger.h"
65 #include "gui/widgets/midi_region.h"
66 #include "gui/widgets/pinned_tracklist.h"
67 #include "gui/widgets/midi_note.h"
68 #include "gui/widgets/region.h"
69 #include "gui/widgets/scale_object.h"
70 #include "gui/widgets/ruler.h"
71 #include "gui/widgets/automation_arranger.h"
72 #include "gui/widgets/track.h"
73 #include "gui/widgets/tracklist.h"
74 #include "project.h"
75 #include "settings/settings.h"
76 #include "utils/arrays.h"
77 #include "utils/cairo.h"
78 #include "utils/error.h"
79 #include "utils/flags.h"
80 #include "utils/objects.h"
81 #include "utils/ui.h"
82 #include "zrythm.h"
83 #include "zrythm_app.h"
84 
85 #include <gtk/gtk.h>
86 #include <glib/gi18n.h>
87 
88 /**
89  * Create an AutomationPointat the given Position
90  * in the given Track's AutomationTrack.
91  *
92  * @param pos The pre-snapped position.
93  */
94 void
automation_arranger_widget_create_ap(ArrangerWidget * self,const Position * pos,const double start_y,ZRegion * region,bool autofilling)95 automation_arranger_widget_create_ap (
96   ArrangerWidget * self,
97   const Position * pos,
98   const double     start_y,
99   ZRegion *        region,
100   bool             autofilling)
101 {
102   AutomationTrack * at =
103     region_get_automation_track (region);
104   g_return_if_fail (at);
105   Port * port =
106     port_find_from_identifier (&at->port_id);
107   g_return_if_fail (port);
108 
109   if (!autofilling)
110     {
111       self->action =
112         UI_OVERLAY_ACTION_CREATING_MOVING;
113     }
114 
115   ArrangerObject * region_obj =
116     (ArrangerObject *) region;
117 
118   /* get local pos */
119   Position local_pos;
120   position_from_ticks (
121     &local_pos,
122     pos->ticks -
123     region_obj->pos.ticks);
124 
125   int height =
126     gtk_widget_get_allocated_height (
127       GTK_WIDGET (self));
128   /* do height - because it's uside down */
129   float normalized_val =
130     (float) ((height - start_y) / height);
131   g_message (
132     "normalized val is %f", (double) normalized_val);
133 
134   /* clamp the value because the cursor might be
135    * outside the widget */
136   normalized_val = CLAMP (normalized_val, 0.f, 1.f);
137 
138   float value =
139     control_port_normalized_val_to_real (
140       port, normalized_val);
141 
142   /* create a new ap */
143   AutomationPoint * ap =
144     automation_point_new_float (
145       value, normalized_val, &local_pos);
146   ArrangerObject * ap_obj =
147     (ArrangerObject *) ap;
148 
149   /* set it as start object */
150   self->start_object = ap_obj;
151 
152   /* add it to automation track */
153   automation_region_add_ap (
154     region, ap, F_PUBLISH_EVENTS);
155 
156   /* set position to all counterparts */
157   arranger_object_set_position (
158     ap_obj, &local_pos,
159     ARRANGER_OBJECT_POSITION_TYPE_START,
160     F_NO_VALIDATE);
161 
162   EVENTS_PUSH (
163     ET_ARRANGER_OBJECT_CREATED, ap);
164   arranger_object_select (
165     ap_obj, F_SELECT, F_NO_APPEND,
166     F_NO_PUBLISH_EVENTS);
167 }
168 
169 /**
170  * Change curviness of selected curves.
171  */
172 void
automation_arranger_widget_resize_curves(ArrangerWidget * self,double offset_y)173 automation_arranger_widget_resize_curves (
174   ArrangerWidget * self,
175   double           offset_y)
176 {
177   double diff = offset_y - self->last_offset_y;
178   diff = - diff;
179   diff = diff / 120.0;
180   for (int i = 0;
181        i < AUTOMATION_SELECTIONS->num_automation_points; i++)
182     {
183       AutomationPoint * ap =
184         AUTOMATION_SELECTIONS->automation_points[i];
185       double new_curve_val =
186         CLAMP (
187           ap->curve_opts.curviness + diff,
188           - 1.0, 1.0);
189       automation_point_set_curviness (
190         ap, new_curve_val);
191     }
192 
193   EVENTS_PUSH (
194     ET_ARRANGER_SELECTIONS_CHANGED,
195     AUTOMATION_SELECTIONS);
196 }
197 
198 /** Used for passing a curve algorithm to some
199  * actions. */
200 typedef struct CurveAlgorithmInfo
201 {
202   AutomationPoint * ap;
203   CurveAlgorithm    algo;
204 } CurveAlgorithmInfo;
205 
206 static void
on_curve_algorithm_selected(GtkMenuItem * menu_item,CurveAlgorithmInfo * info)207 on_curve_algorithm_selected (
208   GtkMenuItem *        menu_item,
209   CurveAlgorithmInfo * info)
210 {
211   g_return_if_fail (
212     AUTOMATION_SELECTIONS->num_automation_points ==
213       1 &&
214     AUTOMATION_SELECTIONS->automation_points[0] ==
215       info->ap);
216 
217   /* clone the selections before the change */
218   ArrangerSelections * sel_before =
219     arranger_selections_clone (
220       (ArrangerSelections *) AUTOMATION_SELECTIONS);
221 
222   /* change */
223   info->ap->curve_opts.algo = info->algo;
224 
225   /* perform action */
226   GError * err = NULL;
227   bool ret =
228     arranger_selections_action_perform_edit (
229       sel_before,
230       (ArrangerSelections *) AUTOMATION_SELECTIONS,
231       ARRANGER_SELECTIONS_ACTION_EDIT_PRIMITIVE,
232       true, &err);
233   if (!ret)
234     {
235       HANDLE_ERROR (
236         err, "%s",
237         _("Failed to set curve algorithm"));
238     }
239 
240   free (info);
241   arranger_selections_free_full (sel_before);
242 }
243 
244 /**
245  * Show context menu at x, y.
246  */
247 void
automation_arranger_widget_show_context_menu(ArrangerWidget * self,double x,double y)248 automation_arranger_widget_show_context_menu (
249   ArrangerWidget * self,
250   double           x,
251   double           y)
252 {
253   GtkWidget *menu, *menuitem;
254   menu = gtk_menu_new();
255 
256   ArrangerObject * obj =
257     arranger_widget_get_hit_arranger_object (
258       (ArrangerWidget *) self,
259       ARRANGER_OBJECT_TYPE_AUTOMATION_POINT, x, y);
260   AutomationPoint * ap = (AutomationPoint *) obj;
261 
262   if (ap)
263     {
264       /* add curve algorithm selection */
265       menuitem =
266         gtk_menu_item_new_with_label (
267           _("Curve algorithm"));
268       GtkMenu * submenu =
269         GTK_MENU (gtk_menu_new ());
270       gtk_widget_set_visible (
271         GTK_WIDGET (submenu), 1);
272       GtkMenuItem * submenu_item;
273       for (int i = 0; i < NUM_CURVE_ALGORITHMS; i++)
274         {
275           char name[100];
276           curve_algorithm_get_localized_name (
277             (CurveAlgorithm) i, name);
278           submenu_item =
279             GTK_MENU_ITEM (
280               gtk_check_menu_item_new_with_label (
281                 name));
282           gtk_check_menu_item_set_draw_as_radio (
283             GTK_CHECK_MENU_ITEM (submenu_item), 1);
284           if ((CurveAlgorithm) i ==
285                 ap->curve_opts.algo)
286             {
287               gtk_check_menu_item_set_active (
288                 GTK_CHECK_MENU_ITEM (submenu_item), 1);
289             }
290 
291           CurveAlgorithmInfo * info =
292             object_new (CurveAlgorithmInfo);
293           info->algo = (CurveAlgorithm) i;
294           info->ap = ap;
295           g_signal_connect (
296             G_OBJECT (submenu_item), "activate",
297             G_CALLBACK (on_curve_algorithm_selected),
298             info);
299           gtk_menu_shell_append (
300             GTK_MENU_SHELL (submenu),
301             GTK_WIDGET (submenu_item));
302           gtk_widget_set_visible (
303             GTK_WIDGET (submenu_item), 1);
304         }
305 
306       gtk_menu_item_set_submenu (
307         GTK_MENU_ITEM (menuitem),
308         GTK_WIDGET (submenu));
309       gtk_widget_set_visible (
310         GTK_WIDGET (menuitem), 1);
311 
312       gtk_menu_shell_append (
313         GTK_MENU_SHELL (menu),
314         GTK_WIDGET (menuitem));
315     }
316 
317   gtk_widget_show_all (GTK_WIDGET (menu));
318   gtk_menu_popup_at_pointer (
319     GTK_MENU (menu), NULL);
320 }
321 
322 /**
323  * Called when using the edit tool.
324  *
325  * @return Whether an automation point was moved.
326  */
327 bool
automation_arranger_move_hit_aps(ArrangerWidget * self,double x,double y)328 automation_arranger_move_hit_aps (
329   ArrangerWidget * self,
330   double           x,
331   double           y)
332 {
333   int height =
334     gtk_widget_get_allocated_height (
335       GTK_WIDGET (self));
336 
337   /* get snapped x */
338   Position pos;
339   arranger_widget_px_to_pos (
340     self, x, &pos, true);
341   if (!self->shift_held &&
342       SNAP_GRID_ANY_SNAP (self->snap_grid))
343     {
344       position_snap (
345         &self->earliest_obj_start_pos,
346         &pos, NULL, NULL,
347         self->snap_grid);
348       x =
349         arranger_widget_pos_to_px (
350           self, &pos, true);
351     }
352 
353   /* move any hit automation points */
354   ArrangerObject * obj =
355     arranger_widget_get_hit_arranger_object (
356       self, ARRANGER_OBJECT_TYPE_AUTOMATION_POINT,
357       x, -1);
358   if (obj)
359     {
360       AutomationPoint * ap = (AutomationPoint *) obj;
361       if (automation_point_is_point_hit (ap, x, -1))
362         {
363           arranger_object_select (
364             obj, F_SELECT, F_APPEND,
365             F_NO_PUBLISH_EVENTS);
366 
367           Port * port =
368             automation_point_get_port (ap);
369           g_return_val_if_fail (port, false);
370 
371           /* move it to the y value */
372           /* do height - because it's uside down */
373           float normalized_val =
374             (float) ((height - y) / height);
375 
376           /* clamp the value because the cursor might
377            * be outside the widget */
378           normalized_val =
379             CLAMP (normalized_val, 0.f, 1.f);
380           float value =
381             control_port_normalized_val_to_real (
382               port, normalized_val);
383           automation_point_set_fvalue (
384             ap, value, F_NOT_NORMALIZED,
385             F_PUBLISH_EVENTS);
386 
387           return true;
388         }
389     }
390 
391   return false;
392 }
393