| /* | |
| * RawMIDI - Virtual (sequencer mode) | |
| * Copyright (c) 2003 by Takashi Iwai <tiwai@suse.de> | |
| * | |
| * | |
| * 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.1 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #include "rawmidi_local.h" | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include <sys/ioctl.h> | |
| #include "seq.h" | |
| #include "seq_midi_event.h" | |
| #ifndef PIC | |
| /* entry for static linking */ | |
| const char *_snd_module_rawmidi_virt = ""; | |
| #endif | |
| #ifndef DOC_HIDDEN | |
| typedef struct { | |
| int open; | |
| snd_seq_t *handle; | |
| int port; | |
| snd_midi_event_t *midi_event; | |
| snd_seq_event_t *in_event; | |
| int in_buf_size; | |
| int in_buf_ofs; | |
| char *in_buf_ptr; | |
| char in_tmp_buf[16]; | |
| snd_seq_event_t out_event; | |
| int pending; | |
| } snd_rawmidi_virtual_t; | |
| int _snd_seq_open_lconf(snd_seq_t **seqp, const char *name, | |
| int streams, int mode, snd_config_t *lconf, | |
| snd_config_t *parent_conf); | |
| #endif | |
| static int snd_rawmidi_virtual_close(snd_rawmidi_t *rmidi) | |
| { | |
| snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| virt->open--; | |
| if (virt->open) | |
| return 0; | |
| snd_seq_close(virt->handle); | |
| if (virt->midi_event) | |
| snd_midi_event_free(virt->midi_event); | |
| free(virt); | |
| return 0; | |
| } | |
| static int snd_rawmidi_virtual_nonblock(snd_rawmidi_t *rmidi, int nonblock) | |
| { | |
| snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| return snd_seq_nonblock(virt->handle, nonblock); | |
| } | |
| static int snd_rawmidi_virtual_info(snd_rawmidi_t *rmidi, snd_rawmidi_info_t * info) | |
| { | |
| // snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| info->stream = rmidi->stream; | |
| /* FIXME: what values should be there? */ | |
| info->card = 0; | |
| info->device = 0; | |
| info->subdevice = 0; | |
| info->flags = 0; | |
| strcpy((char *)info->id, "Virtual"); | |
| strcpy((char *)info->name, "Virtual RawMIDI"); | |
| strcpy((char *)info->subname, "Virtual RawMIDI"); | |
| info->subdevices_count = 1; | |
| info->subdevices_avail = 0; | |
| return 0; | |
| } | |
| static int snd_rawmidi_virtual_input_params(snd_rawmidi_virtual_t *virt, snd_rawmidi_params_t *params) | |
| { | |
| int err; | |
| // snd_rawmidi_drain_input(substream); | |
| if (params->buffer_size < sizeof(snd_seq_event_t) || | |
| params->buffer_size > 1024L * 1024L) { | |
| return -EINVAL; | |
| } | |
| if (params->buffer_size != snd_seq_get_input_buffer_size(virt->handle)) { | |
| err = snd_seq_set_input_buffer_size(virt->handle, params->buffer_size); | |
| if (err < 0) | |
| return err; | |
| params->buffer_size = snd_seq_get_input_buffer_size(virt->handle); | |
| /* FIXME: input pool size? */ | |
| } | |
| return 0; | |
| } | |
| static int snd_rawmidi_virtual_output_params(snd_rawmidi_virtual_t *virt, snd_rawmidi_params_t *params) | |
| { | |
| int err; | |
| // snd_rawmidi_drain_output(substream); | |
| if (params->buffer_size < sizeof(snd_seq_event_t) || | |
| params->buffer_size > 1024L * 1024L) { | |
| return -EINVAL; | |
| } | |
| if (params->buffer_size != snd_seq_get_output_buffer_size(virt->handle)) { | |
| err = snd_seq_set_output_buffer_size(virt->handle, params->buffer_size); | |
| if (err < 0) | |
| return err; | |
| params->buffer_size = snd_seq_get_output_buffer_size(virt->handle); | |
| } | |
| return 0; | |
| } | |
| static int snd_rawmidi_virtual_params(snd_rawmidi_t *rmidi, snd_rawmidi_params_t * params) | |
| { | |
| snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| params->stream = rmidi->stream; | |
| if (rmidi->stream == SND_RAWMIDI_STREAM_INPUT) | |
| return snd_rawmidi_virtual_input_params(virt, params); | |
| else | |
| return snd_rawmidi_virtual_output_params(virt, params); | |
| } | |
| static int snd_rawmidi_virtual_status(snd_rawmidi_t *rmidi, snd_rawmidi_status_t * status) | |
| { | |
| // snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| memset(status, 0, sizeof(*status)); | |
| status->stream = rmidi->stream; | |
| return 0; | |
| } | |
| static int snd_rawmidi_virtual_drop(snd_rawmidi_t *rmidi) | |
| { | |
| snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| if (rmidi->stream == SND_RAWMIDI_STREAM_OUTPUT) { | |
| snd_seq_drop_output(virt->handle); | |
| snd_midi_event_reset_encode(virt->midi_event); | |
| virt->pending = 0; | |
| } else { | |
| snd_seq_drop_input(virt->handle); | |
| snd_midi_event_reset_decode(virt->midi_event); | |
| virt->in_buf_ofs = 0; | |
| } | |
| return 0; | |
| } | |
| static int snd_rawmidi_virtual_drain(snd_rawmidi_t *rmidi) | |
| { | |
| snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| int err; | |
| if (rmidi->stream == SND_RAWMIDI_STREAM_OUTPUT) { | |
| if (virt->pending) { | |
| err = snd_seq_event_output(virt->handle, &virt->out_event); | |
| if (err < 0) | |
| return err; | |
| virt->pending = 0; | |
| } | |
| snd_seq_drain_output(virt->handle); | |
| snd_seq_sync_output_queue(virt->handle); | |
| } | |
| return snd_rawmidi_virtual_drop(rmidi); | |
| } | |
| static ssize_t snd_rawmidi_virtual_write(snd_rawmidi_t *rmidi, const void *buffer, size_t size) | |
| { | |
| snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| ssize_t result = 0; | |
| ssize_t size1; | |
| int err; | |
| if (virt->pending) { | |
| err = snd_seq_event_output(virt->handle, &virt->out_event); | |
| if (err < 0) { | |
| if (err != -EAGAIN) | |
| /* we got some fatal error. removing this event | |
| * at the next time | |
| */ | |
| virt->pending = 0; | |
| return err; | |
| } | |
| virt->pending = 0; | |
| } | |
| while (size > 0) { | |
| size1 = snd_midi_event_encode(virt->midi_event, buffer, size, &virt->out_event); | |
| if (size1 <= 0) | |
| break; | |
| size -= size1; | |
| result += size1; | |
| buffer += size1; | |
| if (virt->out_event.type == SND_SEQ_EVENT_NONE) | |
| continue; | |
| snd_seq_ev_set_subs(&virt->out_event); | |
| snd_seq_ev_set_source(&virt->out_event, virt->port); | |
| snd_seq_ev_set_direct(&virt->out_event); | |
| err = snd_seq_event_output(virt->handle, &virt->out_event); | |
| if (err < 0) { | |
| virt->pending = 1; | |
| return result > 0 ? result : err; | |
| } | |
| } | |
| if (result > 0) | |
| snd_seq_drain_output(virt->handle); | |
| return result; | |
| } | |
| static ssize_t snd_rawmidi_virtual_read(snd_rawmidi_t *rmidi, void *buffer, size_t size) | |
| { | |
| snd_rawmidi_virtual_t *virt = rmidi->private_data; | |
| ssize_t result = 0; | |
| int size1, err; | |
| while (size > 0) { | |
| if (! virt->in_buf_ofs) { | |
| err = snd_seq_event_input_pending(virt->handle, 1); | |
| if (err <= 0 && result > 0) | |
| return result; | |
| err = snd_seq_event_input(virt->handle, &virt->in_event); | |
| if (err < 0) | |
| return result > 0 ? result : err; | |
| if (virt->in_event->type == SND_SEQ_EVENT_SYSEX) { | |
| virt->in_buf_ptr = virt->in_event->data.ext.ptr; | |
| virt->in_buf_size = virt->in_event->data.ext.len; | |
| } else { | |
| virt->in_buf_ptr = virt->in_tmp_buf; | |
| virt->in_buf_size = snd_midi_event_decode(virt->midi_event, | |
| (unsigned char *)virt->in_tmp_buf, | |
| sizeof(virt->in_tmp_buf), | |
| virt->in_event); | |
| } | |
| if (virt->in_buf_size <= 0) | |
| continue; | |
| } | |
| size1 = virt->in_buf_size - virt->in_buf_ofs; | |
| if ((size_t)size1 > size) { | |
| memcpy(buffer, virt->in_buf_ptr + virt->in_buf_ofs, size); | |
| virt->in_buf_ofs += size; | |
| result += size; | |
| break; | |
| } | |
| memcpy(buffer, virt->in_buf_ptr + virt->in_buf_ofs, size1); | |
| size -= size1; | |
| result += size1; | |
| buffer += size1; | |
| virt->in_buf_ofs = 0; | |
| } | |
| return result; | |
| } | |
| static const snd_rawmidi_ops_t snd_rawmidi_virtual_ops = { | |
| .close = snd_rawmidi_virtual_close, | |
| .nonblock = snd_rawmidi_virtual_nonblock, | |
| .info = snd_rawmidi_virtual_info, | |
| .params = snd_rawmidi_virtual_params, | |
| .status = snd_rawmidi_virtual_status, | |
| .drop = snd_rawmidi_virtual_drop, | |
| .drain = snd_rawmidi_virtual_drain, | |
| .write = snd_rawmidi_virtual_write, | |
| .read = snd_rawmidi_virtual_read, | |
| }; | |
| /*! \page rawmidi RawMidi interface | |
| \section rawmidi_virt Virtual RawMidi interface | |
| The "virtual" plugin creates a virtual RawMidi instance on the ALSA | |
| sequencer, which can be accessed through the connection of the sequencer | |
| ports. | |
| There is no connection established as default. | |
| For creating a virtual RawMidi instance, pass "virtual" as its name at | |
| creation. | |
| Example: | |
| \code | |
| snd_rawmidi_open(&read_handle, &write_handle, "virtual", 0); | |
| \endcode | |
| */ | |
| int snd_rawmidi_virtual_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp, | |
| const char *name, snd_seq_t *seq_handle, int port, | |
| int merge, int mode) | |
| { | |
| int err; | |
| snd_rawmidi_t *rmidi = NULL; | |
| snd_rawmidi_virtual_t *virt = NULL; | |
| struct pollfd pfd; | |
| if (inputp) | |
| *inputp = 0; | |
| if (outputp) | |
| *outputp = 0; | |
| virt = calloc(1, sizeof(*virt)); | |
| if (virt == NULL) { | |
| err = -ENOMEM; | |
| goto _err; | |
| } | |
| virt->handle = seq_handle; | |
| virt->port = port; | |
| err = snd_midi_event_new(256, &virt->midi_event); | |
| if (err < 0) | |
| goto _err; | |
| snd_midi_event_init(virt->midi_event); | |
| snd_midi_event_no_status(virt->midi_event, !merge); | |
| if (inputp) { | |
| rmidi = calloc(1, sizeof(*rmidi)); | |
| if (rmidi == NULL) { | |
| err = -ENOMEM; | |
| goto _err; | |
| } | |
| if (name) | |
| rmidi->name = strdup(name); | |
| rmidi->type = SND_RAWMIDI_TYPE_VIRTUAL; | |
| rmidi->stream = SND_RAWMIDI_STREAM_INPUT; | |
| rmidi->mode = mode; | |
| err = snd_seq_poll_descriptors(seq_handle, &pfd, 1, POLLIN); | |
| if (err < 0) | |
| goto _err; | |
| rmidi->poll_fd = pfd.fd; | |
| rmidi->ops = &snd_rawmidi_virtual_ops; | |
| rmidi->private_data = virt; | |
| virt->open++; | |
| *inputp = rmidi; | |
| } | |
| if (outputp) { | |
| rmidi = calloc(1, sizeof(*rmidi)); | |
| if (rmidi == NULL) { | |
| err = -ENOMEM; | |
| goto _err; | |
| } | |
| if (name) | |
| rmidi->name = strdup(name); | |
| rmidi->type = SND_RAWMIDI_TYPE_VIRTUAL; | |
| rmidi->stream = SND_RAWMIDI_STREAM_OUTPUT; | |
| rmidi->mode = mode; | |
| err = snd_seq_poll_descriptors(seq_handle, &pfd, 1, POLLOUT); | |
| if (err < 0) | |
| goto _err; | |
| rmidi->poll_fd = pfd.fd; | |
| rmidi->ops = &snd_rawmidi_virtual_ops; | |
| rmidi->private_data = virt; | |
| virt->open++; | |
| *outputp = rmidi; | |
| } | |
| return 0; | |
| _err: | |
| if (seq_handle) | |
| snd_seq_close(seq_handle); | |
| if (virt) { | |
| if (virt->midi_event) | |
| snd_midi_event_free(virt->midi_event); | |
| free(virt); | |
| } | |
| if (inputp) | |
| free(*inputp); | |
| if (outputp) | |
| free(*outputp); | |
| free(rmidi); | |
| return err; | |
| } | |
| int _snd_rawmidi_virtual_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp, | |
| char *name, snd_config_t *root ATTRIBUTE_UNUSED, | |
| snd_config_t *conf, int mode) | |
| { | |
| snd_config_iterator_t i, next; | |
| const char *slave_str = NULL; | |
| int err; | |
| int streams, seq_mode; | |
| int merge = 1; | |
| int port; | |
| unsigned int caps; | |
| snd_seq_t *seq_handle; | |
| snd_config_for_each(i, next, conf) { | |
| snd_config_t *n = snd_config_iterator_entry(i); | |
| const char *id; | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (snd_rawmidi_conf_generic_id(id)) | |
| continue; | |
| if (strcmp(id, "slave") == 0) { | |
| err = snd_config_get_string(n, &slave_str); | |
| if (err < 0) | |
| return err; | |
| continue; | |
| } | |
| if (strcmp(id, "merge") == 0) { | |
| merge = snd_config_get_bool(n); | |
| continue; | |
| } | |
| return -EINVAL; | |
| } | |
| streams = 0; | |
| if (inputp) | |
| streams |= SND_SEQ_OPEN_INPUT; | |
| if (outputp) | |
| streams |= SND_SEQ_OPEN_OUTPUT; | |
| if (! streams) | |
| return -EINVAL; | |
| seq_mode = 0; | |
| if (mode & SND_RAWMIDI_NONBLOCK) | |
| seq_mode |= SND_SEQ_NONBLOCK; | |
| if (! slave_str) | |
| slave_str = "default"; | |
| err = _snd_seq_open_lconf(&seq_handle, slave_str, streams, seq_mode, | |
| root, conf); | |
| if (err < 0) | |
| return err; | |
| caps = 0; | |
| if (inputp) | |
| caps |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SYNC_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; | |
| if (outputp) | |
| caps |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SYNC_READ | SND_SEQ_PORT_CAP_SUBS_READ; | |
| if (inputp && outputp) | |
| caps |= SNDRV_SEQ_PORT_CAP_DUPLEX; | |
| port = snd_seq_create_simple_port(seq_handle, "Virtual RawMIDI", | |
| caps, SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC); | |
| if (port < 0) { | |
| snd_seq_close(seq_handle); | |
| return port; | |
| } | |
| return snd_rawmidi_virtual_open(inputp, outputp, name, seq_handle, port, | |
| merge, mode); | |
| } | |
| #ifndef DOC_HIDDEN | |
| SND_DLSYM_BUILD_VERSION(_snd_rawmidi_virtual_open, SND_RAWMIDI_DLSYM_VERSION); | |
| #endif |