New keyboard design:

* making use of Cairo
* displaying pressed keys via small indicator
* displaying velocity of pressed key
This commit is contained in:
Markus Schmidt 2018-03-12 21:30:39 +01:00
parent 32fba4955c
commit b5c06ee4e6
3 changed files with 190 additions and 42 deletions

View File

@ -218,7 +218,7 @@ process_received_message_async(gpointer evp)
if (ev->data[2] == 0)
piano_keyboard_set_note_off(keyboard, ev->data[1]);
else
piano_keyboard_set_note_on(keyboard, ev->data[1]);
piano_keyboard_set_note_on(keyboard, ev->data[1], ev->data[2]);
}
if (b0 == MIDI_NOTE_OFF) {
@ -1028,6 +1028,7 @@ velocity_event_handler(GtkRange *range, gpointer notused)
assert(current_velocity);
*current_velocity = gtk_range_get_value(range);
keyboard->current_velocity = gtk_range_get_value(range);
}
#ifdef HAVE_X11
@ -1555,6 +1556,14 @@ init_gtk_1(int *argc, char ***argv)
void
init_gtk_2(void)
{
/* PianoKeyboard widget. */
keyboard = PIANO_KEYBOARD(piano_keyboard_new());
if (!enable_gui) {
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(keyboard));
return;
}
GtkTable *table;
GtkWidget *label;
GtkCellRenderer *renderer;
@ -1563,9 +1572,7 @@ init_gtk_2(void)
table = GTK_TABLE(gtk_table_new(4, 8, FALSE));
gtk_table_set_row_spacings(table, 5);
gtk_table_set_col_spacings(table, 5);
if (enable_gui)
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table));
/* Channel label and spin. */
label = gtk_label_new("Channel:");
@ -1651,13 +1658,7 @@ init_gtk_2(void)
g_signal_connect(G_OBJECT(sustain_button), "pressed", G_CALLBACK(sustain_event_handler), (void *)1);
g_signal_connect(G_OBJECT(sustain_button), "released", G_CALLBACK(sustain_event_handler), (void *)0);
/* PianoKeyboard widget. */
keyboard = PIANO_KEYBOARD(piano_keyboard_new());
if (enable_gui)
gtk_table_attach_defaults(table, GTK_WIDGET(keyboard), 0, 8, 3, 4);
else
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(keyboard));
gtk_table_attach_defaults(table, GTK_WIDGET(keyboard), 0, 8, 3, 4);
g_signal_connect(G_OBJECT(keyboard), "note-on", G_CALLBACK(note_on_event_handler), NULL);
g_signal_connect(G_OBJECT(keyboard), "note-off", G_CALLBACK(note_off_event_handler), NULL);

View File

