seq_midi.c 14.1 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3
/*
 *   Generic MIDI synth driver for ALSA sequencer
 *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
4
 *                         Jaroslav Kysela <perex@perex.cz>
Linus Torvalds's avatar
Linus Torvalds committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 *
 *   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
 *
 */
 
/* 
Possible options for midisynth module:
	- automatic opening of midi ports on first received event or subscription
	  (close will be performed when client leaves)
*/


#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
33
#include <linux/module.h>
34
#include <linux/mutex.h>
Linus Torvalds's avatar
Linus Torvalds committed
35 36 37 38 39 40 41
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/seq_kernel.h>
#include <sound/seq_device.h>
#include <sound/seq_midi_event.h>
#include <sound/initval.h>

42
MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@perex.cz>");
Linus Torvalds's avatar
Linus Torvalds committed
43 44 45 46 47 48 49 50 51 52
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth.");
MODULE_LICENSE("GPL");
static int output_buffer_size = PAGE_SIZE;
module_param(output_buffer_size, int, 0644);
MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes.");
static int input_buffer_size = PAGE_SIZE;
module_param(input_buffer_size, int, 0644);
MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes.");

/* data for this midi synth driver */
53 54
struct seq_midisynth {
	struct snd_card *card;
Linus Torvalds's avatar
Linus Torvalds committed
55 56
	int device;
	int subdevice;
57 58
	struct snd_rawmidi_file input_rfile;
	struct snd_rawmidi_file output_rfile;
Linus Torvalds's avatar
Linus Torvalds committed
59 60
	int seq_client;
	int seq_port;
61 62
	struct snd_midi_event *parser;
};
Linus Torvalds's avatar
Linus Torvalds committed
63

64
struct seq_midisynth_client {
Linus Torvalds's avatar
Linus Torvalds committed
65 66 67
	int seq_client;
	int num_ports;
	int ports_per_device[SNDRV_RAWMIDI_DEVICES];
68 69
 	struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES];
};
Linus Torvalds's avatar
Linus Torvalds committed
70

71
static struct seq_midisynth_client *synths[SNDRV_CARDS];
72
static DEFINE_MUTEX(register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
73 74

/* handle rawmidi input event (MIDI v1.0 stream) */
75
static void snd_midi_input_event(struct snd_rawmidi_substream *substream)
Linus Torvalds's avatar
Linus Torvalds committed
76
{
77 78 79
	struct snd_rawmidi_runtime *runtime;
	struct seq_midisynth *msynth;
	struct snd_seq_event ev;
Linus Torvalds's avatar
Linus Torvalds committed
80 81 82 83 84 85
	char buf[16], *pbuf;
	long res, count;

	if (substream == NULL)
		return;
	runtime = substream->runtime;
86
	msynth = runtime->private_data;
Linus Torvalds's avatar
Linus Torvalds committed
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
	if (msynth == NULL)
		return;
	memset(&ev, 0, sizeof(ev));
	while (runtime->avail > 0) {
		res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf));
		if (res <= 0)
			continue;
		if (msynth->parser == NULL)
			continue;
		pbuf = buf;
		while (res > 0) {
			count = snd_midi_event_encode(msynth->parser, pbuf, res, &ev);
			if (count < 0)
				break;
			pbuf += count;
			res -= count;
			if (ev.type != SNDRV_SEQ_EVENT_NONE) {
				ev.source.port = msynth->seq_port;
				ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
				snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0);
				/* clear event and reset header */
				memset(&ev, 0, sizeof(ev));
			}
		}
	}
}

114
static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count)
Linus Torvalds's avatar
Linus Torvalds committed
115
{
116
	struct snd_rawmidi_runtime *runtime;
Linus Torvalds's avatar
Linus Torvalds committed
117 118
	int tmp;

119 120
	if (snd_BUG_ON(!substream || !buf))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
121 122
	runtime = substream->runtime;
	if ((tmp = runtime->avail) < count) {
123
		if (printk_ratelimit())
124
			pr_err("ALSA: seq_midi: MIDI output buffer overrun\n");
Linus Torvalds's avatar
Linus Torvalds committed
125 126 127 128 129 130 131
		return -ENOMEM;
	}
	if (snd_rawmidi_kernel_write(substream, buf, count) < count)
		return -EINVAL;
	return 0;
}

