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 /**
21  * \file
22  *
23  * Piano roll backend.
24  *
25  * This is meant to be serialized along with each project.
26  */
27 
28 #include <stdlib.h>
29 
30 #include "audio/channel.h"
31 #include "gui/backend/event.h"
32 #include "gui/backend/event_manager.h"
33 #include "gui/backend/piano_roll.h"
34 #include "audio/track.h"
35 #include "project.h"
36 #include "settings/settings.h"
37 #include "utils/arrays.h"
38 #include "utils/objects.h"
39 #include "zrythm_app.h"
40 
41 DRUM_LABELS;
42 
43 MidiNoteDescriptor *
midi_note_descriptor_new(void)44 midi_note_descriptor_new (void)
45 {
46   MidiNoteDescriptor * self =
47     object_new (MidiNoteDescriptor);
48 
49   return self;
50 }
51 
52 void
midi_note_descriptor_free(MidiNoteDescriptor * self)53 midi_note_descriptor_free (
54   MidiNoteDescriptor * self)
55 {
56   g_free_and_null (self->custom_name);
57   g_free_and_null (self->note_name);
58   g_free_and_null (self->note_name_pango);
59 
60   object_zero_and_free (self);
61 }
62 
63 /**
64  * Inits the descriptors to the default values.
65  *
66  * FIXME move them to tracks since each track
67  * might have different arrangement of drums.
68  */
69 static void
init_descriptors(PianoRoll * self)70 init_descriptors (
71   PianoRoll * self)
72 {
73   int idx = 0;
74   for (int i = 127; i >= 0; i--)
75     {
76       /* do piano */
77       MidiNoteDescriptor * descr =
78         midi_note_descriptor_new ();
79       self->piano_descriptors[idx] = descr;
80 
81       descr->index = idx;
82       descr->value = i;
83       descr->marked = 0;
84       descr->visible = 1;
85       descr->custom_name = g_strdup ("");
86 
87       descr->note_name =
88         g_strdup_printf (
89           "%s%d",
90           chord_descriptor_note_to_string (i % 12),
91           i / 12 - 1);
92       descr->note_name_pango =
93         g_strdup_printf (
94           "%s<sup>%d</sup>",
95           chord_descriptor_note_to_string (i % 12),
96           i / 12 - 1);
97       idx++;
98     }
99 
100   /* do drum - put 35 to 81 first */
101   idx = 0;
102   for (int i = 35; i <= 81; i++)
103     {
104       MidiNoteDescriptor * descr =
105         midi_note_descriptor_new ();
106       self->drum_descriptors[idx] = descr;
107 
108       descr->index = idx;
109       descr->value = i;
110       descr->marked = 0;
111       descr->visible = 1;
112       descr->custom_name =
113         g_strdup (drum_labels[idx]);
114 
115       descr->note_name =
116         g_strdup_printf (
117           "%s%d",
118           chord_descriptor_note_to_string (i % 12),
119           i / 12 - 1);
120       descr->note_name_pango =
121         g_strdup_printf (
122           "%s<sup>%d</sup>",
123           chord_descriptor_note_to_string (i % 12),
124           i / 12 - 1);
125       idx++;
126     }
127   for (int i = 0; i < 128; i++)
128     {
129       if (i >= 35 && i <= 81)
130         continue;
131 
132       MidiNoteDescriptor * descr =
133         midi_note_descriptor_new ();
134       self->drum_descriptors[idx] = descr;
135 
136       descr->index = idx;
137       descr->value = i;
138       descr->marked = 0;
139       descr->visible = 1;
140       descr->custom_name =
141         g_strdup_printf (
142           "#%d: %s%d",
143           i,
144           chord_descriptor_note_to_string (i % 12),
145           i / 12 - 1);
146 
147       descr->note_name =
148         g_strdup_printf (
149           "%s%d",
150           chord_descriptor_note_to_string (i % 12),
151           i / 12 - 1);
152       descr->note_name_pango =
153         g_strdup_printf (
154           "%s<sup>%d</sup>",
155           chord_descriptor_note_to_string (i % 12),
156           i / 12 - 1);
157       idx++;
158     }
159 
160   g_warn_if_fail (idx == 128);
161 }
162 
163 /* 1 = black */
164 static const int notes[12] = {
165     0,
166     1,
167     0,
168     1,
169     0,
170     0,
171     1,
172     0,
173     1,
174     0,
175     1,
176     0 };
177 
178 /**
179  * Returns if the key is black.
180  */
181 int
piano_roll_is_key_black(int note)182 piano_roll_is_key_black (
183   int        note)
184 {
185   return notes[note % 12] == 1;
186 }
187 
188 /**
189  * Adds the note if it doesn't exist in the array.
190  */
191 void
piano_roll_add_current_note(PianoRoll * self,int note)192 piano_roll_add_current_note (
193   PianoRoll * self,
194   int         note)
195 {
196   if (!array_contains_int (
197          self->current_notes,
198          self->num_current_notes,
199          note))
200     {
201       array_append (
202         self->current_notes,
203         self->num_current_notes,
204         note);
205     }
206 }
207 
208 /**
209  * Removes the note if it exists in the array.
210  */
211 void
piano_roll_remove_current_note(PianoRoll * self,int note)212 piano_roll_remove_current_note (
213   PianoRoll * self,
214   int         note)
215 {
216   if (array_contains_int (
217       self->current_notes,
218       self->num_current_notes,
219       note))
220     {
221       array_delete_primitive (
222         self->current_notes,
223         self->num_current_notes,
224         note);
225     }
226 }
227 
228 /**
229  * Returns 1 if it contains the given note, 0
230  * otherwise.
231  */
232 int
piano_roll_contains_current_note(PianoRoll * self,int note)233 piano_roll_contains_current_note (
234   PianoRoll * self,
235   int         note)
236 {
237   return
238     array_contains_int (
239       self->current_notes,
240       self->num_current_notes, note);
241 }
242 
243 /**
244  * Inits the PianoRoll after a Project has been
245  * loaded.
246  */
247 void
piano_roll_init_loaded(PianoRoll * self)248 piano_roll_init_loaded (
249   PianoRoll * self)
250 {
251   if (!ZRYTHM_TESTING)
252     {
253       self->highlighting =
254         g_settings_get_enum (
255           S_UI, "piano-roll-highlight");
256     }
257 
258   init_descriptors (self);
259 }
260 
261 
262 /**
263  * Returns the MidiNoteDescriptor matching the value
264  * (0-127).
265  */
266 const MidiNoteDescriptor *
piano_roll_find_midi_note_descriptor_by_val(PianoRoll * self,bool drum_mode,const uint8_t val)267 piano_roll_find_midi_note_descriptor_by_val (
268   PianoRoll *   self,
269   bool          drum_mode,
270   const uint8_t val)
271 {
272   g_return_val_if_fail (val < 128, NULL);
273 
274   MidiNoteDescriptor * descr;
275   for (int i = 0; i < 128; i++)
276     {
277       if (drum_mode)
278         descr = self->drum_descriptors[i];
279       else
280         descr = self->piano_descriptors[i];
281 
282       if (descr->value == (int) val)
283         return descr;
284     }
285   g_return_val_if_reached (NULL);
286 }
287 
288 void
midi_note_descriptor_set_custom_name(MidiNoteDescriptor * descr,char * str)289 midi_note_descriptor_set_custom_name (
290   MidiNoteDescriptor * descr,
291   char *               str)
292 {
293   descr->custom_name = g_strdup (str);
294 }
295 
296 void
piano_roll_set_highlighting(PianoRoll * self,PianoRollHighlighting highlighting)297 piano_roll_set_highlighting (
298   PianoRoll * self,
299   PianoRollHighlighting highlighting)
300 {
301   self->highlighting = highlighting;
302 
303   g_settings_set_enum (
304     S_UI, "piano-roll-highlight",
305     highlighting);
306 
307   EVENTS_PUSH (
308     ET_PIANO_ROLL_HIGHLIGHTING_CHANGED, NULL);
309 }
310 
311 /**
312  * Returns the current track whose regions are
313  * being shown in the piano roll.
314  */
315 Track *
piano_roll_get_current_track(const PianoRoll * self)316 piano_roll_get_current_track (
317   const PianoRoll * self)
318 {
319   /* TODO */
320   return NULL;
321 }
322 
323 void
piano_roll_set_notes_zoom(PianoRoll * self,float notes_zoom,int fire_events)324 piano_roll_set_notes_zoom (
325   PianoRoll * self,
326   float         notes_zoom,
327   int         fire_events)
328 {
329   if (notes_zoom < 1.f || notes_zoom > 4.5f)
330     return;
331 
332   self->notes_zoom = notes_zoom;
333 
334   if (fire_events)
335     {
336       EVENTS_PUSH (
337         ET_PIANO_ROLL_KEY_HEIGHT_CHANGED, NULL);
338     }
339 }
340 
341 /**
342  * Sets the MIDI modifier.
343  */
344 void
piano_roll_set_midi_modifier(PianoRoll * self,MidiModifier modifier)345 piano_roll_set_midi_modifier (
346   PianoRoll * self,
347   MidiModifier modifier)
348 {
349   self->midi_modifier = modifier;
350 
351 #if 0
352   g_settings_set_enum (
353     S_UI, "piano-roll-midi-modifier",
354     modifier);
355 #endif
356 
357   EVENTS_PUSH (
358     ET_PIANO_ROLL_MIDI_MODIFIER_CHANGED,
359     NULL);
360 }
361 
362 void
piano_roll_init(PianoRoll * self)363 piano_roll_init (PianoRoll * self)
364 {
365   self->schema_version =
366     PIANO_ROLL_SCHEMA_VERSION;
367   self->notes_zoom = 3.f;
368 
369   self->midi_modifier = MIDI_MODIFIER_VELOCITY;
370 
371   editor_settings_init (&self->editor_settings);
372 
373   if (!ZRYTHM_TESTING)
374     {
375       self->highlighting =
376         g_settings_get_enum (
377           S_UI, "piano-roll-highlight");
378       self->midi_modifier =
379         g_settings_get_enum (
380           S_UI, "piano-roll-midi-modifier");
381     }
382 
383   init_descriptors (self);
384 }
385 
386 /**
387  * Only clones what is needed for serialization.
388  */
389 PianoRoll *
piano_roll_clone(const PianoRoll * src)390 piano_roll_clone (
391   const PianoRoll * src)
392 {
393   PianoRoll * self = object_new (PianoRoll);
394   self->schema_version = PIANO_ROLL_SCHEMA_VERSION;
395 
396   self->notes_zoom = src->notes_zoom;
397   self->midi_modifier = src->midi_modifier;
398   self->editor_settings = src->editor_settings;
399 
400   return self;
401 }
402 
403 PianoRoll *
piano_roll_new(void)404 piano_roll_new (void)
405 {
406   PianoRoll * self = object_new (PianoRoll);
407   self->schema_version = PIANO_ROLL_SCHEMA_VERSION;
408 
409   return self;
410 }
411 
412 void
piano_roll_free(PianoRoll * self)413 piano_roll_free (
414   PianoRoll * self)
415 {
416   for (int i = 0; i < 128; i++)
417     {
418       object_free_w_func_and_null (
419         midi_note_descriptor_free,
420         self->piano_descriptors[i]);
421       object_free_w_func_and_null (
422         midi_note_descriptor_free,
423         self->drum_descriptors[i]);
424     }
425 
426   object_zero_and_free (self);
427 }
428