| /* | |
| * Sequencer Interface - middle-level routines | |
| * | |
| * Copyright (c) 1999 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 "seq_local.h" | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include <ctype.h> | |
| #include <sys/ioctl.h> | |
| /** | |
| * \brief queue controls - start/stop/continue | |
| * \param seq sequencer handle | |
| * \param q queue id to control | |
| * \param type event type | |
| * \param value event value | |
| * \param ev event instance | |
| * | |
| * This function sets up general queue control event and sends it. | |
| * To send at scheduled time, set the schedule in \a ev. | |
| * If \a ev is NULL, the event is composed locally and sent immediately | |
| * to the specified queue. In any cases, you need to call #snd_seq_drain_output() | |
| * appropriately to feed the event. | |
| * | |
| * \sa snd_seq_alloc_queue() | |
| */ | |
| int snd_seq_control_queue(snd_seq_t *seq, int q, int type, int value, snd_seq_event_t *ev) | |
| { | |
| snd_seq_event_t tmpev; | |
| if (ev == NULL) { | |
| snd_seq_ev_clear(&tmpev); | |
| ev = &tmpev; | |
| snd_seq_ev_set_direct(ev); | |
| } | |
| snd_seq_ev_set_queue_control(ev, type, q, value); | |
| return snd_seq_event_output(seq, ev); | |
| } | |
| /** | |
| * \brief create a port - simple version | |
| * \param seq sequencer handle | |
| * \param name the name of the port | |
| * \param caps capability bits | |
| * \param type type bits | |
| * \return the created port number or negative error code | |
| * | |
| * Creates a port with the given capability and type bits. | |
| * | |
| * \sa snd_seq_create_port(), snd_seq_delete_simple_port() | |
| */ | |
| int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, | |
| unsigned int caps, unsigned int type) | |
| { | |
| snd_seq_port_info_t pinfo; | |
| int result; | |
| memset(&pinfo, 0, sizeof(pinfo)); | |
| if (name) | |
| strncpy(pinfo.name, name, sizeof(pinfo.name) - 1); | |
| pinfo.capability = caps; | |
| pinfo.type = type; | |
| pinfo.midi_channels = 16; | |
| pinfo.midi_voices = 64; /* XXX */ | |
| pinfo.synth_voices = 0; /* XXX */ | |
| result = snd_seq_create_port(seq, &pinfo); | |
| if (result < 0) | |
| return result; | |
| else | |
| return pinfo.addr.port; | |
| } | |
| /** | |
| * \brief delete the port | |
| * \param seq sequencer handle | |
| * \param port port id | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_delete_port(), snd_seq_create_simple_port() | |
| */ | |
| int snd_seq_delete_simple_port(snd_seq_t *seq, int port) | |
| { | |
| return snd_seq_delete_port(seq, port); | |
| } | |
| /** | |
| * \brief simple subscription (w/o exclusive & time conversion) | |
| * \param seq sequencer handle | |
| * \param myport the port id as receiver | |
| * \param src_client sender client id | |
| * \param src_port sender port id | |
| * \return 0 on success or negative error code | |
| * | |
| * Connect from the given sender client:port to the given destination port in the | |
| * current client. | |
| * | |
| * \sa snd_seq_subscribe_port(), snd_seq_disconnect_from() | |
| */ | |
| int snd_seq_connect_from(snd_seq_t *seq, int myport, int src_client, int src_port) | |
| { | |
| snd_seq_port_subscribe_t subs; | |
| memset(&subs, 0, sizeof(subs)); | |
| subs.sender.client = src_client; | |
| subs.sender.port = src_port; | |
| /*subs.dest.client = seq->client;*/ | |
| subs.dest.client = snd_seq_client_id(seq); | |
| subs.dest.port = myport; | |
| return snd_seq_subscribe_port(seq, &subs); | |
| } | |
| /** | |
| * \brief simple subscription (w/o exclusive & time conversion) | |
| * \param seq sequencer handle | |
| * \param myport the port id as sender | |
| * \param dest_client destination client id | |
| * \param dest_port destination port id | |
| * \return 0 on success or negative error code | |
| * | |
| * Connect from the given receiver port in the current client | |
| * to the given destination client:port. | |
| * | |
| * \sa snd_seq_subscribe_port(), snd_seq_disconnect_to() | |
| */ | |
| int snd_seq_connect_to(snd_seq_t *seq, int myport, int dest_client, int dest_port) | |
| { | |
| snd_seq_port_subscribe_t subs; | |
| memset(&subs, 0, sizeof(subs)); | |
| /*subs.sender.client = seq->client;*/ | |
| subs.sender.client = snd_seq_client_id(seq); | |
| subs.sender.port = myport; | |
| subs.dest.client = dest_client; | |
| subs.dest.port = dest_port; | |
| return snd_seq_subscribe_port(seq, &subs); | |
| } | |
| /** | |
| * \brief simple disconnection | |
| * \param seq sequencer handle | |
| * \param myport the port id as receiver | |
| * \param src_client sender client id | |
| * \param src_port sender port id | |
| * \return 0 on success or negative error code | |
| * | |
| * Remove connection from the given sender client:port | |
| * to the given destination port in the current client. | |
| * | |
| * \sa snd_seq_unsubscribe_port(), snd_seq_connect_from() | |
| */ | |
| int snd_seq_disconnect_from(snd_seq_t *seq, int myport, int src_client, int src_port) | |
| { | |
| snd_seq_port_subscribe_t subs; | |
| memset(&subs, 0, sizeof(subs)); | |
| subs.sender.client = src_client; | |
| subs.sender.port = src_port; | |
| /*subs.dest.client = seq->client;*/ | |
| subs.dest.client = snd_seq_client_id(seq); | |
| subs.dest.port = myport; | |
| return snd_seq_unsubscribe_port(seq, &subs); | |
| } | |
| /** | |
| * \brief simple disconnection | |
| * \param seq sequencer handle | |
| * \param myport the port id as sender | |
| * \param dest_client destination client id | |
| * \param dest_port destination port id | |
| * \return 0 on success or negative error code | |
| * | |
| * Remove connection from the given sender client:port | |
| * to the given destination port in the current client. | |
| * | |
| * \sa snd_seq_unsubscribe_port(), snd_seq_connect_to() | |
| */ | |
| int snd_seq_disconnect_to(snd_seq_t *seq, int myport, int dest_client, int dest_port) | |
| { | |
| snd_seq_port_subscribe_t subs; | |
| memset(&subs, 0, sizeof(subs)); | |
| /*subs.sender.client = seq->client;*/ | |
| subs.sender.client = snd_seq_client_id(seq); | |
| subs.sender.port = myport; | |
| subs.dest.client = dest_client; | |
| subs.dest.port = dest_port; | |
| return snd_seq_unsubscribe_port(seq, &subs); | |
| } | |
| /* | |
| * set client information | |
| */ | |
| /** | |
| * \brief set client name | |
| * \param seq sequencer handle | |
| * \param name name string | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_set_client_info() | |
| */ | |
| int snd_seq_set_client_name(snd_seq_t *seq, const char *name) | |
| { | |
| snd_seq_client_info_t info; | |
| int err; | |
| if ((err = snd_seq_get_client_info(seq, &info)) < 0) | |
| return err; | |
| strncpy(info.name, name, sizeof(info.name) - 1); | |
| return snd_seq_set_client_info(seq, &info); | |
| } | |
| /** | |
| * \brief add client event filter | |
| * \param seq sequencer handle | |
| * \param event_type event type to be added | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_set_client_info() | |
| */ | |
| int snd_seq_set_client_event_filter(snd_seq_t *seq, int event_type) | |
| { | |
| snd_seq_client_info_t info; | |
| int err; | |
| if ((err = snd_seq_get_client_info(seq, &info)) < 0) | |
| return err; | |
| snd_seq_client_info_event_filter_add(&info, event_type); | |
| return snd_seq_set_client_info(seq, &info); | |
| } | |
| /** | |
| * \brief set client MIDI protocol version | |
| * \param seq sequencer handle | |
| * \param midi_version MIDI protocol version to set | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_set_client_info() | |
| */ | |
| int snd_seq_set_client_midi_version(snd_seq_t *seq, int midi_version) | |
| { | |
| snd_seq_client_info_t info; | |
| int err; | |
| if ((err = snd_seq_get_client_info(seq, &info)) < 0) | |
| return err; | |
| snd_seq_client_info_set_midi_version(&info, midi_version); | |
| return snd_seq_set_client_info(seq, &info); | |
| } | |
| /** | |
| * \brief enable/disable client's automatic conversion of UMP/legacy events | |
| * \param seq sequencer handle | |
| * \param enable 0 or 1 to disable/enable the conversion | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_set_client_info() | |
| */ | |
| int snd_seq_set_client_ump_conversion(snd_seq_t *seq, int enable) | |
| { | |
| snd_seq_client_info_t info; | |
| int err; | |
| if ((err = snd_seq_get_client_info(seq, &info)) < 0) | |
| return err; | |
| snd_seq_client_info_set_ump_conversion(&info, enable); | |
| return snd_seq_set_client_info(seq, &info); | |
| } | |
| /** | |
| * \brief change the output pool size of the given client | |
| * \param seq sequencer handle | |
| * \param size output pool size | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_set_client_pool() | |
| */ | |
| int snd_seq_set_client_pool_output(snd_seq_t *seq, size_t size) | |
| { | |
| snd_seq_client_pool_t info; | |
| int err; | |
| if ((err = snd_seq_get_client_pool(seq, &info)) < 0) | |
| return err; | |
| info.output_pool = size; | |
| return snd_seq_set_client_pool(seq, &info); | |
| } | |
| /** | |
| * \brief change the output room size of the given client | |
| * \param seq sequencer handle | |
| * \param size output room size | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_set_client_pool() | |
| */ | |
| int snd_seq_set_client_pool_output_room(snd_seq_t *seq, size_t size) | |
| { | |
| snd_seq_client_pool_t info; | |
| int err; | |
| if ((err = snd_seq_get_client_pool(seq, &info)) < 0) | |
| return err; | |
| info.output_room = size; | |
| return snd_seq_set_client_pool(seq, &info); | |
| } | |
| /** | |
| * \brief change the input pool size of the given client | |
| * \param seq sequencer handle | |
| * \param size input pool size | |
| * \return 0 on success or negative error code | |
| * | |
| * \sa snd_seq_set_client_pool() | |
| */ | |
| int snd_seq_set_client_pool_input(snd_seq_t *seq, size_t size) | |
| { | |
| snd_seq_client_pool_t info; | |
| int err; | |
| if ((err = snd_seq_get_client_pool(seq, &info)) < 0) | |
| return err; | |
| info.input_pool = size; | |
| return snd_seq_set_client_pool(seq, &info); | |
| } | |
| /** | |
| * \brief reset client output pool | |
| * \param seq sequencer handle | |
| * \return 0 on success or negative error code | |
| * | |
| * So far, this works identically like #snd_seq_drop_output(). | |
| */ | |
| int snd_seq_reset_pool_output(snd_seq_t *seq) | |
| { | |
| return snd_seq_drop_output(seq); | |
| } | |
| /** | |
| * \brief reset client input pool | |
| * \param seq sequencer handle | |
| * \return 0 on success or negative error code | |
| * | |
| * So far, this works identically like #snd_seq_drop_input(). | |
| */ | |
| int snd_seq_reset_pool_input(snd_seq_t *seq) | |
| { | |
| return snd_seq_drop_input(seq); | |
| } | |
| /** | |
| * \brief wait until all events are processed | |
| * \param seq sequencer handle | |
| * \return 0 on success or negative error code | |
| * | |
| * This function waits until all events of this client are processed. | |
| * | |
| * \sa snd_seq_drain_output() | |
| */ | |
| int snd_seq_sync_output_queue(snd_seq_t *seq) | |
| { | |
| int err; | |
| snd_seq_client_pool_t info; | |
| int saved_room; | |
| struct pollfd pfd; | |
| assert(seq); | |
| /* reprogram the room size to full */ | |
| if ((err = snd_seq_get_client_pool(seq, &info)) < 0) | |
| return err; | |
| saved_room = info.output_room; | |
| info.output_room = info.output_pool; /* wait until all gone */ | |
| if ((err = snd_seq_set_client_pool(seq, &info)) < 0) | |
| return err; | |
| /* wait until all events are purged */ | |
| pfd.fd = seq->poll_fd; | |
| pfd.events = POLLOUT; | |
| err = poll(&pfd, 1, -1); | |
| /* restore the room size */ | |
| info.output_room = saved_room; | |
| snd_seq_set_client_pool(seq, &info); | |
| return err; | |
| } | |
| /** | |
| * \brief parse the given string and get the sequencer address | |
| * \param seq sequencer handle | |
| * \param addr the address pointer to be returned | |
| * \param arg the string to be parsed | |
| * \return 0 on success or negative error code | |
| * | |
| * This function parses the sequencer client and port numbers from the given string. | |
| * The client and port tokens are separated by either colon or period, e.g. 128:1. | |
| * When \a seq is not NULL, the function accepts also a client name not only | |
| * digit numbers. | |
| * Actually \a arg need to be only a prefix of the wanted client. | |
| * That is, if a client named "Foobar XXL Master 2012" with number 128 is available, | |
| * then parsing "Foobar" will return the address 128:0 if no other client is | |
| * an exact match. | |
| */ | |
| int snd_seq_parse_address(snd_seq_t *seq, snd_seq_addr_t *addr, const char *arg) | |
| { | |
| char *p, *buf; | |
| const char *s; | |
| char c; | |
| long client, port = 0; | |
| int len; | |
| assert(addr && arg); | |
| c = *arg; | |
| if (c == '"' || c == '\'') { | |
| s = ++arg; | |
| while (*s && *s != c) s++; | |
| len = s - arg; | |
| if (*s) | |
| s++; | |
| if (*s) { | |
| if (*s != '.' && *s != ':') | |
| return -EINVAL; | |
| if ((port = atoi(s + 1)) < 0) | |
| return -EINVAL; | |
| } | |
| } else { | |
| if ((p = strpbrk(arg, ":.")) != NULL) { | |
| if ((port = atoi(p + 1)) < 0) | |
| return -EINVAL; | |
| len = (int)(p - arg); /* length of client name */ | |
| } else { | |
| len = strlen(arg); | |
| } | |
| } | |
| if (len == 0) | |
| return -EINVAL; | |
| buf = alloca(len + 1); | |
| strncpy(buf, arg, len); | |
| buf[len] = '\0'; | |
| addr->port = port; | |
| if (safe_strtol(buf, &client) == 0) { | |
| addr->client = client; | |
| } else { | |
| /* convert from the name */ | |
| snd_seq_client_info_t cinfo; | |
| if (! seq) | |
| return -EINVAL; | |
| if (len <= 0) | |
| return -EINVAL; | |
| client = -1; | |
| cinfo.client = -1; | |
| while (snd_seq_query_next_client(seq, &cinfo) >= 0) { | |
| if (!strncmp(arg, cinfo.name, len)) { | |
| if (strlen(cinfo.name) == (size_t)len) { | |
| /* exact match */ | |
| addr->client = cinfo.client; | |
| return 0; | |
| } | |
| if (client < 0) | |
| client = cinfo.client; | |
| } | |
| } | |
| if (client >= 0) { | |
| /* prefix match */ | |
| addr->client = client; | |
| return 0; | |
| } | |
| return -ENOENT; /* not found */ | |
| } | |
| return 0; | |
| } | |