1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2 
3 #ifndef SPECTMORPH_INST_EDIT_NOTE_HH
4 #define SPECTMORPH_INST_EDIT_NOTE_HH
5 
6 #include "smcheckbox.hh"
7 #include "smparamlabel.hh"
8 #include "smbutton.hh"
9 #include "smshortcut.hh"
10 #include "smtoolbutton.hh"
11 #include "smcombobox.hh"
12 #include "smfixedgrid.hh"
13 #include "smscrollview.hh"
14 #include "smsynthinterface.hh"
15 #include "smsimplelines.hh"
16 
17 namespace SpectMorph
18 {
19 
20 class NoteWidget : public Widget
21 {
22   int first = 12;
23   int cols = 13;
24   int rows = 9;
25   int step = 12;
26 
27   struct NoteRect
28   {
29     int  note;
30     Rect rect;
31   };
32   std::vector<NoteRect> note_rects;
33 
34   std::string
35   note_to_text (int i, bool verbose = false)
36   {
37     std::vector<std::string> note_name { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
38     if (!verbose)
39       {
40         /* short names */
41         if (note_name [i % 12][1] == '#')
42           return "";
43         else if (i % 12)
44           return string_printf ("%s", note_name[i % 12].c_str());
45       }
46     /* full note name */
47     return string_printf ("%s%d", note_name[i % 12].c_str(), i / 12 - 2);
48   }
49   int mouse_note = -1;
50   int left_pressed_note = -1;
51   int right_pressed_note = -1;
52   Instrument     *instrument      = nullptr;
53   SynthInterface *synth_interface = nullptr;
54   std::vector<int> active_notes;
55 
56 public:
57   NoteWidget (Widget *parent, Instrument *instrument, SynthInterface *synth_interface) :
58     Widget (parent),
59     instrument (instrument),
60     synth_interface (synth_interface)
61   {
62     connect (instrument->signal_samples_changed, this, &NoteWidget::on_samples_changed);
63   }
64   void
65   draw (const DrawEvent& devent) override
66   {
67     DrawUtils du (devent.cr);
68 
69     du.round_box (0, 0, width(), height(), 1, 5, Color::null(), Color (0.7, 0.7, 0.7));
70 
71     auto cr = devent.cr;
72 
73     std::map<int, int> note_used = instrument->used_count();
74 
75     note_rects.clear();
76     for (int xpos = 0; xpos < cols; xpos++)
77       {
78         for (int ypos = 0; ypos < rows; ypos++)
79           {
80             double x = xpos * width() / cols;
81             double y = ypos * height() / rows;
82             int n = first + xpos + (rows - 1 - ypos) * step;
83             note_rects.push_back ({n, Rect (x, y, width() / cols, height() / rows)});
84             bool white_key = !note_to_text (n).empty();
85             if (white_key)
86               {
87                 du.set_color (Color (0.0, 0.0, 0.0));
88                 du.text (note_to_text (n), x, y, width() / cols, height() / rows, TextAlign::CENTER);
89               }
90             else
91               {
92                 cairo_set_source_rgb (cr, 0.3, 0.3, 0.3);
93                 cairo_rectangle (cr, x, y, width() / cols, height() / rows);
94                 cairo_fill (cr);
95               }
96             for (size_t i = 0; i < instrument->size(); i++)
97               {
98                 Sample *sample = instrument->sample (i);
99                 if (sample && n == sample->midi_note())
100                   {
101                     double xspace = width() / cols / 15;
102                     double yspace = height() / rows / 15;
103                     const Rect frame_rect (x + xspace, y + yspace, width() / cols - 2 * xspace, height() / rows - 2 * yspace);
104 
105                     Color frame_color;
106                     if (int (i) != instrument->selected())
107                       {
108                         if (white_key)
109                           frame_color = Color (0.3, 0.3, 0.3);
110                         else
111                           frame_color = Color (0.7, 0.7, 0.7);
112                         du.round_box (x + xspace, y + yspace, width() / cols - 2 * xspace, height() / rows - 2 * yspace, 3, 5, white_key ? Color (0.3, 0.3, 0.3) : Color (0.7, 0.7, 0.7));
113                       }
114                     else
115                       {
116                         frame_color = ThemeColor::MENU_ITEM;
117                       }
118                     /* draw red frame if one note is assigned twice */
119                     if (note_used[n] > 1)
120                       frame_color = Color (0.7, 0.0, 0.0);
121 
122                     du.round_box (frame_rect, 3, 5, frame_color);
123                   }
124               }
125             bool note_playing = false;
126             for (auto note : active_notes)
127               if (n == note)
128                 note_playing = true;
129             if (n == mouse_note || note_playing)
130               {
131                 double xspace = width() / cols / 10;
132                 double yspace = height() / rows / 10;
133 
134                 Color frame_color, fill_color, text_color;
135                 if (note_playing)
136                   {
137                     frame_color = Color::null();
138                     fill_color  = ThemeColor::SLIDER;
139                     text_color  = Color (0, 0, 0);
140                   }
141                 else
142                   {
143                     frame_color = Color (0.8, 0.8, 0.8);
144                     fill_color  = Color (0.9, 0.9, 0.9);
145                     text_color  = Color (0.5, 0.0, 0.0);
146                   }
147                 du.round_box (x + xspace, y + yspace, width() / cols - 2 * xspace, height() / rows - 2 * yspace, 1, 5, frame_color, fill_color);
148 
149                 du.set_color (text_color);
150                 du.text (note_to_text (n, true), x, y, width() / cols, height() / rows, TextAlign::CENTER);
151               }
152           }
153       }
154 
155     du.set_color (Color (0.4, 0.4, 0.4));
156     cairo_set_line_width (cr, 1);
157     for (int r = 1; r < rows; r++)
158       {
159         double y = r * height() / rows;
160         cairo_move_to (cr, 0, y);
161         cairo_line_to (cr, width(), y);
162         cairo_stroke (cr);
163       }
164     for (int c = 1; c < cols; c++)
165       {
166         double x = c * width() / cols;
167         cairo_move_to (cr, x, 0);
168         cairo_line_to (cr, x, height());
169         cairo_stroke (cr);
170       }
171     du.round_box (0, 0, width(), height(), 1, 5, Color (0.4, 0.4, 0.4));
172   }
173   void
174   mouse_move (const MouseEvent& event) override
175   {
176     for (auto note_rect : note_rects)
177       if (note_rect.rect.contains (event.x, event.y))
178         {
179           if (note_rect.note != mouse_note)
180             {
181               mouse_note = note_rect.note;
182               update();
183             }
184         }
185   }
186   void
187   mouse_press (const MouseEvent& event) override
188   {
189     if (event.double_click && event.button == LEFT_BUTTON)
190       {
191         Sample *sample = instrument->sample (instrument->selected());
192 
193         if (!sample)
194           return;
195         sample->set_midi_note (mouse_note);
196       }
197     else if (event.button == LEFT_BUTTON)
198       {
199         left_pressed_note = mouse_note;
200         synth_interface->synth_inst_edit_note (left_pressed_note, true, 2);
201       }
202     else if (event.button == RIGHT_BUTTON)
203       {
204         right_pressed_note = mouse_note;
205         synth_interface->synth_inst_edit_note (right_pressed_note, true, 0);
206       }
207     update();
208   }
209   void
210   mouse_release (const MouseEvent& event) override
211   {
212     if (event.button == LEFT_BUTTON && left_pressed_note >= 0)
213       {
214         synth_interface->synth_inst_edit_note (left_pressed_note, false, 2);
215         left_pressed_note = -1;
216         update();
217       }
218     else if (event.button == RIGHT_BUTTON)
219       {
220         synth_interface->synth_inst_edit_note (right_pressed_note, false, 0);
221         right_pressed_note = -1;
222         update();
223       }
224   }
225   void
226   on_samples_changed()
227   {
228     update();
229   }
230   void
231   set_active_notes (std::vector<int>& notes)
232   {
233     if (active_notes != notes)
234       {
235         active_notes = notes;
236         update();
237       }
238   }
239 };
240 
241 class InstEditNote : public Window
242 {
243   NoteWidget *note_widget = nullptr;
244 public:
245   InstEditNote (Window *window, Instrument *instrument, SynthInterface *synth_interface) :
246     Window (*window->event_loop(), "SpectMorph - Instrument Note", 13 * 40 + 2 * 8, 9 * 40 + 6 * 8, 0, false, window->native_window())
247   {
248     set_close_callback ([this]() {
249       signal_closed();
250       delete_later();
251      });
252 
253     Shortcut *play_shortcut = new Shortcut (this, ' ');
254     connect (play_shortcut->signal_activated, [this]() { signal_toggle_play(); });
255 
256     note_widget = new NoteWidget (this, instrument, synth_interface);
257     FixedGrid grid;
258     grid.add_widget (note_widget, 1, 1, width() / 8 - 2, height() / 8 - 6);
259 
260     Label *left = new Label (this, "Left Click");
261     Label *left_txt = new Label (this, "Play Reference");
262     left->set_bold (true);
263 
264     Label *right = new Label (this, "Right Click");
265     Label *right_txt = new Label (this, "Play Instrument");
266     right->set_bold (true);
267 
268     Label *dbl = new Label (this, "Double Click");
269     Label *dbl_txt = new Label (this, "Set Midi Note");
270     dbl->set_bold (true);
271 
272     Label *space = new Label (this, "Space");
273     Label *space_txt = new Label (this, "Play Selected Note");
274     space->set_bold (true);
275 
276     grid.dx = 4;
277     grid.dy = height() / 8 - 5;
278 
279     double xw = 12;
280     grid.add_widget (dbl, 0, 2, xw, 3);
281     grid.add_widget (dbl_txt, xw, 2, 20, 3);
282     grid.add_widget (space, 0, 0, xw, 3);
283     grid.add_widget (space_txt, xw, 0, 20, 3);
284 
285     grid.dx = width() / 8 / 2;
286     grid.dy = height() / 8 - 5;
287 
288     grid.add_widget (new VLine (this, Color (0.6, 0.6, 0.6), 2), 0, 0, 1, 5);
289 
290     grid.dx += 4;
291 
292     xw = 13;
293     grid.add_widget (left, 0, 0, xw, 3);
294     grid.add_widget (left_txt, xw, 0, 20, 3);
295     grid.add_widget (right, 0, 2, xw, 3);
296     grid.add_widget (right_txt, xw, 2, 20, 3);
297 
298     show();
299   }
300   void
301   set_active_notes (std::vector<int>& notes)
302   {
303     note_widget->set_active_notes (notes);
304   }
305   Signal<> signal_toggle_play;
306   Signal<> signal_closed;
307 };
308 
309 }
310 
311 #endif
312