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 /** \file
21  */
22 
23 #include <math.h>
24 
25 #include "audio/automation_point.h"
26 #include "audio/automation_region.h"
27 #include "audio/automation_track.h"
28 #include "audio/channel.h"
29 #include "audio/control_port.h"
30 #include "audio/instrument_track.h"
31 #include "audio/port.h"
32 #include "audio/position.h"
33 #include "audio/track.h"
34 #include "gui/backend/event.h"
35 #include "gui/backend/event_manager.h"
36 #include "gui/widgets/automation_arranger.h"
37 #include "gui/widgets/automation_point.h"
38 #include "gui/widgets/center_dock.h"
39 #include "plugins/lv2_plugin.h"
40 #include "plugins/plugin.h"
41 #include "project.h"
42 #include "settings/settings.h"
43 #include "utils/flags.h"
44 #include "utils/math.h"
45 #include "utils/objects.h"
46 #include "utils/string.h"
47 #include "zrythm_app.h"
48 
49 static AutomationPoint *
_create_new(const Position * pos)50 _create_new (
51   const Position *        pos)
52 {
53   AutomationPoint * self =
54     object_new (AutomationPoint);
55 
56   self->schema_version =
57     AUTOMATION_POINT_SCHEMA_VERSION;
58 
59   ArrangerObject * obj =
60     (ArrangerObject *) self;
61   arranger_object_init (obj);
62   obj->pos = *pos;
63   obj->type = ARRANGER_OBJECT_TYPE_AUTOMATION_POINT;
64   curve_opts_init (&self->curve_opts);
65   self->curve_opts.curviness = 0;
66   self->curve_opts.algo =
67     ZRYTHM_TESTING ?
68       CURVE_ALGORITHM_SUPERELLIPSE :
69       (CurveAlgorithm)
70       g_settings_get_enum (
71         S_P_EDITING_AUTOMATION,
72         "curve-algorithm");
73 
74   self->index = -1;
75 
76   return self;
77 }
78 
79 /**
80  * Sets the ZRegion and the index in the
81  * region that the AutomationPoint
82  * belongs to, in all its counterparts.
83  */
84 void
automation_point_set_region_and_index(AutomationPoint * ap,ZRegion * region,int index)85 automation_point_set_region_and_index (
86   AutomationPoint * ap,
87   ZRegion *         region,
88   int               index)
89 {
90   g_return_if_fail (ap && region);
91   ArrangerObject * obj = (ArrangerObject *) ap;
92   region_identifier_copy (
93     &obj->region_id, &region->id);
94   ap->index = index;
95 
96   /* set the info to the transient too */
97   if ((ZRYTHM_HAVE_UI || ZRYTHM_TESTING) &&
98       PROJECT->loaded && obj->transient &&
99       arranger_object_should_orig_be_visible (obj))
100     {
101       ArrangerObject * trans_obj = obj->transient;
102       AutomationPoint * trans_ap =
103         (AutomationPoint *) trans_obj;
104       region_identifier_copy (
105         &trans_obj->region_id, &region->id);
106       trans_ap->index = index;
107     }
108 }
109 
110 int
automation_point_is_equal(AutomationPoint * a,AutomationPoint * b)111 automation_point_is_equal (
112   AutomationPoint * a,
113   AutomationPoint * b)
114 {
115   ArrangerObject * a_obj =
116     (ArrangerObject *) a;
117   ArrangerObject * b_obj =
118     (ArrangerObject *) b;
119   return
120     position_is_equal_ticks (
121       &a_obj->pos, &b_obj->pos) &&
122     math_floats_equal_epsilon (
123       a->fvalue, b->fvalue, 0.001f);
124 }
125 
126 /**
127  * Creates an AutomationPoint in the given
128  * AutomationTrack at the given Position.
129  */
130 AutomationPoint *
automation_point_new_float(const float value,const float normalized_val,const Position * pos)131 automation_point_new_float (
132   const float         value,
133   const float         normalized_val,
134   const Position *    pos)
135 {
136   AutomationPoint * self =
137     _create_new (pos);
138 
139   if (ZRYTHM_TESTING)
140     {
141       math_assert_nonnann (value);
142       math_assert_nonnann (normalized_val);
143     }
144 
145   self->fvalue = value;
146   self->normalized_val = normalized_val;
147 
148   return self;
149 }
150 
151 /**
152  * Returns if the curve of the AutomationPoint
153  * curves upwards as you move right on the x axis.
154  */
155 bool
automation_point_curves_up(AutomationPoint * self)156 automation_point_curves_up (
157   AutomationPoint * self)
158 {
159   ZRegion * region =
160     arranger_object_get_region (
161       (ArrangerObject *) self);
162   AutomationPoint * next_ap =
163     automation_region_get_next_ap (
164       region, self, true, true);
165 
166   if (!next_ap)
167     return false;
168 
169   if (next_ap->fvalue > self->fvalue)
170     return true;
171   else
172     return false;
173 }
174 
175 /**
176  * Sets the value from given real or normalized
177  * value and notifies interested parties.
178  *
179  * @param is_normalized Whether the given value is
180  *   normalized.
181  */
182 void
automation_point_set_fvalue(AutomationPoint * self,float real_val,bool is_normalized,bool pub_events)183 automation_point_set_fvalue (
184   AutomationPoint * self,
185   float             real_val,
186   bool              is_normalized,
187   bool              pub_events)
188 {
189   g_return_if_fail (self);
190 
191   Port * port =
192     automation_point_get_port (self);
193   g_return_if_fail (IS_PORT_AND_NONNULL (port));
194 
195   if (ZRYTHM_TESTING)
196     {
197       math_assert_nonnann (real_val);
198     }
199 
200   float normalized_val;
201   if (is_normalized)
202     {
203       g_message ("received normalized val %f",
204         (double) real_val);
205       normalized_val =
206         CLAMP (real_val, 0.f, 1.f);
207       real_val =
208         control_port_normalized_val_to_real (
209           port, normalized_val);
210     }
211   else
212     {
213       g_message (
214         "reveived real val %f", (double) real_val);
215       real_val =
216         CLAMP (real_val, port->minf, port->maxf);
217       normalized_val =
218         control_port_real_val_to_normalized (
219           port, real_val);
220     }
221   g_message ("setting to %f", (double) real_val);
222   self->fvalue = real_val;
223   self->normalized_val = normalized_val;
224 
225   if (ZRYTHM_TESTING)
226     {
227       math_assert_nonnann (self->fvalue);
228       math_assert_nonnann (self->normalized_val);
229     }
230 
231   ZRegion * region =
232     arranger_object_get_region (
233       (ArrangerObject *) self);
234   g_return_if_fail (region);
235 
236   /* don't set value - wait for engine to process
237    * it */
238 #if 0
239   control_port_set_val_from_normalized (
240     port, self->normalized_val, Z_F_AUTOMATING);
241 #endif
242 
243   if (pub_events)
244     {
245       EVENTS_PUSH (
246         ET_ARRANGER_OBJECT_CHANGED, self);
247     }
248 }
249 
250 /**
251  * The function to return a point on the curve.
252  *
253  * See https://stackoverflow.com/questions/17623152/how-map-tween-a-number-based-on-a-dynamic-curve
254  *
255  * @param ap The start point (0, 0).
256  * @param x Normalized x.
257  */
258 double
automation_point_get_normalized_value_in_curve(AutomationPoint * self,double x)259 automation_point_get_normalized_value_in_curve (
260   AutomationPoint * self,
261   double            x)
262 {
263   g_return_val_if_fail (
264     self && x >= 0.0 && x <= 1.0, 0.0);
265 
266   ZRegion * region =
267     arranger_object_get_region (
268       (ArrangerObject *) self);
269   AutomationPoint * next_ap =
270     automation_region_get_next_ap (
271       region, self, true, true);
272   if (!next_ap)
273     {
274       return self->fvalue;
275     }
276 
277   double dy;
278 
279   int start_higher =
280     next_ap->normalized_val < self->normalized_val;
281   dy =
282     curve_get_normalized_y (
283       x, &self->curve_opts, start_higher);
284   return dy;
285 }
286 
287 /**
288  * Sets the curviness of the AutomationPoint.
289  */
290 void
automation_point_set_curviness(AutomationPoint * self,const curviness_t curviness)291 automation_point_set_curviness (
292   AutomationPoint * self,
293   const curviness_t curviness)
294 {
295   if (math_doubles_equal (
296         self->curve_opts.curviness, curviness))
297     return;
298 
299   self->curve_opts.curviness = curviness;
300 }
301 
302 /**
303  * Convenience function to return the control port
304  * that this AutomationPoint is for.
305  */
306 Port *
automation_point_get_port(const AutomationPoint * const self)307 automation_point_get_port (
308   const AutomationPoint * const self)
309 {
310   const AutomationTrack * const at =
311     automation_point_get_automation_track (self);
312   g_return_val_if_fail (at, NULL);
313   Port * port =
314     port_find_from_identifier (&at->port_id);
315   g_return_val_if_fail (port, NULL);
316 
317   return port;
318 }
319 
320 /**
321  * Convenience function to return the
322  * AutomationTrack that this AutomationPoint is in.
323  */
324 AutomationTrack *
automation_point_get_automation_track(const AutomationPoint * const self)325 automation_point_get_automation_track (
326   const AutomationPoint * const self)
327 {
328   g_return_val_if_fail (self, NULL);
329   const ZRegion * const region =
330     arranger_object_get_region (
331       (const ArrangerObject * const) self);
332   g_return_val_if_fail (region, NULL);
333   return region_get_automation_track (region);
334 }
335