1 /*
2 * Copyright (C) 2019-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 <stdlib.h>
21
22 #include "audio/automation_point.h"
23 #include "audio/automation_region.h"
24 #include "audio/position.h"
25 #include "audio/region.h"
26 #include "gui/backend/automation_selections.h"
27 #include "gui/backend/event.h"
28 #include "gui/backend/event_manager.h"
29 #include "gui/widgets/automation_arranger.h"
30 #include "gui/widgets/automation_editor_space.h"
31 #include "gui/widgets/bot_dock_edge.h"
32 #include "gui/widgets/center_dock.h"
33 #include "gui/widgets/clip_editor_inner.h"
34 #include "gui/widgets/clip_editor.h"
35 #include "project.h"
36 #include "utils/arrays.h"
37 #include "utils/flags.h"
38 #include "utils/mem.h"
39 #include "utils/object_utils.h"
40 #include "utils/objects.h"
41 #include "zrythm_app.h"
42
43 int
automation_region_sort_func(const void * _a,const void * _b)44 automation_region_sort_func (
45 const void * _a, const void * _b)
46 {
47 AutomationPoint * a =
48 *(AutomationPoint * const *) _a;
49 AutomationPoint * b =
50 *(AutomationPoint * const *)_b;
51 ArrangerObject * a_obj =
52 (ArrangerObject *) a;
53 ArrangerObject * b_obj =
54 (ArrangerObject *) b;
55 long ret =
56 position_compare (
57 &a_obj->pos, &b_obj->pos);
58 if (ret == 0 &&
59 a->index <
60 b->index)
61 {
62 return -1;
63 }
64
65 return (int) CLAMP (ret, -1, 1);
66 }
67
68 ZRegion *
automation_region_new(const Position * start_pos,const Position * end_pos,unsigned int track_name_hash,int at_idx,int idx_inside_at)69 automation_region_new (
70 const Position * start_pos,
71 const Position * end_pos,
72 unsigned int track_name_hash,
73 int at_idx,
74 int idx_inside_at)
75 {
76 ZRegion * self = object_new (ZRegion);
77
78 self->id.type = REGION_TYPE_AUTOMATION;
79
80 self->aps_size = 2;
81 self->aps =
82 object_new_n (self->aps_size, AutomationPoint);
83
84 region_init (
85 self, start_pos, end_pos, track_name_hash,
86 at_idx, idx_inside_at);
87
88 return self;
89 }
90
91 /**
92 * Prints the automation in this Region.
93 */
94 void
automation_region_print_automation(ZRegion * self)95 automation_region_print_automation (
96 ZRegion * self)
97 {
98 AutomationPoint * ap;
99 ArrangerObject * ap_obj;
100 for (int i = 0; i < self->num_aps; i++)
101 {
102 ap = self->aps[i];
103 ap_obj = (ArrangerObject *) ap;
104 g_message ("%d", i);
105 position_print (&ap_obj->pos);
106 }
107 }
108
109 /**
110 * Forces sort of the automation points.
111 */
112 void
automation_region_force_sort(ZRegion * self)113 automation_region_force_sort (
114 ZRegion * self)
115 {
116 /* sort by position */
117 qsort (self->aps,
118 (size_t) self->num_aps,
119 sizeof (AutomationPoint *),
120 automation_region_sort_func);
121
122 /* refresh indices */
123 for (int i = 0; i < self->num_aps; i++)
124 {
125 automation_point_set_region_and_index (
126 self->aps[i], self, i);
127 }
128 }
129
130 /**
131 * Adds an AutomationPoint to the Region.
132 */
133 void
automation_region_add_ap(ZRegion * self,AutomationPoint * ap,int pub_events)134 automation_region_add_ap (
135 ZRegion * self,
136 AutomationPoint * ap,
137 int pub_events)
138 {
139 g_return_if_fail (
140 IS_REGION (self) && IS_ARRANGER_OBJECT (ap));
141
142 /* add point */
143 array_double_size_if_full (
144 self->aps, self->num_aps, self->aps_size,
145 AutomationPoint *);
146 array_append (
147 self->aps, self->num_aps, ap);
148
149 /* re-sort */
150 automation_region_force_sort (self);
151
152 if (pub_events)
153 {
154 EVENTS_PUSH (ET_ARRANGER_OBJECT_CREATED, ap);
155 }
156 }
157
158
159 /**
160 * Returns the AutomationPoint before the given
161 * one.
162 */
163 AutomationPoint *
automation_region_get_prev_ap(ZRegion * self,AutomationPoint * ap)164 automation_region_get_prev_ap (
165 ZRegion * self,
166 AutomationPoint * ap)
167 {
168 if (ap->index > 0)
169 return self->aps[ap->index - 1];
170
171 return NULL;
172 }
173
174 /**
175 * Returns the AutomationPoint after the given
176 * one.
177 *
178 * @param check_positions Compare positions instead
179 * of just getting the next index.
180 * @param check_transients Also check the transient
181 * of each object. This only matters if \ref
182 * check_positions is true. FIXME not used at
183 * the moment. Keep it around for abit then
184 * delete it if not needed.
185 */
186 AutomationPoint *
automation_region_get_next_ap(ZRegion * self,AutomationPoint * ap,bool check_positions,bool check_transients)187 automation_region_get_next_ap (
188 ZRegion * self,
189 AutomationPoint * ap,
190 bool check_positions,
191 bool check_transients)
192 {
193 g_return_val_if_fail (
194 self && ap, NULL);
195
196 if (check_positions)
197 {
198 check_transients =
199 ZRYTHM_HAVE_UI &&
200 MW_AUTOMATION_ARRANGER &&
201 MW_AUTOMATION_ARRANGER->action ==
202 UI_OVERLAY_ACTION_MOVING_COPY;
203 ArrangerObject * obj = (ArrangerObject *) ap;
204 AutomationPoint * next_ap = NULL;
205 ArrangerObject * next_obj = NULL;
206 for (int i = 0; i < self->num_aps; i++)
207 {
208 for (int j = 0;
209 j < (check_transients ? 2 : 1); j++)
210 {
211 AutomationPoint * cur_ap =
212 self->aps[i];
213 ArrangerObject * cur_obj =
214 (ArrangerObject *) cur_ap;
215 if (j == 1)
216 {
217 if (cur_obj->transient)
218 {
219 cur_obj = cur_obj->transient;
220 cur_ap =
221 (AutomationPoint *)
222 cur_obj;
223 }
224 else
225 continue;
226 }
227
228 if (cur_ap == ap)
229 continue;
230
231 if (position_is_after_or_equal (
232 &cur_obj->pos, &obj->pos) &&
233 (!next_obj ||
234 position_is_before (
235 &cur_obj->pos,
236 &next_obj->pos)))
237 {
238 next_obj = cur_obj;
239 next_ap = cur_ap;
240 }
241 }
242 }
243 return next_ap;
244 }
245 else if (ap->index < self->num_aps - 1)
246 return self->aps[ap->index + 1];
247
248 return NULL;
249 }
250
251 /**
252 * Removes the AutomationPoint from the ZRegion,
253 * optionally freeing it.
254 *
255 * @param free Free the AutomationPoint after
256 * removing it.
257 */
258 void
automation_region_remove_ap(ZRegion * self,AutomationPoint * ap,bool freeing_region,int free)259 automation_region_remove_ap (
260 ZRegion * self,
261 AutomationPoint * ap,
262 bool freeing_region,
263 int free)
264 {
265 g_return_if_fail (
266 IS_REGION (self) && IS_ARRANGER_OBJECT (ap));
267
268 g_message ("removing %p", ap);
269 /* deselect */
270 arranger_object_select (
271 (ArrangerObject *) ap, F_NO_SELECT,
272 F_APPEND,
273 F_NO_PUBLISH_EVENTS);
274
275 if (self->last_recorded_ap == ap)
276 {
277 self->last_recorded_ap = NULL;
278 }
279
280 array_delete (
281 self->aps, self->num_aps, ap);
282
283 if (!freeing_region)
284 {
285 for (int i = 0; i < self->num_aps; i++)
286 {
287 automation_point_set_region_and_index (
288 self->aps[i], self, i);
289 }
290 }
291
292 if (free)
293 {
294 /* free later otherwise causes problems
295 * while recording */
296 free_later (ap, arranger_object_free);
297 }
298
299 EVENTS_PUSH (
300 ET_ARRANGER_OBJECT_REMOVED,
301 ARRANGER_OBJECT_TYPE_AUTOMATION_POINT);
302 }
303
304 /**
305 * Returns the automation points since the last
306 * recorded automation point (if the last recorded
307 * automation point was before the current pos).
308 */
309 void
automation_region_get_aps_since_last_recorded(ZRegion * self,Position * pos,AutomationPoint ** aps,int * num_aps)310 automation_region_get_aps_since_last_recorded (
311 ZRegion * self,
312 Position * pos,
313 AutomationPoint ** aps,
314 int * num_aps)
315 {
316 *num_aps = 0;
317
318 ArrangerObject * last_recorded_obj =
319 (ArrangerObject *) self->last_recorded_ap;
320 if (!last_recorded_obj ||
321 position_is_before_or_equal (
322 pos, &last_recorded_obj->pos))
323 return;
324
325 for (int i = 0; i < self->num_aps; i++)
326 {
327 AutomationPoint * ap = self->aps[i];
328 ArrangerObject * ap_obj =
329 (ArrangerObject *) ap;
330
331 if (position_is_after (
332 &ap_obj->pos, &last_recorded_obj->pos) &&
333 position_is_before_or_equal (
334 &ap_obj->pos, pos))
335 {
336 aps[*num_aps] = ap;
337 (*num_aps)++;
338 }
339 }
340 }
341
342 /**
343 * Returns an automation point found within +/-
344 * delta_ticks from the position, or NULL.
345 *
346 * @param before_only Only check previous automation
347 * points.
348 */
349 AutomationPoint *
automation_region_get_ap_around(ZRegion * self,Position * _pos,double delta_ticks,bool before_only)350 automation_region_get_ap_around (
351 ZRegion * self,
352 Position * _pos,
353 double delta_ticks,
354 bool before_only)
355 {
356 Position pos;
357 position_set_to_pos (&pos, _pos);
358 AutomationTrack * at =
359 region_get_automation_track (self);
360 /* FIXME only check aps in this region */
361 AutomationPoint * ap =
362 automation_track_get_ap_before_pos (
363 at, &pos, true);
364 ArrangerObject * ap_obj =
365 (ArrangerObject *) ap;
366 if (ap &&
367 pos.ticks -
368 ap_obj->pos.ticks <=
369 (double) delta_ticks)
370 {
371 return ap;
372 }
373 else if (!before_only)
374 {
375 position_add_ticks (&pos, delta_ticks);
376 ap =
377 automation_track_get_ap_before_pos (
378 at, &pos, true);
379 ap_obj = (ArrangerObject *) ap;
380 if (ap)
381 {
382 double diff =
383 ap_obj->pos.ticks -
384 _pos->ticks;
385 if (diff >= 0.0)
386 return ap;
387 }
388 }
389
390 return NULL;
391 }
392
393 bool
automation_region_validate(ZRegion * self)394 automation_region_validate (
395 ZRegion * self)
396 {
397 for (int i = 0; i < self->num_aps; i++)
398 {
399 AutomationPoint * ap = self->aps[i];
400
401 g_return_val_if_fail (ap->index == i, false);
402 }
403
404 return true;
405 }
406
407 /**
408 * Frees members only but not the ZRegion itself.
409 *
410 * Regions should be free'd using region_free.
411 */
412 void
automation_region_free_members(ZRegion * self)413 automation_region_free_members (
414 ZRegion * self)
415 {
416 int i;
417 for (i = self->num_aps - 1; i >= 0; i--)
418 {
419 automation_region_remove_ap (
420 self, self->aps[i], true, F_FREE);
421 }
422
423 free (self->aps);
424 }
425