132
static int event_process_midi(struct snd_seq_event *ev, int direct,
Linus Torvalds's avatar
Linus Torvalds committed
133 134
			      void *private_data, int atomic, int hop)
{
135
	struct seq_midisynth *msynth = private_data;
Linus Torvalds's avatar
Linus Torvalds committed
136
	unsigned char msg[10];	/* buffer for constructing midi messages */
137
	struct snd_rawmidi_substream *substream;
138
	int len;
Linus Torvalds's avatar
Linus Torvalds committed
139

140 141
	if (snd_BUG_ON(!msynth))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
142 143 144 145 146 147
	substream = msynth->output_rfile.output;
	if (substream == NULL)
		return -ENODEV;
	if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {	/* special case, to save space */
		if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
			/* invalid event */
148
			pr_debug("ALSA: seq_midi: invalid sysex event flags = 0x%x\n", ev->flags);
Linus Torvalds's avatar
Linus Torvalds committed
149 150
			return 0;
		}
151
		snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream);
Linus Torvalds's avatar
Linus Torvalds committed
152 153 154 155
		snd_midi_event_reset_decode(msynth->parser);
	} else {
		if (msynth->parser == NULL)
			return -EIO;
156 157 158 159
		len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
		if (len < 0)
			return 0;
		if (dump_midi(substream, msg, len) < 0)
Linus Torvalds's avatar
Linus Torvalds committed
160 161 162 163 164 165
			snd_midi_event_reset_decode(msynth->parser);
	}
	return 0;
}


166 167
static int snd_seq_midisynth_new(struct seq_midisynth *msynth,
				 struct snd_card *card,
Linus Torvalds's avatar
Linus Torvalds committed
168 169 170 171 172 173 174 175 176 177 178 179
				 int device,
				 int subdevice)
{
	if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0)
		return -ENOMEM;
	msynth->card = card;
	msynth->device = device;
	msynth->subdevice = subdevice;
	return 0;
}

/* open associated midi device for input */
180
static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe *info)
Linus Torvalds's avatar
Linus Torvalds committed
181 182
{
	int err;
183 184 185
	struct seq_midisynth *msynth = private_data;
	struct snd_rawmidi_runtime *runtime;
	struct snd_rawmidi_params params;
Linus Torvalds's avatar
Linus Torvalds committed
186 187

	/* open midi port */
188 189 190 191
	if ((err = snd_rawmidi_kernel_open(msynth->card, msynth->device,
					   msynth->subdevice,
					   SNDRV_RAWMIDI_LFLG_INPUT,
					   &msynth->input_rfile)) < 0) {
192
		pr_debug("ALSA: seq_midi: midi input open failed!!!\n");
Linus Torvalds's avatar
Linus Torvalds committed
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
		return err;
	}
	runtime = msynth->input_rfile.input->runtime;
	memset(&params, 0, sizeof(params));
	params.avail_min = 1;
	params.buffer_size = input_buffer_size;
	if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, &params)) < 0) {
		snd_rawmidi_kernel_release(&msynth->input_rfile);
		return err;
	}
	snd_midi_event_reset_encode(msynth->parser);
	runtime->event = snd_midi_input_event;
	runtime->private_data = msynth;
	snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0);
	return 0;
}

/* close associated midi device for input */
211
static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscribe *info)
Linus Torvalds's avatar
Linus Torvalds committed
212 213
{
	int err;
214
	struct seq_midisynth *msynth = private_data;
Linus Torvalds's avatar
Linus Torvalds committed
215

216 217
	if (snd_BUG_ON(!msynth->input_rfile.input))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
218 219 220 221 222
	err = snd_rawmidi_kernel_release(&msynth->input_rfile);
	return err;
}

/* open associated midi device for output */
223
static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info)
Linus Torvalds's avatar
Linus Torvalds committed
224 225
{
	int err;
226 227
	struct seq_midisynth *msynth = private_data;
	struct snd_rawmidi_params params;
Linus Torvalds's avatar
Linus Torvalds committed
228 229

	/* open midi port */
230 231 232 233
	if ((err = snd_rawmidi_kernel_open(msynth->card, msynth->device,
					   msynth->subdevice,
					   SNDRV_RAWMIDI_LFLG_OUTPUT,
					   &msynth->output_rfile)) < 0) {
234
		pr_debug("ALSA: seq_midi: midi output open failed!!!\n");
Linus Torvalds's avatar
Linus Torvalds committed
235 236 237 238 239
		return err;
	}
	memset(&params, 0, sizeof(params));
	params.avail_min = 1;
	params.buffer_size = output_buffer_size;
240
	params.no_active_sensing = 1;
Linus Torvalds's avatar
Linus Torvalds committed
241 242 243 244 245 246 247 248 249
	if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, &params)) < 0) {
		snd_rawmidi_kernel_release(&msynth->output_rfile);
		return err;
	}
	snd_midi_event_reset_decode(msynth->parser);
	return 0;
}

