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 #include <math.h>
21 
22 #include "audio/automation_track.h"
23 #include "audio/automation_point.h"
24 #include "audio/automation_region.h"
25 #include "audio/control_port.h"
26 #include "audio/instrument_track.h"
27 #include "audio/track.h"
28 #include "gui/backend/event_manager.h"
29 #include "gui/widgets/arranger.h"
30 #include "gui/widgets/center_dock.h"
31 #include "gui/widgets/custom_button.h"
32 #include "gui/widgets/region.h"
33 #include "gui/widgets/timeline_arranger.h"
34 #include "gui/widgets/track.h"
35 #include "project.h"
36 #include "utils/arrays.h"
37 #include "utils/flags.h"
38 #include "utils/math.h"
39 #include "utils/mem.h"
40 #include "utils/objects.h"
41 #include "utils/string.h"
42 #include "zrythm_app.h"
43 
44 #include <glib/gi18n.h>
45 
46 static AutomationTrack *
_at_create(void)47 _at_create (void)
48 {
49   AutomationTrack * self =
50     object_new (AutomationTrack);
51 
52   port_identifier_init (&self->port_id);
53 
54   self->schema_version =
55     AUTOMATION_TRACK_SCHEMA_VERSION;
56 
57   return self;
58 }
59 
60 void
automation_track_init_loaded(AutomationTrack * self,AutomationTracklist * atl)61 automation_track_init_loaded (
62   AutomationTrack *     self,
63   AutomationTracklist * atl)
64 {
65   self->atl = atl;
66 
67   /* init regions */
68   self->regions_size =
69     (size_t) self->num_regions;
70   int i;
71   ZRegion * region;
72   for (i = 0; i < self->num_regions; i++)
73     {
74       region = self->regions[i];
75       arranger_object_init_loaded (
76         (ArrangerObject *) region);
77     }
78 }
79 
80 AutomationTrack *
automation_track_new(Port * port)81 automation_track_new (
82   Port * port)
83 {
84   AutomationTrack * self = _at_create ();
85 
86   self->regions_size = 1;
87   self->regions =
88     object_new_n (
89       self->regions_size, ZRegion *);
90 
91   self->height = TRACK_DEF_HEIGHT;
92 
93   g_return_val_if_fail (
94     port_identifier_validate (&port->id), NULL);
95   port_identifier_copy (
96     &self->port_id, &port->id);
97 
98   port->at = self;
99 
100   if (port->id.flags & PORT_FLAG_MIDI_AUTOMATABLE)
101     {
102       self->automation_mode =
103         AUTOMATION_MODE_RECORD;
104       self->record_mode =
105         AUTOMATION_RECORD_MODE_TOUCH;
106     }
107   else
108     self->automation_mode = AUTOMATION_MODE_READ;
109 
110   return self;
111 }
112 
113 NONNULL
114 bool
automation_track_validate(AutomationTrack * self)115 automation_track_validate (
116   AutomationTrack * self)
117 {
118   g_return_val_if_fail (
119     self->schema_version ==
120       AUTOMATION_TRACK_SCHEMA_VERSION &&
121     port_identifier_validate (&self->port_id),
122     false);
123 
124   unsigned int track_name_hash =
125     self->port_id.track_name_hash;
126   if (self->port_id.owner_type ==
127         PORT_OWNER_TYPE_PLUGIN)
128     {
129       g_return_val_if_fail (
130         self->port_id.plugin_id.track_name_hash ==
131           track_name_hash, false);
132     }
133   AutomationTrack * found_at =
134     automation_track_find_from_port_id (
135       &self->port_id, false);
136   if (found_at != self)
137     {
138       g_message (
139         "The automation track for the following "
140         "port identifier was not found");
141       port_identifier_print (&self->port_id);
142       g_message (
143         "automation tracks:");
144       AutomationTracklist * atl =
145         automation_track_get_automation_tracklist (
146           self);
147       automation_tracklist_print_ats (atl);
148       g_return_val_if_reached (false);
149     }
150   for (int j = 0; j < self->num_regions; j++)
151     {
152       ZRegion * r = self->regions[j];
153       g_return_val_if_fail (
154         r->id.track_name_hash == track_name_hash &&
155         r->id.at_idx == self->index &&
156         r->id.idx ==j, false);
157       for (int k = 0; k < r->num_aps; k++)
158         {
159           AutomationPoint * ap = r->aps[k];
160           ArrangerObject * obj =
161             (ArrangerObject *) ap;
162           g_return_val_if_fail (
163             obj->region_id.track_name_hash ==
164               track_name_hash, false);
165         }
166       for (int k = 0; k < r->num_midi_notes; k++)
167         {
168           MidiNote * mn = r->midi_notes[k];
169           ArrangerObject * obj =
170             (ArrangerObject *) mn;
171           g_return_val_if_fail (
172             obj->region_id.track_name_hash ==
173               track_name_hash, false);
174         }
175       for (int k = 0; k < r->num_chord_objects;
176            k++)
177         {
178           ChordObject * co = r->chord_objects[k];
179           ArrangerObject * obj =
180             (ArrangerObject *) co;
181           g_return_val_if_fail (
182             obj->region_id.track_name_hash ==
183               track_name_hash, false);
184         }
185     }
186 
187   return true;
188 }
189 
190 /**
191  * Gets the automation mode as a localized string.
192  */
193 void
automation_mode_get_localized(AutomationMode mode,char * buf)194 automation_mode_get_localized (
195   AutomationMode mode,
196   char *         buf)
197 {
198   switch (mode)
199     {
200     case AUTOMATION_MODE_READ:
201       strcpy (buf, _("On"));
202       break;
203     case AUTOMATION_MODE_RECORD:
204       strcpy (buf, _("Rec"));
205       break;
206     case AUTOMATION_MODE_OFF:
207       strcpy (buf, _("Off"));
208       break;
209     default:
210       g_return_if_reached ();
211     }
212 }
213 
214 /**
215  * Gets the automation mode as a localized string.
216  */
217 void
automation_record_mode_get_localized(AutomationRecordMode mode,char * buf)218 automation_record_mode_get_localized (
219   AutomationRecordMode mode,
220   char *         buf)
221 {
222   switch (mode)
223     {
224     case AUTOMATION_RECORD_MODE_TOUCH:
225       strcpy (buf, _("Touch"));
226       break;
227     case AUTOMATION_RECORD_MODE_LATCH:
228       strcpy (buf, _("Latch"));
229       break;
230     default:
231       g_return_if_reached ();
232     }
233 }
234 
235 /**
236  * Adds an automation ZRegion to the AutomationTrack.
237  *
238  * @note This must not be used directly. Use
239  *   track_add_region() instead.
240  */
241 void
automation_track_add_region(AutomationTrack * self,ZRegion * region)242 automation_track_add_region (
243   AutomationTrack * self,
244   ZRegion *         region)
245 {
246   automation_track_insert_region (
247     self, region, self->num_regions);
248 }
249 
250 /**
251  * Inserts an automation ZRegion to the
252  * AutomationTrack at the given index.
253  *
254  * @note This must not be used directly. Use
255  *   track_insert_region() instead.
256  */
257 void
automation_track_insert_region(AutomationTrack * self,ZRegion * region,int idx)258 automation_track_insert_region (
259   AutomationTrack * self,
260   ZRegion *         region,
261   int               idx)
262 {
263   g_return_if_fail (idx >= 0);
264   g_return_if_fail (
265     region->name &&
266     region->id.type == REGION_TYPE_AUTOMATION);
267 
268   array_double_size_if_full (
269     self->regions, self->num_regions,
270     self->regions_size, ZRegion *);
271   for (int i = self->num_regions; i > idx; i--)
272     {
273       self->regions[i] = self->regions[i - 1];
274       self->regions[i]->id.idx = i;
275       region_update_identifier (self->regions[i]);
276     }
277   self->num_regions++;
278 
279   self->regions[idx] = region;
280   region_set_automation_track (region, self);
281   region->id.idx = idx;
282   region_update_identifier (region);
283 }
284 
285 AutomationTracklist *
automation_track_get_automation_tracklist(AutomationTrack * self)286 automation_track_get_automation_tracklist (
287   AutomationTrack * self)
288 {
289   Track * track =
290     automation_track_get_track (self);
291   return
292     track_get_automation_tracklist (track);
293 }
294 
295 /**
296  * Returns the ZRegion that starts before
297  * given Position, if any.
298  *
299  * @param ends_after Whether to only check for
300  *   regions that also end after \ref pos (ie,
301  *   the region surrounds \ref pos), otherwise
302  *   get the region that ends last.
303  */
304 ZRegion *
automation_track_get_region_before_pos(const AutomationTrack * self,const Position * pos,bool ends_after)305 automation_track_get_region_before_pos (
306   const AutomationTrack * self,
307   const Position *        pos,
308   bool                    ends_after)
309 {
310   if (ends_after)
311     {
312       for (int i = self->num_regions - 1; i >= 0;
313            i--)
314         {
315           ZRegion * region = self->regions[i];
316           ArrangerObject * r_obj =
317             (ArrangerObject *) region;
318           if (position_is_before_or_equal (
319                 &r_obj->pos, pos) &&
320               position_is_after_or_equal (
321                 &r_obj->end_pos, pos))
322             return region;
323         }
324     }
325   else
326     {
327       /* find latest region */
328       ZRegion * latest_r = NULL;
329       long latest_distance = LONG_MIN;
330       for (int i = self->num_regions - 1; i >= 0;
331            i--)
332         {
333           ZRegion * region = self->regions[i];
334           ArrangerObject * r_obj =
335             (ArrangerObject *) region;
336           long distance_from_r_end =
337             r_obj->end_pos.frames - pos->frames;
338           if (position_is_before_or_equal (
339                 &r_obj->pos, pos) &&
340               distance_from_r_end > latest_distance)
341             {
342               latest_distance = distance_from_r_end;
343               latest_r = region;
344             }
345         }
346       return latest_r;
347     }
348   return NULL;
349 }
350 
351 /**
352  * Returns the automation point before the Position
353  * on the timeline.
354  *
355  * @param ends_after Whether to only check in
356  *   regions that also end after \ref pos (ie,
357  *   the region surrounds \ref pos), otherwise
358  *   check in the region that ends last.
359  */
360 AutomationPoint *
automation_track_get_ap_before_pos(const AutomationTrack * self,const Position * pos,bool ends_after)361 automation_track_get_ap_before_pos (
362   const AutomationTrack * self,
363   const Position *        pos,
364   bool                    ends_after)
365 {
366   ZRegion * r =
367     automation_track_get_region_before_pos (
368       self, pos, ends_after);
369   ArrangerObject * r_obj = (ArrangerObject *) r;
370 
371   if (!r || arranger_object_get_muted (r_obj))
372     {
373       return NULL;
374     }
375 
376   /* if region ends before pos, assume pos is the
377    * region's end pos */
378   long local_pos =
379     region_timeline_frames_to_local (
380       r,
381       !ends_after &&
382         (r_obj->end_pos.frames < pos->frames) ?
383           r_obj->end_pos.frames - 1 :
384           pos->frames,
385       F_NORMALIZE);
386   /*g_debug ("local pos %ld", local_pos);*/
387 
388   AutomationPoint * ap;
389   ArrangerObject * obj;
390   for (int i = r->num_aps - 1; i >= 0; i--)
391     {
392       ap = r->aps[i];
393       obj = (ArrangerObject *) ap;
394       if (obj->pos.frames <= local_pos)
395         return ap;
396     }
397 
398   return NULL;
399 }
400 
401 /**
402  * Finds the AutomationTrack associated with
403  * `port`.
404  *
405  * FIXME use hashtable
406  *
407  * @param track The track that owns the port, if
408  *   known.
409  */
410 AutomationTrack *
automation_track_find_from_port(Port * port,Track * track,bool basic_search)411 automation_track_find_from_port (
412   Port *  port,
413   Track * track,
414   bool    basic_search)
415 {
416   if (!track)
417     {
418       track = port_get_track (port, 1);
419     }
420   g_return_val_if_fail (track, NULL);
421 
422   AutomationTracklist * atl =
423     track_get_automation_tracklist (track);
424   g_return_val_if_fail (atl, NULL);
425   for (int i = 0; i < atl->num_ats; i++)
426     {
427       AutomationTrack * at = atl->ats[i];
428       if (basic_search)
429         {
430           PortIdentifier * src = &port->id;
431           PortIdentifier * dest = &at->port_id;
432           if (
433             string_is_equal (
434               dest->label, src->label) &&
435             dest->owner_type == src->owner_type &&
436             dest->type == src->type &&
437             dest->flow == src->flow &&
438             dest->flags == src->flags &&
439             dest->track_name_hash ==
440               src->track_name_hash)
441             {
442               if (dest->owner_type ==
443                     PORT_OWNER_TYPE_PLUGIN)
444                 {
445                   if (!plugin_identifier_is_equal (
446                         &dest->plugin_id,
447                         &src->plugin_id))
448                     {
449                       continue;
450                     }
451 
452                   Plugin * pl =
453                     port_get_plugin (port, true);
454                   g_return_val_if_fail (
455                     IS_PLUGIN_AND_NONNULL (pl),
456                     NULL);
457 
458                   if (pl->setting->descr->protocol ==
459                         PROT_LV2)
460                     {
461                       /* if lv2 plugin port (not
462                        * standard zrythm-provided
463                        * port), make sure the symbol
464                        * matches (some plugins have
465                        * multiple ports with the same
466                        * label but different
467                        * symbol) */
468                       if (src->flags ^
469                             PORT_FLAG_GENERIC_PLUGIN_PORT &&
470                           !string_is_equal (
471                             dest->sym, src->sym))
472                         {
473                           continue;
474                         }
475                       return at;
476                     }
477                   /* if not lv2, also search by
478                    * index */
479                   else if (dest->port_index ==
480                              src->port_index)
481                     {
482                       return at;
483                     }
484                 }
485               else
486                 {
487                   if (dest->port_index ==
488                         src->port_index)
489                     {
490                       return at;
491                     }
492                 }
493             }
494         }
495       /* not basic search */
496       else
497         {
498           if (port_identifier_is_equal (
499                 &port->id, &at->port_id))
500             {
501               return at;
502             }
503         }
504     }
505 
506   return NULL;
507 }
508 
509 /**
510  * @note This is expensive and should only be used
511  *   if \ref PortIdentifier.at_idx is not set. Use
512  *   port_get_automation_track() instead.
513  *
514  * @param basic_search If true, only basic port
515  *   identifier members are checked.
516  */
517 AutomationTrack *
automation_track_find_from_port_id(PortIdentifier * id,bool basic_search)518 automation_track_find_from_port_id (
519   PortIdentifier * id,
520   bool             basic_search)
521 {
522   Port * port = port_find_from_identifier (id);
523   g_return_val_if_fail (
524     port &&
525       port_identifier_is_equal (id, &port->id),
526     NULL);
527 
528   return
529     automation_track_find_from_port (
530       port, NULL, basic_search);
531 }
532 
533 /**
534  * Returns whether the automation in the automation
535  * track should be read.
536  *
537  * @param cur_time Current time from
538  *   g_get_monotonic_time() passed here to ensure
539  *   the same timestamp is used in sequential calls.
540  */
541 bool
automation_track_should_read_automation(AutomationTrack * at,gint64 cur_time)542 automation_track_should_read_automation (
543   AutomationTrack * at,
544   gint64            cur_time)
545 {
546   if (at->automation_mode == AUTOMATION_MODE_OFF)
547     return false;
548 
549   /* TODO last argument should be true but doesnt
550    * work properly atm */
551   if (automation_track_should_be_recording (
552         at, cur_time, false))
553     return false;
554 
555   return true;
556 }
557 
558 /**
559  * Returns if the automation track should currently
560  * be recording data.
561  *
562  * Returns false if in touch mode after the release
563  * time has passed.
564  *
565  * This function assumes that the transport will
566  * be rolling.
567  *
568  * @param cur_time Current time from
569  *   g_get_monotonic_time() passed here to ensure
570  *   the same timestamp is used in sequential calls.
571  * @param record_aps If set to true, this function
572  *   will return whether we should be recording
573  *   automation point data. If set to false, this
574  *   function will return whether we should be
575  *   recording a region (eg, if an automation point
576  *   was already created before and we are still
577  *   recording inside a region regardless of whether
578  *   we should create/edit automation points or not.
579  */
580 bool
automation_track_should_be_recording(AutomationTrack * at,gint64 cur_time,bool record_aps)581 automation_track_should_be_recording (
582   AutomationTrack * at,
583   gint64            cur_time,
584   bool              record_aps)
585 {
586   if (at->automation_mode == AUTOMATION_MODE_RECORD)
587     {
588       if (at->record_mode ==
589             AUTOMATION_RECORD_MODE_LATCH)
590         {
591           /* in latch mode, we are always recording,
592            * even if the value doesn't change
593            * (an automation point will be created
594            * as soon as latch mode is armed) and
595            * then only when changes are made) */
596           return true;
597         }
598       if (at->record_mode ==
599             AUTOMATION_RECORD_MODE_TOUCH)
600         {
601           Port * port = at->port;
602           g_return_val_if_fail (
603             IS_PORT_AND_NONNULL (port), false);
604           gint64 diff =
605             cur_time - port->last_change;
606           if (diff <
607                 AUTOMATION_RECORDING_TOUCH_REL_MS * 1000)
608             {
609               /* still recording */
610               return true;
611             }
612           else if (!record_aps)
613             return at->recording_started;
614         }
615     }
616 
617   return false;
618 }
619 
620 /**
621  * Unselects all arranger objects.
622  */
623 void
automation_track_unselect_all(AutomationTrack * self)624 automation_track_unselect_all (
625   AutomationTrack * self)
626 {
627   for (int i = 0; i < self->num_regions; i++)
628     {
629       ZRegion * region = self->regions[i];
630       arranger_object_select (
631         (ArrangerObject *) region, false, false,
632         F_NO_PUBLISH_EVENTS);
633     }
634 }
635 
636 /**
637  * Removes a region from the automation track.
638  */
639 void
automation_track_remove_region(AutomationTrack * self,ZRegion * region)640 automation_track_remove_region (
641   AutomationTrack * self,
642   ZRegion *         region)
643 {
644   g_return_if_fail (IS_REGION (region));
645 
646   array_delete (
647     self->regions, self->num_regions, region);
648 
649   for (int i = region->id.idx;
650        i < self->num_regions; i++)
651     {
652       ZRegion * r = self->regions[i];
653       r->id.idx = i;
654       region_update_identifier (r);
655     }
656 }
657 
658 /**
659  * Removes and frees all arranger objects
660  * recursively.
661  */
662 void
automation_track_clear(AutomationTrack * self)663 automation_track_clear (
664   AutomationTrack * self)
665 {
666   for (int i = self->num_regions - 1; i >= 0; i--)
667     {
668       ZRegion * region = self->regions[i];
669       Track * track =
670         automation_track_get_track (self);
671       g_return_if_fail (
672         IS_TRACK_AND_NONNULL (track));
673       track_remove_region (
674         track, region, F_NO_PUBLISH_EVENTS, F_FREE);
675     }
676   self->num_regions = 0;
677 }
678 
679 Track *
automation_track_get_track(AutomationTrack * self)680 automation_track_get_track (
681   AutomationTrack * self)
682 {
683   Track * track =
684     tracklist_find_track_by_name_hash (
685       TRACKLIST, self->port_id.track_name_hash);
686   g_return_val_if_fail (track, NULL);
687 
688   return track;
689 }
690 
691 /**
692  * Sets the index of the AutomationTrack in the
693  * AutomationTracklist.
694  */
695 void
automation_track_set_index(AutomationTrack * self,int index)696 automation_track_set_index (
697   AutomationTrack * self,
698   int               index)
699 {
700   self->index = index;
701 
702   for (int i = 0; i < self->num_regions; i++)
703     {
704       ZRegion * region = self->regions[i];
705       g_return_if_fail (region);
706       region->id.at_idx = index;
707       region_update_identifier (region);
708     }
709 }
710 
711 /**
712  * Returns the actual parameter value at the given
713  * position.
714  *
715  * If there is no automation point/curve during
716  * the position, it returns the current value
717  * of the parameter it is automating.
718  *
719  * @param normalized Whether to return the value
720  *   normalized.
721  * @param ends_after Whether to only check in
722  *   regions that also end after \ref pos (ie,
723  *   the region surrounds \ref pos), otherwise
724  *   check in the region that ends last.
725  */
726 float
automation_track_get_val_at_pos(AutomationTrack * self,Position * pos,bool normalized,bool ends_after)727 automation_track_get_val_at_pos (
728   AutomationTrack * self,
729   Position *        pos,
730   bool              normalized,
731   bool              ends_after)
732 {
733   AutomationPoint * ap =
734     automation_track_get_ap_before_pos (
735       self, pos, ends_after);
736   ArrangerObject * ap_obj =
737     (ArrangerObject *) ap;
738 
739   Port * port =
740     port_find_from_identifier (&self->port_id);
741   g_return_val_if_fail (port, 0.f);
742 
743   /* no automation points yet, return negative
744    * (no change) */
745   if (!ap)
746     {
747       return
748         port_get_control_value (port, normalized);
749     }
750 
751   ZRegion * region =
752     arranger_object_get_region (ap_obj);
753   ArrangerObject * r_obj =
754     (ArrangerObject *) region;
755 
756   /* if region ends before pos, assume pos is the
757    * region's end pos */
758   long localp =
759     region_timeline_frames_to_local (
760       region,
761       !ends_after &&
762         (r_obj->end_pos.frames < pos->frames) ?
763           r_obj->end_pos.frames - 1 :
764           pos->frames,
765       F_NORMALIZE);
766   /*g_debug ("local pos %ld", localp);*/
767 
768   AutomationPoint * next_ap =
769     automation_region_get_next_ap (
770       region, ap, false, false);
771   ArrangerObject * next_ap_obj =
772     (ArrangerObject *) next_ap;
773 
774   /* return value at last ap */
775   if (!next_ap)
776     {
777       if (normalized)
778         {
779           return ap->normalized_val;
780         }
781       else
782         {
783           return ap->fvalue;
784         }
785     }
786 
787   int prev_ap_lower =
788     ap->normalized_val <= next_ap->normalized_val;
789   float cur_next_diff =
790     (float)
791     fabsf (
792       ap->normalized_val - next_ap->normalized_val);
793 
794   /* ratio of how far in we are in the curve */
795   long ap_frames =
796     position_to_frames (&ap_obj->pos);
797   long next_ap_frames =
798     position_to_frames (&next_ap_obj->pos);
799   double ratio =
800     (double) (localp - ap_frames) /
801     (double) (next_ap_frames - ap_frames);
802   g_return_val_if_fail (ratio >= 0, 0.f);
803 
804   float result =
805     (float)
806     automation_point_get_normalized_value_in_curve (
807       ap, ratio);
808   result = result * cur_next_diff;
809   if (prev_ap_lower)
810     result +=
811       ap->normalized_val;
812   else
813     result +=
814       next_ap->normalized_val;
815 
816   if (normalized)
817     {
818       return result;
819     }
820   else
821     {
822       return
823         control_port_normalized_val_to_real (
824           port, result);
825     }
826 }
827 
828 /**
829  * Updates each position in each child of the
830  * automation track recursively.
831  *
832  * @param from_ticks Whether to update the
833  *   positions based on ticks (true) or frames
834  *   (false).
835  */
836 void
automation_track_update_positions(AutomationTrack * self,bool from_ticks)837 automation_track_update_positions (
838   AutomationTrack * self,
839   bool              from_ticks)
840 {
841   for (int i = 0; i < self->num_regions; i++)
842     {
843       arranger_object_update_positions (
844         (ArrangerObject *) self->regions[i],
845         from_ticks);
846     }
847 }
848 
849 CONST
850 static int
get_y_px_from_height_and_normalized_val(const float height,const float normalized_val)851 get_y_px_from_height_and_normalized_val (
852   const float height,
853   const float normalized_val)
854 {
855   return
856     (int) (height - normalized_val * height);
857 }
858 
859 /**
860  * Returns the y pixels from the value based on the
861  * allocation of the automation track.
862  */
863 int
automation_track_get_y_px_from_normalized_val(AutomationTrack * self,float normalized_val)864 automation_track_get_y_px_from_normalized_val (
865   AutomationTrack * self,
866   float             normalized_val)
867 {
868   return
869     get_y_px_from_height_and_normalized_val (
870       (float) self->height, normalized_val);
871 }
872 
873 /**
874  * Gets the last ZRegion in the AutomationTrack.
875  *
876  * FIXME cache.
877  */
878 ZRegion *
automation_track_get_last_region(AutomationTrack * self)879 automation_track_get_last_region (
880   AutomationTrack * self)
881 {
882   Position pos;
883   position_init (&pos);
884   ZRegion * region, * last_region = NULL;
885   ArrangerObject * r_obj;
886   for (int i = 0; i < self->num_regions; i++)
887     {
888       region = self->regions[i];
889       r_obj = (ArrangerObject *) region;
890       if (position_is_after (
891             &r_obj->end_pos, &pos))
892         {
893           last_region = region;
894           position_set_to_pos (
895             &pos, &r_obj->end_pos);
896         }
897     }
898   return last_region;
899 }
900 
901 bool
automation_track_verify(AutomationTrack * self)902 automation_track_verify (
903   AutomationTrack * self)
904 {
905   for (int i = 0; i < self->num_regions; i++)
906     {
907       ZRegion * r = self->regions[i];
908 
909       for (int j = 0; j < r->num_aps; j++)
910         {
911           AutomationPoint * ap = r->aps[j];
912 
913           if (ZRYTHM_TESTING)
914             {
915               if (!math_assert_nonnann (
916                      ap->fvalue) ||
917                   !math_assert_nonnann (
918                      ap->normalized_val))
919                 {
920                   return false;
921                 }
922             }
923         }
924     }
925   return true;
926 }
927 
928 void
automation_track_set_caches(AutomationTrack * self)929 automation_track_set_caches (
930   AutomationTrack * self)
931 {
932   self->port =
933     port_find_from_identifier (&self->port_id);
934 }
935 
936 /**
937  * Clones the AutomationTrack.
938  */
939 AutomationTrack *
automation_track_clone(AutomationTrack * src)940 automation_track_clone (
941   AutomationTrack * src)
942 {
943   AutomationTrack * dest = _at_create ();
944 
945   dest->regions_size = (size_t) src->num_regions;
946   dest->num_regions = src->num_regions;
947   dest->regions =
948     object_new_n (dest->regions_size, ZRegion *);
949   dest->visible = src->visible;
950   dest->created = src->created;
951   dest->index = src->index;
952   dest->y = src->y;
953   dest->automation_mode = src->automation_mode;
954   dest->record_mode = src->record_mode;
955   dest->height = src->height;
956   g_warn_if_fail (dest->height >= TRACK_MIN_HEIGHT);
957 
958   port_identifier_copy (
959     &dest->port_id, &src->port_id);
960 
961   ZRegion * src_region;
962   for (int j = 0; j < src->num_regions; j++)
963     {
964       src_region = src->regions[j];
965       dest->regions[j] =
966         (ZRegion *)
967         arranger_object_clone (
968           (ArrangerObject *) src_region);
969     }
970 
971   return dest;
972 }
973 
974 void
automation_track_free(AutomationTrack * self)975 automation_track_free (AutomationTrack * self)
976 {
977   for (int i = 0; i < self->num_regions; i++)
978     {
979       object_free_w_func_and_null_cast (
980         arranger_object_free, ArrangerObject *,
981         self->regions[i]);
982     }
983   object_zero_and_free (self->regions);
984 
985   object_zero_and_free (self);
986 }
987