/* * piano.c - piano widget * * Swami * Copyright (C) 1999-2003 Josh Green * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA or point your web browser to http://www.gnu.org. */ #include #include #include #include "piano.h" /* Forward declarations */ static void piano_class_init (PianoClass * klass); static void piano_init (Piano * piano); static void piano_destroy (GtkObject * object); static void piano_realize (GtkWidget * widget); static void piano_size_request (GtkWidget * widget, GtkRequisition * requisition); static void piano_size_allocate (GtkWidget * widget, GtkAllocation * allocation); static gint piano_expose (GtkWidget * widget, GdkEventExpose * event); static gint piano_button_press (GtkWidget * widget, GdkEventButton * event); static gint piano_button_release (GtkWidget * widget, GdkEventButton * event); static gint piano_motion_notify (GtkWidget * widget, GdkEventMotion * event); static void piano_update_mouse (Piano * piano, gint x, gint y); #define POFSY 0 static struct { guint8 selx; guint8 dispx; gboolean white; /* white key or black key? */ } keyinfo[12] = { {6, 2, TRUE}, {11, 7, FALSE}, {15, 11, TRUE}, {20, 16, FALSE}, {26, 20, TRUE}, {33, 29, TRUE}, {38, 34, FALSE}, {42, 38, TRUE}, {47, 43, FALSE}, {51, 47, TRUE}, {56, 52, FALSE}, {62, 56, TRUE} }; /* color used for note C60 marker */ static GdkColor c60clr = { red : 18000, green : 0, blue : 54000 }; static GtkWidgetClass *parent_class = NULL; guint piano_get_type (void) { static guint piano_type = 0; if (!piano_type) { GtkTypeInfo piano_info = { "Piano", sizeof (Piano), sizeof (PianoClass), (GtkClassInitFunc) piano_class_init, (GtkObjectInitFunc) piano_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL, }; piano_type = gtk_type_unique (gtk_widget_get_type (), &piano_info); } return piano_type; } enum { NOTE_ON, NOTE_OFF, DUMMY_SIGNAL /* used to count signals */ }; static gint piano_signals[DUMMY_SIGNAL] = { 0 }; static void piano_class_init (PianoClass * klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GtkObjectClass *) klass; widget_class = (GtkWidgetClass *) klass; parent_class = gtk_type_class (gtk_widget_get_type ()); piano_signals[NOTE_ON] = gtk_signal_new ("note-on", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (PianoClass, note_on), gtk_marshal_NONE__UINT, GTK_TYPE_NONE, 1, GTK_TYPE_UINT); piano_signals[NOTE_OFF] = gtk_signal_new ("note-off", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (PianoClass, note_off), gtk_marshal_NONE__UINT, GTK_TYPE_NONE, 1, GTK_TYPE_UINT); gtk_object_class_add_signals (object_class, piano_signals, DUMMY_SIGNAL); klass->note_on = NULL; klass->note_off = NULL; object_class->destroy = piano_destroy; widget_class->realize = piano_realize; widget_class->expose_event = piano_expose; widget_class->size_request = piano_size_request; widget_class->size_allocate = piano_size_allocate; widget_class->button_press_event = piano_button_press; widget_class->button_release_event = piano_button_release; widget_class->motion_notify_event = piano_motion_notify; } /* ----------------------------------------------- initialize piano widget Draws piano pixmap and black/white (un)selected state pixmaps Sets variables to default states ----------------------------------------------- */ static void piano_init (Piano * piano) { piano->key_mouse = 255; /* no key selected with mouse */ piano->key_lospan = 255; /* no lo span indicator */ piano->key_hispan = 255; /* no high span indicator */ piano->key_root = 255; /* no root key indicator */ } GtkWidget * piano_new (gboolean * selkeys) { Piano *piano; piano = gtk_type_new (piano_get_type ()); piano->selkeys = selkeys; return GTK_WIDGET (piano); } /* draws specified key in its "on" state */ void piano_note_on (Piano * piano, guint8 keynum) { gint xval, mod; GdkRectangle uparea; /* update area rectangle */ g_return_if_fail (piano != NULL); g_return_if_fail (IS_PIANO (piano)); g_return_if_fail (keynum < 128); if (piano->selkeys[keynum]) return; /* already selected? */ /* run user piano key press handler */ gtk_signal_emit (GTK_OBJECT (piano), piano_signals[NOTE_ON], keynum); piano->selkeys[keynum] = TRUE; mod = keynum % 12; xval = keynum / 12 * 63 + keyinfo[mod].dispx; if (keyinfo[mod].white) { gdk_draw_pixmap (piano->keyb_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], piano->selw_pm, 0, 0, xval - 1, 39 + POFSY, 8, 8); uparea.x = xval - 1; uparea.y = 39 + POFSY; uparea.width = 8; } else { gdk_draw_pixmap (piano->keyb_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], piano->selb_pm, 0, 0, xval, 20 + POFSY, 5, 8); uparea.x = xval; uparea.y = 20 + POFSY; uparea.width = 5; } uparea.height = 8; gtk_widget_draw (&piano->widget, &uparea); } /* draws specified key in its "released" state */ void piano_note_off (Piano * piano, guint8 keynum) { gint xval, mod; GdkRectangle uparea; /* update area */ g_return_if_fail (piano != NULL); g_return_if_fail (IS_PIANO (piano)); g_return_if_fail (keynum < 128); if (!piano->selkeys[keynum]) return; /* already unselected? */ /* signal user piano release key handlers */ gtk_signal_emit (GTK_OBJECT (piano), piano_signals[NOTE_OFF], keynum); piano->selkeys[keynum] = FALSE; mod = keynum % 12; xval = keynum / 12 * 63 + keyinfo[mod].dispx; if (keyinfo[mod].white) { gdk_draw_pixmap (piano->keyb_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], piano->unsw_pm, 0, 0, xval - 1, 39 + POFSY, 8, 8); uparea.x = xval - 1; uparea.y = 39 + POFSY; uparea.width = 8; } else { gdk_draw_pixmap (piano->keyb_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], piano->unsb_pm, 0, 0, xval, 20 + POFSY, 5, 8); uparea.x = xval; uparea.y = 20 + POFSY; uparea.width = 5; } uparea.height = 8; gtk_widget_draw (&piano->widget, &uparea); } /* converts a key number to x position in pixels to center of key */ gint piano_key_to_xpos (guint8 keynum) { gint mod, xval; if (keynum > 127) keynum = 127; mod = keynum % 12; xval = keynum / 12 * 63 + keyinfo[mod].dispx; if (keyinfo[mod].white) xval += 2; else xval += 2; /* slight adjustments for adjacent white keys, looks like center */ if (mod == 4 || mod == 11) xval++; else if (mod == 5 || mod == 0) xval--; return (xval); } /* converts a pixel x position to key number */ guint8 piano_xpos_to_key (gint xpos) { gint xval, i; guint8 keynum; xval = xpos % 63; /* pixel offset into keyboard octave */ for (i = 0; i < 12; i++) /* loop through key selection offsets */ if (xval <= keyinfo[i].selx) break; /* is offset within key select */ keynum = xpos / 63 * 12 + i; /* calc key number */ if (keynum > 127) keynum = 127; return (keynum); } static void piano_destroy (GtkObject * object) { Piano *piano; g_return_if_fail (object != NULL); g_return_if_fail (IS_PIANO (object)); piano = PIANO (object); gdk_pixmap_unref (piano->keyb_pm); gdk_pixmap_unref (piano->selw_pm); gdk_pixmap_unref (piano->unsw_pm); gdk_pixmap_unref (piano->selb_pm); gdk_pixmap_unref (piano->unsb_pm); gdk_gc_unref (piano->shadowgc); gdk_gc_unref (piano->c60gc); gdk_color_free (&piano->shadclr); gdk_color_free (&piano->c60clr); if (GTK_OBJECT_CLASS (parent_class)->destroy) (*GTK_OBJECT_CLASS (parent_class)->destroy) (object); } static void piano_realize (GtkWidget * widget) { Piano *piano; GdkWindowAttr attributes; gint attributes_mask; gint i, x, mod; GdkPixmap *pixmap; GdkColormap *cmap; g_return_if_fail (widget != NULL); g_return_if_fail (IS_PIANO (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); piano = PIANO (widget); attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); widget->style = gtk_style_attach (widget->style, widget->window); gdk_window_set_user_data (widget->window, widget); gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); pixmap = gdk_pixmap_new (piano->widget.window, 676, 48, -1); gdk_draw_rectangle (pixmap, piano->widget.style->white_gc, TRUE, 1, 1 + POFSY, 674, 46); gdk_draw_line (pixmap, piano->widget.style->black_gc, 0, 0 + POFSY, 675, 0 + POFSY); gdk_draw_line (pixmap, piano->widget.style->black_gc, 0, 47 + POFSY, 675, 47 + POFSY); gdk_draw_line (pixmap, piano->widget.style->black_gc, 1, 44 + POFSY, 674, 44 + POFSY); /* allocate color and gc for piano key shadow */ piano->shadowgc = gdk_gc_new (piano->widget.window); cmap = gtk_widget_get_colormap (widget); piano->shadclr.red = piano->shadclr.green = piano->shadclr.blue = 49152; gdk_colormap_alloc_color (cmap, &piano->shadclr, FALSE, TRUE); gdk_gc_set_foreground (piano->shadowgc, &piano->shadclr); /* allocate color and gc for note C-60 marker */ piano->c60gc = gdk_gc_new (piano->widget.window); piano->c60clr = c60clr; gdk_colormap_alloc_color (cmap, &piano->c60clr, FALSE, TRUE); gdk_gc_set_foreground (piano->c60gc, &piano->c60clr); gdk_draw_line (pixmap, piano->shadowgc, 1, 45 + POFSY, 674, 45 + POFSY); gdk_draw_line (pixmap, piano->shadowgc, 1, 46 + POFSY, 674, 46 + POFSY); for (i = 0, x = 0; i < 76; i++, x += 9) { gdk_draw_line (pixmap, piano->widget.style->black_gc, x, 1 + POFSY, x, 46 + POFSY); mod = i % 7; if ((mod != 0) && (mod != 3) && (i != 75)) gdk_draw_rectangle (pixmap, piano->widget.style->black_gc, TRUE, x - 2, 1 + POFSY, 5, 27); } /* draw note C-60 marker */ gdk_draw_rectangle (pixmap, piano->c60gc, TRUE, 318, 32, 4, 5); // gdk_draw_line (pixmap, piano->c60gc, 317, 33, 317, 35); // gdk_draw_line (pixmap, piano->c60gc, 322, 33, 322, 35); piano->keyb_pm = pixmap; /* capture small pixmap of white key unselected state */ piano->unsw_pm = gdk_pixmap_new (piano->widget.window, 8, 8, -1); gdk_draw_pixmap (piano->unsw_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], pixmap, 1, 39 + POFSY, 0, 0, 8, 8); /* copy and modify unselected white key state to get selected white key */ piano->selw_pm = gdk_pixmap_new (piano->widget.window, 8, 8, -1); gdk_draw_pixmap (piano->selw_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], piano->unsw_pm, 0, 0, 0, 0, 8, 8); gdk_draw_rectangle (piano->selw_pm, piano->widget.style->black_gc, TRUE, 2, 0, 4, 3); gdk_draw_rectangle (piano->selw_pm, piano->widget.style->white_gc, TRUE, 0, 5, 8, 3); /* capture small pixmap of black key unselected state */ piano->unsb_pm = gdk_pixmap_new (piano->widget.window, 5, 8, -1); gdk_draw_pixmap (piano->unsb_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], pixmap, 7, 20 + POFSY, 0, 0, 5, 8); /* copy and modify unselected black key state to get selected black key */ piano->selb_pm = gdk_pixmap_new (piano->widget.window, 5, 8, -1); gdk_draw_pixmap (piano->selb_pm, piano->widget.style->fg_gc[GTK_WIDGET_STATE (&piano->widget)], piano->unsb_pm, 0, 0, 0, 0, 5, 8); gdk_draw_rectangle (piano->selb_pm, piano->widget.style->white_gc, TRUE, 1, 0, 3, 3); gdk_draw_line (piano->selb_pm, piano->widget.style->white_gc, 0, 7, 1, 7); gdk_draw_line (piano->selb_pm, piano->widget.style->white_gc, 3, 7, 4, 7); } static void piano_size_request (GtkWidget * widget, GtkRequisition * requisition) { requisition->width = PIANO_DEFAULT_SIZEX; requisition->height = PIANO_DEFAULT_SIZEY; } static void piano_size_allocate (GtkWidget * widget, GtkAllocation * allocation) { Piano *piano; g_return_if_fail (widget != NULL); g_return_if_fail (IS_PIANO (widget)); g_return_if_fail (allocation != NULL); widget->allocation = *allocation; piano = PIANO (widget); if (GTK_WIDGET_REALIZED (widget)) { gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); } } static gint piano_expose (GtkWidget * widget, GdkEventExpose * event) { Piano *piano; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_PIANO (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); piano = PIANO (widget); gdk_draw_pixmap (widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], piano->keyb_pm, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } static gint piano_button_press (GtkWidget * widget, GdkEventButton * event) { Piano *piano; gint i, x, y, xval; guint8 keynum; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_PIANO (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); piano = PIANO (widget); if (piano->key_mouse < 128) return (FALSE); /* A key is active (with mouse)? */ if ((event->button != 1) || (event->x < 0) || (event->x > 672) || (event->y < 0 + POFSY) || (event->y > 47 + POFSY)) return (FALSE); x = event->x; y = event->y; xval = x % 63; /* pixel offset into keyboard octave */ if (y <= 26 + POFSY) { /* possible black key? */ for (i = 0; i < 12; i++) /* loop through key selection offsets */ if (xval <= keyinfo[i].selx) break; /* is offset within key select */ } else { /* button press is below black key line */ xval /= 9; /* white key number offset within octave */ for (i = 0; i < 12; i++) if (keyinfo[i].white && xval-- <= 0) break; } keynum = x / 63 * 12 + i; /* calc key number */ piano->key_mouse = keynum; /* assign mouse selected key */ piano_note_on (piano, keynum); /* do note on */ gtk_grab_add (widget); /* grab all mouse events */ return (FALSE); } static gint piano_button_release (GtkWidget * widget, GdkEventButton * event) { Piano *piano; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_PIANO (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); piano = PIANO (widget); if (piano->key_mouse > 127) return (FALSE); /* no key is active?(with mouse) */ if (event->button == 1) { gtk_grab_remove (widget); /* no more mouse event grabbing */ piano_note_off (piano, piano->key_mouse); /* unselect active key */ piano->key_mouse = 255; /* no key selected with mouse */ } return FALSE; } static gint piano_motion_notify (GtkWidget * widget, GdkEventMotion * event) { Piano *piano; GdkModifierType mods; gint x, y; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_PIANO (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); piano = PIANO (widget); if (piano->key_mouse > 127) return (FALSE); x = event->x; y = event->y; if (event->is_hint || (event->window != widget->window)) gdk_window_get_pointer (widget->window, &x, &y, &mods); if (mods & GDK_BUTTON1_MASK) piano_update_mouse (piano, x, y); return (FALSE); } static void piano_update_mouse (Piano * piano, gint x, gint y) { gint i; gint xval; guint8 keynum; g_return_if_fail (piano != NULL); g_return_if_fail (IS_PIANO (piano)); if (piano->key_mouse > 127) return; if (x < 0) x = 0; else if (x > 672) x = 672; xval = x % 63; /* pixel offset into keyboard octave */ if (y <= 26 + POFSY || y > 47) { /* possible black key? */ for (i = 0; i < 12; i++) { /* loop through key selection offsets */ if (xval <= keyinfo[i].selx) /* is offset within key select */ break; } } else { /* button press is below black key line */ xval /= 9; /* white key number offset within octave */ for (i = 0; i < 12; i++) { if (keyinfo[i].white) if (xval-- == 0) break; } } keynum = x / 63 * 12 + i; /* calc key number */ if (keynum != piano->key_mouse) { /* still same key selected? */ piano_note_off (piano, piano->key_mouse);/* turn off old mouse sel key */ piano->key_mouse = keynum; /* assign new mouse selected key */ piano_note_on (piano, keynum); /* turn on new key */ } }