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 this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "actions/arranger_selections.h"
21 #include "audio/midi_note.h"
22 #include "audio/velocity.h"
23 #include "gui/backend/event.h"
24 #include "gui/backend/event_manager.h"
25 #include "gui/widgets/arranger.h"
26 #include "gui/widgets/bot_dock_edge.h"
27 #include "gui/widgets/center_dock.h"
28 #include "gui/widgets/clip_editor.h"
29 #include "gui/widgets/clip_editor_inner.h"
30 #include "gui/widgets/midi_arranger.h"
31 #include "gui/widgets/midi_editor_space.h"
32 #include "gui/widgets/midi_modifier_arranger.h"
33 #include "gui/widgets/midi_note.h"
34 #include "gui/widgets/ruler.h"
35 #include "gui/widgets/timeline_panel.h"
36 #include "gui/widgets/timeline_ruler.h"
37 #include "gui/widgets/velocity.h"
38 #include "project.h"
39 #include "utils/arrays.h"
40 #include "utils/objects.h"
41 #include "utils/flags.h"
42 #include "zrythm_app.h"
43 
44 /**
45  * Sets the start velocities of all velocities
46  * in the current region.
47  */
48 void
midi_modifier_arranger_widget_set_start_vel(ArrangerWidget * self)49 midi_modifier_arranger_widget_set_start_vel (
50   ArrangerWidget * self)
51 {
52   ZRegion * region =
53     clip_editor_get_region (CLIP_EDITOR);
54   g_return_if_fail (
55     region && region->id.type == REGION_TYPE_MIDI);
56 
57   for (int i = 0; i < region->num_midi_notes; i++)
58     {
59       MidiNote * mn = region->midi_notes[i];
60       Velocity * vel = mn->vel;
61       vel->vel_at_start = vel->vel;
62     }
63 }
64 
65 /**
66  * Returns the enclosed velocities.
67  *
68  * @param hit Return hit or unhit velocities.
69  */
70 static Velocity **
get_enclosed_velocities(ArrangerWidget * self,double offset_x,int * num_vels,bool hit)71 get_enclosed_velocities (
72   ArrangerWidget * self,
73   double           offset_x,
74   int *            num_vels,
75   bool             hit)
76 {
77   Position selection_start_pos, selection_end_pos;
78   ui_px_to_pos_editor (
79     self->start_x,
80     offset_x >= 0 ?
81       &selection_start_pos :
82       &selection_end_pos,
83     F_PADDING);
84   ui_px_to_pos_editor (
85     self->start_x + offset_x,
86     offset_x >= 0 ?
87       &selection_end_pos :
88       &selection_start_pos,
89     F_PADDING);
90 
91   ZRegion * region =
92     clip_editor_get_region (CLIP_EDITOR);
93   g_return_val_if_fail (
94     region && region->id.type == REGION_TYPE_MIDI,
95     NULL);
96 
97   /* find enclosed velocities */
98   size_t velocities_size = 1;
99   Velocity ** velocities =
100     object_new_n (
101       velocities_size, Velocity *);
102   *num_vels = 0;
103   midi_region_get_velocities_in_range (
104     region,
105     &selection_start_pos, &selection_end_pos,
106     &velocities, num_vels, &velocities_size, hit);
107 
108   return velocities;
109 }
110 
111 void
midi_modifier_arranger_widget_select_vels_in_range(ArrangerWidget * self,double offset_x)112 midi_modifier_arranger_widget_select_vels_in_range (
113   ArrangerWidget * self,
114   double           offset_x)
115 {
116   /* find enclosed velocities */
117   int num_velocities = 0;
118   Velocity ** velocities =
119     get_enclosed_velocities (
120       self, offset_x, &num_velocities, true);
121 
122   arranger_selections_clear (
123     (ArrangerSelections *) MA_SELECTIONS,
124     F_NO_FREE, F_NO_PUBLISH_EVENTS);
125   for (int i = 0; i < num_velocities; i++)
126     {
127       Velocity * vel = velocities[i];
128       MidiNote * mn = velocity_get_midi_note (vel);
129 
130       arranger_object_select (
131         (ArrangerObject *) mn, F_SELECT, F_APPEND,
132         F_NO_PUBLISH_EVENTS);
133     }
134 
135   free (velocities);
136 }
137 
138 /**
139  * Gets velocities hit by the given point, or x
140  * only.
141  *
142  * @param y Y or -1 to ignore.
143  */
144 /*void*/
145 /*midi_modifier_arranger_widget_get_hit_velocities (*/
146   /*ArrangerWidget *  self,*/
147   /*double            x,*/
148   /*double            y,*/
149   /*ArrangerObject ** objs,*/
150   /*int *             num_objs)*/
151 /*{*/
152   /*ZRegion * r = clip_editor_get_region (CLIP_EDITOR);*/
153   /*if (!r)*/
154     /*break;*/
155 
156   /*for (int i = 0; i < r->num_midi_notes; i++)*/
157     /*{*/
158       /*MidiNote * mn = r->midi_notes[i];*/
159       /*Velocity * vel = mn->vel;*/
160       /*obj = (ArrangerObject *) vel;*/
161 
162       /*if (obj->deleted_temporarily)*/
163         /*continue;*/
164 
165       /*arranger_object_set_full_rectangle (obj, self);*/
166 
167       /*add_object_if_overlap (*/
168         /*self, rect, x, y, array,*/
169         /*array_size, obj);*/
170     /*}*/
171 /*}*/
172 
173 /**
174  * Draws a ramp from the start coordinates to the
175  * given coordinates.
176  */
177 void
midi_modifier_arranger_widget_ramp(ArrangerWidget * self,double offset_x,double offset_y)178 midi_modifier_arranger_widget_ramp (
179   ArrangerWidget * self,
180   double           offset_x,
181   double           offset_y)
182 {
183   /* find enclosed velocities */
184   int num_velocities = 0;
185   Velocity ** velocities =
186     get_enclosed_velocities (
187       self, offset_x, &num_velocities, true);
188 
189   /* ramp */
190   Velocity * vel;
191   int px, val;
192   double y1, y2, x1, x2;
193   int height =
194     gtk_widget_get_allocated_height (
195       GTK_WIDGET (self));
196   Position start_pos;
197   for (int i = 0; i < num_velocities; i++)
198     {
199       vel = velocities[i];
200       MidiNote * mn =
201         velocity_get_midi_note (vel);
202       midi_note_get_global_start_pos (
203         mn, &start_pos);
204       px =
205         ui_pos_to_px_editor (
206           &start_pos, F_PADDING);
207 
208       x1 = self->start_x;
209       x2 = self->start_x + offset_x;
210       y1 = height - self->start_y;
211       y2 = height - (self->start_y + offset_y);
212       /*g_message ("x1 %f.0 x2 %f.0 y1 %f.0 y2 %f.0",*/
213                  /*x1, x2, y1, y2);*/
214 
215       /* y = y1 + ((y2 - y1)/(x2 - x1))*(x - x1)
216        * http://stackoverflow.com/questions/2965144/ddg#2965188 */
217       /* get val in pixels */
218       val =
219         (int)
220         (y1 +
221          ((y2 - y1)/(x2 - x1)) *
222            ((double) px - x1));
223 
224       /* normalize and multiply by 127 to get
225        * velocity value */
226       val =
227         (int)
228         (((double) val / (double) height) * 127.0);
229       val = CLAMP (val, 1, 127);
230       /*g_message ("val %d", val);*/
231 
232       velocity_set_val (
233         vel, val);
234     }
235   free (velocities);
236 
237   /* find velocities not hit */
238   num_velocities = 0;
239   velocities =
240     get_enclosed_velocities (
241       self, offset_x, &num_velocities, false);
242 
243   /* reset their value */
244   for (int i = 0; i < num_velocities; i++)
245     {
246       vel = velocities[i];
247       velocity_set_val (vel, vel->vel_at_start);
248     }
249   free (velocities);
250 
251   EVENTS_PUSH (ET_VELOCITIES_RAMPED, NULL);
252 }
253 
254 void
midi_modifier_arranger_widget_resize_velocities(ArrangerWidget * self,double offset_y)255 midi_modifier_arranger_widget_resize_velocities (
256   ArrangerWidget * self,
257   double           offset_y)
258 {
259   int height =
260     gtk_widget_get_allocated_height (
261       GTK_WIDGET (self));
262 
263   /* adjust for circle radius */
264   double start_y =
265     self->start_y;
266     /*self->start_y - VELOCITY_WIDTH / 2;*/
267   /*offset_y -= VELOCITY_WIDTH / 2;*/
268   /*g_message ("start y %f offset y %f res %f height %f", start_y, offset_y, start_y + offset_y,*/
269     /*(double) height);*/
270 
271   double start_ratio =
272     CLAMP (
273       1.0 - start_y / (double) height,
274       0.0, 1.0);
275   double ratio =
276     CLAMP (
277       1.0 -
278       (start_y + offset_y) /
279         (double) height,
280       0.0, 1.0);
281   g_return_if_fail (start_ratio <= 1.0);
282   g_return_if_fail (ratio <= 1.0);
283   int start_val = (int) (start_ratio * 127.0);
284   int val = (int) (ratio * 127.0);
285   self->vel_diff = val - start_val;
286 
287   /*Velocity * vel = self->start_velocity;*/
288   /*vel =*/
289     /*velocity_get_main_trans_velocity (vel);*/
290   /*velocity_set_val (*/
291     /*vel, CLAMP (ratio * 127, 1, 127));*/
292   /*int diff = vel->vel - self->start_vel_val;*/
293   /*self->vel_diff =*/
294   /*if (vel->widget)*/
295     /*velocity_widget_update_tooltip (*/
296       /*vel->widget, 1);*/
297   /*g_message ("diff %d", diff);*/
298 
299   for (int i = 0;
300        i < MA_SELECTIONS->num_midi_notes; i++)
301     {
302       Velocity * vel =
303         MA_SELECTIONS->midi_notes[i]->vel;
304       Velocity * vel_at_start =
305         ((MidiArrangerSelections *)
306         self->sel_at_start)->midi_notes[i]->vel;
307 
308       velocity_set_val (
309         vel,
310         CLAMP (
311           vel_at_start->vel + self->vel_diff,
312           1, 127));
313 
314       EVENTS_PUSH (
315         ET_ARRANGER_OBJECT_CHANGED, vel);
316 
317 #if 0
318       ArrangerObject * vel_obj =
319         (ArrangerObject *) vel;
320       if (GTK_IS_WIDGET (vel_obj->widget))
321         {
322           arranger_object_widget_update_tooltip (
323             Z_ARRANGER_OBJECT_WIDGET (
324               vel_obj->widget), 1);
325         }
326 #endif
327     }
328 }
329 
330 /**
331  * Sets the value of each velocity hit at x to the
332  * value corresponding to y.
333  *
334  * Used with the pencil tool.
335  *
336  * @param append_to_selections Append the hit
337  *   velocities to the selections.
338  */
339 void
midi_modifier_arranger_set_hit_velocity_vals(ArrangerWidget * self,double x,double y,bool append_to_selections)340 midi_modifier_arranger_set_hit_velocity_vals (
341   ArrangerWidget * self,
342   double           x,
343   double           y,
344   bool             append_to_selections)
345 {
346   ArrangerObject * objs[800];
347   int              num_objs;
348   arranger_widget_get_hit_objects_at_point (
349     self, ARRANGER_OBJECT_TYPE_VELOCITY, x, -1,
350     objs, &num_objs);
351   g_message ("%d velocities hit", num_objs);
352 
353   int height =
354     gtk_widget_get_allocated_height (
355       GTK_WIDGET (self));
356   double ratio = 1.0 - y / (double) height;
357   int val = CLAMP ((int) (ratio * 127.0), 1, 127);
358 
359   for (int i = 0; i < num_objs; i++)
360     {
361       ArrangerObject * obj = objs[i];
362       Velocity * vel = (Velocity *) obj;
363       MidiNote * mn = velocity_get_midi_note (vel);
364       ArrangerObject * mn_obj =
365         (ArrangerObject *) mn;
366 
367       /* if object not already selected, add to
368        * selections */
369       if (!arranger_selections_contains_object (
370             (ArrangerSelections *) MA_SELECTIONS,
371             mn_obj))
372         {
373           /* add a clone of midi note before the
374            * change to sel_at_start */
375           ArrangerObject * clone =
376             arranger_object_clone (
377               (ArrangerObject *) mn);
378           arranger_selections_add_object (
379             self->sel_at_start, clone);
380 
381           if (append_to_selections)
382             {
383               arranger_object_select (
384                 obj, F_SELECT, F_APPEND,
385                 F_NO_PUBLISH_EVENTS);
386             }
387         }
388 
389       velocity_set_val (vel, val);
390     }
391 }
392