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, &region->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       &region_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