/*
* jackclient.c
*
*
* this file is part of:
*
* JackDirector 0.1 --- Start/Stop Transport by midi command, play a metronome
*
* jack-director - 2014 (C) by Sergio Atzori <sergioatzori [at] gmail.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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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
*/
/*
* system include files
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <math.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#include <jack/ringbuffer.h>
#include <jack/transport.h>
/*
* my headers and definitions
*/
#include "jack-director.h"
#include "jackclient.h"
#include "util.h"
#include "config.h"
#include "string.h"
#define RBSIZE 16
#ifdef __MINGW32__
#include <pthread.h>
#endif
#ifndef WIN32
#include <signal.h>
#include <pthread.h>
#include <sys/mman.h>
#endif
#ifndef MAX
#define MAX(a,b) ( (a) < (b) ? (b) : (a) )
#endif
/* Midi clock output messages */
#define MIDI_RT_CLOCK (0xF8)
#define MIDI_RT_START (0xFA)
#define MIDI_RT_CONTINUE (0xFB)
#define MIDI_RT_STOP (0xFC)
typedef struct {
uint8_t buffer[256];
uint32_t size;
} midimsg;
unsigned short director = 1;
const double PI = 3.14;
jack_port_t *midi_input;
jack_port_t *midi_output;
jack_port_t *audio_output1;
jack_port_t *audio_output2;
jack_client_t *client;
static jack_ringbuffer_t *rb = NULL;
static pthread_mutex_t msg_thread_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t data_ready = PTHREAD_COND_INITIALIZER;
float time_beats_per_bar = 4.0;
float time_beat_type = 4.0;
double time_ticks_per_beat = 1920.0;
volatile int time_reset = 1; /* true when time values change */
unsigned long sr;
jack_nframes_t tone_length, wave_length;
jack_default_audio_sample_t *wave;
long offset = 0;
static jack_transport_state_t m_xstate = JackTransportStopped;
static int64_t song_position_sync = -1;
/* bitwise flags -- used w/ msg_filter */
// enum {
// MSG_NO_TRANSPORT = 1, /**< do not send start/stop/continue messages */
// MSG_NO_POSITION = 2 /**< do not send absolute song position */
// };
/* jack_position_t - excerpt */
struct bbtpos {
jack_position_bits_t valid; /**< which other fields are valid */
int32_t bar; /**< current bar */
int32_t beat; /**< current beat-within-bar */
int32_t tick; /**< current tick-within-beat */
double bar_start_tick; /**< number of ticks that have elapsed between frame 0 and the first beat of the current measure. */
};
static short msg_filter = 0; /** bitwise flags, MSG_NO_.. */
static struct bbtpos last_xpos; /** keep track of transport locates */
volatile int tempo_change = 0;
static double mclk_last_tick = 0.0;
// jack_nframes_t m_accum;
static void signalHandler(int sig) {
char *msgbuf[50];
printerror("signal received, exiting", true, "");
director = 0;
jack_deactivate(client);
jack_client_close(client);
jack_ringbuffer_free(rb);
exit(0);
}
static void signalUsr1(int sig) {
// jack_client_close(client);
printerror("USR1 signal received, reloading configuration", false, "");
SetDefaultValues();
ReadConfigFile();
setMetronome();
}
void InstallSignalHandler() {
signal(SIGTERM, signalHandler);
signal(SIGINT, signalHandler);
signal(SIGHUP, signalHandler);
#ifdef WIN32
signal(SIGHQUIT, signalHandler);
#endif
signal(SIGUSR1, signalUsr1);
}
static int jackProcess(jack_nframes_t nframes, void *arg) {
int i;
void* port_buf = jack_port_get_buffer(midi_input, nframes);
assert(port_buf);
// jack_nframes_t event_index = 0;
jack_nframes_t event_count = jack_midi_get_event_count(port_buf);
for(i = 0; i < event_count; i++) {
jack_midi_event_t midi_event;
int rmidi = jack_midi_event_get(&midi_event, port_buf, i);
if (rmidi == 0 && jack_ringbuffer_write_space(rb) >= sizeof(midimsg)) {
midimsg m;
m.size = midi_event.size;
memcpy(m.buffer, midi_event.buffer, MAX(sizeof(m.buffer), midi_event.size));
jack_ringbuffer_write(rb, (void *) &m, sizeof(midimsg));
}
}
jack_position_t pos;
if (jack_transport_query(client, &pos) != JackTransportRolling || current_mute) {
processSilence(nframes);
} else {
processAudio(nframes);
offset = pos.frame % wave_length;
}
if (pthread_mutex_trylock (&msg_thread_lock) == 0) {
pthread_cond_signal (&data_ready);
pthread_mutex_unlock (&msg_thread_lock);
}
jackMidiClock(nframes);
return 0;
}
static void processSilence(jack_nframes_t nframes) {
jack_default_audio_sample_t *buffer1 = (jack_default_audio_sample_t *) jack_port_get_buffer(audio_output1, nframes);
jack_default_audio_sample_t *buffer2 = (jack_default_audio_sample_t *) jack_port_get_buffer(audio_output2, nframes);
memset(buffer1, 0, sizeof(jack_default_audio_sample_t) * nframes);
memset(buffer2, 0, sizeof(jack_default_audio_sample_t) * nframes);
}
static void processAudio(jack_nframes_t nframes) {
jack_default_audio_sample_t *outs[2];
outs[0] = audio_output1;
outs[1] = audio_output2;
unsigned short count = 0;
jack_nframes_t frames_left_res = nframes;
if (offset > wave_length) {
jack_position_t pos;
offset = pos.frame % wave_length;
}
while (count < 2) {
int tmpoffset = offset;
jack_nframes_t frames_left = nframes;
jack_default_audio_sample_t *buffer = (jack_default_audio_sample_t *) jack_port_get_buffer(outs[count], nframes);
while (wave_length - tmpoffset < frames_left) {
memcpy(buffer + (nframes - frames_left), wave + tmpoffset, sizeof(jack_default_audio_sample_t) * (wave_length - tmpoffset));
frames_left -= wave_length - tmpoffset;
tmpoffset = 0;
}
if (frames_left > 0) {
memcpy(buffer + (nframes - frames_left), wave + tmpoffset, sizeof(jack_default_audio_sample_t) * frames_left);
// offset += frames_left;
}
frames_left_res = frames_left;
count++;
}
if (frames_left_res > 0) {
offset += frames_left_res;
}
}
static void jackShutdown(void *arg) {
printmessage("JACK shut down, exiting", "");
exit(1);
}
static void midiAction(midimsg* event) {
if (event->size == 0) {
return;
}
unsigned short type = event->buffer[0] & 0xf0;
unsigned short channel = event->buffer[0] & 0xf;
char msgbuf[256], suffix[32];
jack_position_t *pos;
jack_transport_state_t tstate = jack_transport_query(client, pos);
current_transport = (int) !(tstate == JackTransportStopped);
switch (type) {
case 0x90:
// assert(event->size == 3);
if (channel == config.midich) {
if ((int) event->buffer[1] == config.ntrans && (int) event->buffer[2] >= config.nthres) {
if (current_transport == 0) {
printmessage("Starting Transport", "");
jack_transport_start(client);
current_transport = 1;
} else {
printmessage("Stopping Transport", "");
jack_transport_stop(client);
current_transport = 0;
}
}
if ((int) event->buffer[1] == config.nstart && (int) event->buffer[2] >= config.nthres) {
if (current_transport == 0) {
printmessage("Starting Transport", "");
jack_transport_start(client);
current_transport = 1;
}
}
if ((int) event->buffer[1] == config.nstop && (int) event->buffer[2] >= config.nthres) {
if (current_transport == 1) {
printmessage("Stopping Transport", "");
jack_transport_stop(client);
current_transport = 0;
}
}
}
sprintf(msgbuf, "note on (channel %2d): pitch %3d, velocity %3d", channel, event->buffer[1], event->buffer[2]);
break;
case 0x80:
// assert(event->size == 3);
sprintf(msgbuf, "note off (channel %2d): pitch %3d, velocity %3d", channel, event->buffer[1], event->buffer[2]);
break;
case 0xb0:
// assert(event->size == 3);
sprintf(msgbuf, "control change (channel %2d): controller %3d, value %3d", channel, event->buffer[1], event->buffer[2]);
break;
case 0xc0:
// assert(event->size == 2);
sprintf(msgbuf, "program change (channel %2d): program %3d", channel, event->buffer[1]);
midiProgramChange((unsigned short) channel, (unsigned short) event->buffer[1]);
break;
default:
// assert(event->size == 3);
sprintf(msgbuf, "type %02x control change (channel %2d): val1 %3d, val2 %3d", type, channel, event->buffer[1], event->buffer[2]);
break;
}
sprintf(suffix, " (event bytes:%i)", (int) event->size);
if (config.debug > 3) {
printmessage(msgbuf, suffix);
}
}
/**
* calculate song position (14 bit integer)
* from current jack BBT info.
*
* see "Song Position Pointer" at
* http://www.midi.org/techspecs/midimessages.php
*
* Because this value is also used internally to sync/send
* start/continue realtime messages, a 64 bit integer
* is used to cover the full range of jack transport.
*/
static const int64_t calc_song_pos(jack_position_t *xpos, int off) {
if (!(xpos->valid & JackPositionBBT)) return -1;
if (off < 0) {
/* auto offset */
if (xpos->bar == 1 && xpos->beat == 1 && xpos->tick == 0) off = 0;
else off = rintf(xpos->beats_per_minute * 4.0 * (config.mcresyncdelay / (double)1000) / 60.0);
}
/* MIDI Beat Clock: 24 ticks per quarter note
* one MIDI-beat = six MIDI clocks
* -> 4 MIDI-beats per quarter note (jack beat)
* Note: jack counts bars and beats starting at 1
*/
int64_t pos =
off
+ 4 * ((xpos->bar - 1) * xpos->beats_per_bar + (xpos->beat - 1))
+ floor(4.0 * xpos->tick / xpos->ticks_per_beat);
return pos;
}
void midiProgramChange(unsigned short channel, unsigned short program) {
if (midi_prg[channel][program].bpm != NULL) {
char temp[256];
unsigned short skip = 0;
sprintf(temp, "%i (ch%i) -> ", program, channel);
MIDI_CMD pc = midi_prg[channel][program];
if (pc.ignore && (current_prg == program && current_midich == channel)) {
skip = 1;
}
if (skip) {
sprintf(temp, "%s [ IGNORE ]", temp);
} else {
if (current_bpm != pc.bpm) {
tempo_change = 1;
}
current_bpm = pc.bpm;
time_reset = 1;
sprintf(temp, "%s bpm:%i", temp, pc.bpm);
current_mute = pc.mute;
if (pc.mute) {
sprintf(temp, "%s mute", temp);
}
if (pc.stop) {
current_transport = 0;
jack_transport_stop(client);
sprintf(temp, "%s stop", temp);
}
if (pc.nolocate == 0) {
current_locate = pc.locate;
jack_transport_locate(client, current_locate);
sprintf(temp, "%s locate:%i", temp, pc.locate);
}
}
setMetronome();
printmessage("ProgramChange: ", temp);
}
current_prg = program;
current_midich = channel;
}
static const int64_t send_pos_message(void* port_buf, jack_position_t *xpos, int off) {
if (config.mcnoposition) return -1;
uint8_t *buffer;
const int64_t bcnt = calc_song_pos(xpos, off);
/* send '0xf2' Song Position Pointer.
* This is an internal 14 bit register that holds the number of
* MIDI beats (1 beat = six MIDI clocks) since the start of the song.
*/
if (bcnt < 0 || bcnt >= 16384) {
return -1;
}
buffer = jack_midi_event_reserve(port_buf, 0, 3);
if(!buffer) {
return -1;
}
buffer[0] = 0xf2;
buffer[1] = (bcnt)&0x7f; // LSB
buffer[2] = (bcnt>>7)&0x7f; // MSB
return bcnt;
}
/**
* send 1 byte MIDI Message
* @param port_buf buffer to write event to
* @param time sample offset of event
* @param rt_msg message byte
*/
static void send_rt_message(void* port_buf, jack_nframes_t time, uint8_t rt_msg) {
uint8_t *buffer;
buffer = jack_midi_event_reserve(port_buf, time, 1);
if(buffer) {
buffer[0] = rt_msg;
}
}
/**
* compare two BBT positions
*/
static int pos_changed (struct bbtpos *xp0, jack_position_t *xp1) {
if (!(xp0->valid & JackPositionBBT)) return -1;
if (!(xp1->valid & JackPositionBBT)) return -2;
if ( xp0->bar == xp1->bar
&& xp0->beat == xp1->beat
&& xp0->tick == xp1->tick
) return 0;
return 1;
}
/**
* copy relevant BBT info from jack_position_t
*/
static void remember_pos (struct bbtpos *xp0, jack_position_t *xp1) {
if (!(xp1->valid & JackPositionBBT)) return;
xp0->valid = xp1->valid;
xp0->bar = xp1->bar;
xp0->beat = xp1->beat;
xp0->tick = xp1->tick;
xp0->bar_start_tick = xp1->bar_start_tick;
}
void jackMidiClock(jack_nframes_t nframes) { /* Based on JackMidiClock 0.4.0-bad61589 */
jack_position_t xpos;
double samples_per_beat;
jack_nframes_t bbt_offset = 0;
int ticks_sent_this_cycle = 0;
/* query jack transport state */
jack_transport_state_t xstate = jack_transport_query(client, &xpos);
void* port_buf = jack_port_get_buffer(midi_output, nframes);
/* prepare MIDI buffer */
jack_midi_clear_buffer(port_buf);
// if (client_state != Run) {
// return 0;
// }
/* send position updates if stopped and located */
if (xstate == JackTransportStopped && xstate == m_xstate) {
if (pos_changed(&last_xpos, &xpos) > 0) {
song_position_sync = send_pos_message(port_buf, &xpos, -1);
}
}
remember_pos(&last_xpos, &xpos);
/* send RT messages start/stop/continue if transport state changed */
if( xstate != m_xstate ) {
switch(xstate) {
case JackTransportStopped:
if (config.mcnotransport == 0) {
send_rt_message(port_buf, 0, MIDI_RT_STOP);
}
song_position_sync = send_pos_message(port_buf, &xpos, -1);
break;
case JackTransportRolling:
/* handle transport locate while rolling.
* jack transport state changes Rolling -> Starting -> Rolling
*/
if(m_xstate == JackTransportStarting && (config.mcnoposition == 0)) {
if (song_position_sync < 0) {
/* send stop IFF not stopped, yet */
send_rt_message(port_buf, 0, MIDI_RT_STOP);
}
if (song_position_sync != 0) {
/* re-set 'continue' message sync point */
if ((song_position_sync = send_pos_message(port_buf, &xpos, -1)) < 0) {
if (config.mcnotransport == 0) {
send_rt_message(port_buf, 0, MIDI_RT_CONTINUE);
}
}
} else {
/* 'start' at 0, don't queue 'continue' message */
song_position_sync = -1;
}
break;
}
case JackTransportStarting:
if(m_xstate == JackTransportStarting) {
break;
}
if( xpos.frame == 0 ) {
if (config.mcnotransport == 0) {
send_rt_message(port_buf, 0, MIDI_RT_START);
song_position_sync = 0;
}
} else {
/* only send continue message here if song-position
* is not used .
* w/song-pos it queued just-in-time
*/
if (config.mcnotransport == 0 && config.mcnoposition == 0) {
send_rt_message(port_buf, 0, MIDI_RT_CONTINUE);
}
}
break;
default:
break;
}
/* initial beat tick */
if (xstate == JackTransportRolling
&& ((xpos.frame == 0) || (config.mcnoposition))
) {
send_rt_message(port_buf, 0, MIDI_RT_CLOCK);
}
mclk_last_tick = xpos.frame;
m_xstate = xstate;
}
if((xstate != JackTransportRolling)) {
return 0;
}
/* calculate clock tick interval */
samples_per_beat = (double) xpos.frame_rate * 60.0 / (double) current_bpm;
/* quarter-notes per beat is [usually] independent of the meter:
*
* certainly for 2/4, 3/4, 4/4 etc.
* should be true as well for 6/8, 2/2
* TODO x-check w/jack-timecode-master implementations
*
* quarter_notes_per_beat = xpos.beat_type / 4.0; // ?!
*/
const double quarter_notes_per_beat = 1.0;
/* MIDI Beat Clock: Send 24 ticks per quarter note */
const double samples_per_quarter_note = samples_per_beat / quarter_notes_per_beat;
const double clock_tick_interval = samples_per_quarter_note / 24.0;
/* send clock ticks for this cycle */
while(tempo_change == 0) {
const double next_tick = mclk_last_tick + clock_tick_interval;
const int64_t next_tick_offset = llrint(next_tick) - xpos.frame - bbt_offset;
if (next_tick_offset >= nframes) break;
if (next_tick_offset >= 0) {
if (song_position_sync > 0 && (config.mcnoposition == 0)) {
/* send 'continue' realtime message on time */
const int64_t sync = calc_song_pos(&xpos, 0);
/* 4 MIDI-beats per quarter note (jack beat) */
if (sync + ticks_sent_this_cycle / 4 >= song_position_sync) {
if (config.mcnotransport == 0) {
send_rt_message(port_buf, next_tick_offset, MIDI_RT_CONTINUE);
}
song_position_sync = -1;
}
}
/* enqueue clock tick */
send_rt_message(port_buf, next_tick_offset, MIDI_RT_CLOCK);
}
mclk_last_tick = next_tick;
ticks_sent_this_cycle++;
}
tempo_change = 0;
}
/* JACK timebase callback.
*
* Runs in the process thread. Realtime, must not wait.
*/
static void jackTimebase(jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos, int new_pos, void *arg) {
double min; /* minutes since frame 0 */
long abs_tick; /* ticks since frame 0 */
long abs_beat; /* beats since frame 0 */
if (new_pos || time_reset) {
pos->valid = JackPositionBBT;
pos->beats_per_bar = time_beats_per_bar;
pos->beat_type = time_beat_type;
pos->ticks_per_beat = time_ticks_per_beat;
pos->beats_per_minute = current_bpm;
time_reset = 0; /* time change complete */
/* Compute BBT info from frame number. This is relatively
* simple here, but would become complex if we supported tempo
* or time signature changes at specific locations in the
* transport timeline. */
min = pos->frame / ((double) pos->frame_rate * 60.0);
abs_tick = min * pos->beats_per_minute * pos->ticks_per_beat;
abs_beat = abs_tick / pos->ticks_per_beat;
pos->bar = abs_beat / pos->beats_per_bar;
pos->beat = abs_beat - (pos->bar * pos->beats_per_bar) + 1;
pos->tick = abs_tick - (abs_beat * pos->ticks_per_beat);
pos->bar_start_tick = pos->bar * pos->beats_per_bar *
pos->ticks_per_beat;
pos->bar++; /* adjust start to bar 1 */
char *msgbuf[128];
sprintf(msgbuf, "Frame: %" PRIu32 "\tBBT: %3" PRIi32 "|%" PRIi32 "|%04" PRIi32, pos->frame, pos->bar, pos->beat, pos->tick);
printmessage("new position: ", msgbuf);
} else {
/* Compute BBT info based on previous period. */
pos->tick += nframes * pos->ticks_per_beat * pos->beats_per_minute
/ (pos->frame_rate * 60);
while (pos->tick >= pos->ticks_per_beat) {
pos->tick -= pos->ticks_per_beat;
if (++pos->beat > pos->beats_per_bar) {
pos->beat = 1;
++pos->bar;
pos->bar_start_tick +=
pos->beats_per_bar
* pos->ticks_per_beat;
}
}
}
}
void setMetronome() {
jack_default_audio_sample_t scale;
int i, attack_length, decay_length;
double *amp;
double max_amp = (config.mamplitude / (double)100);
int attack_percent = config.mattack, decay_percent = config.mdecay, dur_arg = config.mduration;
jack_status_t status;
char msgbuf[512];
sprintf(msgbuf, "Amplitude set is %.1f", msgbuf, max_amp);
sprintf(msgbuf, "%s\nAttack set is %i%", msgbuf, attack_percent);
sprintf(msgbuf, "%s\nDecay set is %i%", msgbuf, decay_percent);
sprintf(msgbuf, "%s\nDuration set is %i%", msgbuf, dur_arg);
printerror(msgbuf, false, "");
/* setup wave table parameters */
wave_length = 60 * sr / current_bpm;
tone_length = sr * dur_arg / 1000;
attack_length = tone_length * attack_percent / 100;
decay_length = tone_length * decay_percent / 100;
scale = 2 * PI * config.mfreq / sr;
if (tone_length >= wave_length) {
char xbuf[128];
sprintf(xbuf, "Invalid duration (tone length = %u, wave length = %u", tone_length, wave_length);
printmessage("Exiting: ", xbuf);
exit(1);
}
if (attack_length + decay_length > (int)tone_length) {
char xbuf[128];
sprintf(xbuf, "Invalid attack/decay");
printmessage("Exiting: ", xbuf);
exit(1);
}
/* Build the wave table */
wave = (jack_default_audio_sample_t *) malloc (wave_length * sizeof(jack_default_audio_sample_t));
amp = (double *) malloc (tone_length * sizeof(double));
for (i = 0; i < attack_length; i++) {
amp[i] = max_amp * i / ((double) attack_length);
}
for (i = attack_length; i < (int)tone_length - decay_length; i++) {
amp[i] = max_amp;
}
for (i = (int)tone_length - decay_length; i < (int)tone_length; i++) {
amp[i] = - max_amp * (i - (double) tone_length) / ((double) decay_length);
}
for (i = 0; i < (int)tone_length; i++) {
wave[i] = amp[i] * sin (scale * i);
}
for (i = tone_length; i < (int)wave_length; i++) {
wave[i] = 0;
}
}
int jackClient() {
// fprintf(stdout, "config.name=%s", config.name);
// fflush(stdout);
if ((client = jack_client_open(AllocString(config.name), JackNoStartServer, NULL)) == 0) {
printmessage("JACK server not running?", "");
return 1;
}
current_bpm = config.bpm;
char msgbuf[512];
sr = jack_get_sample_rate(client);
sprintf(msgbuf, "SampleRate set is %" PRIu32 "Hz", sr);
printerror(msgbuf, false, "");
setMetronome();
rb = jack_ringbuffer_create(RBSIZE * sizeof(midimsg));
jack_set_process_callback(client, jackProcess, 0);
jack_on_shutdown(client, jackShutdown, 0);
midi_input = jack_port_register(client, "midi_in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
midi_output = jack_port_register(client, "midi_out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
audio_output1 = jack_port_register(client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
audio_output2 = jack_port_register(client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 1);
if (jack_activate(client)) {
printmessage("Cannot activate client", "");
return 1;
}
/* Become timebase master */
if (jack_set_timebase_callback(client, 0, jackTimebase, NULL) != 0) {
printerror("Unable to take over timebase.", true, "");
}
jack_transport_stop(client);
#ifndef WIN32
if (mlockall (MCL_CURRENT | MCL_FUTURE)) {
printerror("Warning: Can not lock memory.", true, "");
}
#endif
pthread_mutex_lock(&msg_thread_lock);
while(director) {
const int mqlen = jack_ringbuffer_read_space(rb) / sizeof(midimsg);
int i;
for (i=0; i < mqlen; ++i) {
midimsg m;
jack_ringbuffer_read(rb, (char *) &m, sizeof(midimsg));
midiAction(&m);
}
pthread_cond_wait(&data_ready, &msg_thread_lock);
}
pthread_mutex_unlock(&msg_thread_lock);
jack_client_close(client);
return 0;
}