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