gimp/modules/controller_midi.c

671 lines
20 KiB
C
Raw Normal View History

/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* controller_midi.c
* Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
*
* 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 <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <gtk/gtk.h>
#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,
PROP_CHANNEL
};
#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;
gint midi_channel;
GIOChannel *io;
guint io_id;
/* 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 void midi_event (ControllerMidi *midi,
gint channel,
gint event_id,
gdouble value);
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 <mitch@gimp.org>",
"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",
_("Device:"),
_("The name of the device to read MIDI events from."),
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
GIMP_MODULE_PARAM_SERIALIZE));
g_object_class_install_property (object_class, PROP_CHANNEL,
g_param_spec_int ("channel",
_("Channel:"),
_("The MIDI channel to read events from. Set to -1 for reading from all MIDI channels."),
-1, 15, -1,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
GIMP_MODULE_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->midi_channel = -1;
midi->io = NULL;
midi->io_id = 0;
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;
case PROP_CHANNEL:
midi->midi_channel = g_value_get_int (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;
case PROP_CHANNEL:
g_value_set_int (value, midi->midi_channel);
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)
{
2004-06-20 01:44:47 +08:00
if (event_id < (128 + 128 + 128))
{
if (! midi_events[event_id].name)
{
if (event_id < 128)
midi_events[event_id].name = g_strdup_printf ("note-on-%02x",
event_id);
2004-06-20 01:44:47 +08:00
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_source_remove (midi->io_id);
midi->io_id = 0;
g_io_channel_unref (midi->io);
midi->io = NULL;
}
if (midi->device)
g_free (midi->device);
midi->device = g_strdup (device);
if (midi->device && strlen (midi->device))
{
gint fd;
#ifdef G_OS_WIN32
fd = open (midi->device, O_RDONLY);
#else
fd = open (midi->device, O_RDONLY | O_NONBLOCK);
#endif
if (fd >= 0)
{
gchar *name = g_strdup_printf (_("Reading from %s"), midi->device);
g_object_set (midi, "name", name, NULL);
g_free (name);
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);
midi->io_id = 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;
}
static void
midi_event (ControllerMidi *midi,
gint channel,
gint event_id,
gdouble value)
{
if (channel == -1 ||
midi->midi_channel == -1 ||
channel == midi->midi_channel)
{
GimpControllerEvent event = { 0, };
event.any.type = GIMP_CONTROLLER_EVENT_VALUE;
event.any.source = GIMP_CONTROLLER (midi);
event.any.event_id = event_id;
g_value_init (&event.value.value, G_TYPE_DOUBLE);
g_value_set_double (&event.value.value, value);
gimp_controller_event (GIMP_CONTROLLER (midi), &event);
g_value_unset (&event.value.value);
}
}
#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 */
{
switch (buf[pos])
{
case 0xf8: /* timing clock */
case 0xf9: /* (undefined) */
case 0xfa: /* start */
case 0xfb: /* continue */
case 0xfc: /* stop */
case 0xfd: /* (undefined) */
case 0xfe: /* active sensing */
case 0xff: /* system reset */
/* nop */
#if 0
g_print ("MIDI: realtime message (%02x)\n", buf[pos]);
#endif
break;
}
}
else
{
midi->swallow = FALSE; /* any status bytes ends swallowing */
if (buf[pos] >= 0xf0) /* system messages */
{
switch (buf[pos])
{
case 0xf0: /* sysex start */
midi->swallow = TRUE;
D (g_print ("MIDI: sysex start\n"));
break;
case 0xf1: /* time code */
midi->swallow = TRUE; /* type + data */
D (g_print ("MIDI: time code\n"));
break;
case 0xf2: /* song position */
midi->swallow = TRUE; /* lsb + msb */
D (g_print ("MIDI: song position\n"));
break;
case 0xf3: /* song select */
midi->swallow = TRUE; /* song number */
D (g_print ("MIDI: song select\n"));
break;
case 0xf4: /* (undefined) */
case 0xf5: /* (undefined) */
D (g_print ("MIDI: undefined system message\n"));
break;
case 0xf6: /* tune request */
D (g_print ("MIDI: tune request\n"));
break;
case 0xf7: /* sysex end */
D (g_print ("MIDI: sysex end\n"));
break;
}
}
else /* channel messages */
{
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)
{
D (g_print ("MIDI (ch %02d): note on (%02x vel %02x)\n",
midi->channel,
midi->key, midi->velocity));
midi_event (midi, midi->channel, midi->key,
(gdouble) midi->velocity / 127.0);
}
else if (midi->command == 0x8)
{
D (g_print ("MIDI (ch %02d): note off (%02x vel %02x)\n",
midi->channel, midi->key, midi->velocity));
midi_event (midi, midi->channel, midi->key + 128,
(gdouble) midi->velocity / 127.0);
}
else
{
D (g_print ("MIDI (ch %02d): polyphonic aftertouch (%02x pressure %02x)\n",
midi->channel, 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 (ch %02d): controller %d (value %d)\n",
midi->channel, midi->key, midi->velocity));
midi_event (midi, midi->channel, midi->key + 128 + 128,
(gdouble) midi->velocity / 127.0);
midi->key = -1;
midi->velocity = -1;
break;
case 0xc: /* program change */
midi->key = buf[pos++];
D (g_print ("MIDI (ch %02d): program change (%d)\n",
midi->channel, midi->key));
midi->key = -1;
break;
case 0xd: /* channel key pressure */
midi->velocity = buf[pos++];
D (g_print ("MIDI (ch %02d): channel aftertouch (%d)\n",
midi->channel, 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 (ch %02d): pitch (%d)\n",
midi->channel, midi->velocity));
midi->msb = -1;
midi->lsb = -1;
midi->velocity = -1;
break;
}
}
}
return TRUE;
}