@ -35,6 +35,8 @@
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <cairo.h>
#include <math.h>
#include "pianokeyboard.h"
@ -77,17 +79,12 @@ draw_keyboard_cue(PianoKeyboard *pk)
static void
draw_note(PianoKeyboard *pk, int note)
{
GtkWidget *widget = GTK_WIDGET(pk);
if (note < pk->min_note)
return;
if (note > pk->max_note)
return;
int is_white, x, w, h;
GdkColor black = {0, 0, 0, 0};
GdkColor white = {0, 65535, 65535, 65535};
GdkGC *gc = GTK_WIDGET(pk)->style->fg_gc[0];
GtkWidget *widget;
int is_white, x, w, h, pressed;
is_white = pk->notes[note].white;
@ -95,28 +92,23 @@ draw_note(PianoKeyboard *pk, int note)
w = pk->notes[note].w;
h = pk->notes[note].h;
if (pk->notes[note].pressed || pk->notes[note].sustained)
is_white = !is_white;
if (is_white)
gdk_gc_set_rgb_fg_color(gc, &white);
else
gdk_gc_set_rgb_fg_color(gc, &black);
gdk_draw_rectangle(GTK_WIDGET(pk)->window, gc, TRUE, x, 0, w, h);
gdk_gc_set_rgb_fg_color(gc, &black);
gdk_draw_rectangle(GTK_WIDGET(pk)->window, gc, FALSE, x, 0, w, h);
if (pk->enable_keyboard_cue)
draw_keyboard_cue(pk);
/* We need to redraw black keys that partially obscure the white one. */
pressed = (int)(pk->notes[note].pressed || pk->notes[note].sustained);
if (is_white) {
piano_keyboard_draw_white_key(widget, x, 0, w, h, pressed, pk->notes[note].velocity);
} else {
piano_keyboard_draw_black_key(widget, x, 0, w, h, pressed, pk->notes[note].velocity);
}
if (note < NNOTES - 2 && !pk->notes[note + 1].white)
draw_note(pk, note + 1);
if (note > 0 && !pk->notes[note - 1].white)
draw_note(pk, note - 1);
if (pk->enable_keyboard_cue)
draw_keyboard_cue(pk);
/*
* XXX: This doesn't really belong here. Originally I wanted to pack PianoKeyboard into GtkFrame
* packed into GtkAlignment. I failed to make it behave the way I want. GtkFrame would need
@ -147,6 +139,7 @@ press_key(PianoKeyboard *pk, int key)
pk->notes[key].sustained = 0;
pk->notes[key].pressed = 1;
pk->notes[key].velocity = pk->current_velocity;
g_signal_emit_by_name(GTK_WIDGET(pk), "note-on", key);
draw_note(pk, key);
@ -487,7 +480,7 @@ recompute_dimensions(PianoKeyboard *pk)
(white_key * key_width) -
(black_key_width * black_key_left_shift(note));
pk->notes[note].w = black_key_width;
pk->notes[note].h = (height * 2) / 3;
pk->notes[note].h = (height * 3) / 5;
pk->notes[note].white = 0;
continue;
}
@ -623,10 +616,11 @@ piano_keyboard_sustain_release(PianoKeyboard *pk)
}
void
piano_keyboard_set_note_on(PianoKeyboard *pk, int note)
piano_keyboard_set_note_on(PianoKeyboard *pk, int note, int vel)
{
if (pk->notes[note].pressed == 0) {
pk->notes[note].pressed = 1;
pk->notes[note].velocity = vel;
draw_note(pk, note);
}
}
@ -673,3 +667,137 @@ piano_keyboard_enable_all_midi_notes(PianoKeyboard* pk)
recompute_dimensions(pk);
}
void
piano_keyboard_draw_white_key (GtkWidget *widget, int x, int y, int w, int h, int pressed, int vel)
{
cairo_pattern_t *pat;
GdkWindow *window = widget->window;
cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
cairo_set_line_width(c, 1);
cairo_rectangle(c, x, y, w, h);
cairo_clip_preserve(c);
pat = cairo_pattern_create_linear (x, y, x, y + h);
cairo_pattern_add_color_stop_rgb (pat, 0.0, 0.25, 0.25, 0.2);
cairo_pattern_add_color_stop_rgb (pat, 0.1, 0.957, 0.914, 0.925);
cairo_pattern_add_color_stop_rgb (pat, 1.0, 0.796, 0.787, 0.662);
cairo_set_source(c, pat);
cairo_fill(c);
cairo_move_to(c, x + 0.5, y);
cairo_line_to(c, x + 0.5, y + h);
cairo_set_source_rgba(c, 1, 1, 1, 0.75);
cairo_stroke(c);
cairo_move_to(c, x + w - 0.5, y);
cairo_line_to(c, x + w - 0.5, y + h);
cairo_set_source_rgba(c, 0, 0, 0, 0.5);
cairo_stroke(c);
if (pressed)
piano_keyboard_draw_pressed(c, x, y, w, h, vel);
piano_keyboard_draw_key_shadow(c, x, y, w, h);
cairo_destroy(c);
}
void
piano_keyboard_draw_black_key (GtkWidget *widget, int x, int y, int w, int h, int pressed, int vel)
{
cairo_pattern_t *pat;
GdkWindow *window = widget->window;
cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
cairo_set_line_width(c, 1);
cairo_rectangle(c, x, y, w, h);
cairo_clip_preserve(c);
pat = cairo_pattern_create_linear (x, y, x, y + h);
cairo_pattern_add_color_stop_rgb (pat, 0.0, 0, 0, 0);
cairo_pattern_add_color_stop_rgb (pat, 0.1, 0.27, 0.27, 0.27);
cairo_pattern_add_color_stop_rgb (pat, 1.0, 0, 0, 0);
cairo_set_source(c, pat);
cairo_fill(c);
pat = cairo_pattern_create_linear (x + 1, y, x + 1, y + h - w);
cairo_pattern_add_color_stop_rgb (pat, 0.0, 0, 0, 0);
cairo_pattern_add_color_stop_rgb (pat, 0.1, 0.55, 0.55, 0.55);
cairo_pattern_add_color_stop_rgb (pat, 0.5, 0.45, 0.45, 0.45);
cairo_pattern_add_color_stop_rgb (pat, 0.5001, 0.35, 0.35, 0.35);
cairo_pattern_add_color_stop_rgb (pat, 1.0, 0.25, 0.25, 0.25);
cairo_set_source(c, pat);
cairo_rectangle(c, x + 1, y, w - 2, y + h - w);
cairo_fill(c);
if (pressed)
piano_keyboard_draw_pressed(c, x, y, w, h, vel);
piano_keyboard_draw_key_shadow(c, x, y, w, h);
cairo_destroy(c);
}
void
piano_keyboard_draw_pressed (cairo_t *c, int x, int y, int w, int h, int vel)
{
float m = w * .15; // margin
float s = w - m * 2.; // size
float _vel = ((float)vel / 127.);
float hue = _vel * 140 + 220; // hue 220 .. 360 - blue over pink to red
float sat = .5 + _vel * 0.3; // saturation 0.5 .. 0.8
float val = 1. - _vel * 0.2; // lightness 1.0 .. 0.8
cairo_rectangle(c, x + m, y + h - m - s * 2, s, s * 2);
hsv HSV = {hue, sat, val};
rgb RGB = hsv2rgb(HSV);
cairo_set_source_rgb(c, RGB.r, RGB.g, RGB.b);
cairo_fill(c);
}
void
piano_keyboard_draw_key_shadow (cairo_t *c, int x, int y, int w, int h)
{
cairo_pattern_t *pat;
pat = cairo_pattern_create_linear (x, y, x, y + (int)(h * 0.2));
cairo_pattern_add_color_stop_rgba (pat, 0.0, 0, 0, 0, 0.4);
cairo_pattern_add_color_stop_rgba (pat, 1.0, 0, 0, 0, 0);
cairo_rectangle(c, x, y, w, (int)(h * 0.2));
cairo_set_source(c, pat);
cairo_fill(c);
}
rgb
hsv2rgb(hsv HSV)
{
rgb RGB;
double H = HSV.h, S = HSV.s, V = HSV.v,
P, Q, T,
fract;
(H == 360.)?(H = 0.):(H /= 60.);
fract = H - floor(H);
P = V*(1. - S);
Q = V*(1. - S*fract);
T = V*(1. - S*(1. - fract));
if (0. <= H && H < 1.)
RGB = (rgb){.r = V, .g = T, .b = P};
else if (1. <= H && H < 2.)
RGB = (rgb){.r = Q, .g = V, .b = P};
else if (2. <= H && H < 3.)
RGB = (rgb){.r = P, .g = V, .b = T};
else if (3. <= H && H < 4.)
RGB = (rgb){.r = P, .g = Q, .b = V};
else if (4. <= H && H < 5.)
RGB = (rgb){.r = T, .g = P, .b = V};
else if (5. <= H && H < 6.)
RGB = (rgb){.r = V, .g = P, .b = Q};
else
RGB = (rgb){.r = 0., .g = 0., .b = 0.};
return RGB;
}

View File

@ -6,10 +6,10 @@
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
@ -65,6 +65,7 @@ struct Note {
int w; /* Width of the key, in pixels. */
int h; /* Height of the key, in pixels. */
int white; /* 1 if key is white; 0 otherwise. */
int velocity;
};
struct _PianoKeyboard
@ -78,6 +79,7 @@ struct _PianoKeyboard
int note_being_pressed_using_mouse;
int min_note;
int max_note;
int current_velocity;
volatile struct Note notes[NNOTES];
/* Table used to translate from PC keyboard character to MIDI note number. */
GArray *key_bindings;
@ -88,16 +90,33 @@ struct _PianoKeyboardClass
GtkDrawingAreaClass parent_class;
};
typedef struct {
double r;
double g;
double b;
} rgb;
typedef struct {
double h;
double s;
double v;
} hsv;
GType piano_keyboard_get_type (void) G_GNUC_CONST;
GtkWidget* piano_keyboard_new (void);
void piano_keyboard_sustain_press (PianoKeyboard *pk);
void piano_keyboard_sustain_release (PianoKeyboard *pk);
void piano_keyboard_set_note_on (PianoKeyboard *pk, int note);
void piano_keyboard_set_note_on (PianoKeyboard *pk, int note, int vel);
void piano_keyboard_set_note_off (PianoKeyboard *pk, int note);
void piano_keyboard_set_keyboard_cue (PianoKeyboard *pk, int enabled);
void piano_keyboard_set_octave (PianoKeyboard *pk, int octave);
gboolean piano_keyboard_set_keyboard_layout (PianoKeyboard *pk, const char *layout);
void piano_keyboard_enable_all_midi_notes(PianoKeyboard* pk);
void piano_keyboard_draw_white_key (GtkWidget *widget, int x, int y, int w, int h, int pressed, int val);
void piano_keyboard_draw_black_key (GtkWidget *widget, int x, int y, int w, int h, int pressed, int val);
void piano_keyboard_draw_pressed (cairo_t *c, int x, int y, int w, int h, int val);
void piano_keyboard_draw_key_shadow (cairo_t *c, int x, int y, int w, int h);
rgb hsv2rgb(hsv HSV);
G_END_DECLS