1 /* Piano-keyboard based on jack-keyboard
3 * Copyright (C) 2019 Robin Gareus <robin@gareus.org>
4 * Copyright (c) 2007, 2008 Edward Tomasz NapieraĆa <trasz@FreeBSD.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program 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 General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include <cairo/cairo.h>
27 #include <pango/pango.h>
28 #include <pango/pangocairo.h>
30 #include <gdk/gdkkeysyms.h>
33 #include "gtkmm2ext/keyboard.h"
35 #include "pianokeyboard.h"
38 # define M_PI 3.14159265358979323846
42 # define MIN(A, B) ((A) < (B)) ? (A) : (B)
46 # define MAX(A, B) ((A) > (B)) ? (A) : (B)
49 #define PIANO_KEYBOARD_DEFAULT_WIDTH 730
50 #define PIANO_KEYBOARD_DEFAULT_HEIGHT 70
52 #define PIANO_MIN_NOTE 21
53 #define PIANO_MAX_NOTE 108
54 #define OCTAVE_MIN (-1)
55 #define OCTAVE_MAX (7)
58 APianoKeyboard::annotate_layout (cairo_t* cr, int note) const
60 int nkey = note - _octave * 12;
62 if (nkey < 0 || nkey >= NNOTES) {
66 std::map<int, std::string>::const_iterator kv = _note_bindings.find (nkey);
67 if (kv == _note_bindings.end ()) {
71 int x = _notes[note].x;
72 int w = _notes[note].w;
73 int h = _notes[note].h;
77 snprintf (buf, 16, "%lc",
78 gdk_keyval_to_unicode (gdk_keyval_to_upper (gdk_keyval_from_name (kv->second.c_str ()))));
79 PangoLayout* pl = pango_cairo_create_layout (cr);
80 pango_layout_set_font_description (pl, _font_cue);
81 pango_layout_set_text (pl, buf, -1);
82 pango_layout_set_alignment (pl, PANGO_ALIGN_LEFT);
83 pango_layout_get_pixel_size (pl, &tw, &th);
85 if (_notes[note].white) {
86 cairo_set_source_rgba (cr, 0.0, 0.0, 0.5, 1.0);
88 cairo_set_source_rgba (cr, 1.0, 1.0, 0.5, 1.0);
93 if (_notes[note].white) {
94 cairo_move_to (cr, x + (w - tw) / 2, h * 2 / 3 + 3);
96 cairo_move_to (cr, x + (w - tw) / 2, h - th - 3);
98 pango_cairo_show_layout (cr, pl);
105 APianoKeyboard::annotate_note (cairo_t* cr, int note) const
107 assert ((note % 12) == 0);
109 int x = _notes[note].x;
110 int w = _notes[note].w;
111 int h = _notes[note].h;
115 sprintf (buf, "C%d", (note / 12) - 1);
116 PangoLayout* pl = pango_cairo_create_layout (cr);
117 pango_layout_set_font_description (pl, _font_octave);
118 pango_layout_set_text (pl, buf, -1);
119 pango_layout_set_alignment (pl, PANGO_ALIGN_LEFT);
120 pango_layout_get_pixel_size (pl, &tw, &th);
122 if (th < w && tw < h * .3) {
124 cairo_move_to (cr, x + (w - th) / 2, h - 3);
125 cairo_rotate (cr, M_PI / -2.0);
127 cairo_set_line_width (cr, 1.0);
128 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
129 pango_cairo_show_layout (cr, pl);
132 cairo_rel_move_to (cr, -.5, -.5);
133 pango_cairo_update_layout (cr, pl);
134 cairo_set_source_rgba (cr, 1, 1, 1, 0.3);
135 pango_cairo_layout_path (cr, pl);
136 cairo_set_line_width (cr, 1.5);
146 APianoKeyboard::draw_note (cairo_t* cr, int note) const
148 if (note < _min_note || note > _max_note) {
152 int is_white = _notes[note].white;
153 int x = _notes[note].x;
154 int w = _notes[note].w;
155 int h = _notes[note].h;
157 if (_notes[note].pressed || _notes[note].sustained) {
159 cairo_set_source_rgb (cr, 0.7, 0.5, 0.5);
161 cairo_set_source_rgb (cr, 0.6, 0.4, 0.4);
163 } else if (_highlight_grand_piano_range && (note < PIANO_MIN_NOTE || note > PIANO_MAX_NOTE)) {
165 cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
167 cairo_set_source_rgb (cr, 0.3, 0.3, 0.3);
171 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
173 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
177 cairo_set_line_width (cr, 1.0);
179 cairo_rectangle (cr, x, 0, w, h);
182 cairo_set_source_rgb (cr, 0.0f, 0.0f, 0.0f); /* black outline */
183 cairo_rectangle (cr, x, 0, w, h);
186 if (_annotate_octave && (note % 12) == 0) {
187 annotate_note (cr, note);
190 if (_annotate_layout) {
191 annotate_layout (cr, note);
194 /* We need to redraw black keys that partially obscure the white one. */
195 if (note < NNOTES - 2 && !_notes[note + 1].white) {
196 draw_note (cr, note + 1);
199 if (note > 0 && !_notes[note - 1].white) {
200 draw_note (cr, note - 1);
205 APianoKeyboard::queue_note_draw (int note)
207 queue_draw_area (_notes[note].x, 0, _notes[note].w, _notes[note].h);
211 APianoKeyboard::press_key (int key, int vel)
214 assert (key < NNOTES);
216 /* This is for keyboard autorepeat protection. */
217 if (_notes[key].pressed) {
221 if (_sustain_new_notes) {
222 _notes[key].sustained = true;
224 _notes[key].sustained = false;
227 if (_monophonic && _last_key != key) {
228 bool signal_off = _notes[_last_key].pressed || _notes[_last_key].sustained;
229 _notes[_last_key].pressed = false;
230 _notes[_last_key].sustained = false;
232 NoteOff (_last_key); /* EMIT SIGNAL */
234 queue_note_draw (_last_key);
238 _notes[key].pressed = true;
240 NoteOn (key, vel); /* EMIT SIGNAL */
241 queue_note_draw (key);
245 APianoKeyboard::release_key (int key)
248 assert (key < NNOTES);
250 if (!_notes[key].pressed) {
254 if (_sustain_new_notes) {
255 _notes[key].sustained = true;
258 _notes[key].pressed = false;
260 if (_notes[key].sustained) {
264 NoteOff (key); /* EMIT SIGNAL */
265 queue_note_draw (key);
269 APianoKeyboard::stop_unsustained_notes ()
271 for (int i = 0; i < NNOTES; ++i) {
272 if (_notes[i].pressed && !_notes[i].sustained) {
273 _notes[i].pressed = false;
274 NoteOff (i); /* EMIT SIGNAL */
281 APianoKeyboard::stop_sustained_notes ()
283 for (int i = 0; i < NNOTES; ++i) {
284 if (_notes[i].sustained) {
285 _notes[i].sustained = false;
286 if (_notes[i].pressed) {
289 NoteOff (i); /* EMIT SIGNAL */
296 APianoKeyboard::key_binding (const char* key) const
298 std::map<std::string, int>::const_iterator kv;
299 if (key && (kv = _key_bindings.find (key)) != _key_bindings.end ()) {
306 APianoKeyboard::bind_key (const char* key, int note)
308 _key_bindings[key] = note;
309 _note_bindings[note] = key;
313 APianoKeyboard::clear_notes ()
315 _key_bindings.clear ();
316 _note_bindings.clear ();
320 APianoKeyboard::bind_keys_qwerty ()
324 bind_key ("space", 128);
325 bind_key ("Tab", 129);
327 /* Lower keyboard row - "zxcvbnm". */
328 bind_key ("z", 12); /* C0 */
341 /* Upper keyboard row, first octave - "qwertyu". */
355 /* Upper keyboard row, the rest - "iop". */
371 APianoKeyboard::bind_keys_qwertz ()
375 /* The only difference between QWERTY and QWERTZ is that the "y" and "z" are swapped together. */
381 APianoKeyboard::bind_keys_azerty ()
385 bind_key ("space", 128);
386 bind_key ("Tab", 129);
388 /* Lower keyboard row - "wxcvbn,". */
389 bind_key ("w", 12); /* C0 */
400 bind_key ("comma", 23);
402 /* Upper keyboard row, first octave - "azertyu". */
404 bind_key ("eacute", 25);
406 bind_key ("quotedbl", 27);
409 bind_key ("parenleft", 30);
411 bind_key ("minus", 32);
413 bind_key ("egrave", 34);
416 /* Upper keyboard row, the rest - "iop". */
418 bind_key ("ccedilla", 37);
420 bind_key ("agrave", 39);
425 APianoKeyboard::bind_keys_dvorak ()
429 bind_key ("space", 128);
430 bind_key ("Tab", 129);
432 /* Lower keyboard row - ";qjkxbm". */
433 bind_key ("semicolon", 12); /* C0 */
445 bind_key ("w", 24); /* overlaps with upper row */
451 /* Upper keyboard row, first octave - "',.pyfg". */
452 bind_key ("apostrophe", 24);
454 bind_key ("comma", 26);
456 bind_key ("period", 28);
465 /* Upper keyboard row, the rest - "crl". */
472 bind_key("slash", 41); /* extra F */
473 bind_key("bracketright", 42);
474 bind_key("equal", 43);
479 APianoKeyboard::bind_keys_basic_qwerty ()
483 bind_key ("space", 128);
484 bind_key ("Tab", 129);
486 /* simple - middle rows only */
487 bind_key ("a", 12); /* C0 */
500 bind_key ("k", 24); /* C1 */
504 bind_key ("semicolon", 28);
505 bind_key ("apostrophe", 29);
509 APianoKeyboard::bind_keys_basic_qwertz ()
513 bind_key ("space", 128);
514 bind_key ("Tab", 129);
516 /* simple - middle rows only */
517 bind_key ("a", 12); /* C0 */
530 bind_key ("k", 24); /* C1 */
534 bind_key ("semicolon", 28);
535 bind_key ("apostrophe", 29);
539 get_keycode (GdkEventKey* event)
541 /* We're not using event->keyval, because we need keyval with level set to 0.
542 E.g. if user holds Shift and presses '7', we want to get a '7', not '&'. */
545 /* gdkkeys-quartz.c does not implement gdk_keymap_lookup_key */
547 gdk_keymap_translate_keyboard_state (NULL, event->hardware_keycode,
548 (GdkModifierType)0, 0,
549 &keyval, NULL, NULL, NULL);
552 kk.keycode = event->hardware_keycode;
556 guint keyval = gdk_keymap_lookup_key (NULL, &kk);
558 return gdk_keyval_name (gdk_keyval_to_lower (keyval));
562 APianoKeyboard::on_key_press_event (GdkEventKey* event)
564 if (Gtkmm2ext::Keyboard::modifier_state_contains (event->state, Gtkmm2ext::Keyboard::PrimaryModifier)) {
568 char const* key = get_keycode (event);
569 int note = key_binding (key);
579 /* Rest is used on release */
587 note += _octave * 12;
591 assert (note < NNOTES);
593 _note_stack[key] = note;
595 press_key (note, _key_velocity);
601 APianoKeyboard::on_key_release_event (GdkEventKey* event)
603 if (Gtkmm2ext::Keyboard::modifier_state_contains (event->state, Gtkmm2ext::Keyboard::PrimaryModifier)) {
606 char const* key = get_keycode (event);
612 int note = key_binding (key);
615 Rest (); /* EMIT SIGNAL */
626 std::map<std::string, int>::const_iterator kv = _note_stack.find (key);
627 if (kv == _note_stack.end ()) {
631 release_key (kv->second);
632 _note_stack.erase (key);
638 APianoKeyboard::get_note_for_xy (int x, int y) const
640 int height = get_height ();
643 if (y <= ((height * 2) / 3)) { /* might be a black key */
644 for (note = 0; note <= _max_note; ++note) {
645 if (_notes[note].white) {
649 if (x >= _notes[note].x && x <= _notes[note].x + _notes[note].w) {
655 for (note = 0; note <= _max_note; ++note) {
656 if (!_notes[note].white) {
660 if (x >= _notes[note].x && x <= _notes[note].x + _notes[note].w) {
669 APianoKeyboard::get_velocity_for_note_at_y (int note, int y) const
674 int vel = _min_velocity + (_max_velocity - _min_velocity) * y / _notes[note].h;
678 } else if (vel > 127) {
685 APianoKeyboard::on_button_press_event (GdkEventButton* event)
690 int note = get_note_for_xy (x, y);
692 if (event->button != 1)
695 if (event->type == GDK_BUTTON_PRESS) {
700 if (_note_being_pressed_using_mouse >= 0) {
701 release_key (_note_being_pressed_using_mouse);
704 press_key (note, get_velocity_for_note_at_y (note, y));
705 _note_being_pressed_using_mouse = note;
707 } else if (event->type == GDK_BUTTON_RELEASE) {
711 if (_note_being_pressed_using_mouse >= 0) {
712 release_key (_note_being_pressed_using_mouse);
715 _note_being_pressed_using_mouse = -1;
722 APianoKeyboard::on_button_release_event (GdkEventButton* event)
724 return on_button_press_event (event);
728 APianoKeyboard::on_motion_notify_event (GdkEventMotion* event)
732 if ((event->state & GDK_BUTTON1_MASK) == 0)
738 note = get_note_for_xy (x, y);
740 if (note != _note_being_pressed_using_mouse && note >= 0) {
741 if (_note_being_pressed_using_mouse >= 0) {
742 release_key (_note_being_pressed_using_mouse);
744 press_key (note, get_velocity_for_note_at_y (note, y));
745 _note_being_pressed_using_mouse = note;
752 APianoKeyboard::on_expose_event (GdkEventExpose* event)
754 cairo_t* cr = gdk_cairo_create (GDK_DRAWABLE (get_window ()->gobj ()));
755 cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height);
759 sprintf (buf, "ArdourMono %dpx", MAX (8, MIN (20, _notes[1].w / 2 + 3)));
760 _font_cue = pango_font_description_from_string (buf);
761 sprintf (buf, "ArdourMono %dpx", MAX (8, MIN (20, MIN (_notes[0].w * 11 / 15 , _notes[0].h / 7))));
762 _font_octave = pango_font_description_from_string (buf);
764 for (int i = 0; i < NNOTES; ++i) {
769 r.width = _notes[i].w;
770 r.height = _notes[i].h;
772 switch (gdk_region_rect_in (event->region, &r)) {
773 case GDK_OVERLAP_RECTANGLE_PART:
774 case GDK_OVERLAP_RECTANGLE_IN:
782 pango_font_description_free (_font_cue);
783 pango_font_description_free (_font_octave);
790 APianoKeyboard::on_size_request (Gtk::Requisition* requisition)
792 requisition->width = PIANO_KEYBOARD_DEFAULT_WIDTH;
793 requisition->height = PIANO_KEYBOARD_DEFAULT_HEIGHT;
794 if (_annotate_layout) {
795 requisition->height += 16;
797 if (_annotate_octave) {
798 requisition->height += 24;
803 APianoKeyboard::is_black (int key) const
805 int note_in_octave = key % 12;
806 switch (note_in_octave) {
820 APianoKeyboard::black_key_left_shift (int key) const
822 int note_in_octave = key % 12;
823 switch (note_in_octave) {
841 APianoKeyboard::recompute_dimensions ()
844 int number_of_white_keys = 0;
845 int skipped_white_keys = 0;
847 for (note = _min_note; note <= _max_note; ++note) {
848 if (!is_black (note)) {
849 ++number_of_white_keys;
852 for (note = 0; note < _min_note; ++note) {
853 if (!is_black (note)) {
854 ++skipped_white_keys;
858 int width = get_width ();
859 int height = get_height ();
861 int key_width = width / number_of_white_keys;
862 int black_key_width = key_width * 0.8;
863 int useful_width = number_of_white_keys * key_width;
865 int widget_margin = (width - useful_width) / 2;
868 for (note = 0, white_key = -skipped_white_keys; note < NNOTES; ++note) {
869 if (is_black (note)) {
870 /* This note is black key. */
871 _notes[note].x = widget_margin +
872 (white_key * key_width) -
873 (black_key_width * black_key_left_shift (note));
874 _notes[note].w = black_key_width;
875 _notes[note].h = (height * 2) / 3;
876 _notes[note].white = 0;
880 /* This note is white key. */
881 _notes[note].x = widget_margin + white_key * key_width;
882 _notes[note].w = key_width;
883 _notes[note].h = height;
884 _notes[note].white = 1;
891 APianoKeyboard::on_size_allocate (Gtk::Allocation& allocation)
893 DrawingArea::on_size_allocate (allocation);
894 recompute_dimensions ();
897 APianoKeyboard::APianoKeyboard ()
900 add_events (KEY_PRESS_MASK | KEY_RELEASE_MASK | BUTTON_PRESS_MASK | BUTTON_RELEASE_MASK | POINTER_MOTION_MASK | POINTER_MOTION_HINT_MASK);
902 _sustain_new_notes = false;
903 _highlight_grand_piano_range = true;
904 _annotate_layout = false;
905 _annotate_octave = false;
908 _note_being_pressed_using_mouse = -1;
921 APianoKeyboard::~APianoKeyboard ()
926 APianoKeyboard::set_grand_piano_highlight (bool enabled)
928 _highlight_grand_piano_range = enabled;
933 APianoKeyboard::set_annotate_layout (bool enabled)
935 _annotate_layout = enabled;
940 APianoKeyboard::set_annotate_octave (bool enabled)
942 _annotate_octave = enabled;
947 APianoKeyboard::set_monophonic (bool monophonic)
949 _monophonic = monophonic;
953 APianoKeyboard::set_velocities (int min_vel, int max_vel, int key_vel)
955 if (min_vel <= max_vel && min_vel > 0 && max_vel < 128) {
956 _min_velocity = min_vel;
957 _max_velocity = max_vel;
960 if (key_vel > 0 && key_vel < 128) {
961 _key_velocity = key_vel;
966 APianoKeyboard::sustain_press ()
968 if (_sustain_new_notes) {
971 _sustain_new_notes = true;
972 SustainChanged (true); /* EMIT SIGNAL */
976 APianoKeyboard::sustain_release ()
978 stop_sustained_notes ();
979 if (_sustain_new_notes) {
980 _sustain_new_notes = false;
981 SustainChanged (false); /* EMIT SIGNAL */
986 APianoKeyboard::set_note_on (int note)
988 if (!_notes[note].pressed) {
989 _notes[note].pressed = true;
990 queue_note_draw (note);
995 APianoKeyboard::set_note_off (int note)
997 if (_notes[note].pressed || _notes[note].sustained) {
998 _notes[note].pressed = false;
999 _notes[note].sustained = false;
1000 queue_note_draw (note);
1005 APianoKeyboard::set_octave (int octave)
1009 } else if (octave > 7) {
1014 set_octave_range (_octave_range);
1018 APianoKeyboard::set_octave_range (int octave_range)
1020 if (octave_range < 2) {
1023 if (octave_range > 11) {
1027 _octave_range = octave_range;
1029 /* -1 <= _octave <= 7
1030 * key-bindings are at offset 12 .. 40
1031 * default piano range: _octave = 4, range = 7 -> note 21..108
1034 switch (_octave_range) {
1040 _min_note = (_octave + 1) * 12;
1044 _min_note = (_octave + 0) * 12;
1047 _min_note = (_octave - 1) * 12;
1051 _min_note = (_octave - 2) * 12;
1055 _min_note = (_octave - 3) * 12;
1058 _min_note = (_octave - 4) * 12;
1062 int upper_offset = 0;
1064 if (_min_note < 3) {
1067 } else if (_octave_range > 5) {
1068 /* extend down to A */
1073 _max_note = MIN (127, upper_offset + _min_note + _octave_range * 12);
1075 if (_max_note == 127) {
1076 _min_note = MAX (0, _max_note - _octave_range * 12);
1079 recompute_dimensions ();
1084 APianoKeyboard::set_keyboard_layout (Layout layout)
1088 bind_keys_qwerty ();
1091 bind_keys_qwertz ();
1094 bind_keys_azerty ();
1097 bind_keys_dvorak ();
1100 bind_keys_basic_qwerty ();
1103 bind_keys_basic_qwertz ();