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