1
Fork 0
a2jmidid/a2jmidid.c

617 lines
12 KiB
C

/*
* ALSA SEQ < - > JACK MIDI bridge
*
* Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org>
* Copyright (c) 2007,2008,2009,2011 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 "config.h"
#include <signal.h>
#include <semaphore.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#include <jack/ringbuffer.h>
#if HAVE_DBUS_1
# include <dbus/dbus.h>
#endif
#include <getopt.h>
#include "list.h"
#include "structs.h"
#include "port.h"
#include "port_thread.h"
#include "port_hash.h"
#include "log.h"
#if HAVE_DBUS_1
# include "dbus.h"
#endif
#include "a2jmidid.h"
#include "paths.h"
#include "conf.h"
#include "jack.h"
#include "siginfo/siginfo.h"
#if HAVE_GITVERSION_H
#include "gitversion.h"
#endif
#include "dbus_iface_control.h"
#define MAIN_LOOP_SLEEP_INTERVAL 50 // in milliseconds
bool g_keep_walking = true;
bool g_keep_alsa_walking = false;
bool g_stop_request = false;
static bool g_started = false;
struct a2j * g_a2j = NULL;
size_t g_max_jack_port_name_size;
bool g_disable_port_uniqueness = false;
bool g_filter_note_on = true;
bool g_a2j_export_hw_ports = false;
char * g_a2j_jack_server_name = "default";
static
void
a2j_sigint_handler(
int i)
{
((void)(i)); /* unreferenced parameter */
g_keep_walking = false;
}
static
bool
a2j_stream_init(
struct a2j * self,
int dir)
{
struct a2j_stream *str = &self->stream[dir];
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_attach(
struct a2j_stream * stream_ptr)
{
((void)(stream_ptr)); /* unreferenced parameter */
}
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_info("port deleted: %s", port_ptr->name);
a2j_port_free(port_ptr);
}
}
static
void
a2j_stream_close(
struct a2j * self,
int dir)
{
struct a2j_stream *str = &self->stream[dir];
if (str->codec)
snd_midi_event_free(str->codec);
if (str->new_ports)
jack_ringbuffer_free(str->new_ports);
}
struct a2j * a2j_new(void)
{
int error;
void * thread_status;
struct a2j *self = calloc(1, sizeof(struct a2j));
a2j_debug("midi: new");
if (!self)
{
a2j_error("calloc() failed to allocate a2j struct");
goto fail;
}
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;
}
error = snd_seq_open(&self->seq, "hw", SND_SEQ_OPEN_DUPLEX, 0);
if (error < 0)
{
a2j_error("failed to open alsa seq");
goto close_playback_stream;
}
error = snd_seq_set_client_name(self->seq, "a2jmidid");
if (error < 0)
{
a2j_error("snd_seq_set_client_name() failed");
goto close_seq_client;
}
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);
if (self->port_id < 0)
{
a2j_error("snd_seq_create_simple_port() failed");
goto close_seq_client;
}
self->client_id = snd_seq_client_id(self->seq);
if (self->client_id < 0)
{
a2j_error("snd_seq_client_id() failed");
goto close_seq_client;
}
self->queue = snd_seq_alloc_queue(self->seq);
if (self->queue < 0)
{
a2j_error("snd_seq_alloc_queue() failed");
goto close_seq_client;
}
snd_seq_start_queue(self->seq, self->queue, 0);
a2j_stream_attach(self->stream + A2J_PORT_CAPTURE);
a2j_stream_attach(self->stream + A2J_PORT_PLAYBACK);
error = snd_seq_nonblock(self->seq, 1);
if (error < 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]);
self->jack_client = a2j_jack_client_create(self, A2J_JACK_CLIENT_NAME, g_a2j_jack_server_name);
if (self->jack_client == NULL)
{
goto free_self;
}
if (sem_init(&self->io_semaphore, 0, 0) < 0)
{
a2j_error("can't create IO semaphore");
goto close_jack_client;
}
if (jack_activate(self->jack_client))
{
a2j_error("can't activate jack client");
goto sem_destroy;
}
g_keep_alsa_walking = true;
if (pthread_create(&self->alsa_input_thread, NULL, a2j_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 */
error = snd_seq_connect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
if (error < 0)
{
a2j_error("snd_seq_connect_from() failed");
goto join_input_thread;
}
if (pthread_create(&self->alsa_output_thread, NULL, a2j_alsa_output_thread, self) < 0)
{
a2j_error("cannot start ALSA output thread");
goto disconnect;
}
return self;
disconnect:
g_keep_alsa_walking = false; /* tell alsa threads to stop */
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->io_semaphore);
close_jack_client:
error = jack_client_close(self->jack_client);
if (error != 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);
fail:
return NULL;
}
static void a2j_destroy(struct a2j * self)
{
int error;
void * thread_status;
a2j_debug("midi: delete");
g_keep_alsa_walking = false; /* tell alsa threads to stop */
/* do something that we need to do anyway and will wake the input thread, then join */
snd_seq_disconnect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
pthread_join(self->alsa_input_thread, &thread_status);
/* wake output thread and join, then destroy the semaphore */
sem_post(&self->io_semaphore);
pthread_join(self->alsa_output_thread, &thread_status);
sem_destroy(&self->io_semaphore);
jack_ringbuffer_reset(self->port_add);
jack_deactivate(self->jack_client);
a2j_stream_detach(self->stream + A2J_PORT_CAPTURE);
a2j_stream_detach(self->stream + A2J_PORT_PLAYBACK);
error = jack_client_close(self->jack_client);
if (error != 0)
{
a2j_error("Cannot close jack client (%d)", error);
}
snd_seq_close(self->seq);
self->seq = NULL;
a2j_stream_close(self, A2J_PORT_PLAYBACK);
a2j_stream_close(self, A2J_PORT_CAPTURE);
jack_ringbuffer_free(self->outbound_events);
jack_ringbuffer_free(self->port_add);
jack_ringbuffer_free(self->port_del);
free(self);
}
bool a2j_start(void)
{
if (g_started)
{
a2j_error("Bridge already started");
return false;
}
a2j_info("Bridge starting...");
a2j_info("Using JACK server '%s'", g_a2j_jack_server_name);
a2j_info("Hardware ports %s be exported.", g_a2j_export_hw_ports ? "will": "will not");
g_a2j = a2j_new();
if (g_a2j == NULL)
{
a2j_error("a2j_new() failed.");
return false;
}
a2j_info("Bridge started");
#if HAVE_DBUS_1
if (a2j_dbus_is_available())
{
a2j_dbus_signal_emit_bridge_started();
}
#endif
g_started = true;
return true;
}
bool a2j_stop(void)
{
if (!g_started)
{
a2j_error("Bridge already stopped");
return false;
}
a2j_info("Bridge stopping...");
a2j_destroy(g_a2j);
g_a2j = NULL;
a2j_info("Bridge stopped");
g_started = false;
#if HAVE_DBUS_1
if (a2j_dbus_is_available())
{
a2j_dbus_signal_emit_bridge_stopped();
}
#endif
return true;
}
bool
a2j_is_started(void)
{
return g_started;
}
static
void
a2j_help(
const char * self)
{
a2j_info("Usage: %s [-j jack-server] [-e | --export-hw] [-u] [-n]", self);
a2j_info("Defaults:");
a2j_info("-j default");
}
int
main(
int argc,
char *argv[])
{
bool dbus;
struct stat st;
char timestamp_str[26];
//test_list_sort();
st.st_mtime = 0;
stat(argv[0], &st);
ctime_r(&st.st_mtime, timestamp_str);
timestamp_str[24] = 0;
g_max_jack_port_name_size = jack_port_name_size();
if (!a2j_paths_init())
{
goto fail;
}
#if HAVE_DBUS_1
dbus = argc == 2 && strcmp(argv[1], "dbus") == 0;
#else
dbus = false;
#endif
if (!a2j_log_init(dbus))
{
goto fail_paths_uninit;
}
if (!dbus)
{
struct option long_opts[] = { { "export-hw", 0, 0, 'e' }, { 0, 0, 0, 0 } };
int option_index = 0;
int c;
while ((c = getopt_long(argc, argv, "j:eu", long_opts, &option_index)) != -1)
{
switch (c)
{
case 'j':
g_a2j_jack_server_name = strdup(optarg);
break;
case 'e':
g_a2j_export_hw_ports = true;
break;
case 'u':
g_disable_port_uniqueness = true;
break;
case 'n':
g_filter_note_on = false;
break;
default:
a2j_help(argv[0]);
return 1;
}
}
}
else
{
//a2j_conf_load();
}
if (dbus)
{
a2j_info("----------------------------");
}
a2j_info("JACK MIDI <-> ALSA sequencer MIDI bridge, version " A2J_VERSION
#if (HAVE_GITVERSION_H)
" (" GIT_VERSION ")"
#endif
" built on %s", timestamp_str);
a2j_info("Copyright 2006,2007 Dmitry S. Baikov");
a2j_info("Copyright 2007,2008,2009,2011,2012 Nedko Arnaudov");
if (dbus)
{
a2j_info("----------------------------");
a2j_info("Activated.");
}
else
{
a2j_info("");
}
/* setup our SIGSEGV magic that prints nice stack in our logfile */
if (dbus)
{
setup_siginfo();
}
signal(SIGINT, &a2j_sigint_handler);
signal(SIGTERM, &a2j_sigint_handler);
if (dbus)
{
#if HAVE_DBUS_1
if (!a2j_dbus_init())
{
a2j_error("a2j_dbus_init() failed.");
goto fail_uninit_log;
}
#endif
}
else
{
if (!a2j_start())
{
goto fail_uninit_log;
}
a2j_info("Press ctrl-c to stop the bridge");
}
while (g_keep_walking)
{
if (dbus)
{
#if HAVE_DBUS_1
if (!a2j_dbus_run(MAIN_LOOP_SLEEP_INTERVAL))
{
a2j_warning("Disconnect message was received from D-Bus.");
break;
}
#endif
}
else
{
usleep(MAIN_LOOP_SLEEP_INTERVAL * 1000);
}
if (g_stop_request)
{
g_stop_request = false;
a2j_stop();
if (!dbus)
{
break;
}
}
if (g_started)
{
a2j_free_ports(g_a2j->port_del);
a2j_update_ports(g_a2j);
}
}
if (g_started)
{
a2j_stop();
}
#if HAVE_DBUS_1
if (dbus)
{
a2j_dbus_uninit();
a2j_info("Deactivated.");
a2j_info("----------------------------");
}
#endif
fail_uninit_log:
a2j_log_uninit();
fail_paths_uninit:
a2j_paths_uninit();
fail:
return 0;
}