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