/* close associated midi device for output */
250
static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info)
Linus Torvalds's avatar
Linus Torvalds committed
251
{
252
	struct seq_midisynth *msynth = private_data;
Linus Torvalds's avatar
Linus Torvalds committed
253

254 255
	if (snd_BUG_ON(!msynth->output_rfile.output))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
256 257 258 259 260
	snd_rawmidi_drain_output(msynth->output_rfile.output);
	return snd_rawmidi_kernel_release(&msynth->output_rfile);
}

/* delete given midi synth port */
261
static void snd_seq_midisynth_delete(struct seq_midisynth *msynth)
Linus Torvalds's avatar
Linus Torvalds committed
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
{
	if (msynth == NULL)
		return;

	if (msynth->seq_client > 0) {
		/* delete port */
		snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port);
	}

	if (msynth->parser)
		snd_midi_event_free(msynth->parser);
}

/* register new midi synth port */
static int
277
snd_seq_midisynth_register_port(struct snd_seq_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
278
{
279 280 281 282
	struct seq_midisynth_client *client;
	struct seq_midisynth *msynth, *ms;
	struct snd_seq_port_info *port;
	struct snd_rawmidi_info *info;
283
	struct snd_rawmidi *rmidi = dev->private_data;
Linus Torvalds's avatar
Linus Torvalds committed
284 285
	int newclient = 0;
	unsigned int p, ports;
286 287
	struct snd_seq_port_callback pcallbacks;
	struct snd_card *card = dev->card;
Linus Torvalds's avatar
Linus Torvalds committed
288 289 290
	int device = dev->device;
	unsigned int input_count = 0, output_count = 0;

291 292
	if (snd_BUG_ON(!card || device < 0 || device >= SNDRV_RAWMIDI_DEVICES))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
	info = kmalloc(sizeof(*info), GFP_KERNEL);
	if (! info)
		return -ENOMEM;
	info->device = device;
	info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
	info->subdevice = 0;
	if (snd_rawmidi_info_select(card, info) >= 0)
		output_count = info->subdevices_count;
	info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
	if (snd_rawmidi_info_select(card, info) >= 0) {
		input_count = info->subdevices_count;
	}
	ports = output_count;
	if (ports < input_count)
		ports = input_count;
	if (ports == 0) {
		kfree(info);
		return -ENODEV;
	}
	if (ports > (256 / SNDRV_RAWMIDI_DEVICES))
		ports = 256 / SNDRV_RAWMIDI_DEVICES;

315
	mutex_lock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
316 317 318
	client = synths[card->number];
	if (client == NULL) {
		newclient = 1;
319
		client = kzalloc(sizeof(*client), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
320
		if (client == NULL) {
321
			mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
322 323 324
			kfree(info);
			return -ENOMEM;
		}
325 326
		client->seq_client =
			snd_seq_create_kernel_client(
327 328
				card, 0, "%s", card->shortname[0] ?
				(const char *)card->shortname : "External MIDI");
Linus Torvalds's avatar
Linus Torvalds committed
329 330
		if (client->seq_client < 0) {
			kfree(client);
331
			mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
332 333 334
			kfree(info);
			return -ENOMEM;
		}
335
	}
Linus Torvalds's avatar
Linus Torvalds committed
336

337
	msynth = kcalloc(ports, sizeof(struct seq_midisynth), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
	port = kmalloc(sizeof(*port), GFP_KERNEL);
	if (msynth == NULL || port == NULL)
		goto __nomem;

	for (p = 0; p < ports; p++) {
		ms = &msynth[p];

		if (snd_seq_midisynth_new(ms, card, device, p) < 0)
			goto __nomem;

		/* declare port */
		memset(port, 0, sizeof(*port));
		port->addr.client = client->seq_client;
		port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p;
		port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
		memset(info, 0, sizeof(*info));
		info->device = device;
		if (p < output_count)
			info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
		else
			info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
		info->subdevice = p;
		if (snd_rawmidi_info_select(card, info) >= 0)
			strcpy(port->name, info->subname);
		if (! port->name[0]) {
			if (info->name[0]) {
				if (ports > 1)
					snprintf(port->name, sizeof(port->name), "%s-%d", info->name, p);
				else
					snprintf(port->name, sizeof(port->name), "%s", info->name);
			} else {
				/* last resort */
				if (ports > 1)
					sprintf(port->name, "MIDI %d-%d-%d", card->number, device, p);
				else
					sprintf(port->name, "MIDI %d-%d", card->number, device);
			}
		}
		if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count)
			port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
		if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count)
			port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
		if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) &&
		    info->flags & SNDRV_RAWMIDI_INFO_DUPLEX)
			port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
383 384 385
		port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
			| SNDRV_SEQ_PORT_TYPE_HARDWARE
			| SNDRV_SEQ_PORT_TYPE_PORT;
Linus Torvalds's avatar
Linus Torvalds committed
386 387 388 389 390 391 392 393 394 395
		port->midi_channels = 16;
		memset(&pcallbacks, 0, sizeof(pcallbacks));
		pcallbacks.owner = THIS_MODULE;
		pcallbacks.private_data = ms;
		pcallbacks.subscribe = midisynth_subscribe;
		pcallbacks.unsubscribe = midisynth_unsubscribe;
		pcallbacks.use = midisynth_use;
		pcallbacks.unuse = midisynth_unuse;
		pcallbacks.event_input = event_process_midi;
		port->kernel = &pcallbacks;
396 397
		if (rmidi->ops && rmidi->ops->get_port_info)
			rmidi->ops->get_port_info(rmidi, p, port);
Linus Torvalds's avatar
Linus Torvalds committed
398 399 400 401 402 403 404 405 406 407
		if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0)
			goto __nomem;
		ms->seq_client = client->seq_client;
		ms->seq_port = port->addr.port;
	}
	client->ports_per_device[device] = ports;
	client->ports[device] = msynth;
	client->num_ports++;
	if (newclient)
		synths[card->number] = client;
