website/index.html: Extend Feedback section (codeberg.org)
[a2jmidid.git] / port.c
bloba7c52bf2c6655790a4b27f477d4e7b8ba2ba6d42
1 /* -*- Mode: C ; c-basic-offset: 2 -*- */
2 /*
3 * ALSA SEQ < - > JACK MIDI bridge
5 * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org>
6 * Copyright (c) 2007,2008,2009,2011 Nedko Arnaudov <nedko@arnaudov.name>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; version 2 of the License.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include <stdbool.h>
23 #include <ctype.h>
24 #include <semaphore.h>
25 #include <alsa/asoundlib.h>
26 #include <jack/jack.h>
27 #include <jack/ringbuffer.h>
29 #include "list.h"
30 #include "structs.h"
31 #include "port_hash.h"
32 #include "log.h"
33 #include "port.h"
35 extern bool g_disable_port_uniqueness;
37 /* This should be part of JACK API */
38 #define JACK_IS_VALID_PORT_NAME_CHAR(c) \
39 (isalnum(c) || \
40 (c) == '/' || \
41 (c) == '_' || \
42 (c) == ':' || \
43 (c) == '(' || \
44 (c) == ')' || \
45 (c) == '-' || \
46 (c) == '[' || \
47 (c) == ']')
49 static
50 int
51 a2j_alsa_connect_from(
52 struct a2j * self,
53 int client,
54 int port)
56 snd_seq_port_subscribe_t* sub;
57 snd_seq_addr_t seq_addr;
58 int err;
60 snd_seq_port_subscribe_alloca(&sub);
61 seq_addr.client = client;
62 seq_addr.port = port;
63 snd_seq_port_subscribe_set_sender(sub, &seq_addr);
64 seq_addr.client = self->client_id;
65 seq_addr.port = self->port_id;
66 snd_seq_port_subscribe_set_dest(sub, &seq_addr);
68 snd_seq_port_subscribe_set_time_update(sub, 1);
69 snd_seq_port_subscribe_set_queue(sub, self->queue);
70 snd_seq_port_subscribe_set_time_real(sub, 1);
72 if ((err=snd_seq_subscribe_port(self->seq, sub)))
73 a2j_error("can't subscribe to %d:%d - %s", client, port, snd_strerror(err));
74 return err;
77 void
78 a2j_port_setdead(
79 a2j_port_hash_t hash,
80 snd_seq_addr_t addr)
82 struct a2j_port *port = a2j_port_get(hash, addr);
83 if (port)
85 port->is_dead = true; // see jack_process_internal
87 else
89 a2j_debug("port_setdead: not found (%d:%d)", addr.client, addr.port);
93 void
94 a2j_port_free(
95 struct a2j_port * port)
97 //snd_seq_disconnect_from(self->seq, self->port_id, port->remote.client, port->remote.port);
98 //snd_seq_disconnect_to(self->seq, self->port_id, port->remote.client, port->remote.port);
99 if (port->inbound_events)
100 jack_ringbuffer_free(port->inbound_events);
101 if (port->jack_port != JACK_INVALID_PORT)
102 jack_port_unregister(port->a2j_ptr->jack_client, port->jack_port);
104 free(port);
107 void
108 a2j_port_fill_name(
109 struct a2j_port * port_ptr,
110 int type,
111 snd_seq_client_info_t * client_info_ptr,
112 const snd_seq_port_info_t * port_info_ptr,
113 bool make_unique)
115 char *c;
116 int ret;
118 if (make_unique)
120 ret = snprintf(
121 port_ptr->name,
122 g_max_jack_port_name_size,
123 "%s [%d] (%s): %s",
124 snd_seq_client_info_get_name(client_info_ptr),
125 snd_seq_client_info_get_client(client_info_ptr),
126 type == A2J_PORT_CAPTURE ? "capture": "playback",
127 snd_seq_port_info_get_name(port_info_ptr));
129 else
131 ret = snprintf(
132 port_ptr->name,
133 g_max_jack_port_name_size,
134 "%s (%s): %s",
135 snd_seq_client_info_get_name(client_info_ptr),
136 type == A2J_PORT_CAPTURE ? "capture": "playback",
137 snd_seq_port_info_get_name(port_info_ptr));
140 // replace all offending characters with ' '
141 for (c = port_ptr->name; *c; ++c)
142 if (!JACK_IS_VALID_PORT_NAME_CHAR(*c))
143 *c = ' ';
145 if (ret < 0 || ret >= (int)g_max_jack_port_name_size)
147 /* force terminating nul char because the man page does not specify whether the resulting buffer is nul terminated in this case */
148 port_ptr->name[g_max_jack_port_name_size] = 0;
149 a2j_warning("port name \"%s\" was truncated because of the max size support by JACK (%zu)", port_ptr->name, g_max_jack_port_name_size);
153 struct a2j_port *
154 a2j_port_create(
155 struct a2j * self,
156 int type,
157 snd_seq_addr_t addr,
158 const snd_seq_port_info_t * info)
160 struct a2j_port *port;
161 int err;
162 int client;
163 snd_seq_client_info_t * client_info_ptr;
164 int jack_caps;
165 struct a2j_stream * stream_ptr;
167 stream_ptr = &self->stream[type];
169 err = snd_seq_client_info_malloc(&client_info_ptr);
170 if (err != 0)
172 a2j_error("Failed to allocate client info");
173 goto fail;
176 client = snd_seq_port_info_get_client(info);
178 err = snd_seq_get_any_client_info(self->seq, client, client_info_ptr);
179 if (err != 0)
181 a2j_error("Failed to get client info");
182 goto fail_free_client_info;
185 a2j_debug("client name: '%s'", snd_seq_client_info_get_name(client_info_ptr));
186 a2j_debug("port name: '%s'", snd_seq_port_info_get_name(info));
188 port = calloc(1, sizeof(struct a2j_port) + g_max_jack_port_name_size);
189 if (!port)
191 goto fail_free_client_info;
194 port->a2j_ptr = self;
196 port->jack_port = JACK_INVALID_PORT;
197 port->remote = addr;
199 a2j_port_fill_name(port, type, client_info_ptr, info, !g_disable_port_uniqueness);
201 /* Add port to list early, before registering to JACK, so map functionality is guaranteed to work during port registration */
202 list_add_tail(&port->siblings, &stream_ptr->list);
204 if (type == A2J_PORT_CAPTURE)
206 jack_caps = JackPortIsOutput;
208 else
210 jack_caps = JackPortIsInput;
213 /* mark anything that looks like a hardware port as physical&terminal */
214 if (snd_seq_port_info_get_type(info) & (SND_SEQ_PORT_TYPE_HARDWARE|SND_SEQ_PORT_TYPE_PORT|SND_SEQ_PORT_TYPE_SPECIFIC))
216 jack_caps |= JackPortIsPhysical|JackPortIsTerminal;
219 port->jack_port = jack_port_register(self->jack_client, port->name, JACK_DEFAULT_MIDI_TYPE, jack_caps, 0);
220 if (port->jack_port == JACK_INVALID_PORT)
222 a2j_error("jack_port_register() failed for '%s'", port->name);
223 goto fail_free_port;
226 if (type == A2J_PORT_CAPTURE)
228 err = a2j_alsa_connect_from(self, port->remote.client, port->remote.port);
230 else
232 err = snd_seq_connect_to(self->seq, self->port_id, port->remote.client, port->remote.port);
233 if (err != 0)
235 a2j_error("snd_seq_connect_to() for %d:%d failed with error %d", (int)port->remote.client, (int)port->remote.port, err);
239 if (err)
241 a2j_info("port skipped: %s", port->name);
242 goto fail_free_port;
245 port->inbound_events = jack_ringbuffer_create(MAX_EVENT_SIZE*16);
247 a2j_info("port created: %s", port->name);
248 snd_seq_client_info_free(client_info_ptr);
249 return port;
251 fail_free_port:
252 list_del(&port->siblings);
254 a2j_port_free(port);
256 fail_free_client_info:
257 snd_seq_client_info_free(client_info_ptr);
259 fail:
260 return NULL;