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, ®ion->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, ®ion->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