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