1 /*
2 * Copyright (C) 2018-2020 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/midi_event.h"
23 #include "audio/midi_note.h"
24 #include "audio/position.h"
25 #include "audio/track.h"
26 #include "audio/velocity.h"
27 #include "gui/backend/midi_arranger_selections.h"
28 #include "gui/widgets/arranger.h"
29 #include "gui/widgets/bot_dock_edge.h"
30 #include "gui/widgets/center_dock.h"
31 #include "gui/widgets/clip_editor.h"
32 #include "gui/widgets/main_window.h"
33 #include "gui/widgets/midi_arranger.h"
34 #include "gui/widgets/midi_note.h"
35 #include "gui/widgets/velocity.h"
36 #include "project.h"
37 #include "utils/arrays.h"
38 #include "utils/flags.h"
39 #include "utils/objects.h"
40 #include "utils/string.h"
41
42 /**
43 * Creates a new MidiNote.
44 */
45 MidiNote *
midi_note_new(RegionIdentifier * region_id,Position * start_pos,Position * end_pos,uint8_t val,uint8_t vel)46 midi_note_new (
47 RegionIdentifier * region_id,
48 Position * start_pos,
49 Position * end_pos,
50 uint8_t val,
51 uint8_t vel)
52 {
53 g_return_val_if_fail (region_id, NULL);
54
55 MidiNote * self = object_new (MidiNote);
56
57 self->schema_version = MIDI_NOTE_SCHEMA_VERSION;
58 self->magic = MIDI_NOTE_MAGIC;
59
60 ArrangerObject * obj =
61 (ArrangerObject *) self;
62 arranger_object_init (obj);
63 obj->pos = *start_pos;
64 obj->end_pos = *end_pos;
65 obj->type = ARRANGER_OBJECT_TYPE_MIDI_NOTE;
66
67 region_identifier_copy (
68 &obj->region_id, region_id);
69 self->val = val;
70 self->vel =
71 velocity_new (self, vel);
72
73 return self;
74 }
75
76 /**
77 * Sets the region the MidiNote belongs to.
78 */
79 void
midi_note_set_region_and_index(MidiNote * self,ZRegion * region,int idx)80 midi_note_set_region_and_index (
81 MidiNote * self,
82 ZRegion * region,
83 int idx)
84 {
85 ArrangerObject * obj = (ArrangerObject *) self;
86 region_identifier_copy (
87 &obj->region_id, ®ion->id);
88 self->pos = idx;
89 }
90
91 /**
92 * For debugging.
93 */
94 void
midi_note_print(MidiNote * mn)95 midi_note_print (
96 MidiNote * mn)
97 {
98 ArrangerObject * obj = (ArrangerObject *) mn;
99 char start_pos_str[300];
100 position_to_string (&obj->pos, start_pos_str);
101 char end_pos_str[300];
102 position_to_string (&obj->end_pos, end_pos_str);
103 g_message (
104 "MidiNote: start pos %s - end pos %s",
105 start_pos_str, end_pos_str);
106 }
107
108 /**
109 * Listen to the given MidiNote.
110 *
111 * @param listen Turn note on if 1, or turn it
112 * off if 0.
113 */
114 void
midi_note_listen(MidiNote * mn,bool listen)115 midi_note_listen (
116 MidiNote * mn,
117 bool listen)
118 {
119 /*g_message (*/
120 /*"%s: %" PRIu8 " listen %d", __func__,*/
121 /*mn->val, listen);*/
122
123 ArrangerObject * obj =
124 (ArrangerObject *) mn;
125
126 Track * track =
127 arranger_object_get_track (obj);
128 g_return_if_fail (
129 track && track->processor->midi_in);
130 MidiEvents * events =
131 track->processor->midi_in->midi_events;
132
133 if (listen)
134 {
135 /* if note is on but pitch changed */
136 if (mn->currently_listened &&
137 mn->val != mn->last_listened_val)
138 {
139 /* create midi note off */
140 /*g_message (*/
141 /*"%s: adding note off for %" PRIu8,*/
142 /*__func__, mn->last_listened_val);*/
143 midi_events_add_note_off (
144 events, 1, mn->last_listened_val,
145 0, 1);
146
147 /* create note on at the new value */
148 /*g_message (*/
149 /*"%s: adding note on for %" PRIu8,*/
150 /*__func__, mn->val);*/
151 midi_events_add_note_on (
152 events, 1, mn->val, mn->vel->vel,
153 0, 1);
154 mn->last_listened_val = mn->val;
155 }
156 /* if note is on and pitch is the same */
157 else if (mn->currently_listened &&
158 mn->val == mn->last_listened_val)
159 {
160 /* do nothing */
161 }
162 /* if note is not on */
163 else if (!mn->currently_listened)
164 {
165 /* turn it on */
166 /*g_message (*/
167 /*"%s: adding note on for %" PRIu8,*/
168 /*__func__, mn->val);*/
169 midi_events_add_note_on (
170 events, 1, mn->val, mn->vel->vel,
171 0, 1);
172 mn->last_listened_val = mn->val;
173 mn->currently_listened = 1;
174 }
175 }
176 /* if turning listening off */
177 else if (mn->currently_listened)
178 {
179 /* create midi note off */
180 /*g_message (*/
181 /*"%s: adding note off for %" PRIu8,*/
182 /*__func__, mn->last_listened_val);*/
183 midi_events_add_note_off (
184 events, 1, mn->last_listened_val,
185 0, 1);
186 mn->currently_listened = 0;
187 mn->last_listened_val = 255;
188 }
189 }
190
191 /**
192 * Returns 1 if the MidiNote's match, 0 if not.
193 */
194 int
midi_note_is_equal(MidiNote * src,MidiNote * dest)195 midi_note_is_equal (
196 MidiNote * src,
197 MidiNote * dest)
198 {
199 ArrangerObject * src_obj =
200 (ArrangerObject *) src;
201 ArrangerObject * dest_obj =
202 (ArrangerObject *) dest;
203 return
204 position_is_equal_ticks (
205 &src_obj->pos, &dest_obj->pos) &&
206 position_is_equal_ticks (
207 &src_obj->end_pos, &dest_obj->end_pos) &&
208 src->val == dest->val &&
209 src->muted == dest->muted &&
210 region_identifier_is_equal (
211 &src_obj->region_id, &dest_obj->region_id);
212 }
213
214 /**
215 * Gets the global Position of the MidiNote's
216 * start_pos.
217 *
218 * @param pos Position to fill in.
219 */
220 void
midi_note_get_global_start_pos(MidiNote * self,Position * pos)221 midi_note_get_global_start_pos (
222 MidiNote * self,
223 Position * pos)
224 {
225 ArrangerObject * self_obj =
226 (ArrangerObject *) self;
227 ArrangerObject * region_obj =
228 (ArrangerObject *)
229 arranger_object_get_region (self_obj);
230 position_set_to_pos (
231 pos, &self_obj->pos);
232 position_add_ticks (
233 pos,
234 position_to_ticks (
235 ®ion_obj->pos));
236 }
237
238 /**
239 * Gets the MIDI note's value as a string (eg
240 * "C#4").
241 *
242 * @param use_markup Use markup to show the octave
243 * as a superscript.
244 */
245 void
midi_note_get_val_as_string(MidiNote * self,char * buf,const int use_markup)246 midi_note_get_val_as_string (
247 MidiNote * self,
248 char * buf,
249 const int use_markup)
250 {
251 const char * note_str =
252 chord_descriptor_note_to_string (
253 self->val % 12);
254 const int note_val = self->val / 12 - 1;
255 if (use_markup)
256 {
257 sprintf (
258 buf, "%s<sup>%d</sup>",
259 note_str, note_val);
260 if (DEBUGGING)
261 {
262 char tmp[50];
263 sprintf (tmp, "%s (%d)", buf, self->pos);
264 strcpy (buf, tmp);
265 }
266 }
267 else
268 {
269 sprintf (
270 buf, "%s%d",
271 note_str, note_val);
272 }
273 }
274
275 void
midi_note_set_cache_val(MidiNote * self,const uint8_t val)276 midi_note_set_cache_val (
277 MidiNote * self,
278 const uint8_t val)
279 {
280 self->cache_val = val;
281 }
282
283 /**
284 * Sends a note off if currently playing and sets
285 * the pitch of the MidiNote.
286 */
287 void
midi_note_set_val(MidiNote * midi_note,const uint8_t val)288 midi_note_set_val (
289 MidiNote * midi_note,
290 const uint8_t val)
291 {
292 g_return_if_fail (val < 128);
293
294 /* if currently playing set a note off event. */
295 if (midi_note_hit (
296 midi_note, PLAYHEAD->frames) &&
297 TRANSPORT_IS_ROLLING)
298 {
299 ZRegion * region =
300 arranger_object_get_region (
301 (ArrangerObject *) midi_note);
302 ArrangerObject * r_obj =
303 (ArrangerObject *) region;
304 g_return_if_fail (r_obj);
305 Track * track =
306 arranger_object_get_track (r_obj);
307 g_return_if_fail (track);
308
309 MidiEvents * midi_events =
310 track->processor->piano_roll->midi_events;
311
312 zix_sem_wait (&midi_events->access_sem);
313 TrackLane * lane =
314 region_get_lane (region);
315 midi_events_add_note_off (
316 midi_events, lane->midi_ch,
317 midi_note->val, 0, 1);
318 zix_sem_post (&midi_events->access_sem);
319 }
320
321 midi_note->val = val;
322 }
323
324 /**
325 * Shifts MidiNote's position and/or value.
326 *
327 * @param delta Y (0-127)
328 */
329 void
midi_note_shift_pitch(MidiNote * self,const int delta)330 midi_note_shift_pitch (
331 MidiNote * self,
332 const int delta)
333 {
334 self->val = (uint8_t) ((int) self->val + delta);
335 midi_note_set_val (
336 self, self->val);
337 }
338
339 ZRegion *
midi_note_get_region(MidiNote * self)340 midi_note_get_region (
341 MidiNote * self)
342 {
343 ArrangerObject * obj = (ArrangerObject *) self;
344 return region_find (&obj->region_id);
345 }
346
347 /**
348 * Returns if the MIDI note is hit at given pos (in
349 * the timeline).
350 */
351 int
midi_note_hit(MidiNote * self,const long gframes)352 midi_note_hit (
353 MidiNote * self,
354 const long gframes)
355 {
356 ZRegion * region =
357 arranger_object_get_region (
358 (ArrangerObject *) self);
359 ArrangerObject * region_obj =
360 (ArrangerObject *) region;
361
362 /* get local positions */
363 long local_pos =
364 region_timeline_frames_to_local (
365 region, gframes, 1);
366
367 /* add clip_start position to start from
368 * there */
369 long clip_start_frames =
370 region_obj->clip_start_pos.frames;
371 local_pos += clip_start_frames;
372
373 /* check for note on event on the
374 * boundary */
375 /* FIXME ok? it was < and >= before */
376 ArrangerObject * midi_note_obj =
377 (ArrangerObject *) self;
378 if (midi_note_obj->pos.frames <= local_pos &&
379 midi_note_obj->end_pos.frames > local_pos)
380 return 1;
381
382 return 0;
383 }
384