1/*
2 *  RawMIDI - Virtual (sequencer mode)
3 *  Copyright (c) 2003 by Takashi Iwai <tiwai@suse.de>
4 *
5 *
6 *   This library is free software; you can redistribute it and/or modify
7 *   it under the terms of the GNU Lesser General Public License as
8 *   published by the Free Software Foundation; either version 2.1 of
9 *   the License, or (at your option) any later version.
10 *
11 *   This program is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *   GNU Lesser General Public License for more details.
15 *
16 *   You should have received a copy of the GNU Lesser General Public
17 *   License along with this library; if not, write to the Free Software
18 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 *
20 */
21
22#include "rawmidi_local.h"
23#include <unistd.h>
24#include <string.h>
25#include <fcntl.h>
26#include <sys/ioctl.h>
27#include "seq.h"
28#include "seq_midi_event.h"
29
30#ifndef PIC
31/* entry for static linking */
32const char *_snd_module_rawmidi_virt = "";
33#endif
34
35
36#ifndef DOC_HIDDEN
37typedef struct {
38	int open;
39
40	snd_seq_t *handle;
41	int port;
42
43	snd_midi_event_t *midi_event;
44
45	snd_seq_event_t *in_event;
46	int in_buf_size;
47	int in_buf_ofs;
48	char *in_buf_ptr;
49	char in_tmp_buf[16];
50
51	snd_seq_event_t out_event;
52	int pending;
53} snd_rawmidi_virtual_t;
54
55int _snd_seq_open_lconf(snd_seq_t **seqp, const char *name,
56			int streams, int mode, snd_config_t *lconf,
57			snd_config_t *parent_conf);
58#endif
59
60static int snd_rawmidi_virtual_close(snd_rawmidi_t *rmidi)
61{
62	snd_rawmidi_virtual_t *virt = rmidi->private_data;
63	virt->open--;
64	if (virt->open)
65		return 0;
66	snd_seq_close(virt->handle);
67	if (virt->midi_event)
68		snd_midi_event_free(virt->midi_event);
69	free(virt);
70	return 0;
71}
72
73static int snd_rawmidi_virtual_nonblock(snd_rawmidi_t *rmidi, int nonblock)
74{
75	snd_rawmidi_virtual_t *virt = rmidi->private_data;
76
77	return snd_seq_nonblock(virt->handle, nonblock);
78}
79
80static int snd_rawmidi_virtual_info(snd_rawmidi_t *rmidi, snd_rawmidi_info_t * info)
81{
82	// snd_rawmidi_virtual_t *virt = rmidi->private_data;
83
84	info->stream = rmidi->stream;
85	/* FIXME: what values should be there? */
86	info->card = 0;
87	info->device = 0;
88	info->subdevice = 0;
89	info->flags = 0;
90	strcpy((char *)info->id, "Virtual");
91	strcpy((char *)info->name, "Virtual RawMIDI");
92	strcpy((char *)info->subname, "Virtual RawMIDI");
93	info->subdevices_count = 1;
94	info->subdevices_avail = 0;
95	return 0;
96}
97
98static int snd_rawmidi_virtual_input_params(snd_rawmidi_virtual_t *virt, snd_rawmidi_params_t *params)
99{
100	int err;
101
102	// snd_rawmidi_drain_input(substream);
103	if (params->buffer_size < sizeof(snd_seq_event_t) ||
104	    params->buffer_size > 1024L * 1024L) {
105		return -EINVAL;
106	}
107	if (params->buffer_size != snd_seq_get_input_buffer_size(virt->handle)) {
108		err = snd_seq_set_input_buffer_size(virt->handle, params->buffer_size);
109		if (err < 0)
110			return err;
111		params->buffer_size = snd_seq_get_input_buffer_size(virt->handle);
112		/* FIXME: input pool size? */
113	}
114	return 0;
115}
116
117
118static int snd_rawmidi_virtual_output_params(snd_rawmidi_virtual_t *virt, snd_rawmidi_params_t *params)
119{
120	int err;
121
122	// snd_rawmidi_drain_output(substream);
123	if (params->buffer_size < sizeof(snd_seq_event_t) ||
124	    params->buffer_size > 1024L * 1024L) {
125		return -EINVAL;
126	}
127	if (params->buffer_size != snd_seq_get_output_buffer_size(virt->handle)) {
128		err = snd_seq_set_output_buffer_size(virt->handle, params->buffer_size);
129		if (err < 0)
130			return err;
131		params->buffer_size = snd_seq_get_output_buffer_size(virt->handle);
132	}
133	return 0;
134}
135
136
137static int snd_rawmidi_virtual_params(snd_rawmidi_t *rmidi, snd_rawmidi_params_t * params)
138{
139	snd_rawmidi_virtual_t *virt = rmidi->private_data;
140	params->stream = rmidi->stream;
141
142	if (rmidi->stream == SND_RAWMIDI_STREAM_INPUT)
143		return snd_rawmidi_virtual_input_params(virt, params);
144	else
145		return snd_rawmidi_virtual_output_params(virt, params);
146}
147
148static int snd_rawmidi_virtual_status(snd_rawmidi_t *rmidi, snd_rawmidi_status_t * status)
149{
150	// snd_rawmidi_virtual_t *virt = rmidi->private_data;
151	memset(status, 0, sizeof(*status));
152	status->stream = rmidi->stream;
153	return 0;
154}
155
156static int snd_rawmidi_virtual_drop(snd_rawmidi_t *rmidi)
157{
158	snd_rawmidi_virtual_t *virt = rmidi->private_data;
159	if (rmidi->stream == SND_RAWMIDI_STREAM_OUTPUT) {
160		snd_seq_drop_output(virt->handle);
161		snd_midi_event_reset_encode(virt->midi_event);
162		virt->pending = 0;
163	} else {
164		snd_seq_drop_input(virt->handle);
165		snd_midi_event_reset_decode(virt->midi_event);
166		virt->in_buf_ofs = 0;
167	}
168	return 0;
169}
170
171static int snd_rawmidi_virtual_drain(snd_rawmidi_t *rmidi)
172{
173	snd_rawmidi_virtual_t *virt = rmidi->private_data;
174	int err;
175
176	if (rmidi->stream == SND_RAWMIDI_STREAM_OUTPUT) {
177		if (virt->pending) {
178			err = snd_seq_event_output(virt->handle, &virt->out_event);
179			if (err < 0)
180				return err;
181			virt->pending = 0;
182		}
183		snd_seq_drain_output(virt->handle);
184		snd_seq_sync_output_queue(virt->handle);
185	}
186	return snd_rawmidi_virtual_drop(rmidi);
187}
188
189static ssize_t snd_rawmidi_virtual_write(snd_rawmidi_t *rmidi, const void *buffer, size_t size)
190{
191	snd_rawmidi_virtual_t *virt = rmidi->private_data;
192	ssize_t result = 0;
193	ssize_t size1;
194	int err;
195
196	if (virt->pending) {
197		err = snd_seq_event_output(virt->handle, &virt->out_event);
198		if (err < 0) {
199			if (err != -EAGAIN)
200				/* we got some fatal error. removing this event
201				 * at the next time
202				 */
203				virt->pending = 0;
204			return err;
205		}
206		virt->pending = 0;
207	}
208
209	while (size > 0) {
210		size1 = snd_midi_event_encode(virt->midi_event, buffer, size, &virt->out_event);
211		if (size1 <= 0)
212			break;
213		size -= size1;
214		result += size1;
215		buffer += size1;
216		if (virt->out_event.type == SND_SEQ_EVENT_NONE)
217			continue;
218		snd_seq_ev_set_subs(&virt->out_event);
219		snd_seq_ev_set_source(&virt->out_event, virt->port);
220		snd_seq_ev_set_direct(&virt->out_event);
221		err = snd_seq_event_output(virt->handle, &virt->out_event);
222		if (err < 0) {
223			virt->pending = 1;
224			return result > 0 ? result : err;
225		}
226	}
227
228	if (result > 0)
229		snd_seq_drain_output(virt->handle);
230
231	return result;
232}
233
234static ssize_t snd_rawmidi_virtual_read(snd_rawmidi_t *rmidi, void *buffer, size_t size)
235{
236	snd_rawmidi_virtual_t *virt = rmidi->private_data;
237	ssize_t result = 0;
238	int size1, err;
239
240	while (size > 0) {
241		if (! virt->in_buf_ofs) {
242			err = snd_seq_event_input_pending(virt->handle, 1);
243			if (err <= 0 && result > 0)
244				return result;
245			err = snd_seq_event_input(virt->handle, &virt->in_event);
246			if (err < 0)
247				return result > 0 ? result : err;
248
249			if (virt->in_event->type == SND_SEQ_EVENT_SYSEX) {
250				virt->in_buf_ptr = virt->in_event->data.ext.ptr;
251				virt->in_buf_size = virt->in_event->data.ext.len;
252			} else {
253				virt->in_buf_ptr = virt->in_tmp_buf;
254				virt->in_buf_size = snd_midi_event_decode(virt->midi_event,
255									  (unsigned char *)virt->in_tmp_buf,
256									  sizeof(virt->in_tmp_buf),
257									  virt->in_event);
258			}
259			if (virt->in_buf_size <= 0)
260				continue;
261		}
262		size1 = virt->in_buf_size - virt->in_buf_ofs;
263		if ((size_t)size1 > size) {
264			memcpy(buffer, virt->in_buf_ptr + virt->in_buf_ofs, size);
265			virt->in_buf_ofs += size;
266			result += size;
267			break;
268		}
269		memcpy(buffer, virt->in_buf_ptr + virt->in_buf_ofs, size1);
270		size -= size1;
271		result += size1;
272		buffer += size1;
273		virt->in_buf_ofs = 0;
274	}
275
276	return result;
277}
278
279static const snd_rawmidi_ops_t snd_rawmidi_virtual_ops = {
280	.close = snd_rawmidi_virtual_close,
281	.nonblock = snd_rawmidi_virtual_nonblock,
282	.info = snd_rawmidi_virtual_info,
283	.params = snd_rawmidi_virtual_params,
284	.status = snd_rawmidi_virtual_status,
285	.drop = snd_rawmidi_virtual_drop,
286	.drain = snd_rawmidi_virtual_drain,
287	.write = snd_rawmidi_virtual_write,
288	.read = snd_rawmidi_virtual_read,
289};
290
291
292/*! \page rawmidi RawMidi interface
293
294\section rawmidi_virt Virtual RawMidi interface
295
296The "virtual" plugin creates a virtual RawMidi instance on the ALSA
297sequencer, which can be accessed through the connection of the sequencer
298ports.
299There is no connection established as default.
300
301For creating a virtual RawMidi instance, pass "virtual" as its name at
302creation.
303
304Example:
305\code
306snd_rawmidi_open(&read_handle, &write_handle, "virtual", 0);
307\endcode
308
309*/
310
311int snd_rawmidi_virtual_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
312			     const char *name, snd_seq_t *seq_handle, int port,
313			     int merge, int mode)
314{
315	int err;
316	snd_rawmidi_t *rmidi = NULL;
317	snd_rawmidi_virtual_t *virt = NULL;
318	struct pollfd pfd;
319
320	if (inputp)
321		*inputp = 0;
322	if (outputp)
323		*outputp = 0;
324
325	virt = calloc(1, sizeof(*virt));
326	if (virt == NULL) {
327		err = -ENOMEM;
328		goto _err;
329	}
330	virt->handle = seq_handle;
331	virt->port = port;
332	err = snd_midi_event_new(256, &virt->midi_event);
333	if (err < 0)
334		goto _err;
335	snd_midi_event_init(virt->midi_event);
336	snd_midi_event_no_status(virt->midi_event, !merge);
337
338	if (inputp) {
339		rmidi = calloc(1, sizeof(*rmidi));
340		if (rmidi == NULL) {
341			err = -ENOMEM;
342			goto _err;
343		}
344		if (name)
345			rmidi->name = strdup(name);
346		rmidi->type = SND_RAWMIDI_TYPE_VIRTUAL;
347		rmidi->stream = SND_RAWMIDI_STREAM_INPUT;
348		rmidi->mode = mode;
349		err = snd_seq_poll_descriptors(seq_handle, &pfd, 1, POLLIN);
350		if (err < 0)
351			goto _err;
352		rmidi->poll_fd = pfd.fd;
353		rmidi->ops = &snd_rawmidi_virtual_ops;
354		rmidi->private_data = virt;
355		virt->open++;
356		*inputp = rmidi;
357	}
358	if (outputp) {
359		rmidi = calloc(1, sizeof(*rmidi));
360		if (rmidi == NULL) {
361			err = -ENOMEM;
362			goto _err;
363		}
364		if (name)
365			rmidi->name = strdup(name);
366		rmidi->type = SND_RAWMIDI_TYPE_VIRTUAL;
367		rmidi->stream = SND_RAWMIDI_STREAM_OUTPUT;
368		rmidi->mode = mode;
369		err = snd_seq_poll_descriptors(seq_handle, &pfd, 1, POLLOUT);
370		if (err < 0)
371			goto _err;
372		rmidi->poll_fd = pfd.fd;
373		rmidi->ops = &snd_rawmidi_virtual_ops;
374		rmidi->private_data = virt;
375		virt->open++;
376		*outputp = rmidi;
377	}
378
379	return 0;
380
381 _err:
382	if (seq_handle)
383		snd_seq_close(seq_handle);
384	if (virt) {
385		if (virt->midi_event)
386			snd_midi_event_free(virt->midi_event);
387		free(virt);
388	}
389	if (inputp)
390		free(*inputp);
391	if (outputp)
392		free(*outputp);
393	free(rmidi);
394	return err;
395}
396
397int _snd_rawmidi_virtual_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
398			   char *name, snd_config_t *root ATTRIBUTE_UNUSED,
399			   snd_config_t *conf, int mode)
400{
401	snd_config_iterator_t i, next;
402	const char *slave_str = NULL;
403	int err;
404	int streams, seq_mode;
405	int merge = 1;
406	int port;
407	unsigned int caps;
408	snd_seq_t *seq_handle;
409
410	snd_config_for_each(i, next, conf) {
411		snd_config_t *n = snd_config_iterator_entry(i);
412		const char *id;
413		if (snd_config_get_id(n, &id) < 0)
414			continue;
415		if (snd_rawmidi_conf_generic_id(id))
416			continue;
417		if (strcmp(id, "slave") == 0) {
418			err = snd_config_get_string(n, &slave_str);
419			if (err < 0)
420				return err;
421			continue;
422		}
423		if (strcmp(id, "merge") == 0) {
424			merge = snd_config_get_bool(n);
425			continue;
426		}
427		return -EINVAL;
428	}
429
430	streams = 0;
431	if (inputp)
432		streams |= SND_SEQ_OPEN_INPUT;
433	if (outputp)
434		streams |= SND_SEQ_OPEN_OUTPUT;
435	if (! streams)
436		return -EINVAL;
437
438	seq_mode = 0;
439	if (mode & SND_RAWMIDI_NONBLOCK)
440		seq_mode |= SND_SEQ_NONBLOCK;
441
442	if (! slave_str)
443		slave_str = "default";
444	err = _snd_seq_open_lconf(&seq_handle, slave_str, streams, seq_mode,
445				  root, conf);
446	if (err < 0)
447		return err;
448
449	caps = 0;
450	if (inputp)
451		caps |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SYNC_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
452	if (outputp)
453		caps |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SYNC_READ | SND_SEQ_PORT_CAP_SUBS_READ;
454	if (inputp && outputp)
455		caps |= SNDRV_SEQ_PORT_CAP_DUPLEX;
456
457	port = snd_seq_create_simple_port(seq_handle, "Virtual RawMIDI",
458					  caps, SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC);
459	if (port < 0) {
460		snd_seq_close(seq_handle);
461		return port;
462	}
463
464	return snd_rawmidi_virtual_open(inputp, outputp, name, seq_handle, port,
465				     merge, mode);
466}
467
468#ifndef DOC_HIDDEN
469SND_DLSYM_BUILD_VERSION(_snd_rawmidi_virtual_open, SND_RAWMIDI_DLSYM_VERSION);
470#endif
471