| @@ -1,3 +1,5 @@ | |||||
| *.lo | |||||
| *.la | |||||
| .deps/ | .deps/ | ||||
| .libs/ | .libs/ | ||||
| Makefile | Makefile | ||||
| @@ -10,6 +12,7 @@ freewheel.o | |||||
| iodelay.o | iodelay.o | ||||
| ipload.o | ipload.o | ||||
| ipunload.o | ipunload.o | ||||
| property.o | |||||
| jack_alias | jack_alias | ||||
| jack_bufsize | jack_bufsize | ||||
| jack_connect | jack_connect | ||||
| @@ -25,6 +28,7 @@ jack_monitor_client | |||||
| jack_netsource | jack_netsource | ||||
| jack_netsource-netjack_packet.o | jack_netsource-netjack_packet.o | ||||
| jack_netsource-netsource.o | jack_netsource-netsource.o | ||||
| jack_property | |||||
| jack_samplerate | jack_samplerate | ||||
| jack_session_notify | jack_session_notify | ||||
| jack_thread_wait | jack_thread_wait | ||||
| @@ -45,4 +49,4 @@ alsa_out | |||||
| alsa_in-alsa_in.o | alsa_in-alsa_in.o | ||||
| alsa_out-alsa_out.o | alsa_out-alsa_out.o | ||||
| alsa_in-memops.o | alsa_in-memops.o | ||||
| alsa-out-memops.o | |||||
| alsa_out-memops.o | |||||
| @@ -1,5 +1,11 @@ | |||||
| MAINTAINERCLEANFILES = Makefile.in | MAINTAINERCLEANFILES = Makefile.in | ||||
| if HAVE_ALSA_MIDI | |||||
| ALSA_MIDI_DIR = alsa_midi | |||||
| else | |||||
| ALSA_MIDI_DIR = | |||||
| endif | |||||
| if HAVE_READLINE | if HAVE_READLINE | ||||
| JACK_TRANSPORT = jack_transport | JACK_TRANSPORT = jack_transport | ||||
| dist-check-readline: | dist-check-readline: | ||||
| @@ -43,6 +49,7 @@ bin_PROGRAMS = jack_load \ | |||||
| jack_midi_dump \ | jack_midi_dump \ | ||||
| jack_iodelay \ | jack_iodelay \ | ||||
| jack_load_test \ | jack_load_test \ | ||||
| jack_property \ | |||||
| $(JACK_TRANSPORT) \ | $(JACK_TRANSPORT) \ | ||||
| $(NETJACK_TOOLS) | $(NETJACK_TOOLS) | ||||
| @@ -56,6 +63,10 @@ endif | |||||
| AM_CFLAGS = -I.. $(JACK_CFLAGS) $(sndfile_cflags) | AM_CFLAGS = -I.. $(JACK_CFLAGS) $(sndfile_cflags) | ||||
| AM_CXXFLAGS = -I.. $(JACK_CFLAGS) $(sndfile_cflags) | AM_CXXFLAGS = -I.. $(JACK_CFLAGS) $(sndfile_cflags) | ||||
| jack_property_SOURCES = property.c | |||||
| jack_property_LDFLAGS = @OS_LDFLAGS@ | |||||
| jack_property_LDADD = $(top_builddir)/libjack/libjack.la | |||||
| jack_connect_SOURCES = connect.c | jack_connect_SOURCES = connect.c | ||||
| jack_connect_LDFLAGS = @OS_LDFLAGS@ | jack_connect_LDFLAGS = @OS_LDFLAGS@ | ||||
| jack_connect_LDADD = $(top_builddir)/libjack/libjack.la | jack_connect_LDADD = $(top_builddir)/libjack/libjack.la | ||||
| @@ -155,4 +166,7 @@ alsa_out_LDADD = $(top_builddir)/libjack/libjack.la | |||||
| endif #HAVE_ALSA | endif #HAVE_ALSA | ||||
| endif #HAVE_SAMPLERATE | endif #HAVE_SAMPLERATE | ||||
| SUBDIRS = $(ALSA_MIDI_DIR) | |||||
| DIST_SUBDIRS = alsa_midi | |||||
| # XXX ? dist-hook: dist-check-sndfile dist-check-samplerate | # XXX ? dist-hook: dist-check-sndfile dist-check-samplerate | ||||
| @@ -0,0 +1,13 @@ | |||||
| MAINTAINERCLEANFILES = Makefile.in | |||||
| # | |||||
| # in-process ALSA/JACK MIDI bridge client | |||||
| # | |||||
| alsa_mididir = $(ADDON_DIR) | |||||
| alsa_midi_LTLIBRARIES = alsa_midi.la | |||||
| alsa_midi_la_LDFLAGS = -module -avoid-version @OS_LDFLAGS@ | |||||
| alsa_midi_la_SOURCES = alsa_midi.c port.c port_hash.c port_thread.c list.c | |||||
| noinst_HEADERS = a2j.h list.h port.h port_hash.h port_thread.h | |||||
| @@ -0,0 +1,132 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * | |||||
| * 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 | |||||
| */ | |||||
| #ifndef __jack_alsa_midi_h__ | |||||
| #define __jack_alsa_midi_h__ | |||||
| #include <stdbool.h> | |||||
| #include <semaphore.h> | |||||
| #include <jack/midiport.h> | |||||
| #define JACK_INVALID_PORT NULL | |||||
| #define MAX_PORTS 2048 | |||||
| #define MAX_EVENT_SIZE 1024 | |||||
| #define PORT_HASH_BITS 4 | |||||
| #define PORT_HASH_SIZE (1 << PORT_HASH_BITS) | |||||
| /* Beside enum use, these are indeces for (struct a2j).stream array */ | |||||
| #define A2J_PORT_CAPTURE 0 // ALSA playback port -> JACK capture port | |||||
| #define A2J_PORT_PLAYBACK 1 // JACK playback port -> ALSA capture port | |||||
| typedef struct a2j_port * a2j_port_hash_t[PORT_HASH_SIZE]; | |||||
| struct a2j; | |||||
| struct a2j_port | |||||
| { | |||||
| struct a2j_port * next; /* hash - jack */ | |||||
| struct list_head siblings; /* list - main loop */ | |||||
| struct a2j * a2j_ptr; | |||||
| bool is_dead; | |||||
| char name[64]; | |||||
| snd_seq_addr_t remote; | |||||
| jack_port_t * jack_port; | |||||
| jack_ringbuffer_t * inbound_events; // alsa_midi_event_t + data | |||||
| int64_t last_out_time; | |||||
| void * jack_buf; | |||||
| }; | |||||
| struct a2j_stream | |||||
| { | |||||
| snd_midi_event_t *codec; | |||||
| jack_ringbuffer_t *new_ports; | |||||
| a2j_port_hash_t port_hash; | |||||
| struct list_head list; | |||||
| }; | |||||
| struct a2j | |||||
| { | |||||
| jack_client_t * jack_client; | |||||
| snd_seq_t *seq; | |||||
| pthread_t alsa_input_thread; | |||||
| pthread_t alsa_output_thread; | |||||
| int client_id; | |||||
| int port_id; | |||||
| int queue; | |||||
| bool freewheeling; | |||||
| bool running; | |||||
| bool finishing; | |||||
| jack_ringbuffer_t* port_add; // snd_seq_addr_t | |||||
| jack_ringbuffer_t* port_del; // struct a2j_port* | |||||
| jack_ringbuffer_t* outbound_events; // struct a2j_delivery_event | |||||
| jack_nframes_t cycle_start; | |||||
| sem_t output_semaphore; | |||||
| struct a2j_stream stream[2]; | |||||
| }; | |||||
| #define NSEC_PER_SEC ((int64_t)1000*1000*1000) | |||||
| struct a2j_alsa_midi_event | |||||
| { | |||||
| int64_t time; | |||||
| int size; | |||||
| }; | |||||
| #define MAX_JACKMIDI_EV_SIZE 16 | |||||
| struct a2j_delivery_event | |||||
| { | |||||
| struct list_head siblings; | |||||
| /* a jack MIDI event, plus the port its destined for: everything | |||||
| the ALSA output thread needs to deliver the event. time is | |||||
| part of the jack_event. | |||||
| */ | |||||
| jack_midi_event_t jack_event; | |||||
| jack_nframes_t time; /* realtime, not offset time */ | |||||
| struct a2j_port* port; | |||||
| char midistring[MAX_JACKMIDI_EV_SIZE]; | |||||
| }; | |||||
| void a2j_error (const char* fmt, ...); | |||||
| #define A2J_DEBUG | |||||
| /*#undef A2J_DEBUG*/ | |||||
| #ifdef A2J_DEBUG | |||||
| extern bool a2j_do_debug; | |||||
| extern void _a2j_debug (const char* fmt, ...); | |||||
| #define a2j_debug(fmt, ...) if (a2j_do_debug) { _a2j_debug ((fmt), ##__VA_ARGS__); } | |||||
| #else | |||||
| #define a2j_debug(fmt,...) | |||||
| #endif | |||||
| #endif /* __jack_alsa_midi_h__ */ | |||||
| @@ -0,0 +1,41 @@ | |||||
| # a2j_in.la - a libtool library file | |||||
| # Generated by ltmain.sh (GNU libtool) 2.2.6b | |||||
| # | |||||
| # Please DO NOT delete this file! | |||||
| # It is necessary for linking the library. | |||||
| # The name that we can dlopen(3). | |||||
| dlname='a2j_in.so' | |||||
| # Names of this library. | |||||
| library_names='a2j_in.so a2j_in.so a2j_in.so' | |||||
| # The name of the static archive. | |||||
| old_library='' | |||||
| # Linker flags that can not go in dependency_libs. | |||||
| inherited_linker_flags='' | |||||
| # Libraries that this one depends upon. | |||||
| dependency_libs=' -lrt -lm -lpthread -ldl -luuid' | |||||
| # Names of additional weak libraries provided by this library | |||||
| weak_library_names='' | |||||
| # Version information for a2j_in. | |||||
| current=0 | |||||
| age=0 | |||||
| revision=0 | |||||
| # Is this an already installed library? | |||||
| installed=no | |||||
| # Should we warn about portability when linking against -modules? | |||||
| shouldnotlink=yes | |||||
| # Files to dlopen/dlpreopen | |||||
| dlopen='' | |||||
| dlpreopen='' | |||||
| # Directory that this library needs to be installed in: | |||||
| libdir='/usr/lib64/jack' | |||||
| @@ -0,0 +1,860 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * Copyright (c) 2009,2010,2013 Paul Davis <paul@linuxaudiosystems.com> | |||||
| * | |||||
| * 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 <stdbool.h> | |||||
| #include <stdarg.h> | |||||
| #include <string.h> | |||||
| #include <time.h> | |||||
| #include <alsa/asoundlib.h> | |||||
| #include <jack/jack.h> | |||||
| #include <jack/midiport.h> | |||||
| #include <jack/ringbuffer.h> | |||||
| #include "list.h" | |||||
| #include "a2j.h" | |||||
| #include "port_hash.h" | |||||
| #include "port.h" | |||||
| #include "port_thread.h" | |||||
| #ifdef A2J_DEBUG | |||||
| bool a2j_do_debug = false; | |||||
| void | |||||
| _a2j_debug (const char* fmt, ...) | |||||
| { | |||||
| va_list ap; | |||||
| va_start (ap, fmt); | |||||
| vfprintf (stderr, fmt, ap); | |||||
| fputc ('\n', stdout); | |||||
| } | |||||
| #endif | |||||
| void | |||||
| a2j_error (const char* fmt, ...) | |||||
| { | |||||
| va_list ap; | |||||
| va_start (ap, fmt); | |||||
| vfprintf (stdout, fmt, ap); | |||||
| fputc ('\n', stdout); | |||||
| } | |||||
| static bool | |||||
| a2j_stream_init(struct a2j * self, int which) | |||||
| { | |||||
| struct a2j_stream *str = &self->stream[which]; | |||||
| str->new_ports = jack_ringbuffer_create (MAX_PORTS * sizeof(struct a2j_port *)); | |||||
| if (str->new_ports == NULL) { | |||||
| return false; | |||||
| } | |||||
| snd_midi_event_new (MAX_EVENT_SIZE, &str->codec); | |||||
| INIT_LIST_HEAD (&str->list); | |||||
| return true; | |||||
| } | |||||
| static void | |||||
| a2j_stream_detach (struct a2j_stream * stream_ptr) | |||||
| { | |||||
| struct a2j_port * port_ptr; | |||||
| struct list_head * node_ptr; | |||||
| while (!list_empty (&stream_ptr->list)) { | |||||
| node_ptr = stream_ptr->list.next; | |||||
| list_del (node_ptr); | |||||
| port_ptr = list_entry (node_ptr, struct a2j_port, siblings); | |||||
| a2j_debug ("port deleted: %s", port_ptr->name); | |||||
| a2j_port_free (port_ptr); | |||||
| } | |||||
| } | |||||
| static | |||||
| void | |||||
| a2j_stream_close (struct a2j * self, int which) | |||||
| { | |||||
| struct a2j_stream *str = &self->stream[which]; | |||||
| if (str->codec) | |||||
| snd_midi_event_free (str->codec); | |||||
| if (str->new_ports) | |||||
| jack_ringbuffer_free (str->new_ports); | |||||
| } | |||||
| static void | |||||
| stop_threads (struct a2j* self) | |||||
| { | |||||
| if (self->running) { | |||||
| void* thread_status; | |||||
| self->running = false; /* tell alsa io thread to stop, whenever they wake up */ | |||||
| /* do something that we need to do anyway and will wake the io thread, then join */ | |||||
| snd_seq_disconnect_from (self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); | |||||
| a2j_debug ("wait for ALSA input thread\n"); | |||||
| pthread_join (self->alsa_input_thread, &thread_status); | |||||
| a2j_debug ("input thread done\n"); | |||||
| /* wake output thread and join */ | |||||
| sem_post(&self->output_semaphore); | |||||
| pthread_join(self->alsa_output_thread, &thread_status); | |||||
| a2j_debug ("output thread done\n"); | |||||
| } | |||||
| } | |||||
| /* | |||||
| * =================== Input/output port handling ========================= | |||||
| */ | |||||
| void a2j_add_ports (struct a2j_stream * str) | |||||
| { | |||||
| struct a2j_port * port_ptr; | |||||
| while (jack_ringbuffer_read (str->new_ports, (char *)&port_ptr, sizeof(port_ptr))) { | |||||
| a2j_debug("jack: inserted port %s", port_ptr->name); | |||||
| a2j_port_insert (str->port_hash, port_ptr); | |||||
| } | |||||
| } | |||||
| static | |||||
| void | |||||
| a2j_port_event (struct a2j * self, snd_seq_event_t * ev) | |||||
| { | |||||
| const snd_seq_addr_t addr = ev->data.addr; | |||||
| if (addr.client == self->client_id) | |||||
| return; | |||||
| if (ev->type == SND_SEQ_EVENT_PORT_START || ev->type == SND_SEQ_EVENT_PORT_CHANGE) { | |||||
| if (jack_ringbuffer_write_space(self->port_add) >= sizeof(addr)) { | |||||
| a2j_debug("port_event: add/change %d:%d", addr.client, addr.port); | |||||
| jack_ringbuffer_write(self->port_add, (char*)&addr, sizeof(addr)); | |||||
| } else { | |||||
| a2j_error("dropping port_event: add/change %d:%d", addr.client, addr.port); | |||||
| } | |||||
| } else if (ev->type == SND_SEQ_EVENT_PORT_EXIT) { | |||||
| a2j_debug("port_event: del %d:%d", addr.client, addr.port); | |||||
| a2j_port_setdead(self->stream[A2J_PORT_CAPTURE].port_hash, addr); | |||||
| a2j_port_setdead(self->stream[A2J_PORT_PLAYBACK].port_hash, addr); | |||||
| } | |||||
| } | |||||
| /* --- INBOUND FROM ALSA TO JACK ---- */ | |||||
| static void | |||||
| a2j_input_event (struct a2j * self, snd_seq_event_t * alsa_event) | |||||
| { | |||||
| jack_midi_data_t data[MAX_EVENT_SIZE]; | |||||
| struct a2j_stream *str = &self->stream[A2J_PORT_CAPTURE]; | |||||
| long size; | |||||
| struct a2j_port *port; | |||||
| jack_nframes_t now; | |||||
| now = jack_frame_time (self->jack_client); | |||||
| if ((port = a2j_port_get(str->port_hash, alsa_event->source)) == NULL) { | |||||
| return; | |||||
| } | |||||
| /* | |||||
| * RPNs, NRPNs, Bank Change, etc. need special handling | |||||
| * but seems, ALSA does it for us already. | |||||
| */ | |||||
| snd_midi_event_reset_decode(str->codec); | |||||
| if ((size = snd_midi_event_decode(str->codec, data, sizeof(data), alsa_event))<0) { | |||||
| return; | |||||
| } | |||||
| // fixup NoteOn with vel 0 | |||||
| if ((data[0] & 0xF0) == 0x90 && data[2] == 0x00) { | |||||
| data[0] = 0x80 + (data[0] & 0x0F); | |||||
| data[2] = 0x40; | |||||
| } | |||||
| a2j_debug("input: %d bytes at event_frame=%u", (int)size, now); | |||||
| if (jack_ringbuffer_write_space(port->inbound_events) >= (sizeof(struct a2j_alsa_midi_event) + size)) { | |||||
| struct a2j_alsa_midi_event ev; | |||||
| char *ev_charp = (char*) &ev; | |||||
| size_t limit; | |||||
| size_t to_write = sizeof(ev); | |||||
| jack_ringbuffer_data_t vec[2]; | |||||
| jack_ringbuffer_get_write_vector( port->inbound_events, vec ); | |||||
| ev.time = now; | |||||
| ev.size = size; | |||||
| limit = (to_write > vec[0].len ? vec[0].len : to_write); | |||||
| if (limit) { | |||||
| memcpy( vec[0].buf, ev_charp, limit ); | |||||
| to_write -= limit; | |||||
| ev_charp += limit; | |||||
| vec[0].buf += limit; | |||||
| vec[0].len -= limit; | |||||
| } | |||||
| if (to_write) { | |||||
| memcpy( vec[1].buf, ev_charp, to_write ); | |||||
| vec[1].buf += to_write; | |||||
| vec[1].len -= to_write; | |||||
| } | |||||
| to_write = size; | |||||
| ev_charp = (char *)data; | |||||
| limit = (to_write > vec[0].len ? vec[0].len : to_write); | |||||
| if (limit) { | |||||
| memcpy (vec[0].buf, ev_charp, limit); | |||||
| } | |||||
| to_write -= limit; | |||||
| ev_charp += limit; | |||||
| if (to_write) { | |||||
| memcpy (vec[1].buf, ev_charp, to_write); | |||||
| } | |||||
| jack_ringbuffer_write_advance( port->inbound_events, sizeof(ev) + size ); | |||||
| } else { | |||||
| a2j_error ("MIDI data lost (incoming event buffer full): %ld bytes lost", size); | |||||
| } | |||||
| } | |||||
| static int | |||||
| a2j_process_incoming (struct a2j* self, struct a2j_port* port, jack_nframes_t nframes) | |||||
| { | |||||
| jack_nframes_t one_period; | |||||
| struct a2j_alsa_midi_event ev; | |||||
| char *ev_buf; | |||||
| /* grab data queued by the ALSA input thread and write it into the JACK | |||||
| port buffer. it will delivered during the JACK period that this | |||||
| function is called from. | |||||
| */ | |||||
| /* first clear the JACK port buffer in preparation for new data | |||||
| */ | |||||
| a2j_debug ("PORT: %s process input", jack_port_name (port->jack_port)); | |||||
| jack_midi_clear_buffer (port->jack_buf); | |||||
| one_period = jack_get_buffer_size (self->jack_client); | |||||
| while (jack_ringbuffer_peek (port->inbound_events, (char*)&ev, sizeof(ev) ) == sizeof(ev) ) { | |||||
| jack_midi_data_t* buf; | |||||
| jack_nframes_t offset; | |||||
| if (ev.time >= self->cycle_start) { | |||||
| break; | |||||
| } | |||||
| //jack_ringbuffer_read_advance (port->inbound_events, sizeof (ev)); | |||||
| ev_buf = (char *) alloca( sizeof(ev) + ev.size ); | |||||
| if (jack_ringbuffer_peek (port->inbound_events, ev_buf, sizeof(ev) + ev.size ) != sizeof(ev) + ev.size) | |||||
| break; | |||||
| offset = self->cycle_start - ev.time; | |||||
| if (offset > one_period) { | |||||
| /* from a previous cycle, somehow. cram it in at the front */ | |||||
| offset = 0; | |||||
| } else { | |||||
| /* offset from start of the current cycle */ | |||||
| offset = one_period - offset; | |||||
| } | |||||
| a2j_debug ("event at %d offset %d", ev.time, offset); | |||||
| /* make sure there is space for it */ | |||||
| buf = jack_midi_event_reserve (port->jack_buf, offset, ev.size); | |||||
| if (buf) { | |||||
| /* grab the event */ | |||||
| memcpy( buf, ev_buf + sizeof(ev), ev.size ); | |||||
| } else { | |||||
| /* throw it away (no space) */ | |||||
| a2j_error ("threw away MIDI event - not reserved at time %d", ev.time); | |||||
| } | |||||
| jack_ringbuffer_read_advance (port->inbound_events, sizeof(ev) + ev.size); | |||||
| a2j_debug("input on %s: sucked %d bytes from inbound at %d", jack_port_name (port->jack_port), ev.size, ev.time); | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| void* | |||||
| alsa_input_thread (void* arg) | |||||
| { | |||||
| struct a2j * self = arg; | |||||
| int npfd; | |||||
| struct pollfd * pfd; | |||||
| snd_seq_addr_t addr; | |||||
| snd_seq_client_info_t * client_info; | |||||
| snd_seq_port_info_t * port_info; | |||||
| bool initial; | |||||
| snd_seq_event_t * event; | |||||
| int ret; | |||||
| npfd = snd_seq_poll_descriptors_count(self->seq, POLLIN); | |||||
| pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); | |||||
| snd_seq_poll_descriptors(self->seq, pfd, npfd, POLLIN); | |||||
| initial = true; | |||||
| while (self->running) { | |||||
| if ((ret = poll(pfd, npfd, 1000)) > 0) { | |||||
| while (snd_seq_event_input (self->seq, &event) > 0) { | |||||
| if (initial) { | |||||
| snd_seq_client_info_alloca(&client_info); | |||||
| snd_seq_port_info_alloca(&port_info); | |||||
| snd_seq_client_info_set_client(client_info, -1); | |||||
| while (snd_seq_query_next_client(self->seq, client_info) >= 0) { | |||||
| addr.client = snd_seq_client_info_get_client(client_info); | |||||
| if (addr.client == SND_SEQ_CLIENT_SYSTEM || addr.client == self->client_id) { | |||||
| continue; | |||||
| } | |||||
| snd_seq_port_info_set_client(port_info, addr.client); | |||||
| snd_seq_port_info_set_port(port_info, -1); | |||||
| while (snd_seq_query_next_port(self->seq, port_info) >= 0) { | |||||
| addr.port = snd_seq_port_info_get_port(port_info); | |||||
| a2j_update_port(self, addr, port_info); | |||||
| } | |||||
| } | |||||
| initial = false; | |||||
| } | |||||
| if (event->source.client == SND_SEQ_CLIENT_SYSTEM) { | |||||
| a2j_port_event(self, event); | |||||
| } else { | |||||
| a2j_input_event(self, event); | |||||
| } | |||||
| snd_seq_free_event (event); | |||||
| } | |||||
| } | |||||
| } | |||||
| return (void*) 0; | |||||
| } | |||||
| /* --- OUTBOUND FROM JACK TO ALSA ---- */ | |||||
| int | |||||
| a2j_process_outgoing ( | |||||
| struct a2j * self, | |||||
| struct a2j_port * port) | |||||
| { | |||||
| /* collect data from JACK port buffer and queue it for delivery by ALSA output thread */ | |||||
| int nevents; | |||||
| jack_ringbuffer_data_t vec[2]; | |||||
| int i; | |||||
| int written = 0; | |||||
| size_t limit; | |||||
| struct a2j_delivery_event* dev; | |||||
| size_t gap = 0; | |||||
| jack_ringbuffer_get_write_vector (self->outbound_events, vec); | |||||
| dev = (struct a2j_delivery_event*) vec[0].buf; | |||||
| limit = vec[0].len / sizeof (struct a2j_delivery_event); | |||||
| nevents = jack_midi_get_event_count (port->jack_buf); | |||||
| for (i = 0; (i < nevents) && (written < limit); ++i) { | |||||
| jack_midi_event_get (&dev->jack_event, port->jack_buf, i); | |||||
| if (dev->jack_event.size <= MAX_JACKMIDI_EV_SIZE) | |||||
| { | |||||
| dev->time = dev->jack_event.time; | |||||
| dev->port = port; | |||||
| memcpy( dev->midistring, dev->jack_event.buffer, dev->jack_event.size ); | |||||
| written++; | |||||
| ++dev; | |||||
| } | |||||
| } | |||||
| /* anything left? use the second part of the vector, as much as possible */ | |||||
| if (i < nevents) | |||||
| { | |||||
| if (vec[0].len) | |||||
| { | |||||
| gap = vec[0].len - written * sizeof(struct a2j_delivery_event); | |||||
| } | |||||
| dev = (struct a2j_delivery_event*) vec[1].buf; | |||||
| limit += (vec[1].len / sizeof (struct a2j_delivery_event)); | |||||
| while ((i < nevents) && (written < limit)) | |||||
| { | |||||
| jack_midi_event_get(&dev->jack_event, port->jack_buf, i); | |||||
| if (dev->jack_event.size <= MAX_JACKMIDI_EV_SIZE) | |||||
| { | |||||
| dev->time = dev->jack_event.time; | |||||
| dev->port = port; | |||||
| memcpy(dev->midistring, dev->jack_event.buffer, dev->jack_event.size); | |||||
| written++; | |||||
| ++dev; | |||||
| } | |||||
| ++i; | |||||
| } | |||||
| } | |||||
| a2j_debug( "done pushing events: %d ... gap: %d ", (int)written, (int)gap ); | |||||
| /* clear JACK port buffer; advance ring buffer ptr */ | |||||
| jack_ringbuffer_write_advance (self->outbound_events, written * sizeof (struct a2j_delivery_event) + gap); | |||||
| return nevents; | |||||
| } | |||||
| static int | |||||
| time_sorter (struct a2j_delivery_event * a, struct a2j_delivery_event * b) | |||||
| { | |||||
| if (a->time < b->time) { | |||||
| return -1; | |||||
| } else if (a->time > b->time) { | |||||
| return 1; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| static void* | |||||
| alsa_output_thread(void * arg) | |||||
| { | |||||
| struct a2j * self = (struct a2j*) arg; | |||||
| struct a2j_stream *str = &self->stream[A2J_PORT_PLAYBACK]; | |||||
| int i; | |||||
| struct list_head evlist; | |||||
| struct list_head * node_ptr; | |||||
| jack_ringbuffer_data_t vec[2]; | |||||
| snd_seq_event_t alsa_event; | |||||
| struct a2j_delivery_event* ev; | |||||
| float sr; | |||||
| jack_nframes_t now; | |||||
| int err; | |||||
| int limit; | |||||
| while (self->running) { | |||||
| /* first, make a list of all events in the outbound_events FIFO */ | |||||
| INIT_LIST_HEAD(&evlist); | |||||
| jack_ringbuffer_get_read_vector (self->outbound_events, vec); | |||||
| a2j_debug ("output thread: got %d+%d events", | |||||
| (vec[0].len / sizeof (struct a2j_delivery_event)), | |||||
| (vec[1].len / sizeof (struct a2j_delivery_event))); | |||||
| ev = (struct a2j_delivery_event*) vec[0].buf; | |||||
| limit = vec[0].len / sizeof (struct a2j_delivery_event); | |||||
| for (i = 0; i < limit; ++i) { | |||||
| list_add_tail(&ev->siblings, &evlist); | |||||
| ev++; | |||||
| } | |||||
| ev = (struct a2j_delivery_event*) vec[1].buf; | |||||
| limit = vec[1].len / sizeof (struct a2j_delivery_event); | |||||
| for (i = 0; i < limit; ++i) { | |||||
| list_add_tail(&ev->siblings, &evlist); | |||||
| ev++; | |||||
| } | |||||
| if (vec[0].len < sizeof(struct a2j_delivery_event) && (vec[1].len == 0)) { | |||||
| /* no events: wait for some */ | |||||
| a2j_debug ("output thread: wait for events"); | |||||
| sem_wait (&self->output_semaphore); | |||||
| a2j_debug ("output thread: AWAKE ... loop back for events"); | |||||
| continue; | |||||
| } | |||||
| /* now sort this list by time */ | |||||
| list_sort(&evlist, struct a2j_delivery_event, siblings, time_sorter); | |||||
| /* now deliver */ | |||||
| sr = jack_get_sample_rate (self->jack_client); | |||||
| list_for_each(node_ptr, &evlist) | |||||
| { | |||||
| ev = list_entry(node_ptr, struct a2j_delivery_event, siblings); | |||||
| snd_seq_ev_clear(&alsa_event); | |||||
| snd_midi_event_reset_encode(str->codec); | |||||
| if (!snd_midi_event_encode(str->codec, (const unsigned char *)ev->midistring, ev->jack_event.size, &alsa_event)) | |||||
| { | |||||
| continue; // invalid event | |||||
| } | |||||
| snd_seq_ev_set_source(&alsa_event, self->port_id); | |||||
| snd_seq_ev_set_dest(&alsa_event, ev->port->remote.client, ev->port->remote.port); | |||||
| snd_seq_ev_set_direct (&alsa_event); | |||||
| now = jack_frame_time (self->jack_client); | |||||
| ev->time += self->cycle_start; | |||||
| a2j_debug ("@ %d, next event @ %d", now, ev->time); | |||||
| /* do we need to wait a while before delivering? */ | |||||
| if (ev->time > now) { | |||||
| struct timespec nanoseconds; | |||||
| jack_nframes_t sleep_frames = ev->time - now; | |||||
| float seconds = sleep_frames / sr; | |||||
| /* if the gap is long enough, sleep */ | |||||
| if (seconds > 0.001) { | |||||
| nanoseconds.tv_sec = (time_t) seconds; | |||||
| nanoseconds.tv_nsec = (long) NSEC_PER_SEC * (seconds - nanoseconds.tv_sec); | |||||
| a2j_debug ("output thread sleeps for %.2f msec", ((double) nanoseconds.tv_nsec / NSEC_PER_SEC) * 1000.0); | |||||
| if (nanosleep (&nanoseconds, NULL) < 0) { | |||||
| fprintf (stderr, "BAD SLEEP\n"); | |||||
| /* do something ? */ | |||||
| } | |||||
| } | |||||
| } | |||||
| /* its time to deliver */ | |||||
| err = snd_seq_event_output(self->seq, &alsa_event); | |||||
| snd_seq_drain_output (self->seq); | |||||
| now = jack_frame_time (self->jack_client); | |||||
| a2j_debug("alsa_out: written %d bytes to %s at %d, DELTA = %d", ev->jack_event.size, ev->port->name, now, | |||||
| (int32_t) (now - ev->time)); | |||||
| } | |||||
| /* free up space in the FIFO */ | |||||
| jack_ringbuffer_read_advance (self->outbound_events, vec[0].len + vec[1].len); | |||||
| /* and head back for more */ | |||||
| } | |||||
| return (void*) 0; | |||||
| } | |||||
| /** CORE JACK PROCESSING */ | |||||
| /* ALSA */ | |||||
| static void | |||||
| a2j_jack_process_internal (struct a2j * self, int dir, jack_nframes_t nframes) | |||||
| { | |||||
| struct a2j_stream * stream_ptr; | |||||
| int i; | |||||
| struct a2j_port ** port_ptr_ptr; | |||||
| struct a2j_port * port_ptr; | |||||
| int nevents = 0; | |||||
| stream_ptr = &self->stream[dir]; | |||||
| a2j_add_ports(stream_ptr); | |||||
| // process ports | |||||
| for (i = 0 ; i < PORT_HASH_SIZE ; i++) | |||||
| { | |||||
| port_ptr_ptr = &stream_ptr->port_hash[i]; | |||||
| while (*port_ptr_ptr != NULL) | |||||
| { | |||||
| port_ptr = *port_ptr_ptr; | |||||
| if (!port_ptr->is_dead) { | |||||
| port_ptr->jack_buf = jack_port_get_buffer(port_ptr->jack_port, nframes); | |||||
| if (dir == A2J_PORT_CAPTURE) { | |||||
| a2j_process_incoming (self, port_ptr, nframes); | |||||
| } else { | |||||
| nevents += a2j_process_outgoing (self, port_ptr); | |||||
| } | |||||
| } else if (jack_ringbuffer_write_space (self->port_del) >= sizeof(port_ptr)) { | |||||
| a2j_debug("jack: removed port %s", port_ptr->name); | |||||
| *port_ptr_ptr = port_ptr->next; | |||||
| jack_ringbuffer_write(self->port_del, (char*)&port_ptr, sizeof(port_ptr)); | |||||
| continue; | |||||
| } | |||||
| port_ptr_ptr = &port_ptr->next; | |||||
| } | |||||
| } | |||||
| if (dir == A2J_PORT_PLAYBACK && nevents > 0) { | |||||
| int sv; | |||||
| /* if we queued up anything for output, tell the output thread in | |||||
| case its waiting for us. | |||||
| */ | |||||
| sem_getvalue (&self->output_semaphore, &sv); | |||||
| sem_post (&self->output_semaphore); | |||||
| } | |||||
| } | |||||
| static int | |||||
| a2j_process(jack_nframes_t nframes, void * arg) | |||||
| { | |||||
| struct a2j* self = (struct a2j *) arg; | |||||
| if (self->freewheeling) { | |||||
| return 0; | |||||
| } | |||||
| self->cycle_start = jack_last_frame_time (self->jack_client); | |||||
| a2j_jack_process_internal (self, A2J_PORT_CAPTURE, nframes); | |||||
| a2j_jack_process_internal (self, A2J_PORT_PLAYBACK, nframes); | |||||
| return 0; | |||||
| } | |||||
| /* --- */ | |||||
| static | |||||
| void | |||||
| a2j_freewheel(int starting, void * arg) | |||||
| { | |||||
| struct a2j* self = (struct a2j*) arg; | |||||
| self->freewheeling = starting; | |||||
| } | |||||
| static | |||||
| void | |||||
| a2j_shutdown (void * arg) | |||||
| { | |||||
| struct a2j* self = (struct a2j*) self; | |||||
| a2j_debug ("JACK server shutdown notification received."); | |||||
| stop_threads (self); | |||||
| } | |||||
| int | |||||
| connect_to_alsa (struct a2j* self) | |||||
| { | |||||
| int error; | |||||
| void* thread_status; | |||||
| self->port_add = jack_ringbuffer_create (2 * MAX_PORTS * sizeof(snd_seq_addr_t)); | |||||
| if (self->port_add == NULL) { | |||||
| goto free_self; | |||||
| } | |||||
| self->port_del = jack_ringbuffer_create(2 * MAX_PORTS * sizeof(struct a2j_port *)); | |||||
| if (self->port_del == NULL) { | |||||
| goto free_ringbuffer_add; | |||||
| } | |||||
| self->outbound_events = jack_ringbuffer_create (MAX_EVENT_SIZE * 16 * sizeof(struct a2j_delivery_event)); | |||||
| if (self->outbound_events == NULL) { | |||||
| goto free_ringbuffer_del; | |||||
| } | |||||
| if (!a2j_stream_init (self, A2J_PORT_CAPTURE)) { | |||||
| goto free_ringbuffer_outbound; | |||||
| } | |||||
| if (!a2j_stream_init (self, A2J_PORT_PLAYBACK)) { | |||||
| goto close_capture_stream; | |||||
| } | |||||
| if ((error = snd_seq_open(&self->seq, "hw", SND_SEQ_OPEN_DUPLEX, 0)) < 0) { | |||||
| a2j_error("failed to open alsa seq"); | |||||
| goto close_playback_stream; | |||||
| } | |||||
| if ((error = snd_seq_set_client_name(self->seq, "jackmidi")) < 0) { | |||||
| a2j_error("snd_seq_set_client_name() failed"); | |||||
| goto close_seq_client; | |||||
| } | |||||
| if ((self->port_id = snd_seq_create_simple_port( | |||||
| self->seq, | |||||
| "port", | |||||
| SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE | |||||
| #ifndef DEBUG | |||||
| |SND_SEQ_PORT_CAP_NO_EXPORT | |||||
| #endif | |||||
| ,SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { | |||||
| a2j_error("snd_seq_create_simple_port() failed"); | |||||
| goto close_seq_client; | |||||
| } | |||||
| if ((self->client_id = snd_seq_client_id(self->seq)) < 0) { | |||||
| a2j_error("snd_seq_client_id() failed"); | |||||
| goto close_seq_client; | |||||
| } | |||||
| if ((self->queue = snd_seq_alloc_queue(self->seq)) < 0) { | |||||
| a2j_error("snd_seq_alloc_queue() failed"); | |||||
| goto close_seq_client; | |||||
| } | |||||
| snd_seq_start_queue (self->seq, self->queue, 0); | |||||
| if ((error = snd_seq_nonblock(self->seq, 1)) < 0) { | |||||
| a2j_error("snd_seq_nonblock() failed"); | |||||
| goto close_seq_client; | |||||
| } | |||||
| snd_seq_drop_input (self->seq); | |||||
| a2j_add_ports(&self->stream[A2J_PORT_CAPTURE]); | |||||
| a2j_add_ports(&self->stream[A2J_PORT_PLAYBACK]); | |||||
| if (sem_init(&self->output_semaphore, 0, 0) < 0) { | |||||
| a2j_error("can't create IO semaphore"); | |||||
| goto close_jack_client; | |||||
| } | |||||
| self->running = true; | |||||
| if (pthread_create(&self->alsa_input_thread, NULL, alsa_input_thread, self) < 0) { | |||||
| a2j_error("cannot start ALSA input thread"); | |||||
| goto sem_destroy; | |||||
| } | |||||
| /* wake the poll loop in the alsa input thread so initial ports are fetched */ | |||||
| if ((error = snd_seq_connect_from (self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE)) < 0) { | |||||
| a2j_error("snd_seq_connect_from() failed"); | |||||
| goto join_input_thread; | |||||
| } | |||||
| if (pthread_create(&self->alsa_output_thread, NULL, alsa_output_thread, self) < 0) { | |||||
| a2j_error("cannot start ALSA input thread"); | |||||
| goto sem_destroy; | |||||
| } | |||||
| return 0; | |||||
| /* error handling */ | |||||
| self->running = false; /* tell alsa threads to stop */ | |||||
| self->finishing = false; | |||||
| snd_seq_disconnect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); | |||||
| join_input_thread: | |||||
| pthread_join (self->alsa_input_thread, &thread_status); | |||||
| sem_destroy: | |||||
| sem_destroy (&self->output_semaphore); | |||||
| close_jack_client: | |||||
| if ((error = jack_client_close(self->jack_client)) < 0) { | |||||
| a2j_error("Cannot close jack client"); | |||||
| } | |||||
| close_seq_client: | |||||
| snd_seq_close(self->seq); | |||||
| close_playback_stream: | |||||
| a2j_stream_close(self, A2J_PORT_PLAYBACK); | |||||
| close_capture_stream: | |||||
| a2j_stream_close(self, A2J_PORT_CAPTURE); | |||||
| free_ringbuffer_outbound: | |||||
| jack_ringbuffer_free(self->outbound_events); | |||||
| free_ringbuffer_del: | |||||
| jack_ringbuffer_free(self->port_del); | |||||
| free_ringbuffer_add: | |||||
| jack_ringbuffer_free(self->port_add); | |||||
| free_self: | |||||
| free(self); | |||||
| return -1; | |||||
| } | |||||
| /* JACK internal client API: 2 entry points | |||||
| */ | |||||
| int | |||||
| jack_initialize (jack_client_t *client, const char* load_init) | |||||
| { | |||||
| struct a2j* self = calloc(1, sizeof(struct a2j)); | |||||
| if (!self) { | |||||
| return -1; | |||||
| } | |||||
| self->jack_client = client; | |||||
| if (load_init) { | |||||
| char* args = strdup (load_init); | |||||
| char* token; | |||||
| char* ptr = args; | |||||
| char* savep; | |||||
| while (1) { | |||||
| if ((token = strtok_r (ptr, ", ", &savep)) == NULL) { | |||||
| break; | |||||
| } | |||||
| #ifdef A2J_DEBUG | |||||
| if (strncasecmp (token, "debug", 2) == 0) { | |||||
| a2j_do_debug = true; | |||||
| } | |||||
| #endif | |||||
| ptr = NULL; | |||||
| } | |||||
| free (args); | |||||
| } | |||||
| if (connect_to_alsa (self)) { | |||||
| free (self); | |||||
| return -1; | |||||
| } | |||||
| jack_set_process_callback (client, a2j_process, self); | |||||
| jack_set_freewheel_callback (client, a2j_freewheel, self); | |||||
| jack_on_shutdown (client, a2j_shutdown, self); | |||||
| jack_activate (client); | |||||
| return 0; | |||||
| } | |||||
| void | |||||
| jack_finish (void *arg) | |||||
| { | |||||
| struct a2j* self = (struct a2j*) arg; | |||||
| self->finishing = true; | |||||
| stop_threads (self); | |||||
| sem_destroy(&self->output_semaphore); | |||||
| jack_ringbuffer_reset (self->port_add); | |||||
| a2j_stream_detach (&self->stream[A2J_PORT_CAPTURE]); | |||||
| a2j_stream_detach (&self->stream[A2J_PORT_PLAYBACK]); | |||||
| snd_seq_close(self->seq); | |||||
| self->seq = NULL; | |||||
| a2j_stream_close (self, A2J_PORT_CAPTURE); | |||||
| a2j_stream_close (self, A2J_PORT_PLAYBACK); | |||||
| jack_ringbuffer_free(self->outbound_events); | |||||
| jack_ringbuffer_free(self->port_add); | |||||
| jack_ringbuffer_free(self->port_del); | |||||
| free (self); | |||||
| } | |||||
| @@ -0,0 +1,147 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /***************************************************************************** | |||||
| * | |||||
| * list_sort() adapted from linux kernel. | |||||
| * | |||||
| * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |||||
| * | |||||
| *****************************************************************************/ | |||||
| #include <assert.h> | |||||
| #include "list.h" | |||||
| /* list sort from Mark J Roberts (mjr@znex.org) */ | |||||
| void | |||||
| __list_sort( | |||||
| struct list_head *head, | |||||
| int member_offset, | |||||
| int (*cmp)(void * a, void * b)) | |||||
| { | |||||
| struct list_head *p, *q, *e, *list, *tail, *oldhead; | |||||
| int insize, nmerges, psize, qsize, i; | |||||
| list = head->next; | |||||
| list_del(head); | |||||
| insize = 1; | |||||
| for (;;) { | |||||
| p = oldhead = list; | |||||
| list = tail = NULL; | |||||
| nmerges = 0; | |||||
| while (p) { | |||||
| nmerges++; | |||||
| q = p; | |||||
| psize = 0; | |||||
| for (i = 0; i < insize; i++) { | |||||
| psize++; | |||||
| q = q->next == oldhead ? NULL : q->next; | |||||
| if (!q) | |||||
| break; | |||||
| } | |||||
| qsize = insize; | |||||
| while (psize > 0 || (qsize > 0 && q)) { | |||||
| if (!psize) { | |||||
| e = q; | |||||
| q = q->next; | |||||
| qsize--; | |||||
| if (q == oldhead) | |||||
| q = NULL; | |||||
| } else if (!qsize || !q) { | |||||
| e = p; | |||||
| p = p->next; | |||||
| psize--; | |||||
| if (p == oldhead) | |||||
| p = NULL; | |||||
| } else if (cmp((void *)p - member_offset, (void *)q - member_offset) <= 0) { | |||||
| e = p; | |||||
| p = p->next; | |||||
| psize--; | |||||
| if (p == oldhead) | |||||
| p = NULL; | |||||
| } else { | |||||
| e = q; | |||||
| q = q->next; | |||||
| qsize--; | |||||
| if (q == oldhead) | |||||
| q = NULL; | |||||
| } | |||||
| if (tail) | |||||
| tail->next = e; | |||||
| else | |||||
| list = e; | |||||
| e->prev = tail; | |||||
| tail = e; | |||||
| } | |||||
| p = q; | |||||
| } | |||||
| tail->next = list; | |||||
| list->prev = tail; | |||||
| if (nmerges <= 1) | |||||
| break; | |||||
| insize *= 2; | |||||
| } | |||||
| head->next = list; | |||||
| head->prev = list->prev; | |||||
| list->prev->next = head; | |||||
| list->prev = head; | |||||
| } | |||||
| struct test_list_el { | |||||
| int value; | |||||
| struct list_head test_list_node; | |||||
| }; | |||||
| int test_list_sort_comparator(struct test_list_el * e1, struct test_list_el * e2) | |||||
| { | |||||
| return e1->value - e2->value; | |||||
| } | |||||
| void test_list_sort(void) | |||||
| { | |||||
| struct list_head test_list; | |||||
| struct test_list_el *el, *next; | |||||
| struct test_list_el te1 = {.value = 1}; | |||||
| struct test_list_el te2 = {.value = 2}; | |||||
| struct test_list_el te3 = {.value = 3}; | |||||
| struct test_list_el te4 = {.value = 4}; | |||||
| struct test_list_el te5 = {.value = 5}; | |||||
| struct test_list_el te6 = {.value = 6}; | |||||
| struct test_list_el te7 = {.value = 7}; | |||||
| const int expected[] = {1, 2, 3, 4, 5, 6, 7}; | |||||
| int i; | |||||
| INIT_LIST_HEAD(&test_list); | |||||
| list_add_tail(&te2.test_list_node, &test_list); | |||||
| list_add_tail(&te6.test_list_node, &test_list); | |||||
| list_add_tail(&te4.test_list_node, &test_list); | |||||
| list_add_tail(&te5.test_list_node, &test_list); | |||||
| list_add_tail(&te7.test_list_node, &test_list); | |||||
| list_add_tail(&te1.test_list_node, &test_list); | |||||
| list_add_tail(&te3.test_list_node, &test_list); | |||||
| list_sort(&test_list, struct test_list_el, test_list_node, test_list_sort_comparator); | |||||
| i = 0; | |||||
| list_for_each_entry_safe(el, next, &test_list, test_list_node) { | |||||
| assert(el->value == expected[i]); | |||||
| i++; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,903 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /***************************************************************************** | |||||
| * | |||||
| * Linux kernel header adapted for user-mode | |||||
| * The 2.6.17-rt1 version was used. | |||||
| * | |||||
| * Original copyright holders of this code are unknown, they were not | |||||
| * mentioned in the original file. | |||||
| * | |||||
| * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |||||
| * | |||||
| *****************************************************************************/ | |||||
| #ifndef _LINUX_LIST_H | |||||
| #define _LINUX_LIST_H | |||||
| #include <stddef.h> | |||||
| #if !defined(offsetof) | |||||
| #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) | |||||
| #endif | |||||
| /** | |||||
| * container_of - cast a member of a structure out to the containing structure | |||||
| * @ptr: the pointer to the member. | |||||
| * @type: the type of the container struct this is embedded in. | |||||
| * @member: the name of the member within the struct. | |||||
| * | |||||
| */ | |||||
| #define container_of(ptr, type, member) ({ \ | |||||
| const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | |||||
| (type *)( (char *)__mptr - offsetof(type,member) );}) | |||||
| #define prefetch(x) (x = x) | |||||
| /* | |||||
| * These are non-NULL pointers that will result in page faults | |||||
| * under normal circumstances, used to verify that nobody uses | |||||
| * non-initialized list entries. | |||||
| */ | |||||
| #define LIST_POISON1 ((void *) 0x00100100) | |||||
| #define LIST_POISON2 ((void *) 0x00200200) | |||||
| /* | |||||
| * Simple doubly linked list implementation. | |||||
| * | |||||
| * Some of the internal functions ("__xxx") are useful when | |||||
| * manipulating whole lists rather than single entries, as | |||||
| * sometimes we already know the next/prev entries and we can | |||||
| * generate better code by using them directly rather than | |||||
| * using the generic single-entry routines. | |||||
| */ | |||||
| struct list_head { | |||||
| struct list_head *next, *prev; | |||||
| }; | |||||
| #define LIST_HEAD_INIT(name) { &(name), &(name) } | |||||
| #define LIST_HEAD(name) \ | |||||
| struct list_head name = LIST_HEAD_INIT(name) | |||||
| static inline void INIT_LIST_HEAD(struct list_head *list) | |||||
| { | |||||
| list->next = list; | |||||
| list->prev = list; | |||||
| } | |||||
| /* | |||||
| * Insert a new entry between two known consecutive entries. | |||||
| * | |||||
| * This is only for internal list manipulation where we know | |||||
| * the prev/next entries already! | |||||
| */ | |||||
| static inline void __list_add(struct list_head *new, | |||||
| struct list_head *prev, | |||||
| struct list_head *next) | |||||
| { | |||||
| next->prev = new; | |||||
| new->next = next; | |||||
| new->prev = prev; | |||||
| prev->next = new; | |||||
| } | |||||
| /** | |||||
| * list_add - add a new entry | |||||
| * @new: new entry to be added | |||||
| * @head: list head to add it after | |||||
| * | |||||
| * Insert a new entry after the specified head. | |||||
| * This is good for implementing stacks. | |||||
| */ | |||||
| static inline void list_add(struct list_head *new, struct list_head *head) | |||||
| { | |||||
| __list_add(new, head, head->next); | |||||
| } | |||||
| /** | |||||
| * list_add_tail - add a new entry | |||||
| * @new: new entry to be added | |||||
| * @head: list head to add it before | |||||
| * | |||||
| * Insert a new entry before the specified head. | |||||
| * This is useful for implementing queues. | |||||
| */ | |||||
| static inline void list_add_tail(struct list_head *new, struct list_head *head) | |||||
| { | |||||
| __list_add(new, head->prev, head); | |||||
| } | |||||
| /* | |||||
| * Insert a new entry between two known consecutive entries. | |||||
| * | |||||
| * This is only for internal list manipulation where we know | |||||
| * the prev/next entries already! | |||||
| */ | |||||
| static inline void __list_add_rcu(struct list_head * new, | |||||
| struct list_head * prev, struct list_head * next) | |||||
| { | |||||
| new->next = next; | |||||
| new->prev = prev; | |||||
| // smp_wmb(); | |||||
| next->prev = new; | |||||
| prev->next = new; | |||||
| } | |||||
| /** | |||||
| * list_add_rcu - add a new entry to rcu-protected list | |||||
| * @new: new entry to be added | |||||
| * @head: list head to add it after | |||||
| * | |||||
| * Insert a new entry after the specified head. | |||||
| * This is good for implementing stacks. | |||||
| * | |||||
| * The caller must take whatever precautions are necessary | |||||
| * (such as holding appropriate locks) to avoid racing | |||||
| * with another list-mutation primitive, such as list_add_rcu() | |||||
| * or list_del_rcu(), running on this same list. | |||||
| * However, it is perfectly legal to run concurrently with | |||||
| * the _rcu list-traversal primitives, such as | |||||
| * list_for_each_entry_rcu(). | |||||
| */ | |||||
| static inline void list_add_rcu(struct list_head *new, struct list_head *head) | |||||
| { | |||||
| __list_add_rcu(new, head, head->next); | |||||
| } | |||||
| /** | |||||
| * list_add_tail_rcu - add a new entry to rcu-protected list | |||||
| * @new: new entry to be added | |||||
| * @head: list head to add it before | |||||
| * | |||||
| * Insert a new entry before the specified head. | |||||
| * This is useful for implementing queues. | |||||
| * | |||||
| * The caller must take whatever precautions are necessary | |||||
| * (such as holding appropriate locks) to avoid racing | |||||
| * with another list-mutation primitive, such as list_add_tail_rcu() | |||||
| * or list_del_rcu(), running on this same list. | |||||
| * However, it is perfectly legal to run concurrently with | |||||
| * the _rcu list-traversal primitives, such as | |||||
| * list_for_each_entry_rcu(). | |||||
| */ | |||||
| static inline void list_add_tail_rcu(struct list_head *new, | |||||
| struct list_head *head) | |||||
| { | |||||
| __list_add_rcu(new, head->prev, head); | |||||
| } | |||||
| /* | |||||
| * Delete a list entry by making the prev/next entries | |||||
| * point to each other. | |||||
| * | |||||
| * This is only for internal list manipulation where we know | |||||
| * the prev/next entries already! | |||||
| */ | |||||
| static inline void __list_del(struct list_head * prev, struct list_head * next) | |||||
| { | |||||
| next->prev = prev; | |||||
| prev->next = next; | |||||
| } | |||||
| /** | |||||
| * list_del - deletes entry from list. | |||||
| * @entry: the element to delete from the list. | |||||
| * Note: list_empty on entry does not return true after this, the entry is | |||||
| * in an undefined state. | |||||
| */ | |||||
| static inline void list_del(struct list_head *entry) | |||||
| { | |||||
| __list_del(entry->prev, entry->next); | |||||
| entry->next = LIST_POISON1; | |||||
| entry->prev = LIST_POISON2; | |||||
| } | |||||
| /** | |||||
| * list_del_rcu - deletes entry from list without re-initialization | |||||
| * @entry: the element to delete from the list. | |||||
| * | |||||
| * Note: list_empty on entry does not return true after this, | |||||
| * the entry is in an undefined state. It is useful for RCU based | |||||
| * lockfree traversal. | |||||
| * | |||||
| * In particular, it means that we can not poison the forward | |||||
| * pointers that may still be used for walking the list. | |||||
| * | |||||
| * The caller must take whatever precautions are necessary | |||||
| * (such as holding appropriate locks) to avoid racing | |||||
| * with another list-mutation primitive, such as list_del_rcu() | |||||
| * or list_add_rcu(), running on this same list. | |||||
| * However, it is perfectly legal to run concurrently with | |||||
| * the _rcu list-traversal primitives, such as | |||||
| * list_for_each_entry_rcu(). | |||||
| * | |||||
| * Note that the caller is not permitted to immediately free | |||||
| * the newly deleted entry. Instead, either synchronize_rcu() | |||||
| * or call_rcu() must be used to defer freeing until an RCU | |||||
| * grace period has elapsed. | |||||
| */ | |||||
| static inline void list_del_rcu(struct list_head *entry) | |||||
| { | |||||
| __list_del(entry->prev, entry->next); | |||||
| entry->prev = LIST_POISON2; | |||||
| } | |||||
| /* | |||||
| * list_replace_rcu - replace old entry by new one | |||||
| * @old : the element to be replaced | |||||
| * @new : the new element to insert | |||||
| * | |||||
| * The old entry will be replaced with the new entry atomically. | |||||
| */ | |||||
| static inline void list_replace_rcu(struct list_head *old, | |||||
| struct list_head *new) | |||||
| { | |||||
| new->next = old->next; | |||||
| new->prev = old->prev; | |||||
| // smp_wmb(); | |||||
| new->next->prev = new; | |||||
| new->prev->next = new; | |||||
| old->prev = LIST_POISON2; | |||||
| } | |||||
| /** | |||||
| * list_del_init - deletes entry from list and reinitialize it. | |||||
| * @entry: the element to delete from the list. | |||||
| */ | |||||
| static inline void list_del_init(struct list_head *entry) | |||||
| { | |||||
| __list_del(entry->prev, entry->next); | |||||
| INIT_LIST_HEAD(entry); | |||||
| } | |||||
| /** | |||||
| * list_move - delete from one list and add as another's head | |||||
| * @list: the entry to move | |||||
| * @head: the head that will precede our entry | |||||
| */ | |||||
| static inline void list_move(struct list_head *list, struct list_head *head) | |||||
| { | |||||
| __list_del(list->prev, list->next); | |||||
| list_add(list, head); | |||||
| } | |||||
| /** | |||||
| * list_move_tail - delete from one list and add as another's tail | |||||
| * @list: the entry to move | |||||
| * @head: the head that will follow our entry | |||||
| */ | |||||
| static inline void list_move_tail(struct list_head *list, | |||||
| struct list_head *head) | |||||
| { | |||||
| __list_del(list->prev, list->next); | |||||
| list_add_tail(list, head); | |||||
| } | |||||
| /** | |||||
| * list_empty - tests whether a list is empty | |||||
| * @head: the list to test. | |||||
| */ | |||||
| static inline int list_empty(const struct list_head *head) | |||||
| { | |||||
| return head->next == head; | |||||
| } | |||||
| /** | |||||
| * list_empty_careful - tests whether a list is | |||||
| * empty _and_ checks that no other CPU might be | |||||
| * in the process of still modifying either member | |||||
| * | |||||
| * NOTE: using list_empty_careful() without synchronization | |||||
| * can only be safe if the only activity that can happen | |||||
| * to the list entry is list_del_init(). Eg. it cannot be used | |||||
| * if another CPU could re-list_add() it. | |||||
| * | |||||
| * @head: the list to test. | |||||
| */ | |||||
| static inline int list_empty_careful(const struct list_head *head) | |||||
| { | |||||
| struct list_head *next = head->next; | |||||
| return (next == head) && (next == head->prev); | |||||
| } | |||||
| static inline void __list_splice(struct list_head *list, | |||||
| struct list_head *head) | |||||
| { | |||||
| struct list_head *first = list->next; | |||||
| struct list_head *last = list->prev; | |||||
| struct list_head *at = head->next; | |||||
| first->prev = head; | |||||
| head->next = first; | |||||
| last->next = at; | |||||
| at->prev = last; | |||||
| } | |||||
| /** | |||||
| * list_splice - join two lists | |||||
| * @list: the new list to add. | |||||
| * @head: the place to add it in the first list. | |||||
| */ | |||||
| static inline void list_splice(struct list_head *list, struct list_head *head) | |||||
| { | |||||
| if (!list_empty(list)) | |||||
| __list_splice(list, head); | |||||
| } | |||||
| /** | |||||
| * list_splice_init - join two lists and reinitialise the emptied list. | |||||
| * @list: the new list to add. | |||||
| * @head: the place to add it in the first list. | |||||
| * | |||||
| * The list at @list is reinitialised | |||||
| */ | |||||
| static inline void list_splice_init(struct list_head *list, | |||||
| struct list_head *head) | |||||
| { | |||||
| if (!list_empty(list)) { | |||||
| __list_splice(list, head); | |||||
| INIT_LIST_HEAD(list); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * list_entry - get the struct for this entry | |||||
| * @ptr: the &struct list_head pointer. | |||||
| * @type: the type of the struct this is embedded in. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_entry(ptr, type, member) \ | |||||
| container_of(ptr, type, member) | |||||
| /** | |||||
| * list_for_each - iterate over a list | |||||
| * @pos: the &struct list_head to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| */ | |||||
| #define list_for_each(pos, head) \ | |||||
| for (pos = (head)->next; prefetch(pos->next), pos != (head); \ | |||||
| pos = pos->next) | |||||
| /** | |||||
| * __list_for_each - iterate over a list | |||||
| * @pos: the &struct list_head to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * | |||||
| * This variant differs from list_for_each() in that it's the | |||||
| * simplest possible list iteration code, no prefetching is done. | |||||
| * Use this for code that knows the list to be very short (empty | |||||
| * or 1 entry) most of the time. | |||||
| */ | |||||
| #define __list_for_each(pos, head) \ | |||||
| for (pos = (head)->next; pos != (head); pos = pos->next) | |||||
| /** | |||||
| * list_for_each_prev - iterate over a list backwards | |||||
| * @pos: the &struct list_head to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| */ | |||||
| #define list_for_each_prev(pos, head) \ | |||||
| for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ | |||||
| pos = pos->prev) | |||||
| /** | |||||
| * list_for_each_safe - iterate over a list safe against removal of list entry | |||||
| * @pos: the &struct list_head to use as a loop counter. | |||||
| * @n: another &struct list_head to use as temporary storage | |||||
| * @head: the head for your list. | |||||
| */ | |||||
| #define list_for_each_safe(pos, n, head) \ | |||||
| for (pos = (head)->next, n = pos->next; pos != (head); \ | |||||
| pos = n, n = pos->next) | |||||
| /** | |||||
| * list_for_each_entry - iterate over list of given type | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry(pos, head, member) \ | |||||
| for (pos = list_entry((head)->next, typeof(*pos), member); \ | |||||
| prefetch(pos->member.next), &pos->member != (head); \ | |||||
| pos = list_entry(pos->member.next, typeof(*pos), member)) | |||||
| /** | |||||
| * list_for_each_entry_reverse - iterate backwards over list of given type. | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry_reverse(pos, head, member) \ | |||||
| for (pos = list_entry((head)->prev, typeof(*pos), member); \ | |||||
| prefetch(pos->member.prev), &pos->member != (head); \ | |||||
| pos = list_entry(pos->member.prev, typeof(*pos), member)) | |||||
| /** | |||||
| * list_prepare_entry - prepare a pos entry for use as a start point in | |||||
| * list_for_each_entry_continue | |||||
| * @pos: the type * to use as a start point | |||||
| * @head: the head of the list | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_prepare_entry(pos, head, member) \ | |||||
| ((pos) ? : list_entry(head, typeof(*pos), member)) | |||||
| /** | |||||
| * list_for_each_entry_continue - iterate over list of given type | |||||
| * continuing after existing point | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry_continue(pos, head, member) \ | |||||
| for (pos = list_entry(pos->member.next, typeof(*pos), member); \ | |||||
| prefetch(pos->member.next), &pos->member != (head); \ | |||||
| pos = list_entry(pos->member.next, typeof(*pos), member)) | |||||
| /** | |||||
| * list_for_each_entry_from - iterate over list of given type | |||||
| * continuing from existing point | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry_from(pos, head, member) \ | |||||
| for (; prefetch(pos->member.next), &pos->member != (head); \ | |||||
| pos = list_entry(pos->member.next, typeof(*pos), member)) | |||||
| /** | |||||
| * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @n: another type * to use as temporary storage | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry_safe(pos, n, head, member) \ | |||||
| for (pos = list_entry((head)->next, typeof(*pos), member), \ | |||||
| n = list_entry(pos->member.next, typeof(*pos), member); \ | |||||
| &pos->member != (head); \ | |||||
| pos = n, n = list_entry(n->member.next, typeof(*n), member)) | |||||
| /** | |||||
| * list_for_each_entry_safe_continue - iterate over list of given type | |||||
| * continuing after existing point safe against removal of list entry | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @n: another type * to use as temporary storage | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry_safe_continue(pos, n, head, member) \ | |||||
| for (pos = list_entry(pos->member.next, typeof(*pos), member), \ | |||||
| n = list_entry(pos->member.next, typeof(*pos), member); \ | |||||
| &pos->member != (head); \ | |||||
| pos = n, n = list_entry(n->member.next, typeof(*n), member)) | |||||
| /** | |||||
| * list_for_each_entry_safe_from - iterate over list of given type | |||||
| * from existing point safe against removal of list entry | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @n: another type * to use as temporary storage | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry_safe_from(pos, n, head, member) \ | |||||
| for (n = list_entry(pos->member.next, typeof(*pos), member); \ | |||||
| &pos->member != (head); \ | |||||
| pos = n, n = list_entry(n->member.next, typeof(*n), member)) | |||||
| /** | |||||
| * list_for_each_entry_safe_reverse - iterate backwards over list of given type safe against | |||||
| * removal of list entry | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @n: another type * to use as temporary storage | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| */ | |||||
| #define list_for_each_entry_safe_reverse(pos, n, head, member) \ | |||||
| for (pos = list_entry((head)->prev, typeof(*pos), member), \ | |||||
| n = list_entry(pos->member.prev, typeof(*pos), member); \ | |||||
| &pos->member != (head); \ | |||||
| pos = n, n = list_entry(n->member.prev, typeof(*n), member)) | |||||
| /** | |||||
| * list_for_each_rcu - iterate over an rcu-protected list | |||||
| * @pos: the &struct list_head to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * | |||||
| * This list-traversal primitive may safely run concurrently with | |||||
| * the _rcu list-mutation primitives such as list_add_rcu() | |||||
| * as long as the traversal is guarded by rcu_read_lock(). | |||||
| */ | |||||
| #define list_for_each_rcu(pos, head) \ | |||||
| for (pos = (head)->next; \ | |||||
| prefetch(rcu_dereference(pos)->next), pos != (head); \ | |||||
| pos = pos->next) | |||||
| #define __list_for_each_rcu(pos, head) \ | |||||
| for (pos = (head)->next; \ | |||||
| rcu_dereference(pos) != (head); \ | |||||
| pos = pos->next) | |||||
| /** | |||||
| * list_for_each_safe_rcu - iterate over an rcu-protected list safe | |||||
| * against removal of list entry | |||||
| * @pos: the &struct list_head to use as a loop counter. | |||||
| * @n: another &struct list_head to use as temporary storage | |||||
| * @head: the head for your list. | |||||
| * | |||||
| * This list-traversal primitive may safely run concurrently with | |||||
| * the _rcu list-mutation primitives such as list_add_rcu() | |||||
| * as long as the traversal is guarded by rcu_read_lock(). | |||||
| */ | |||||
| #define list_for_each_safe_rcu(pos, n, head) \ | |||||
| for (pos = (head)->next; \ | |||||
| n = rcu_dereference(pos)->next, pos != (head); \ | |||||
| pos = n) | |||||
| /** | |||||
| * list_for_each_entry_rcu - iterate over rcu list of given type | |||||
| * @pos: the type * to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the list_struct within the struct. | |||||
| * | |||||
| * This list-traversal primitive may safely run concurrently with | |||||
| * the _rcu list-mutation primitives such as list_add_rcu() | |||||
| * as long as the traversal is guarded by rcu_read_lock(). | |||||
| */ | |||||
| #define list_for_each_entry_rcu(pos, head, member) \ | |||||
| for (pos = list_entry((head)->next, typeof(*pos), member); \ | |||||
| prefetch(rcu_dereference(pos)->member.next), \ | |||||
| &pos->member != (head); \ | |||||
| pos = list_entry(pos->member.next, typeof(*pos), member)) | |||||
| /** | |||||
| * list_for_each_continue_rcu - iterate over an rcu-protected list | |||||
| * continuing after existing point. | |||||
| * @pos: the &struct list_head to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * | |||||
| * This list-traversal primitive may safely run concurrently with | |||||
| * the _rcu list-mutation primitives such as list_add_rcu() | |||||
| * as long as the traversal is guarded by rcu_read_lock(). | |||||
| */ | |||||
| #define list_for_each_continue_rcu(pos, head) \ | |||||
| for ((pos) = (pos)->next; \ | |||||
| prefetch(rcu_dereference((pos))->next), (pos) != (head); \ | |||||
| (pos) = (pos)->next) | |||||
| /* | |||||
| * Double linked lists with a single pointer list head. | |||||
| * Mostly useful for hash tables where the two pointer list head is | |||||
| * too wasteful. | |||||
| * You lose the ability to access the tail in O(1). | |||||
| */ | |||||
| struct hlist_head { | |||||
| struct hlist_node *first; | |||||
| }; | |||||
| struct hlist_node { | |||||
| struct hlist_node *next, **pprev; | |||||
| }; | |||||
| #define HLIST_HEAD_INIT { .first = NULL } | |||||
| #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } | |||||
| #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) | |||||
| static inline void INIT_HLIST_NODE(struct hlist_node *h) | |||||
| { | |||||
| h->next = NULL; | |||||
| h->pprev = NULL; | |||||
| } | |||||
| static inline int hlist_unhashed(const struct hlist_node *h) | |||||
| { | |||||
| return !h->pprev; | |||||
| } | |||||
| static inline int hlist_empty(const struct hlist_head *h) | |||||
| { | |||||
| return !h->first; | |||||
| } | |||||
| static inline void __hlist_del(struct hlist_node *n) | |||||
| { | |||||
| struct hlist_node *next = n->next; | |||||
| struct hlist_node **pprev = n->pprev; | |||||
| *pprev = next; | |||||
| if (next) | |||||
| next->pprev = pprev; | |||||
| } | |||||
| static inline void hlist_del(struct hlist_node *n) | |||||
| { | |||||
| __hlist_del(n); | |||||
| n->next = LIST_POISON1; | |||||
| n->pprev = LIST_POISON2; | |||||
| } | |||||
| /** | |||||
| * hlist_del_rcu - deletes entry from hash list without re-initialization | |||||
| * @n: the element to delete from the hash list. | |||||
| * | |||||
| * Note: list_unhashed() on entry does not return true after this, | |||||
| * the entry is in an undefined state. It is useful for RCU based | |||||
| * lockfree traversal. | |||||
| * | |||||
| * In particular, it means that we can not poison the forward | |||||
| * pointers that may still be used for walking the hash list. | |||||
| * | |||||
| * The caller must take whatever precautions are necessary | |||||
| * (such as holding appropriate locks) to avoid racing | |||||
| * with another list-mutation primitive, such as hlist_add_head_rcu() | |||||
| * or hlist_del_rcu(), running on this same list. | |||||
| * However, it is perfectly legal to run concurrently with | |||||
| * the _rcu list-traversal primitives, such as | |||||
| * hlist_for_each_entry(). | |||||
| */ | |||||
| static inline void hlist_del_rcu(struct hlist_node *n) | |||||
| { | |||||
| __hlist_del(n); | |||||
| n->pprev = LIST_POISON2; | |||||
| } | |||||
| static inline void hlist_del_init(struct hlist_node *n) | |||||
| { | |||||
| if (!hlist_unhashed(n)) { | |||||
| __hlist_del(n); | |||||
| INIT_HLIST_NODE(n); | |||||
| } | |||||
| } | |||||
| /* | |||||
| * hlist_replace_rcu - replace old entry by new one | |||||
| * @old : the element to be replaced | |||||
| * @new : the new element to insert | |||||
| * | |||||
| * The old entry will be replaced with the new entry atomically. | |||||
| */ | |||||
| static inline void hlist_replace_rcu(struct hlist_node *old, | |||||
| struct hlist_node *new) | |||||
| { | |||||
| struct hlist_node *next = old->next; | |||||
| new->next = next; | |||||
| new->pprev = old->pprev; | |||||
| // smp_wmb(); | |||||
| if (next) | |||||
| new->next->pprev = &new->next; | |||||
| *new->pprev = new; | |||||
| old->pprev = LIST_POISON2; | |||||
| } | |||||
| static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) | |||||
| { | |||||
| struct hlist_node *first = h->first; | |||||
| n->next = first; | |||||
| if (first) | |||||
| first->pprev = &n->next; | |||||
| h->first = n; | |||||
| n->pprev = &h->first; | |||||
| } | |||||
| /** | |||||
| * hlist_add_head_rcu - adds the specified element to the specified hlist, | |||||
| * while permitting racing traversals. | |||||
| * @n: the element to add to the hash list. | |||||
| * @h: the list to add to. | |||||
| * | |||||
| * The caller must take whatever precautions are necessary | |||||
| * (such as holding appropriate locks) to avoid racing | |||||
| * with another list-mutation primitive, such as hlist_add_head_rcu() | |||||
| * or hlist_del_rcu(), running on this same list. | |||||
| * However, it is perfectly legal to run concurrently with | |||||
| * the _rcu list-traversal primitives, such as | |||||
| * hlist_for_each_entry_rcu(), used to prevent memory-consistency | |||||
| * problems on Alpha CPUs. Regardless of the type of CPU, the | |||||
| * list-traversal primitive must be guarded by rcu_read_lock(). | |||||
| */ | |||||
| static inline void hlist_add_head_rcu(struct hlist_node *n, | |||||
| struct hlist_head *h) | |||||
| { | |||||
| struct hlist_node *first = h->first; | |||||
| n->next = first; | |||||
| n->pprev = &h->first; | |||||
| // smp_wmb(); | |||||
| if (first) | |||||
| first->pprev = &n->next; | |||||
| h->first = n; | |||||
| } | |||||
| /* next must be != NULL */ | |||||
| static inline void hlist_add_before(struct hlist_node *n, | |||||
| struct hlist_node *next) | |||||
| { | |||||
| n->pprev = next->pprev; | |||||
| n->next = next; | |||||
| next->pprev = &n->next; | |||||
| *(n->pprev) = n; | |||||
| } | |||||
| static inline void hlist_add_after(struct hlist_node *n, | |||||
| struct hlist_node *next) | |||||
| { | |||||
| next->next = n->next; | |||||
| n->next = next; | |||||
| next->pprev = &n->next; | |||||
| if(next->next) | |||||
| next->next->pprev = &next->next; | |||||
| } | |||||
| /** | |||||
| * hlist_add_before_rcu - adds the specified element to the specified hlist | |||||
| * before the specified node while permitting racing traversals. | |||||
| * @n: the new element to add to the hash list. | |||||
| * @next: the existing element to add the new element before. | |||||
| * | |||||
| * The caller must take whatever precautions are necessary | |||||
| * (such as holding appropriate locks) to avoid racing | |||||
| * with another list-mutation primitive, such as hlist_add_head_rcu() | |||||
| * or hlist_del_rcu(), running on this same list. | |||||
| * However, it is perfectly legal to run concurrently with | |||||
| * the _rcu list-traversal primitives, such as | |||||
| * hlist_for_each_entry_rcu(), used to prevent memory-consistency | |||||
| * problems on Alpha CPUs. | |||||
| */ | |||||
| static inline void hlist_add_before_rcu(struct hlist_node *n, | |||||
| struct hlist_node *next) | |||||
| { | |||||
| n->pprev = next->pprev; | |||||
| n->next = next; | |||||
| // smp_wmb(); | |||||
| next->pprev = &n->next; | |||||
| *(n->pprev) = n; | |||||
| } | |||||
| /** | |||||
| * hlist_add_after_rcu - adds the specified element to the specified hlist | |||||
| * after the specified node while permitting racing traversals. | |||||
| * @prev: the existing element to add the new element after. | |||||
| * @n: the new element to add to the hash list. | |||||
| * | |||||
| * The caller must take whatever precautions are necessary | |||||
| * (such as holding appropriate locks) to avoid racing | |||||
| * with another list-mutation primitive, such as hlist_add_head_rcu() | |||||
| * or hlist_del_rcu(), running on this same list. | |||||
| * However, it is perfectly legal to run concurrently with | |||||
| * the _rcu list-traversal primitives, such as | |||||
| * hlist_for_each_entry_rcu(), used to prevent memory-consistency | |||||
| * problems on Alpha CPUs. | |||||
| */ | |||||
| static inline void hlist_add_after_rcu(struct hlist_node *prev, | |||||
| struct hlist_node *n) | |||||
| { | |||||
| n->next = prev->next; | |||||
| n->pprev = &prev->next; | |||||
| // smp_wmb(); | |||||
| prev->next = n; | |||||
| if (n->next) | |||||
| n->next->pprev = &n->next; | |||||
| } | |||||
| #define hlist_entry(ptr, type, member) container_of(ptr,type,member) | |||||
| #define hlist_for_each(pos, head) \ | |||||
| for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ | |||||
| pos = pos->next) | |||||
| #define hlist_for_each_safe(pos, n, head) \ | |||||
| for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ | |||||
| pos = n) | |||||
| /** | |||||
| * hlist_for_each_entry - iterate over list of given type | |||||
| * @tpos: the type * to use as a loop counter. | |||||
| * @pos: the &struct hlist_node to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the hlist_node within the struct. | |||||
| */ | |||||
| #define hlist_for_each_entry(tpos, pos, head, member) \ | |||||
| for (pos = (head)->first; \ | |||||
| pos && ({ prefetch(pos->next); 1;}) && \ | |||||
| ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | |||||
| pos = pos->next) | |||||
| /** | |||||
| * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point | |||||
| * @tpos: the type * to use as a loop counter. | |||||
| * @pos: the &struct hlist_node to use as a loop counter. | |||||
| * @member: the name of the hlist_node within the struct. | |||||
| */ | |||||
| #define hlist_for_each_entry_continue(tpos, pos, member) \ | |||||
| for (pos = (pos)->next; \ | |||||
| pos && ({ prefetch(pos->next); 1;}) && \ | |||||
| ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | |||||
| pos = pos->next) | |||||
| /** | |||||
| * hlist_for_each_entry_from - iterate over a hlist continuing from existing point | |||||
| * @tpos: the type * to use as a loop counter. | |||||
| * @pos: the &struct hlist_node to use as a loop counter. | |||||
| * @member: the name of the hlist_node within the struct. | |||||
| */ | |||||
| #define hlist_for_each_entry_from(tpos, pos, member) \ | |||||
| for (; pos && ({ prefetch(pos->next); 1;}) && \ | |||||
| ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | |||||
| pos = pos->next) | |||||
| /** | |||||
| * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry | |||||
| * @tpos: the type * to use as a loop counter. | |||||
| * @pos: the &struct hlist_node to use as a loop counter. | |||||
| * @n: another &struct hlist_node to use as temporary storage | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the hlist_node within the struct. | |||||
| */ | |||||
| #define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ | |||||
| for (pos = (head)->first; \ | |||||
| pos && ({ n = pos->next; 1; }) && \ | |||||
| ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | |||||
| pos = n) | |||||
| /** | |||||
| * hlist_for_each_entry_rcu - iterate over rcu list of given type | |||||
| * @tpos: the type * to use as a loop counter. | |||||
| * @pos: the &struct hlist_node to use as a loop counter. | |||||
| * @head: the head for your list. | |||||
| * @member: the name of the hlist_node within the struct. | |||||
| * | |||||
| * This list-traversal primitive may safely run concurrently with | |||||
| * the _rcu list-mutation primitives such as hlist_add_head_rcu() | |||||
| * as long as the traversal is guarded by rcu_read_lock(). | |||||
| */ | |||||
| #define hlist_for_each_entry_rcu(tpos, pos, head, member) \ | |||||
| for (pos = (head)->first; \ | |||||
| rcu_dereference(pos) && ({ prefetch(pos->next); 1;}) && \ | |||||
| ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | |||||
| pos = pos->next) | |||||
| #endif | |||||
| /** | |||||
| * __list_sort - sort the list using given comparator with merge-sort algorithm | |||||
| * @head: is a head of the list to be sorted | |||||
| * @member_offset: is machine offset inside the list entry structure to the | |||||
| * field of type struct list_head which links that entry with | |||||
| * the list. | |||||
| */ | |||||
| extern void __list_sort(struct list_head * head, | |||||
| int member_offset, | |||||
| int (*comparator)(void*,void*)); | |||||
| /** | |||||
| * list_sort - wrapper for __list_sort | |||||
| * @head: is a head of the list to be sorted | |||||
| * @type: is the type of list entry | |||||
| * @member: is the name of the field inside entry that links that entry with | |||||
| * other entries in the list. | |||||
| * @comaprator: function comparing two entries, should return value lesser | |||||
| * than 0 when the first argument is lesser than the second one. | |||||
| */ | |||||
| #define list_sort(head,type,member,comparator) \ | |||||
| ({ \ | |||||
| __list_sort(head, \ | |||||
| offsetof(type, member), \ | |||||
| (int (*)(void*, void*)) comparator); \ | |||||
| }) | |||||
| void test_list_sort(void); | |||||
| @@ -0,0 +1,217 @@ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * Copyright (c) 2009,2010 Paul Davis <paul@linuxaudiosystems.com> | |||||
| * | |||||
| * 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 <stdbool.h> | |||||
| #include <ctype.h> | |||||
| #include <semaphore.h> | |||||
| #include <alsa/asoundlib.h> | |||||
| #include <jack/jack.h> | |||||
| #include <jack/ringbuffer.h> | |||||
| #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; | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * | |||||
| * 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 | |||||
| */ | |||||
| #ifndef PORT_H__757ADD0F_5E53_41F7_8B7F_8119C5E8A9F1__INCLUDED | |||||
| #define PORT_H__757ADD0F_5E53_41F7_8B7F_8119C5E8A9F1__INCLUDED | |||||
| struct a2j_port* a2j_port_create (struct a2j * self, int dir, snd_seq_addr_t addr, const snd_seq_port_info_t * info); | |||||
| void a2j_port_setdead (a2j_port_hash_t hash, snd_seq_addr_t addr); | |||||
| void a2j_port_free (struct a2j_port * port); | |||||
| #endif /* #ifndef PORT_H__757ADD0F_5E53_41F7_8B7F_8119C5E8A9F1__INCLUDED */ | |||||
| @@ -0,0 +1,63 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * | |||||
| * 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 <stdbool.h> | |||||
| #include <semaphore.h> | |||||
| #include <alsa/asoundlib.h> | |||||
| #include <jack/jack.h> | |||||
| #include <jack/ringbuffer.h> | |||||
| #include "list.h" | |||||
| #include "a2j.h" | |||||
| #include "port_hash.h" | |||||
| static inline | |||||
| int | |||||
| a2j_port_hash( | |||||
| snd_seq_addr_t addr) | |||||
| { | |||||
| return (addr.client + addr.port) % PORT_HASH_SIZE; | |||||
| } | |||||
| struct a2j_port * | |||||
| a2j_port_get( | |||||
| a2j_port_hash_t hash, | |||||
| snd_seq_addr_t addr) | |||||
| { | |||||
| struct a2j_port **pport = &hash[a2j_port_hash(addr)]; | |||||
| while (*pport) { | |||||
| struct a2j_port *port = *pport; | |||||
| if (port->remote.client == addr.client && port->remote.port == addr.port) | |||||
| return port; | |||||
| pport = &port->next; | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| void | |||||
| a2j_port_insert( | |||||
| a2j_port_hash_t hash, | |||||
| struct a2j_port * port) | |||||
| { | |||||
| struct a2j_port **pport = &hash[a2j_port_hash(port->remote)]; | |||||
| port->next = *pport; | |||||
| *pport = port; | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * | |||||
| * 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 | |||||
| */ | |||||
| #ifndef PORT_HASH_H__A44CBCD6_E075_49CB_8F73_DF9772511D55__INCLUDED | |||||
| #define PORT_HASH_H__A44CBCD6_E075_49CB_8F73_DF9772511D55__INCLUDED | |||||
| void | |||||
| a2j_port_insert( | |||||
| a2j_port_hash_t hash, | |||||
| struct a2j_port * port); | |||||
| struct a2j_port * | |||||
| a2j_port_get( | |||||
| a2j_port_hash_t hash, | |||||
| snd_seq_addr_t addr); | |||||
| #endif /* #ifndef PORT_HASH_H__A44CBCD6_E075_49CB_8F73_DF9772511D55__INCLUDED */ | |||||
| @@ -0,0 +1,235 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * | |||||
| * 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 <stdbool.h> | |||||
| #include <semaphore.h> | |||||
| #include <alsa/asoundlib.h> | |||||
| #include <jack/jack.h> | |||||
| #include <jack/ringbuffer.h> | |||||
| #include "list.h" | |||||
| #include "a2j.h" | |||||
| #include "port.h" | |||||
| #include "port_hash.h" | |||||
| #include "port_thread.h" | |||||
| struct a2j_port * | |||||
| a2j_find_port_by_addr( | |||||
| struct a2j_stream * stream_ptr, | |||||
| snd_seq_addr_t addr) | |||||
| { | |||||
| struct list_head * node_ptr; | |||||
| struct a2j_port * port_ptr; | |||||
| list_for_each(node_ptr, &stream_ptr->list) | |||||
| { | |||||
| port_ptr = list_entry(node_ptr, struct a2j_port, siblings); | |||||
| if (port_ptr->remote.client == addr.client && port_ptr->remote.port == addr.port) | |||||
| { | |||||
| return port_ptr; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| struct a2j_port * | |||||
| a2j_find_port_by_jack_port_name( | |||||
| struct a2j_stream * stream_ptr, | |||||
| const char * jack_port) | |||||
| { | |||||
| struct list_head * node_ptr; | |||||
| struct a2j_port * port_ptr; | |||||
| list_for_each(node_ptr, &stream_ptr->list) | |||||
| { | |||||
| port_ptr = list_entry(node_ptr, struct a2j_port, siblings); | |||||
| if (strcmp(port_ptr->name, jack_port) == 0) | |||||
| { | |||||
| return port_ptr; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| /* | |||||
| * ==================== Port add/del handling thread ============================== | |||||
| */ | |||||
| static | |||||
| void | |||||
| a2j_update_port_type (struct a2j * self, int dir, snd_seq_addr_t addr, int caps, const snd_seq_port_info_t * info) | |||||
| { | |||||
| struct a2j_stream * stream_ptr; | |||||
| int alsa_mask; | |||||
| struct a2j_port * port_ptr; | |||||
| a2j_debug("update_port_type(%d:%d)", addr.client, addr.port); | |||||
| stream_ptr = &self->stream[dir]; | |||||
| port_ptr = a2j_find_port_by_addr(stream_ptr, addr); | |||||
| if (dir == A2J_PORT_CAPTURE) { | |||||
| alsa_mask = SND_SEQ_PORT_CAP_SUBS_READ; | |||||
| } else { | |||||
| alsa_mask = SND_SEQ_PORT_CAP_SUBS_WRITE; | |||||
| } | |||||
| if (port_ptr != NULL && (caps & alsa_mask) != alsa_mask) { | |||||
| a2j_debug("setdead: %s", port_ptr->name); | |||||
| port_ptr->is_dead = true; | |||||
| } | |||||
| if (port_ptr == NULL && (caps & alsa_mask) == alsa_mask) { | |||||
| if(jack_ringbuffer_write_space(stream_ptr->new_ports) >= sizeof(port_ptr)) { | |||||
| port_ptr = a2j_port_create (self, dir, addr, info); | |||||
| if (port_ptr != NULL) { | |||||
| jack_ringbuffer_write(stream_ptr->new_ports, (char *)&port_ptr, sizeof(port_ptr)); | |||||
| } | |||||
| } else { | |||||
| a2j_error( "dropping new port event... increase MAX_PORTS" ); | |||||
| } | |||||
| } | |||||
| } | |||||
| void | |||||
| a2j_update_port (struct a2j * self, snd_seq_addr_t addr, const snd_seq_port_info_t * info) | |||||
| { | |||||
| unsigned int port_caps = snd_seq_port_info_get_capability(info); | |||||
| unsigned int port_type = snd_seq_port_info_get_type(info); | |||||
| a2j_debug("port %u:%u", addr.client, addr.port); | |||||
| a2j_debug("port type: 0x%08X", port_type); | |||||
| a2j_debug("port caps: 0x%08X", port_caps); | |||||
| if (port_type & SND_SEQ_PORT_TYPE_SPECIFIC) { | |||||
| a2j_debug("SPECIFIC"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) { | |||||
| a2j_debug("MIDI_GENERIC"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_MIDI_GM) { | |||||
| a2j_debug("MIDI_GM"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_MIDI_GS) { | |||||
| a2j_debug("MIDI_GS"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_MIDI_XG) { | |||||
| a2j_debug("MIDI_XG"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_MIDI_MT32) { | |||||
| a2j_debug("MIDI_MT32"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_MIDI_GM2) { | |||||
| a2j_debug("MIDI_GM2"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_SYNTH) { | |||||
| a2j_debug("SYNTH"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_DIRECT_SAMPLE) { | |||||
| a2j_debug("DIRECT_SAMPLE"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_SAMPLE) { | |||||
| a2j_debug("SAMPLE"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_HARDWARE) { | |||||
| a2j_debug("HARDWARE"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_SOFTWARE) { | |||||
| a2j_debug("SOFTWARE"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_SYNTHESIZER) { | |||||
| a2j_debug("SYNTHESIZER"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_PORT) { | |||||
| a2j_debug("PORT"); | |||||
| } | |||||
| if (port_type & SND_SEQ_PORT_TYPE_APPLICATION) { | |||||
| a2j_debug("APPLICATION"); | |||||
| } | |||||
| if (port_type == 0) { | |||||
| a2j_debug("Ignoring port of type 0"); | |||||
| return; | |||||
| } | |||||
| if (port_caps & SND_SEQ_PORT_CAP_NO_EXPORT) { | |||||
| a2j_debug("Ignoring no-export port"); | |||||
| return; | |||||
| } | |||||
| a2j_update_port_type (self, A2J_PORT_CAPTURE, addr, port_caps, info); | |||||
| a2j_update_port_type (self, A2J_PORT_PLAYBACK, addr, port_caps, info); | |||||
| } | |||||
| void | |||||
| a2j_free_ports (jack_ringbuffer_t * ports) | |||||
| { | |||||
| struct a2j_port *port; | |||||
| int sz; | |||||
| while ((sz = jack_ringbuffer_read (ports, (char*)&port, sizeof(port)))) { | |||||
| assert (sz == sizeof(port)); | |||||
| a2j_debug("port deleted: %s", port->name); | |||||
| list_del (&port->siblings); | |||||
| a2j_port_free(port); | |||||
| } | |||||
| } | |||||
| void | |||||
| a2j_update_ports (struct a2j * self) | |||||
| { | |||||
| snd_seq_addr_t addr; | |||||
| int size; | |||||
| while ((size = jack_ringbuffer_read(self->port_add, (char *)&addr, sizeof(addr))) != 0) { | |||||
| snd_seq_port_info_t * info; | |||||
| int err; | |||||
| snd_seq_port_info_alloca(&info); | |||||
| assert (size == sizeof(addr)); | |||||
| assert (addr.client != self->client_id); | |||||
| if ((err = snd_seq_get_any_port_info(self->seq, addr.client, addr.port, info)) >= 0) { | |||||
| a2j_update_port(self, addr, info); | |||||
| } else { | |||||
| a2j_port_setdead(self->stream[A2J_PORT_CAPTURE].port_hash, addr); | |||||
| a2j_port_setdead(self->stream[A2J_PORT_PLAYBACK].port_hash, addr); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| /* -*- Mode: C ; c-basic-offset: 2 -*- */ | |||||
| /* | |||||
| * ALSA SEQ < - > JACK MIDI bridge | |||||
| * | |||||
| * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org> | |||||
| * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name> | |||||
| * | |||||
| * 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 | |||||
| */ | |||||
| #ifndef PORT_THREAD_H__1C6B5065_5556_4AC6_AA9F_44C32A9648C6__INCLUDED | |||||
| #define PORT_THREAD_H__1C6B5065_5556_4AC6_AA9F_44C32A9648C6__INCLUDED | |||||
| void | |||||
| a2j_update_port( | |||||
| struct a2j * self, | |||||
| snd_seq_addr_t addr, | |||||
| const snd_seq_port_info_t * info); | |||||
| void | |||||
| a2j_update_ports( | |||||
| struct a2j * self); | |||||
| void | |||||
| a2j_free_ports( | |||||
| jack_ringbuffer_t * ports); | |||||
| struct a2j_port * | |||||
| a2j_find_port_by_addr( | |||||
| struct a2j_stream * stream_ptr, | |||||
| snd_seq_addr_t addr); | |||||
| struct a2j_port * | |||||
| a2j_find_port_by_jack_port_name( | |||||
| struct a2j_stream * stream_ptr, | |||||
| const char * jack_port); | |||||
| #endif /* #ifndef PORT_THREAD_H__1C6B5065_5556_4AC6_AA9F_44C32A9648C6__INCLUDED */ | |||||
| @@ -23,6 +23,8 @@ | |||||
| #include <stdlib.h> | #include <stdlib.h> | ||||
| #include <jack/jack.h> | #include <jack/jack.h> | ||||
| #include <jack/metadata.h> | |||||
| #include <jack/uuid.h> | |||||
| void | void | ||||
| port_callback (jack_port_id_t port, int yn, void* arg) | port_callback (jack_port_id_t port, int yn, void* arg) | ||||
| @@ -49,6 +51,39 @@ graph_callback (void* arg) | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| void | |||||
| propchange (jack_uuid_t subject, const char* key, jack_property_change_t change) | |||||
| { | |||||
| char buf[JACK_UUID_STRING_SIZE]; | |||||
| const char* action = ""; | |||||
| switch (change) { | |||||
| case PropertyCreated: | |||||
| action = "created"; | |||||
| break; | |||||
| case PropertyChanged: | |||||
| action = "changed"; | |||||
| break; | |||||
| case PropertyDeleted: | |||||
| action = "deleted"; | |||||
| break; | |||||
| } | |||||
| if (jack_uuid_empty (subject)) { | |||||
| printf ("All properties changed!\n"); | |||||
| } else { | |||||
| jack_uuid_unparse (subject, buf); | |||||
| if (key) { | |||||
| printf ("key [%s] for %s %s\n", key, buf, action); | |||||
| } else { | |||||
| printf ("all keys for %s %s\n", buf, action); | |||||
| } | |||||
| } | |||||
| } | |||||
| int | int | ||||
| main (int argc, char *argv[]) | main (int argc, char *argv[]) | ||||
| { | { | ||||
| @@ -81,6 +116,10 @@ main (int argc, char *argv[]) | |||||
| fprintf (stderr, "cannot set graph order registration callback\n"); | fprintf (stderr, "cannot set graph order registration callback\n"); | ||||
| return 1; | return 1; | ||||
| } | } | ||||
| if (jack_set_property_change_callback (client, (JackPropertyChangeCallback) propchange, 0)) { | |||||
| fprintf (stderr, "cannot set property change callback\n"); | |||||
| return 1; | |||||
| } | |||||
| if (jack_activate (client)) { | if (jack_activate (client)) { | ||||
| fprintf (stderr, "cannot activate client"); | fprintf (stderr, "cannot activate client"); | ||||
| return 1; | return 1; | ||||
| @@ -134,21 +134,22 @@ main (int argc, char *argv[]) | |||||
| } | } | ||||
| /* then, load the internal client */ | /* then, load the internal client */ | ||||
| intclient = jack_internal_client_load (client, intclient_name, | |||||
| (JackLoadName|JackLoadInit), | |||||
| &status, load_name, load_init); | |||||
| if (status & JackFailure) { | |||||
| fprintf (stderr, "could not load %s, status = 0x%2.0x\n", | |||||
| load_name, status); | |||||
| return 2; | |||||
| } | |||||
| if (status & JackNameNotUnique) { | |||||
| intclient_name = | |||||
| jack_get_internal_client_name (client, intclient); | |||||
| fprintf (stderr, "unique internal client name `%s' assigned\n", | |||||
| intclient_name); | |||||
| } | |||||
| jack_internal_client_load (client, intclient_name, | |||||
| (JackLoadName|JackLoadInit), | |||||
| &status, intclient, load_name, load_init); | |||||
| if (status & JackFailure) { | |||||
| fprintf (stderr, "could not load %s, status = 0x%2.0x\n", | |||||
| load_name, status); | |||||
| return 2; | |||||
| } | |||||
| if (status & JackNameNotUnique) { | |||||
| intclient_name = jack_get_internal_client_name (client, intclient); | |||||
| fprintf (stderr, "unique internal client name `%s' assigned\n", | |||||
| intclient_name); | |||||
| } | |||||
| fprintf (stdout, "%s is running.\n", load_name); | fprintf (stdout, "%s is running.\n", load_name); | ||||
| if (wait_opt) { | if (wait_opt) { | ||||
| @@ -49,9 +49,11 @@ main (int argc, char *argv[]) | |||||
| /* then, get the internal client handle */ | /* then, get the internal client handle */ | ||||
| client_name = argv[1]; | client_name = argv[1]; | ||||
| intclient = jack_internal_client_handle (client, client_name, &status); | |||||
| if (status & JackFailure) { | |||||
| fprintf (stderr, "client %s not found.\n", client_name); | |||||
| if (jack_internal_client_handle (client, client_name, &status, intclient) != 0) { | |||||
| if (status & JackFailure) { | |||||
| fprintf (stderr, "client %s not found.\n", client_name); | |||||
| } | |||||
| exit (2); | exit (2); | ||||
| } | } | ||||
| @@ -27,7 +27,7 @@ printf_name2uuid (jack_client_t* client, const char* pname) | |||||
| snprintf(client_component, csize, "%s", pname); | snprintf(client_component, csize, "%s", pname); | ||||
| char *uuid = jack_get_uuid_for_client_name(client, client_component); | char *uuid = jack_get_uuid_for_client_name(client, client_component); | ||||
| if (uuid && strcmp(uuid, "-1") != 0) { | |||||
| if (uuid) { | |||||
| printf("%s%s\n", uuid, port_component ); | printf("%s%s\n", uuid, port_component ); | ||||
| } else { | } else { | ||||
| printf("%s\n",pname); | printf("%s\n",pname); | ||||
| @@ -52,6 +52,7 @@ show_usage (void) | |||||
| " input|output, can-monitor, physical, terminal\n\n"); | " input|output, can-monitor, physical, terminal\n\n"); | ||||
| fprintf (stderr, " -t, --type Display port type\n"); | fprintf (stderr, " -t, --type Display port type\n"); | ||||
| fprintf (stderr, " -u, --uuid Display uuid instead of client name (if available)\n"); | fprintf (stderr, " -u, --uuid Display uuid instead of client name (if available)\n"); | ||||
| fprintf (stderr, " -U, --port-uuid Display port uuid\n"); | |||||
| fprintf (stderr, " -h, --help Display this help message\n"); | fprintf (stderr, " -h, --help Display this help message\n"); | ||||
| fprintf (stderr, " --version Output version information and exit\n\n"); | fprintf (stderr, " --version Output version information and exit\n\n"); | ||||
| fprintf (stderr, "For more information see http://jackaudio.org/\n"); | fprintf (stderr, "For more information see http://jackaudio.org/\n"); | ||||
| @@ -73,6 +74,7 @@ main (int argc, char *argv[]) | |||||
| int show_properties = 0; | int show_properties = 0; | ||||
| int show_type = 0; | int show_type = 0; | ||||
| int show_uuid = 0; | int show_uuid = 0; | ||||
| int show_port_uuid = 0; | |||||
| int c; | int c; | ||||
| int option_index; | int option_index; | ||||
| char* aliases[2]; | char* aliases[2]; | ||||
| @@ -88,6 +90,7 @@ main (int argc, char *argv[]) | |||||
| { "properties", 0, 0, 'p' }, | { "properties", 0, 0, 'p' }, | ||||
| { "type", 0, 0, 't' }, | { "type", 0, 0, 't' }, | ||||
| { "uuid", 0, 0, 'u' }, | { "uuid", 0, 0, 'u' }, | ||||
| { "port-uuid", 0, 0, 'U' }, | |||||
| { "help", 0, 0, 'h' }, | { "help", 0, 0, 'h' }, | ||||
| { "version", 0, 0, 'v' }, | { "version", 0, 0, 'v' }, | ||||
| { 0, 0, 0, 0 } | { 0, 0, 0, 0 } | ||||
| @@ -100,7 +103,7 @@ main (int argc, char *argv[]) | |||||
| my_name ++; | my_name ++; | ||||
| } | } | ||||
| while ((c = getopt_long (argc, argv, "s:AclLphvtu", long_options, &option_index)) >= 0) { | |||||
| while ((c = getopt_long (argc, argv, "s:AclLphvtuU", long_options, &option_index)) >= 0) { | |||||
| switch (c) { | switch (c) { | ||||
| case 's': | case 's': | ||||
| server_name = (char *) malloc (sizeof (char) * strlen(optarg)); | server_name = (char *) malloc (sizeof (char) * strlen(optarg)); | ||||
| @@ -130,6 +133,9 @@ main (int argc, char *argv[]) | |||||
| case 'u': | case 'u': | ||||
| show_uuid = 1; | show_uuid = 1; | ||||
| break; | break; | ||||
| case 'U': | |||||
| show_port_uuid = 1; | |||||
| break; | |||||
| case 'h': | case 'h': | ||||
| show_usage (); | show_usage (); | ||||
| return 1; | return 1; | ||||
| @@ -178,8 +184,16 @@ main (int argc, char *argv[]) | |||||
| } | } | ||||
| jack_port_t *port = jack_port_by_name (client, ports[i]); | jack_port_t *port = jack_port_by_name (client, ports[i]); | ||||
| if (show_port_uuid) { | |||||
| char buf[64]; | |||||
| jack_uuid_t uuid; | |||||
| jack_port_uuid (port, uuid); | |||||
| uuid_unparse (uuid, buf); | |||||
| printf (" uuid: %s\n", buf); | |||||
| } | |||||
| if (show_aliases) { | |||||
| if (show_aliases) { | |||||
| int cnt; | int cnt; | ||||
| int i; | int i; | ||||
| @@ -0,0 +1,331 @@ | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| #include <unistd.h> | |||||
| #include <string.h> | |||||
| #include <getopt.h> | |||||
| #include <jack/jack.h> | |||||
| #include <jack/metadata.h> | |||||
| #include <jack/uuid.h> | |||||
| #include <jack/session.h> | |||||
| static int subject_is_client = 0; | |||||
| static int subject_is_port = 0; | |||||
| static jack_uuid_t uuid; | |||||
| static char* subject; | |||||
| static void | |||||
| show_usage (void) | |||||
| { | |||||
| fprintf (stderr, "\nUsage: jack_property [options] UUID [ key [ value [ type ] ] ]\n"); | |||||
| fprintf (stderr, "Set/Display JACK properties (metadata).\n\n"); | |||||
| fprintf (stderr, "Set options:\n"); | |||||
| fprintf (stderr, " -s, --set Set property \"key\" to \"value\" for \"UUID\" with optional MIME type \"type\"\n"); | |||||
| fprintf (stderr, " -d, --delete Remove/delete property \"key\" for \"UUID\"\n"); | |||||
| fprintf (stderr, " -d, --delete UUID Remove/delete all properties for \"UUID\"\n"); | |||||
| fprintf (stderr, " -D, --delete-all Remove/delete all properties\n"); | |||||
| fprintf (stderr, " --client Interpret UUID as a client name, not a UUID\n"); | |||||
| fprintf (stderr, " --port Interpret UUID as a port name, not a UUID\n"); | |||||
| fprintf (stderr, "Display options:\n"); | |||||
| fprintf (stderr, " -l Show all properties\n"); | |||||
| fprintf (stderr, " -l, --list UUID Show value all properties of UUID\n"); | |||||
| fprintf (stderr, " -l, --list UUID key Show value for key of UUID\n"); | |||||
| fprintf (stderr, "For more information see http://jackaudio.org/\n"); | |||||
| } | |||||
| static int | |||||
| get_subject (jack_client_t* client, char* argv[], int* optind) | |||||
| { | |||||
| if (subject_is_client) { | |||||
| char* cstr = argv[(*optind)++]; | |||||
| char* ustr; | |||||
| if ((ustr = jack_get_uuid_for_client_name (client, cstr)) == NULL) { | |||||
| fprintf (stderr, "cannot get UUID for client named %s\n", cstr); | |||||
| return -1; | |||||
| } | |||||
| if (jack_uuid_parse (ustr, uuid)) { | |||||
| fprintf (stderr, "cannot parse client UUID as UUID\n"); | |||||
| return -1; | |||||
| } | |||||
| subject = cstr; | |||||
| } else if (subject_is_port) { | |||||
| char* pstr = argv[(*optind)++]; | |||||
| jack_port_t* port; | |||||
| if ((port = jack_port_by_name (client, pstr)) == NULL) { | |||||
| fprintf (stderr, "cannot find port name %s\n", pstr); | |||||
| return -1; | |||||
| } | |||||
| jack_port_uuid (port, uuid); | |||||
| subject = pstr; | |||||
| } else { | |||||
| char* str = argv[(*optind)++]; | |||||
| if (jack_uuid_parse (str, uuid)) { | |||||
| fprintf (stderr, "cannot parse subject as UUID\n"); | |||||
| return -1; | |||||
| } | |||||
| subject = str; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| int main (int argc, char* argv[]) | |||||
| { | |||||
| jack_client_t* client = NULL; | |||||
| jack_options_t options = JackNoStartServer; | |||||
| char* key = NULL; | |||||
| char* value = NULL; | |||||
| char* type = NULL; | |||||
| int set = 1; | |||||
| int delete = 0; | |||||
| int delete_all = 0; | |||||
| int list_all = 0; | |||||
| int c; | |||||
| int option_index; | |||||
| extern int optind; | |||||
| struct option long_options[] = { | |||||
| { "set", 0, 0, 's' }, | |||||
| { "delete", 0, 0, 'd' }, | |||||
| { "delete-all", 0, 0, 'D' }, | |||||
| { "list", 0, 0, 'l' }, | |||||
| { "all", 0, 0, 'a' }, | |||||
| { "client", 0, 0, 'c' }, | |||||
| { "port", 0, 0, 'p' }, | |||||
| { 0, 0, 0, 0 } | |||||
| }; | |||||
| while ((c = getopt_long (argc, argv, "sdDlaApc", long_options, &option_index)) >= 0) { | |||||
| switch (c) { | |||||
| case 's': | |||||
| if (argc < 5) { | |||||
| show_usage (); | |||||
| exit (1); | |||||
| } | |||||
| set = 1; | |||||
| break; | |||||
| case 'd': | |||||
| if (argc < 3) { | |||||
| show_usage (); | |||||
| return 1; | |||||
| } | |||||
| set = 0; | |||||
| delete = 1; | |||||
| break; | |||||
| case 'D': | |||||
| delete = 0; | |||||
| set = 0; | |||||
| delete_all = 1; | |||||
| break; | |||||
| case 'l': | |||||
| set = 0; | |||||
| delete = 0; | |||||
| delete_all = 0; | |||||
| break; | |||||
| case 'a': | |||||
| list_all = 1; | |||||
| set = 0; | |||||
| delete = 0; | |||||
| delete_all = 0; | |||||
| break; | |||||
| case 'p': | |||||
| subject_is_port = 1; | |||||
| break; | |||||
| case 'c': | |||||
| subject_is_client = 1; | |||||
| break; | |||||
| case '?': | |||||
| default: | |||||
| show_usage (); | |||||
| exit (1); | |||||
| } | |||||
| } | |||||
| if ((client = jack_client_open ("jack-property", options, NULL)) == 0) { | |||||
| fprintf (stderr, "Cannot connect to JACK server\n"); | |||||
| exit (1); | |||||
| } | |||||
| if (delete_all) { | |||||
| if (jack_remove_all_properties (client) == 0) { | |||||
| printf ("JACK metadata successfully delete\n"); | |||||
| exit (0); | |||||
| } | |||||
| exit (1); | |||||
| } | |||||
| if (delete) { | |||||
| int args_left = argc - optind; | |||||
| if (args_left < 1) { | |||||
| show_usage (); | |||||
| exit (1); | |||||
| } | |||||
| /* argc == 3: delete all properties for a subject | |||||
| argc == 4: delete value of key for subject | |||||
| */ | |||||
| if (args_left >= 2) { | |||||
| if (get_subject (client, argv, &optind)) { | |||||
| return 1; | |||||
| } | |||||
| key = argv[optind++]; | |||||
| if (jack_remove_property (client, uuid, key)) { | |||||
| fprintf (stderr, "\"%s\" property not removed for %s\n", key, subject); | |||||
| exit (1); | |||||
| } | |||||
| } else { | |||||
| if (get_subject (client, argv, &optind)) { | |||||
| return 1; | |||||
| } | |||||
| if (jack_remove_properties (client, uuid) < 0) { | |||||
| fprintf (stderr, "cannot remove properties for UUID %s\n", subject); | |||||
| exit (1); | |||||
| } | |||||
| } | |||||
| } else if (set) { | |||||
| int args_left = argc - optind; | |||||
| if (get_subject (client, argv, &optind)) { | |||||
| return -1; | |||||
| } | |||||
| key = argv[optind++]; | |||||
| value = argv[optind++]; | |||||
| if (args_left >= 3) { | |||||
| type = argv[optind++]; | |||||
| } else { | |||||
| type = ""; | |||||
| } | |||||
| if (jack_set_property (client, uuid, key, value, type)) { | |||||
| fprintf (stderr, "cannot set value for key %s of %s\n", value, subject); | |||||
| exit (1); | |||||
| } | |||||
| } else { | |||||
| /* list properties */ | |||||
| int args_left = argc - optind; | |||||
| if (args_left >= 2) { | |||||
| /* list properties for a UUID/key pair */ | |||||
| if (get_subject (client, argv, &optind)) { | |||||
| return -1; | |||||
| } | |||||
| key = argv[optind++]; | |||||
| if (jack_get_property (uuid, key, &value, &type) == 0) { | |||||
| printf ("%s\n", value); | |||||
| free (value); | |||||
| if (type) { | |||||
| free (type); | |||||
| } | |||||
| } else { | |||||
| fprintf (stderr, "Value not found for %s of %s\n", key, subject); | |||||
| exit (1); | |||||
| } | |||||
| } else if (args_left == 1) { | |||||
| /* list all properties for a given UUID */ | |||||
| jack_description_t description; | |||||
| size_t cnt, n; | |||||
| if (get_subject (client, argv, &optind)) { | |||||
| return -1; | |||||
| } | |||||
| if ((cnt = jack_get_properties (uuid, &description)) < 0) { | |||||
| fprintf (stderr, "could not retrieve properties for %s\n", subject); | |||||
| exit (1); | |||||
| } | |||||
| for (n = 0; n < cnt; ++n) { | |||||
| if (description.properties[n].type) { | |||||
| printf ("key: %s value: %s type: %s\n", | |||||
| description.properties[n].key, | |||||
| description.properties[n].data, | |||||
| description.properties[n].type); | |||||
| } else { | |||||
| printf ("key: %s value: %s\n", | |||||
| description.properties[n].key, | |||||
| description.properties[n].data); | |||||
| } | |||||
| } | |||||
| jack_free_description (&description, 0); | |||||
| } else { | |||||
| /* list all properties */ | |||||
| jack_description_t* description; | |||||
| size_t cnt; | |||||
| size_t p; | |||||
| size_t n; | |||||
| char buf[JACK_UUID_STRING_SIZE]; | |||||
| if ((cnt = jack_get_all_properties (&description)) < 0) { | |||||
| fprintf (stderr, "could not retrieve properties for %s\n", subject); | |||||
| exit (1); | |||||
| } | |||||
| for (n = 0; n < cnt; ++n) { | |||||
| jack_uuid_unparse (description[n].subject, buf); | |||||
| printf ("%s\n", buf); | |||||
| for (p = 0; p < description[n].property_cnt; ++p) { | |||||
| if (description[n].properties[p].type) { | |||||
| printf ("key: %s value: %s type: %s\n", | |||||
| description[n].properties[n].key, | |||||
| description[n].properties[n].data, | |||||
| description[n].properties[n].type); | |||||
| } else { | |||||
| printf ("key: %s value: %s\n", | |||||
| description[n].properties[p].key, | |||||
| description[n].properties[p].data); | |||||
| } | |||||
| } | |||||
| jack_free_description (&description[n], 0); | |||||
| } | |||||
| free (description); | |||||
| } | |||||
| } | |||||
| (void) jack_client_close (client); | |||||
| return 0; | |||||
| } | |||||