diff --git a/ChangeLog b/ChangeLog index 3b0fa88be1..bf2ca26e20 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2004-06-19 Michael Natterer + + * modules/Makefile.am + * modules/controller_midi.c: new controller for MIDI input. Maps + all note on and note off events and all MIDI controllers to + GimpContollerEvents. Should parse any MIDI stream. Code based on + blinkenmedia stuff from Daniel Mack. + 2004-06-19 Sven Neumann Applied a patch from Geert Jordaens that implements the diff --git a/modules/Makefile.am b/modules/Makefile.am index 2b54e382e1..9ad3adff3e 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -34,6 +34,7 @@ lib_LTLIBRARIES = \ libcdisplay_gamma.la \ libcdisplay_highcontrast.la \ $(cdisplay_proof_module) \ + libcontroller_midi.la \ $(controller_linux_input_module) @@ -68,3 +69,7 @@ libcdisplay_proof_la_LIBADD = $(libgimpbase) $(libgimpwidgets) $(GTK_LIBS) $(LCM libcontroller_linux_input_la_SOURCES = controller_linux_input.c libcontroller_linux_input_la_LDFLAGS = -avoid-version -module $(no_undefined) libcontroller_linux_input_la_LIBADD = $(libgimpwidgets) $(GTK_LIBS) + +libcontroller_midi_la_SOURCES = controller_midi.c +libcontroller_midi_la_LDFLAGS = -avoid-version -module $(no_undefined) +libcontroller_midi_la_LIBADD = $(libgimpwidgets) $(GTK_LIBS) diff --git a/modules/controller_midi.c b/modules/controller_midi.c new file mode 100644 index 0000000000..118dca34b8 --- /dev/null +++ b/modules/controller_midi.c @@ -0,0 +1,628 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * controller_midi.c + * Copyright (C) 2004 Michael Natterer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#include "libgimpmodule/gimpmodule.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "libgimpwidgets/gimpcontroller.h" + +#include "libgimp/libgimp-intl.h" + + +typedef struct +{ + gchar *name; + gchar *blurb; +} MidiEvent; + + +enum +{ + PROP_0, + PROP_DEVICE +}; + + +#define CONTROLLER_TYPE_MIDI (controller_type) +#define CONTROLLER_MIDI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CONTROLLER_TYPE_MIDI, ControllerMidi)) +#define CONTROLLER_MIDI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CONTROLLER_TYPE_MIDI, ControllerMidiClass)) +#define CONTROLLER_IS_MIDI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CONTROLLER_TYPE_MIDI)) +#define CONTROLLER_IS_MIDI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CONTROLLER_TYPE_MIDI)) + + +typedef struct _ControllerMidi ControllerMidi; +typedef struct _ControllerMidiClass ControllerMidiClass; + +struct _ControllerMidi +{ + GimpController parent_instance; + + gchar *device; + GIOChannel *io; + + /* midi status */ + gboolean swallow; + gint command; + gint channel; + gint key; + gint velocity; + gint msb; + gint lsb; +}; + +struct _ControllerMidiClass +{ + GimpControllerClass parent_class; +}; + + +GType midi_get_type (GTypeModule *module); + +static void midi_class_init (ControllerMidiClass *klass); +static void midi_init (ControllerMidi *midi); + +static void midi_dispose (GObject *object); +static void midi_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void midi_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint midi_get_n_events (GimpController *controller); +static const gchar * midi_get_event_name (GimpController *controller, + gint event_id); +static const gchar * midi_get_event_blurb (GimpController *controller, + gint event_id); + +static gboolean midi_set_device (ControllerMidi *controller, + const gchar *device); +static gboolean midi_read_event (GIOChannel *io, + GIOCondition cond, + gpointer data); + + +static const GimpModuleInfo midi_info = +{ + GIMP_MODULE_ABI_VERSION, + N_("Midi event controller"), + "Michael Natterer ", + "v0.1", + "(c) 2004, released under the GPL", + "June 2004" +}; + + +static GType controller_type = 0; +static GimpControllerClass *parent_class = NULL; + +static MidiEvent midi_events[128 + 128 + 128]; + + +G_MODULE_EXPORT const GimpModuleInfo * +gimp_module_query (GTypeModule *module) +{ + return &midi_info; +} + +G_MODULE_EXPORT gboolean +gimp_module_register (GTypeModule *module) +{ + midi_get_type (module); + + return TRUE; +} + + +GType +midi_get_type (GTypeModule *module) +{ + if (! controller_type) + { + static const GTypeInfo controller_info = + { + sizeof (ControllerMidiClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) midi_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (ControllerMidi), + 0, /* n_preallocs */ + (GInstanceInitFunc) midi_init + }; + + controller_type = g_type_module_register_type (module, + GIMP_TYPE_CONTROLLER, + "ControllerMidi", + &controller_info, 0); + } + + return controller_type; +} + +static void +midi_class_init (ControllerMidiClass *klass) +{ + GimpControllerClass *controller_class = GIMP_CONTROLLER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->dispose = midi_dispose; + object_class->get_property = midi_get_property; + object_class->set_property = midi_set_property; + + g_object_class_install_property (object_class, PROP_DEVICE, + g_param_spec_string ("device", NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + GIMP_CONTROLLER_PARAM_SERIALIZE)); + + controller_class->name = _("Midi Events"); + + controller_class->get_n_events = midi_get_n_events; + controller_class->get_event_name = midi_get_event_name; + controller_class->get_event_blurb = midi_get_event_blurb; +} + +static void +midi_init (ControllerMidi *midi) +{ + midi->device = NULL; + midi->io = NULL; + + midi->swallow = TRUE; /* get rid of data bytes at start of stream */ + midi->command = 0x0; + midi->channel = 0x0; + midi->key = -1; + midi->velocity = -1; + midi->msb = -1; + midi->lsb = -1; +} + +static void +midi_dispose (GObject *object) +{ + ControllerMidi *midi = CONTROLLER_MIDI (object); + + midi_set_device (midi, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +midi_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + ControllerMidi *midi = CONTROLLER_MIDI (object); + + switch (property_id) + { + case PROP_DEVICE: + midi_set_device (midi, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +midi_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ControllerMidi *midi = CONTROLLER_MIDI (object); + + switch (property_id) + { + case PROP_DEVICE: + g_value_set_string (value, midi->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +midi_get_n_events (GimpController *controller) +{ + return 128 + 128 + 128; +} + +static const gchar * +midi_get_event_name (GimpController *controller, + gint event_id) +{ + if (event_id <= 383) + { + if (! midi_events[event_id].name) + { + if (event_id < 128) + midi_events[event_id].name = g_strdup_printf ("note-on-%02x", + event_id); + else if (event_id < (128 + 128) + midi_events[event_id].name = g_strdup_printf ("note-off-%02x", + event_id - 128); + else if (event_id < (128 + 128 + 128)) + midi_events[event_id].name = g_strdup_printf ("controller-%03d", + event_id - 256); + } + + return midi_events[event_id].name; + } + + return NULL; +} + +static const gchar * +midi_get_event_blurb (GimpController *controller, + gint event_id) +{ + if (event_id <= 383) + { + if (! midi_events[event_id].blurb) + { + if (event_id <= 127) + midi_events[event_id].blurb = g_strdup_printf ("Note %02x on", + event_id); + else if (event_id <= 255) + midi_events[event_id].blurb = g_strdup_printf ("Note %02x off", + event_id - 128); + else if (event_id <= 383) + midi_events[event_id].blurb = g_strdup_printf ("Controller %03d", + event_id - 256); + } + + return midi_events[event_id].blurb; + } + + return NULL; +} + +static gboolean +midi_set_device (ControllerMidi *midi, + const gchar *device) +{ + midi->swallow = TRUE; + midi->command = 0x0; + midi->channel = 0x0; + midi->key = -1; + midi->velocity = -1; + midi->msb = -1; + midi->lsb = -1; + + if (midi->io) + { + g_io_channel_unref (midi->io); + midi->io = NULL; + } + + if (midi->device) + g_free (midi->device); + + midi->device = g_strdup (device); + + if (device) + { + gint fd; + + fd = open (midi->device, O_RDONLY | O_NONBLOCK); + if (fd >= 0) + { + g_object_set (midi, "name", device, NULL); + + midi->io = g_io_channel_unix_new (fd); + g_io_channel_set_close_on_unref (midi->io, TRUE); + g_io_channel_set_encoding (midi->io, NULL, NULL); + + g_io_add_watch (midi->io, + G_IO_IN, + midi_read_event, + midi); + return TRUE; + } + else + { + gchar *name = g_strdup_printf (_("Device not available: %s"), + g_strerror (errno)); + g_object_set (midi, "name", name, NULL); + g_free (name); + } + } + else + { + g_object_set (midi, "name", _("No device configured"), NULL); + } + + return FALSE; +} + + +#define D(stmnt) stmnt; + +gboolean +midi_read_event (GIOChannel *io, + GIOCondition cond, + gpointer data) +{ + ControllerMidi *midi = CONTROLLER_MIDI (data); + guchar buf[0xf]; + gsize size; + + if (g_io_channel_read_chars (io, + (gchar *) buf, + sizeof (buf), &size, + NULL) == G_IO_STATUS_NORMAL) + { + gint pos = 0; + + while (pos < size) + { +#if 0 + gint i; + + g_print ("MIDI IN (%d bytes), command 0x%02x: ", size, midi->command); + + for (i = 0; i < size; i++) + g_print ("%02x ", buf[i]); + g_print ("\n"); +#endif + + if (buf[pos] & 0x80) /* status byte */ + { + if (buf[pos] >= 0xf8) /* realtime messages */ + { + /* nop */ + +#if 0 + g_print ("MIDI: realtime message (%02x)\n", buf[pos]); +#endif + } + else if (buf[pos] >= 0xf0) /* system messages */ + { + if (buf[pos] == 0xf0) /* sysex start */ + { + midi->swallow = TRUE; + + D (g_print ("MIDI: sysex start\n")); + } + else + { + midi->swallow = FALSE; /* any status bytes ends swallowing */ + + switch (buf[pos]) + { + case 0xf7: /* sysex end */ + D (g_print ("MIDI: sysex end\n")); + break; + + case 0xf6: /* tune request */ + D (g_print ("MIDI: tune request\n")); + break; + + case 0xf5: /* undefined */ + case 0xf4: /* undefined */ + D (g_print ("MIDI: undefined system message\n")); + break; + + case 0xf3: /* song select */ + midi->swallow = TRUE; /* song number */ + + D (g_print ("MIDI: song select\n")); + break; + + case 0xf2: /* song position */ + midi->swallow = TRUE; /* lsb + msb */ + + D (g_print ("MIDI: song position\n")); + break; + + case 0xf1: /* time code */ + midi->swallow = TRUE; /* type + data */ + + D (g_print ("MIDI: time code\n")); + break; + } + } + } + else /* channel messages */ + { + midi->swallow = FALSE; /* any status byte ends swallowing */ + + midi->command = buf[pos] >> 4; + midi->channel = buf[pos] & 0xf; + + /* reset running status */ + midi->key = -1; + midi->velocity = -1; + midi->msb = -1; + midi->lsb = -1; + } + + pos++; /* status byte consumed */ + continue; + } + + if (midi->swallow) + { + pos++; /* consume any data byte */ + continue; + } + + switch (midi->command) + { + case 0x8: /* note off */ + case 0x9: /* note on */ + case 0xa: /* aftertouch */ + + if (midi->key == -1) + { + midi->key = buf[pos++]; /* key byte consumed */ + continue; + } + + if (midi->velocity == -1) + midi->velocity = buf[pos++]; /* velocity byte consumed */ + + /* note on with velocity = 0 means note off */ + if (midi->command == 0x9 && midi->velocity == 0x0) + midi->command = 0x8; + + if (midi->command == 0x9) + { + GimpControllerEvent event = { 0, }; + + D (g_print ("MIDI: note on (%02x vel %02x)\n", + midi->key, midi->velocity)); + + event.any.type = GIMP_CONTROLLER_EVENT_VALUE; + event.any.source = GIMP_CONTROLLER (data); + event.any.event_id = midi->key; + + g_value_init (&event.value.value, G_TYPE_DOUBLE); + g_value_set_double (&event.value.value, + (gdouble) midi->velocity / 127.0); + + gimp_controller_event (GIMP_CONTROLLER (midi), &event); + + g_value_unset (&event.value.value); + } + else if (midi->command == 0x8) + { + GimpControllerEvent event = { 0, }; + + D (g_print ("MIDI: note off (%02x vel %02x)\n", + midi->key, midi->velocity)); + + event.any.type = GIMP_CONTROLLER_EVENT_VALUE; + event.any.source = GIMP_CONTROLLER (data); + event.any.event_id = midi->key + 128; + + g_value_init (&event.value.value, G_TYPE_DOUBLE); + g_value_set_double (&event.value.value, + (gdouble) midi->velocity / 127.0); + + gimp_controller_event (GIMP_CONTROLLER (midi), &event); + + g_value_unset (&event.value.value); + } + else + { + D (g_print ("MIDI: polyphonic aftertouch (%02x pressure %02x)\n", + midi->key, midi->velocity)); + } + + midi->key = -1; + midi->velocity = -1; + break; + + case 0xb: /* controllers, sustain */ + if (midi->key == -1) + { + midi->key = buf[pos++]; + continue; + } + + if (midi->velocity == -1) + midi->velocity = buf[pos++]; + + D (g_print ("MIDI: controller %d (value %d)\n", + midi->key, midi->velocity)); + + { + GimpControllerEvent event = { 0, }; + + event.any.type = GIMP_CONTROLLER_EVENT_VALUE; + event.any.source = GIMP_CONTROLLER (data); + event.any.event_id = midi->key + 128 + 128; + + g_value_init (&event.value.value, G_TYPE_DOUBLE); + g_value_set_double (&event.value.value, + (gdouble) midi->velocity / 127.0); + + gimp_controller_event (GIMP_CONTROLLER (midi), &event); + + g_value_unset (&event.value.value); + } + + midi->key = -1; + midi->velocity = -1; + break; + + case 0xc: /* program change */ + midi->key = buf[pos++]; + + D (g_print ("MIDI: program change (%d)\n", midi->key)); + + midi->key = -1; + break; + + case 0xd: /* channel key pressure */ + midi->velocity = buf[pos++]; + + D (g_print ("MIDI: channel aftertouch (%d)\n", midi->velocity)); + + midi->velocity = -1; + break; + + case 0xe: /* pitch bend */ + if (midi->lsb == -1) + { + midi->lsb = buf[pos++]; + continue; + } + + if (midi->msb == -1) + midi->msb = buf[pos++]; + + midi->velocity = midi->lsb | (midi->msb << 7); + + D (g_print ("MIDI: pitch (%d)\n", midi->velocity)); + + midi->msb = -1; + midi->lsb = -1; + midi->velocity = -1; + break; + } + } + } + + return TRUE; +}