/* * ALSA SEQ < - > JACK MIDI bridge * * Copyright (c) 2006,2007 Dmitry S. Baikov * Copyright (c) 2007,2008,2009 Nedko Arnaudov * Copyright (c) 2009,2010 Paul Davis * * 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; version 2 of the License. * * 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 */ #include #include #include #include #include #include #include "list.h" #include "a2j.h" #include "port_hash.h" #include "port.h" /* This should be part of JACK API */ #define JACK_IS_VALID_PORT_NAME_CHAR(c) \ (isalnum(c) || \ (c) == '/' || \ (c) == '_' || \ (c) == '(' || \ (c) == ')' || \ (c) == '-' || \ (c) == '[' || \ (c) == ']') static int a2j_alsa_connect_from (struct a2j * self, int client, int port) { snd_seq_port_subscribe_t* sub; snd_seq_addr_t seq_addr; int err; snd_seq_port_subscribe_alloca (&sub); seq_addr.client = client; seq_addr.port = port; snd_seq_port_subscribe_set_sender (sub, &seq_addr); seq_addr.client = self->client_id; seq_addr.port = self->port_id; snd_seq_port_subscribe_set_dest (sub, &seq_addr); snd_seq_port_subscribe_set_time_update (sub, 1); snd_seq_port_subscribe_set_queue (sub, self->queue); snd_seq_port_subscribe_set_time_real (sub, 1); if ((err = snd_seq_subscribe_port (self->seq, sub))) { a2j_error ("can't subscribe to %d:%d - %s", client, port, snd_strerror(err)); } return err; } void a2j_port_setdead (a2j_port_hash_t hash, snd_seq_addr_t addr) { struct a2j_port *port = a2j_port_get(hash, addr); if (port) { port->is_dead = true; // see jack_process_internal } else { a2j_debug("port_setdead: not found (%d:%d)", addr.client, addr.port); } } void a2j_port_free (struct a2j_port * port) { // snd_seq_disconnect_from (self->seq, self->port_id, port->remote.client, port->remote.port); // snd_seq_disconnect_to (self->seq, self->port_id, port->remote.client, port->remote.port); if (port->inbound_events) { jack_ringbuffer_free (port->inbound_events); } if (port->jack_port != JACK_INVALID_PORT && !port->a2j_ptr->finishing) { jack_port_unregister (port->a2j_ptr->jack_client, port->jack_port); } free (port); } void a2j_port_fill_name (struct a2j_port * port_ptr, int dir, snd_seq_client_info_t * client_info_ptr, const snd_seq_port_info_t * port_info_ptr, bool make_unique) { char *c; if (make_unique) { snprintf (port_ptr->name, sizeof(port_ptr->name), "%s [%d] %s %s", snd_seq_client_info_get_name(client_info_ptr), snd_seq_client_info_get_client(client_info_ptr), snd_seq_port_info_get_name(port_info_ptr), (dir == A2J_PORT_CAPTURE ? "in" : "out")); } else { snprintf (port_ptr->name, sizeof(port_ptr->name), "%s %s %s", snd_seq_client_info_get_name(client_info_ptr), snd_seq_port_info_get_name(port_info_ptr), (dir == A2J_PORT_CAPTURE ? "in" : "out")); } // replace all offending characters with ' ' for (c = port_ptr->name; *c; ++c) { if (!JACK_IS_VALID_PORT_NAME_CHAR(*c)) { *c = ' '; } } } struct a2j_port * a2j_port_create (struct a2j * self, int dir, snd_seq_addr_t addr, const snd_seq_port_info_t * info) { struct a2j_port *port; int err; int client; snd_seq_client_info_t * client_info_ptr; int jack_caps; struct a2j_stream * stream_ptr; stream_ptr = &self->stream[dir]; if ((err = snd_seq_client_info_malloc (&client_info_ptr)) != 0) { a2j_error("Failed to allocate client info"); goto fail; } client = snd_seq_port_info_get_client (info); err = snd_seq_get_any_client_info (self->seq, client, client_info_ptr); if (err != 0) { a2j_error("Failed to get client info"); goto fail_free_client_info; } a2j_debug ("client name: '%s'", snd_seq_client_info_get_name(client_info_ptr)); a2j_debug ("port name: '%s'", snd_seq_port_info_get_name(info)); port = calloc (1, sizeof(struct a2j_port)); if (!port) { goto fail_free_client_info; } port->a2j_ptr = self; port->jack_port = JACK_INVALID_PORT; port->remote = addr; a2j_port_fill_name (port, dir, client_info_ptr, info, false); /* Add port to list early, before registering to JACK, so map functionality is guaranteed to work during port registration */ list_add_tail (&port->siblings, &stream_ptr->list); if (dir == A2J_PORT_CAPTURE) { jack_caps = JackPortIsOutput; } else { jack_caps = JackPortIsInput; } /* mark anything that looks like a hardware port as physical&terminal */ if (snd_seq_port_info_get_type (info) & (SND_SEQ_PORT_TYPE_HARDWARE|SND_SEQ_PORT_TYPE_PORT|SND_SEQ_PORT_TYPE_SPECIFIC)) { jack_caps |= JackPortIsPhysical|JackPortIsTerminal; } port->jack_port = jack_port_register (self->jack_client, port->name, JACK_DEFAULT_MIDI_TYPE, jack_caps, 0); if (port->jack_port == JACK_INVALID_PORT) { a2j_error("jack_port_register() failed for '%s'", port->name); goto fail_free_port; } if (dir == A2J_PORT_CAPTURE) { err = a2j_alsa_connect_from (self, port->remote.client, port->remote.port); } else { err = snd_seq_connect_to (self->seq, self->port_id, port->remote.client, port->remote.port); } if (err) { a2j_debug("port skipped: %s", port->name); goto fail_free_port; } port->inbound_events = jack_ringbuffer_create(MAX_EVENT_SIZE*16); a2j_debug("port created: %s", port->name); return port; fail_free_port: list_del (&port->siblings); a2j_port_free (port); fail_free_client_info: snd_seq_client_info_free (client_info_ptr); fail: return NULL; }