408
	mutex_unlock(&register_mutex);
Takashi Iwai's avatar
Takashi Iwai committed
409 410
	kfree(info);
	kfree(port);
Linus Torvalds's avatar
Linus Torvalds committed
411 412 413 414 415 416 417 418 419 420 421 422 423 424
	return 0;	/* success */

      __nomem:
	if (msynth != NULL) {
	      	for (p = 0; p < ports; p++)
	      		snd_seq_midisynth_delete(&msynth[p]);
		kfree(msynth);
	}
	if (newclient) {
		snd_seq_delete_kernel_client(client->seq_client);
		kfree(client);
	}
	kfree(info);
	kfree(port);
425
	mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
426 427 428 429 430
	return -ENOMEM;
}

/* release midi synth port */
static int
431
snd_seq_midisynth_unregister_port(struct snd_seq_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
432
{
433 434 435
	struct seq_midisynth_client *client;
	struct seq_midisynth *msynth;
	struct snd_card *card = dev->card;
Linus Torvalds's avatar
Linus Torvalds committed
436 437
	int device = dev->device, p, ports;
	
438
	mutex_lock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
439 440
	client = synths[card->number];
	if (client == NULL || client->ports[device] == NULL) {
441
		mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
		return -ENODEV;
	}
	ports = client->ports_per_device[device];
	client->ports_per_device[device] = 0;
	msynth = client->ports[device];
	client->ports[device] = NULL;
	for (p = 0; p < ports; p++)
		snd_seq_midisynth_delete(&msynth[p]);
	kfree(msynth);
	client->num_ports--;
	if (client->num_ports <= 0) {
		snd_seq_delete_kernel_client(client->seq_client);
		synths[card->number] = NULL;
		kfree(client);
	}
457
	mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
458 459 460 461 462 463
	return 0;
}


static int __init alsa_seq_midi_init(void)
{
464
	static struct snd_seq_dev_ops ops = {
Linus Torvalds's avatar
Linus Torvalds committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
		snd_seq_midisynth_register_port,
		snd_seq_midisynth_unregister_port,
	};
	memset(&synths, 0, sizeof(synths));
	snd_seq_autoload_lock();
	snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH, &ops, 0);
	snd_seq_autoload_unlock();
	return 0;
}

static void __exit alsa_seq_midi_exit(void)
{
	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH);
}

module_init(alsa_seq_midi_init)
module_exit(alsa_seq_midi_exit)