1 /*
2  * Copyright (C) 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/chord_descriptor.h"
23 #include "gui/backend/piano_roll.h"
24 #include "gui/widgets/piano_keyboard.h"
25 #include "utils/ui.h"
26 #include "zrythm.h"
27 #include "zrythm_app.h"
28 
G_DEFINE_TYPE(PianoKeyboardWidget,piano_keyboard_widget,GTK_TYPE_DRAWING_AREA)29 G_DEFINE_TYPE (
30   PianoKeyboardWidget, piano_keyboard_widget,
31   GTK_TYPE_DRAWING_AREA)
32 
33 /**
34  * Draws an orange circle if the note is enabled.
35  */
36 static void
37 draw_orange_circle (
38   PianoKeyboardWidget * self,
39   cairo_t *             cr,
40   double                key_width,
41   double                cur_offset,
42   int                   i)
43 {
44   double height =
45     (double)
46     gtk_widget_get_allocated_height (
47       GTK_WIDGET (self));
48   if (self->chord_descr)
49     {
50       if (self->chord_descr->notes[
51             self->start_key + i])
52         {
53           double circle_radius = key_width / 3.0;
54           bool is_black =
55             piano_roll_is_key_black (
56               self->start_key + i);
57           gdk_cairo_set_source_rgba (
58             cr, &UI_COLORS->dark_orange);
59           cairo_set_source_rgba (cr, 1, 0, 0, 1);
60           cairo_arc (
61             cr,
62             cur_offset + key_width / 2.0,
63             is_black ?
64               height / 3.0 :
65               height / 1.2,
66             circle_radius, 0, 2 * M_PI);
67           cairo_fill (cr);
68         }
69     }
70 }
71 
72 static gboolean
piano_keyboard_draw_cb(GtkWidget * widget,cairo_t * cr,PianoKeyboardWidget * self)73 piano_keyboard_draw_cb (
74   GtkWidget *           widget,
75   cairo_t *             cr,
76   PianoKeyboardWidget * self)
77 {
78   GtkStyleContext *context =
79     gtk_widget_get_style_context (widget);
80 
81   int width =
82     gtk_widget_get_allocated_width (widget);
83   int height =
84     gtk_widget_get_allocated_height (widget);
85 
86   gtk_render_background (
87     context, cr, 0, 0, width, height);
88 
89   int num_white_keys = 0;
90   for (int i = 0; i < self->num_keys; i++)
91     {
92       if (!piano_roll_is_key_black (
93             self->start_key + i))
94         num_white_keys++;
95     }
96 
97   /* draw all white keys */
98   double key_width =
99     (double) width / (double) num_white_keys;
100   double cur_offset = 0.0;
101   for (int i = 0; i < self->num_keys; i++)
102     {
103       bool is_black =
104         piano_roll_is_key_black (
105           self->start_key + i);
106       if (is_black)
107         continue;
108 
109       cairo_set_source_rgba (cr, 0, 0, 0, 1);
110       cairo_rectangle (
111         cr, cur_offset, 0, key_width, height);
112       cairo_stroke_preserve (cr);
113       cairo_set_source_rgba (cr, 1, 1, 1, 1);
114       cairo_fill (cr);
115 
116       /* draw orange circle if part of chord */
117       draw_orange_circle (
118         self, cr, key_width, cur_offset, i);
119 
120       cur_offset += key_width;
121     }
122 
123   /* draw all black keys */
124   /*int num_black_keys = self->num_keys - num_white_keys;*/
125   cur_offset = 0.0;
126   for (int i = 0; i < self->num_keys; i++)
127     {
128       bool is_black =
129         piano_roll_is_key_black (
130           self->start_key + i);
131       if (!is_black)
132         {
133           bool is_next_black =
134             piano_roll_is_next_key_black (
135               self->start_key + i);
136 
137           if (is_next_black)
138             cur_offset += key_width / 2.0;
139           else
140             cur_offset += key_width;
141 
142           continue;
143         }
144 
145       cairo_set_source_rgba (cr, 0, 0, 0, 1);
146       cairo_rectangle (
147         cr, cur_offset, 0, key_width, height / 1.4);
148       cairo_fill (cr);
149 
150       /* draw orange circle if part of chord */
151       draw_orange_circle (
152         self, cr, key_width, cur_offset, i);
153 
154       cur_offset += key_width / 2.0;
155     }
156 
157  return FALSE;
158 }
159 
160 void
piano_keyboard_widget_refresh(PianoKeyboardWidget * self)161 piano_keyboard_widget_refresh (
162   PianoKeyboardWidget * self)
163 {
164   gtk_widget_queue_draw (GTK_WIDGET (self));
165 }
166 
167 /**
168  * Creates a piano keyboard widget.
169  */
170 PianoKeyboardWidget *
piano_keyboard_widget_new_for_chord_key(ChordDescriptor * descr)171 piano_keyboard_widget_new_for_chord_key (
172   ChordDescriptor * descr)
173 {
174   PianoKeyboardWidget * self =
175     piano_keyboard_widget_new (
176       GTK_ORIENTATION_HORIZONTAL);
177 
178   self->chord_descr = descr;
179   self->editable = true;
180   self->playable = false;
181   self->scrollable = false;
182   self->start_key = 0;
183   self->num_keys = 48;
184 
185   return self;
186 }
187 
188 /**
189  * Creates a piano keyboard widget.
190  */
191 PianoKeyboardWidget *
piano_keyboard_widget_new(GtkOrientation orientation)192 piano_keyboard_widget_new (
193   GtkOrientation orientation)
194 {
195   PianoKeyboardWidget * self =
196     g_object_new (PIANO_KEYBOARD_WIDGET_TYPE, NULL);
197 
198   g_signal_connect (
199     G_OBJECT(self), "draw",
200     G_CALLBACK (piano_keyboard_draw_cb),  self);
201 
202   return self;
203 }
204 
205 static void
piano_keyboard_widget_class_init(PianoKeyboardWidgetClass * _klass)206 piano_keyboard_widget_class_init (
207   PianoKeyboardWidgetClass * _klass)
208 {
209 }
210 
211 static void
piano_keyboard_widget_init(PianoKeyboardWidget * self)212 piano_keyboard_widget_init (
213   PianoKeyboardWidget * self)
214 {
215   gtk_widget_set_visible (GTK_WIDGET (self), true);
216 
217   self->editable = true;
218   self->playable = false;
219   self->scrollable = false;
220   self->start_key = 0;
221   self->num_keys = 36;
222 }
223