diff --git a/zalsa/Makefile.am b/zalsa/Makefile.am new file mode 100644 index 0000000..7f52b0d --- /dev/null +++ b/zalsa/Makefile.am @@ -0,0 +1,20 @@ +MAINTAINERCLEANFILES = Makefile.in + +# +# in-process ALSA/JACK MIDI bridge client +# + +zalsadir = $(ADDON_DIR) + +zalsa_LTLIBRARIES = zalsa_in.la zalsa_out.la + +zalsa_in_la_CPPFLAGS = -DAPPNAME=\"zalsa_in\" -DVERSION=\"0.4.0\" +zalsa_in_la_SOURCES = zita-a2j.cc alsathread.cc jackclient.cc pxthread.cc lfqueue.cc +zalsa_in_la_LDFLAGS = -module -avoid-version -lzita-resampler -lzita-alsa-pcmi -ljack -lasound -lpthread -lm -lrt @OS_LDFLAGS@ + +zalsa_out_la_CPPFLAGS = -DAPPNAME=\"zalsa_out\" -DVERSION=\"0.4.0\" +zalsa_out_la_SOURCES = zita-j2a.cc alsathread.cc jackclient.cc pxthread.cc lfqueue.cc +zalsa_out_la_LDFLAGS = -module -avoid-version -lzita-resampler -lzita-alsa-pcmi -ljack -lasound -lpthread -lm -lrt @OS_LDFLAGS@ + +noinst_HEADERS = alsathread.h jackclient.h lfqueue.h pxthread.h + diff --git a/zalsa/alsathread.cc b/zalsa/alsathread.cc new file mode 100644 index 0000000..15293ef --- /dev/null +++ b/zalsa/alsathread.cc @@ -0,0 +1,226 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#include +#include +#include +#include +#include +#include "alsathread.h" + + +Alsathread::Alsathread (Alsa_pcmi *alsadev, int mode) : + _alsadev (alsadev ), + _mode (mode), + _state (INIT), + _fsize (alsadev->fsize ()), + _audioq (0), + _commq (0), + _alsaq (0) +{ + // Compute DLL filter coefficients. + _dt = (double) _fsize / _alsadev->fsamp (); + _w1 = 2 * M_PI * 0.1 * _dt; + _w2 = _w1 * _w1; + _w1 *= 1.6; + // Time wraps around after 2^28 usecs. + _tq = ldexp (1e-6, 28); +} + + +Alsathread::~Alsathread (void) +{ + _alsadev->pcm_stop (); +} + + +int Alsathread::start (Lfq_audio *audioq, Lfq_int32 *commq, Lfq_adata *alsaq, int rtprio) +{ + // Start the ALSA thread. + _audioq = audioq; + _commq = commq; + _alsaq = alsaq; + _state = WAIT; + if (thr_start (SCHED_FIFO, rtprio, 0x10000)) return 1; + return 0; +} + + +void Alsathread::send (int k, double t) +{ + Adata *D; + + // Send (state, frame count, timestamp) to Jack thread. + if (_alsaq->wr_avail ()) + { + D = _alsaq->wr_datap (); + D->_state = _state; + D->_nsamp = k; + D->_timer = t; + _alsaq->wr_commit (); + } +} + + +// The following two functions transfer data between the audio queue +// and the ALSA device. Note that we do *not* check the queue's fill +// state, and it may overrun or underrun. It actually will in the first +// few iterations and in error conditions. This is entirely intentional. +// The queue keeps correct read and write counters even in that case, +// and the main control loop and error recovery depend on it working +// and being used in this way. + +int Alsathread::capture (void) +{ + int c, n, k; + float *p; + + // Start reading from ALSA device. + _alsadev->capt_init (_fsize); + if (_state == PROC) + { + // Input frames from the ALSA device to the audio queue. + // The outer loop takes care of wraparound. + for (n = _fsize; n; n -= k) + { + p = _audioq->wr_datap (); // Audio queue write pointer. + k = _audioq->wr_linav (); // Number of frames that can be + if (k > n) k = n; // written without wraparound. + for (c = 0; c < _audioq->nchan (); c++) + { + // Copy and interleave one channel. + _alsadev->capt_chan (c, p + c, k, _audioq->nchan ()); + } + _audioq->wr_commit (k); // Update audio queue state. + } + } + // Finish reading from ALSA device. + _alsadev->capt_done (_fsize); + return _fsize; +} + + +int Alsathread::playback (void) +{ + int c, n, k; + float *p; + + // Start writing to ALSA device. + _alsadev->play_init (_fsize); + c = 0; + if (_state == PROC) + { + // Output frames from the audio queue to the ALSA device. + // The outer loop takes care of wraparound. + for (n = _fsize; n; n -= k) + { + p = _audioq->rd_datap (); // Audio queue read pointer. + k = _audioq->rd_linav (); // Number of frames that can + if (k > n) k = n; // be read without wraparound. + for (c = 0; c < _audioq->nchan (); c++) + { + // De-interleave and copy one channel. + _alsadev->play_chan (c, p + c, k, _audioq->nchan ()); + } + _audioq->rd_commit (k); // Update audio queue state. + } + } + // Clear all or remaining channels. + while (c < _alsadev->nplay ()) _alsadev->clear_chan (c++, _fsize); + // Finish writing to ALSA device. + _alsadev->play_done (_fsize); + return _fsize; +} + + +void Alsathread::thr_main (void) +{ + int na, nu; + double tw, er; + + _alsadev->pcm_start (); + while (_state != TERM) + { + // Wait for next cycle, then take timestamp. + na = _alsadev->pcm_wait (); + tw = 1e-6 * (int)(jack_get_time () & 0x0FFFFFFF); + // Check for errors - requires restart. + if (_alsadev->state () && (na == 0)) + { + _state = WAIT; + send (0, 0); + continue; + } + + // Check for commands from the Jack thread. + if (_commq->rd_avail ()) + { + _state = _commq->rd_int32 (); + if (_state == PROC) _first = true; + if (_state == TERM) send (0, 0); + } + + // We could have more than one period. + nu = 0; + while (na >= _fsize) + { + // Transfer frames. + if (_mode == PLAY) nu += playback (); + else nu += capture (); + // Update loop condition. + na -= _fsize; + // Run the DLL if in PROC state. + if (_state == PROC) + { + if (_first) + { + // Init DLL in first iteration. + _first = false; + _t0 = tw; + _t1 = tw + _dt; + // Required for initial delay calculation. + if (_mode == PLAY) nu -= _fsize; + else nu += _fsize; + } + else + { + // Update the DLL. + er = tw - _t1; + // Check for time wraparound. + if (er < -200) + { + _t1 -= _tq; + er = tw - _t1; + } + // If we have more than one period, use + // the time error only for the last one. + if (na >= _fsize) er = 0; + _t0 = _t1; + _t1 += _w1 * er + _dt; + _dt += _w2 * er; + } + } + } + + // Send number of frames used and timestamp to Jack thread. + if (_state == PROC) send (nu, _t1); + } + _alsadev->pcm_stop (); +} diff --git a/zalsa/alsathread.h b/zalsa/alsathread.h new file mode 100644 index 0000000..b9fb68f --- /dev/null +++ b/zalsa/alsathread.h @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#ifndef __ALSATHREAD_H +#define __ALSATHREAD_H + + +#include +#include "pxthread.h" +#include "lfqueue.h" + + +class Alsathread : public Pxthread +{ +public: + + enum { INIT, WAIT, PROC, TERM }; + enum { PLAY, CAPT }; + + Alsathread (Alsa_pcmi *alsadev, int mode); + virtual ~Alsathread (void); + virtual void thr_main (void); + + int start (Lfq_audio *audioq, Lfq_int32 *commq, Lfq_adata *alsaq, int rtprio); + +private: + + void send (int k, double t); + int capture (void); + int playback (void); + + Alsa_pcmi *_alsadev; + int _mode; + int _state; + int _fsize; + Lfq_audio *_audioq; + Lfq_int32 *_commq; + Lfq_adata *_alsaq; + bool _first; + double _tq; + double _t0; + double _t1; + double _dt; + double _w1; + double _w2; +}; + + +#endif diff --git a/zalsa/jackclient.cc b/zalsa/jackclient.cc new file mode 100644 index 0000000..3812dea --- /dev/null +++ b/zalsa/jackclient.cc @@ -0,0 +1,510 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- +#include + +#include +#include +#include +#include "jackclient.h" +#include "alsathread.h" + + +Jackclient::Jackclient (jack_client_t* cl, const char*jserv, int mode, int nchan) : + _client (cl), + _mode (mode), + _nchan (nchan), + _state (INIT), + _freew (false) +{ + init (jserv); +} + + +Jackclient::~Jackclient (void) +{ + fini (); +} + + +void Jackclient::init (const char *jserv) +{ + int i, spol, flags; + char s [64]; + jack_status_t stat; + struct sched_param spar; + + if (_client == 0) + { + return; + } + jack_set_process_callback (_client, jack_static_process, (void *) this); + jack_set_latency_callback (_client, jack_static_latency, (void *) this); + jack_set_freewheel_callback (_client, jack_static_freewheel, (void *) this); + jack_set_buffer_size_callback (_client, jack_static_buffsize, (void *) this); + jack_on_shutdown (_client, jack_static_shutdown, (void *) this); + + _bsize = 0; + _fsamp = 0; + + _jname = jack_get_client_name (_client); + _bsize = jack_get_buffer_size (_client); + _fsamp = jack_get_sample_rate (_client); + + flags = JackPortIsTerminal | JackPortIsPhysical; + for (i = 0; i < _nchan; i++) + { + if (_mode == PLAY) + { + sprintf (s, "playback_%d", i + 1); + _ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE, + flags | JackPortIsInput, 0); + } + else + { + sprintf (s, "capture_%d", i + 1); + _ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE, + flags | JackPortIsOutput, 0); + } + } + + _rprio = jack_client_real_time_priority (_client) - sched_get_priority_max (spol); + _buff = new float [_bsize * _nchan]; +} + + +void Jackclient::fini (void) +{ + delete[] _buff; +} + + +void Jackclient::jack_static_shutdown (void *arg) +{ + ((Jackclient *) arg)->sendinfo (TERM, 0, 0); +} + + +int Jackclient::jack_static_buffsize (jack_nframes_t nframes, void *arg) +{ + Jackclient *J = (Jackclient *) arg; + + if (J->_bsize == 0) J->_bsize = nframes; + else if (J->_bsize != (int) nframes) J->_state = Jackclient::TERM; + return 0; +} + + +void Jackclient::jack_static_freewheel (int state, void *arg) +{ + ((Jackclient *) arg)->jack_freewheel (state); +} + + +void Jackclient::jack_static_latency (jack_latency_callback_mode_t jlcm, void *arg) +{ + ((Jackclient *) arg)->jack_latency (jlcm); +} + + +int Jackclient::jack_static_process (jack_nframes_t nframes, void *arg) +{ + return ((Jackclient *) arg)->jack_process (nframes); +} + + +void Jackclient::start (Lfq_audio *audioq, + Lfq_int32 *commq, + Lfq_adata *alsaq, + Lfq_jdata *infoq, + double ratio, + int delay, + int ltcor, + int rqual) +{ + double d; + + _audioq = audioq; + _commq = commq; + _alsaq = alsaq; + _infoq = infoq; + _quant = ldexp (1e-6f, 28); + _ratio = ratio; + _rcorr = 1.0; + _resamp.setup (_ratio, _nchan, rqual); + _resamp.set_rrfilt (100); + d = _resamp.inpsize () / 2.0; + if (_mode == PLAY) d *= _ratio; + _delay = delay + d; + _ltcor = ltcor; + _ppsec = (_fsamp + _bsize / 2) / _bsize; + + if (jack_activate (_client)) + { + fprintf(stderr, "Can't activate Jack"); + return; + } + + initwait (_ppsec / 2); + + jack_recompute_total_latencies (_client); +} + + +void Jackclient::initwait (int nwait) +{ + _count = -nwait; + _commq->wr_int32 (Alsathread::WAIT); + _state = WAIT; + if (nwait > _ppsec) sendinfo (WAIT, 0, 0); +} + + +void Jackclient::initsync (void) +{ + // Reset all lock-free queues. + _commq->reset (); + _alsaq->reset (); + _audioq->reset (); + // Reset and prefill the resampler. + _resamp.reset (); + _resamp.inp_count = _resamp.inpsize () / 2 - 1; + _resamp.out_count = 10000; + _resamp.process (); + // Initiliase state variables. + _t_a0 = _t_a1 = 0; + _k_a0 = _k_a1 = 0; + _k_j0 = 0; + // Initialise loop filter state. + _z1 = _z2 = _z3 = 0; + // Activate the ALSA thread, + _commq->wr_int32 (Alsathread::PROC); + _state = SYNC0; + sendinfo (SYNC0, 0, 0); +} + + +void Jackclient::setloop (double bw) +{ + double w; + + // Set the loop bandwidth to bw Hz. + w = 6.28f * 20 * bw * _bsize / _fsamp; + _w0 = 1.0 - exp (-w); + w = 6.28f * bw * _ratio / _fsamp; + _w1 = w * 1.6; + _w2 = w * _bsize / 1.6; +} + + +void Jackclient::playback (int nframes) +{ + int i, j, n; + float *p, *q; + + // Interleave inputs into _buff. + for (i = 0; i < _nchan; i++) + { + p = (float *)(jack_port_get_buffer (_ports [i], nframes)); + q = _buff + i; + for (j = 0; j < _bsize; j++) q [j * _nchan] = p [j]; + } + // Resample _buff and write to audio queue. + // The while loop takes care of wraparound. + _resamp.inp_count = _bsize; + _resamp.inp_data = _buff; + while (_resamp.inp_count) + { + _resamp.out_count = _audioq->wr_linav (); + _resamp.out_data = _audioq->wr_datap (); + n = _resamp.out_count; + _resamp.process (); + n -= _resamp.out_count; + _audioq->wr_commit (n); + // Adjust state by the number of frames used. + _k_j0 += n; + } +} + + +void Jackclient::capture (int nframes) +{ + int i, j, n; + float *p, *q; + + // Read from audio queue and resample. + // The while loop takes care of wraparound. + _resamp.out_count = _bsize; + _resamp.out_data = _buff; + while (_resamp.out_count) + { + _resamp.inp_count = _audioq->rd_linav (); + _resamp.inp_data = _audioq->rd_datap (); + n = _resamp.inp_count; + _resamp.process (); + n -= _resamp.inp_count; + _audioq->rd_commit (n); + // Adjust state by the number of frames used. + _k_j0 += n; + } + // Deinterleave _buff to outputs. + for (i = 0; i < _nchan; i++) + { + p = _buff + i; + q = (float *)(jack_port_get_buffer (_ports [i], nframes)); + for (j = 0; j < _bsize; j++) q [j] = p [j * _nchan]; + } +} + + +void Jackclient::silence (int nframes) +{ + int i; + float *q; + + // Write silence to all jack ports. + for (i = 0; i < _nchan; i++) + { + q = (float *)(jack_port_get_buffer (_ports [i], nframes)); + memset (q, 0, nframes * sizeof (float)); + } +} + + +void Jackclient::sendinfo (int state, double error, double ratio) +{ + Jdata *J; + + if (_infoq->wr_avail ()) + { + J = _infoq->wr_datap (); + J->_state = state; + J->_error = error; + J->_ratio = ratio; + _infoq->wr_commit (); + } +} + + +void Jackclient::jack_freewheel (int state) +{ + _freew = state ? true : false; + if (_freew) initwait (_ppsec / 4); +} + + +void Jackclient::jack_latency (jack_latency_callback_mode_t jlcm) +{ + jack_latency_range_t R; + int i; + + if (_state < WAIT) return; + if (_mode == PLAY) + { + if (jlcm != JackPlaybackLatency) return; + R.min = R.max = (int)(_delay / _ratio) + _ltcor; + } + else + { + if (jlcm != JackCaptureLatency) return; + R.min = R.max = (int)(_delay * _ratio) + _ltcor; + } + for (i = 0; i < _nchan; i++) + { + jack_port_set_latency_range (_ports [i], jlcm, &R); + } +} + + +int Jackclient::jack_process (int nframes) +{ + int dk, n; + Adata *D; + jack_nframes_t ft; + double tj, err, d1, d2; + + // Buffer size change or other evil. + if (_state == TERM) + { + sendinfo (TERM, 0, 0); + return 0; + } + // Skip cylce if ports may not yet exist. + if (_state < WAIT) return 0; + + // Start synchronisation 1/2 second after entering + // the WAIT state. This delay allows the ALSA thread + // to restart cleanly if necessary. Disabled while + // freewheeling. + if (_state == WAIT) + { + if (_freew) return 0; + if (_mode == CAPT) silence (nframes); + if (++_count == 0) initsync (); + else return 0; + } + + // Compute the start time of the current cycle. + // Jack should really provide the usecs directly. + ft = jack_last_frame_time (_client); + tj = 1e-6 * (jack_frames_to_time (_client, ft) & 0x0FFFFFFF); + + // Check for any skipped cycles. This is invalid + // the first time, but won't be used then anyway. + dk = ft - _ft - _bsize; + _ft = ft; + + // Check if we have timing data from the ALSA thread. + n = _alsaq->rd_avail (); + // If the data queue is full restart synchronisation. + // This can happen e.g. on a jack engine timeout, or + // when too many cycles have been skipped. + if (n == _alsaq->size ()) + { + initwait (_ppsec / 2); + return 0; + } + if (n) + { + // Else move interval end to start, and update the + // interval end keeping only the most recent data. + if (_state < SYNC2) _state++; + _t_a0 = _t_a1; + _k_a0 = _k_a1; + while (_alsaq->rd_avail ()) + { + D = _alsaq->rd_datap (); + // Restart synchronisation in case of + // an error in the ALSA interface. + if (D->_state == Alsathread::WAIT) + { + initwait (_ppsec / 2); + return 0; + } + _t_a1 = D->_timer; + _k_a1 += D->_nsamp; + _alsaq->rd_commit (); + } + } + + err = 0; + if (_state >= SYNC2) + { + // Compute the delay error. + d1 = modtime (tj - _t_a0); + d2 = modtime (_t_a1 - _t_a0); + if (_mode == PLAY) + { + n = _k_j0 - _k_a0; // Must be done as integer as both terms will overflow. + err = n - (_k_a1 - _k_a0) * d1 / d2 + _resamp.inpdist () * _ratio - _delay; + } + else + { + n = _k_a0 - _k_j0; + err = n + (_k_a1 - _k_a0) * d1 / d2 + _resamp.inpdist () - _delay ; + } + + n = (int)(floor (err + 0.5)); + if (_state == SYNC2) + { + // We have the first delay error value. Adjust both the state + // variables and the audio queue by the same number of frames + // to obtain the actually wanted delay, and start tracking. + if (_mode == PLAY) + { + _audioq->wr_commit (-n); + _k_j0 -= n; + } + else + { + _audioq->rd_commit (n); + _k_j0 += n; + } + err -= n; + setloop (1.0); + _state = PROC1; + } + else if (_state >= PROC1) + { + // Check error conditions. + if (dk) + { + // Jack skipped some cycles. Adjust both the state + // and the audio queue so we can just continue. + // The loop will correct the remaining fraction + // of a frame. + if (_mode == PLAY) + { + dk = (int)(dk * _ratio + 0.5); + if (abs (dk + n) < _bsize / 4) + { + _audioq->wr_commit (dk); + _k_j0 += dk; + err += dk; + n = 0; + } + } + else + { + dk = (int)(dk / _ratio + 0.5); + if (abs (dk - n) < _bsize / 4) + { + _audioq->rd_commit (dk); + _k_j0 += dk; + err -= dk; + n = 0; + } + } + } + if (fabs (err) >= 50) + { + // Something is really wrong, wait 15 seconds then restart. + initwait (15 * _ppsec); + return 0; + } + } + } + + // Switch to lower bandwidth after 4 seconds. + if ((_state == PROC1) && (++_count == 4 * _ppsec)) + { + _state = PROC2; + setloop (0.05); + } + + if (_state >= PROC1) + { + // Run loop filter and set resample ratio. + _z1 += _w0 * (_w1 * err - _z1); + _z2 += _w0 * (_z1 - _z2); + _z3 += _w2 * _z2; + _rcorr = 1 - _z2 - _z3; + if (_rcorr > 1.05) _rcorr = 1.05; + if (_rcorr < 0.95) _rcorr = 0.95; + _resamp.set_rratio (_rcorr); + sendinfo (_state, err, _rcorr); + + // Resample and transfer between audio + // queue and jack ports. + if (_mode == PLAY) playback (nframes); + else capture (nframes); + } + else if (_mode == CAPT) silence (nframes); + + return 0; +} diff --git a/zalsa/jackclient.h b/zalsa/jackclient.h new file mode 100644 index 0000000..f67db42 --- /dev/null +++ b/zalsa/jackclient.h @@ -0,0 +1,127 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#ifndef __JACKCLIENT_H +#define __JACKCLIENT_H + + +#include +#include +#include "lfqueue.h" + + +class Jackclient +{ +public: + + Jackclient (jack_client_t*, const char *jserv, int mode, int nchan); + virtual ~Jackclient (void); + + enum { PLAY, CAPT }; + enum { INIT, TERM, WAIT, SYNC0, SYNC1, SYNC2, PROC1, PROC2 }; + + void start (Lfq_audio *audioq, + Lfq_int32 *commq, + Lfq_adata *alsaq, + Lfq_jdata *infoq, + double ratio, + int delay, + int ltcor, + int rqual); + + const char *jname (void) const { return _jname; } + int fsamp (void) const { return _fsamp; } + int bsize (void) const { return _bsize; } + int rprio (void) const { return _rprio; } + +private: + + void init (const char *jserv); + void fini (void); + + double modtime (double d) + { + if (d < -200) d += _quant; + if (d > 200) d -= _quant; + return d; + } + + void initwait (int nwait); + void initsync (void); + void setloop (double bw); + void silence (int nframes); + void playback (int nframes); + void capture (int nframes); + void sendinfo (int state, double error, double ratio); + + virtual void thr_main (void) {} + + void jack_freewheel (int state); + void jack_latency (jack_latency_callback_mode_t jlcm); + int jack_process (int nframes); + + jack_client_t *_client; + jack_port_t *_ports [64]; + const char *_jname; + int _mode; + int _nchan; + int _state; + int _count; + int _fsamp; + int _bsize; + int _rprio; + bool _freew; + float *_buff; + + Lfq_audio *_audioq; + Lfq_int32 *_commq; + Lfq_adata *_alsaq; + Lfq_jdata *_infoq; + double _ratio; + double _quant; + int _ppsec; + + jack_nframes_t _ft; + double _t_a0; + double _t_a1; + int _k_a0; + int _k_a1; + int _k_j0; + double _delay; + int _ltcor; + + double _w0; + double _w1; + double _w2; + double _z1; + double _z2; + double _z3; + double _rcorr; + VResampler _resamp; + + static void jack_static_shutdown (void *arg); + static int jack_static_buffsize (jack_nframes_t nframes, void *arg); + static void jack_static_freewheel (int state, void *arg); + static void jack_static_latency (jack_latency_callback_mode_t jlcm, void *arg); + static int jack_static_process (jack_nframes_t nframes, void *arg); +}; + + +#endif diff --git a/zalsa/lfqueue.cc b/zalsa/lfqueue.cc new file mode 100644 index 0000000..f66d07e --- /dev/null +++ b/zalsa/lfqueue.cc @@ -0,0 +1,89 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#include +#include "lfqueue.h" + + +Lfq_adata::Lfq_adata (int size) : + _size (size), + _mask (size - 1), + _nwr (0), + _nrd (0) +{ + assert (!(_size & _mask)); + _data = new Adata [_size]; +} + +Lfq_adata::~Lfq_adata (void) +{ + delete[] _data; +} + + +Lfq_jdata::Lfq_jdata (int size) : + _size (size), + _mask (size - 1), + _nwr (0), + _nrd (0) +{ + assert (!(_size & _mask)); + _data = new Jdata [_size]; +} + +Lfq_jdata::~Lfq_jdata (void) +{ + delete[] _data; +} + + +Lfq_int32::Lfq_int32 (int size) : + _size (size), + _mask (size - 1), + _nwr (0), + _nrd (0) +{ + assert (!(_size & _mask)); + _data = new int32_t [_size]; +} + +Lfq_int32::~Lfq_int32 (void) +{ + delete[] _data; +} + + +Lfq_audio::Lfq_audio (int nsamp, int nchan) : + _size (nsamp), + _mask (nsamp - 1), + _nch (nchan), + _nwr (0), + _nrd (0) +{ + assert (!(_size & _mask)); + _data = new float [_nch * _size]; +} + +Lfq_audio::~Lfq_audio (void) +{ + delete[] _data; +} + + diff --git a/zalsa/lfqueue.h b/zalsa/lfqueue.h new file mode 100644 index 0000000..d4096c8 --- /dev/null +++ b/zalsa/lfqueue.h @@ -0,0 +1,181 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#ifndef __LFQUEUE_H +#define __LFQUEUE_H + + +#include +#include + + +class Adata +{ +public: + + int32_t _state; + int32_t _nsamp; + double _timer; +}; + + +class Lfq_adata +{ +public: + + Lfq_adata (int size); + ~Lfq_adata (void); + + void reset (void) { _nwr = _nrd = 0; } + int size (void) const { return _size; } + + int wr_avail (void) const { return _size - _nwr + _nrd; } + Adata *wr_datap (void) { return _data + (_nwr & _mask); } + void wr_commit (void) { _nwr++; } + + int rd_avail (void) const { return _nwr - _nrd; } + Adata *rd_datap (void) { return _data + (_nrd & _mask); } + void rd_commit (void) { _nrd++; } + +private: + + Adata *_data; + int _size; + int _mask; + int _nwr; + int _nrd; +}; + + +class Jdata +{ +public: + + int32_t _state; + double _error; + double _ratio; +}; + + +class Lfq_jdata +{ +public: + + Lfq_jdata (int size); + ~Lfq_jdata (void); + + void reset (void) { _nwr = _nrd = 0; } + int size (void) const { return _size; } + + int wr_avail (void) const { return _size - _nwr + _nrd; } + Jdata *wr_datap (void) { return _data + (_nwr & _mask); } + void wr_commit (void) { _nwr++; } + + int rd_avail (void) const { return _nwr - _nrd; } + Jdata *rd_datap (void) { return _data + (_nrd & _mask); } + void rd_commit (void) { _nrd++; } + +private: + + Jdata *_data; + int _size; + int _mask; + int _nwr; + int _nrd; +}; + + +class Lfq_int32 +{ +public: + + Lfq_int32 (int size); + ~Lfq_int32 (void); + + int size (void) const { return _size; } + void reset (void) { _nwr = _nrd = 0; } + + int wr_avail (void) const { return _size - _nwr + _nrd; } + int32_t *wr_datap (void) { return _data + (_nwr & _mask); } + void wr_commit (void) { _nwr++; } + + int rd_avail (void) const { return _nwr - _nrd; } + int32_t *rd_datap (void) { return _data + (_nrd & _mask); } + void rd_commit (void) { _nrd++; } + + void wr_int32 (int32_t v) { _data [_nwr++ & _mask] = v; } + void wr_uint32 (uint32_t v) { _data [_nwr++ & _mask] = v; } + void wr_float (float v) { *(float *)(_data + (_nwr++ & _mask)) = v; } + + int32_t rd_int32 (void) { return _data [_nrd++ & _mask]; } + int32_t rd_uint32 (void) { return _data [_nrd++ & _mask]; } + float rd_float (void) { return *(float *)(_data + (_nrd++ & _mask)); } + +private: + + int32_t *_data; + int _size; + int _mask; + int _nwr; + int _nrd; +}; + + +class Lfq_audio +{ +public: + + Lfq_audio (int nsamp, int nchan); + ~Lfq_audio (void); + + int size (void) const { return _size; } + void reset (void) + { + _nwr = _nrd = 0; + memset (_data, 0, _size * _nch * sizeof (float)); + } + + int nchan (void) const { return _nch; } + int nwr (void) const { return _nwr; }; + int nrd (void) const { return _nrd; }; + + int wr_avail (void) const { return _size - _nwr + _nrd; } + int wr_linav (void) const { return _size - (_nwr & _mask); } + float *wr_datap (void) { return _data + _nch * (_nwr & _mask); } + void wr_commit (int k) { _nwr += k; } + + int rd_avail (void) const { return _nwr - _nrd; } + int rd_linav (void) const { return _size - (_nrd & _mask); } + float *rd_datap (void) { return _data + _nch * (_nrd & _mask); } + void rd_commit (int k) { _nrd += k; } + +private: + + float *_data; + int _size; + int _mask; + int _nch; + int _nwr; + int _nrd; +}; + + +#endif + diff --git a/zalsa/pxthread.cc b/zalsa/pxthread.cc new file mode 100644 index 0000000..e3e8849 --- /dev/null +++ b/zalsa/pxthread.cc @@ -0,0 +1,78 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#include "pxthread.h" + + +Pxthread::Pxthread (void) : _thrid (0) +{ +} + + +Pxthread::~Pxthread (void) +{ +} + + +extern "C" void *Pxthread_entry_point (void *arg) +{ + Pxthread *T = (Pxthread *) arg; + T->thr_main (); + return NULL; +} + + +int Pxthread::thr_start (int policy, int priority, size_t stacksize) +{ + int min, max, rc; + pthread_attr_t attr; + struct sched_param parm; + + min = sched_get_priority_min (policy); + max = sched_get_priority_max (policy); + priority += max; + if (priority > max) priority = max; + if (priority < min) priority = min; + parm.sched_priority = priority; + + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setschedpolicy (&attr, policy); + pthread_attr_setschedparam (&attr, &parm); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setstacksize (&attr, stacksize); + + _thrid = 0; + rc = pthread_create (&_thrid, + &attr, + Pxthread_entry_point, + this); + + pthread_attr_destroy (&attr); + + return rc; +} + + +void Pxthread::thr_main (void) +{ +} + diff --git a/zalsa/pxthread.h b/zalsa/pxthread.h new file mode 100644 index 0000000..32d115f --- /dev/null +++ b/zalsa/pxthread.h @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#ifndef __PXTHREAD_H +#define __PXTHREAD_H + + +#include +#include +#include +#include +#include +#include +#include + + +class Pxthread +{ +public: + + Pxthread (void); + virtual ~Pxthread (void); + Pxthread (const Pxthread&); + Pxthread& operator=(const Pxthread&); + + virtual void thr_main (void) = 0; + virtual int thr_start (int policy, int priority, size_t stacksize = 0); + +private: + + pthread_t _thrid; +}; + + +#endif diff --git a/zalsa/zita-a2j.cc b/zalsa/zita-a2j.cc new file mode 100644 index 0000000..e7ef53a --- /dev/null +++ b/zalsa/zita-a2j.cc @@ -0,0 +1,269 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- + + +#include +#include +#include +#include +#include +#include "alsathread.h" +#include "jackclient.h" +#include "lfqueue.h" + + +static Lfq_int32 commq (16); +static Lfq_adata alsaq (256); +static Lfq_jdata infoq (256); +static Lfq_audio *audioq = 0; +static bool stop = false; + +static const char *clopt = "hvLj:d:r:p:n:c:Q:I:"; +static bool v_opt = false; +static bool L_opt = false; +static const char *jname = APPNAME; +static const char *device = 0; +static int fsamp = 48000; +static int bsize = 256; +static int nfrag = 2; +static int nchan = 2; +static int rqual = 48; +static int ltcor = 0; + + +static void help (void) +{ + fprintf (stderr, "\n%s-%s\n", APPNAME, VERSION); + fprintf (stderr, "(C) 2012-2013 Fons Adriaensen \n"); + fprintf (stderr, "Use ALSA capture device as a Jack client, with resampling.\n\n"); + fprintf (stderr, "Usage: %s \n", APPNAME); + fprintf (stderr, "Options:\n"); + fprintf (stderr, " -h Display this text\n"); + fprintf (stderr, " -j Name as Jack client [%s]\n", APPNAME); + fprintf (stderr, " -d ALSA capture device [none]\n"); + fprintf (stderr, " -r Sample rate [48000]\n"); + fprintf (stderr, " -p Period size [256]\n"); + fprintf (stderr, " -n Number of fragments [2]\n"); + fprintf (stderr, " -c Number of channels [2]\n"); + fprintf (stderr, " -Q Resampling quality [48]\n"); + fprintf (stderr, " -I Latency adjustment[0]\n"); + fprintf (stderr, " -L Force 16-bit and 2 channels [off]\n"); + fprintf (stderr, " -v Print tracing information [off]\n"); + exit (1); +} + +static int procoptions (int ac, const char *av []) +{ + int k; + + optind = 1; + opterr = 0; + while ((k = getopt (ac, (char **) av, (char *) clopt)) != -1) + { + if (optarg && (*optarg == '-')) + { + fprintf (stderr, " Missing argument for '-%c' option.\n", k); + fprintf (stderr, " Use '-h' to see all options.\n"); + exit (1); + } + switch (k) + { + case 'h' : help (); exit (0); + case 'v' : v_opt = true; break; + case 'L' : L_opt = true; break; + case 'j' : jname = optarg; break; + case 'd' : device = optarg; break; + case 'r' : fsamp = atoi (optarg); break; + case 'p' : bsize = atoi (optarg); break; + case 'n' : nfrag = atoi (optarg); break; + case 'c' : nchan = atoi (optarg); break; + case 'Q' : rqual = atoi (optarg); break; + case 'I' : ltcor = atoi (optarg); break; + case '?': + if (optopt != ':' && strchr (clopt, optopt)) + { + fprintf (stderr, " Missing argument for '-%c' option.\n", optopt); + } + else if (isprint (optopt)) + { + fprintf (stderr, " Unknown option '-%c'.\n", optopt); + } + else + { + fprintf (stderr, " Unknown option character '0x%02x'.\n", optopt & 255); + } + fprintf (stderr, " Use '-h' to see all options.\n"); + return 1; + default: + return 1; + } + } + return 0; +} + +static int parse_options (const char* load_init) +{ + int argsz; + int argc = 0; + const char** argv; + char* args = strdup (load_init); + char* token; + char* ptr = args; + char* savep; + + if (!load_init) { + return 0; + } + + argsz = 8; /* random guess at "maxargs" */ + argv = (const char **) malloc (sizeof (char *) * argsz); + + argv[argc++] = APPNAME; + + while (1) { + + if ((token = strtok_r (ptr, " ", &savep)) == NULL) { + break; + } + + if (argc == argsz) { + argsz *= 2; + argv = (const char **) realloc (argv, sizeof (char *) * argsz); + } + + argv[argc++] = token; + fprintf (stderr, "stash argv[%d] as %s\n", argc, token); + ptr = NULL; + } + + return procoptions (argc, argv); +} + +static void printinfo (void) +{ + int n; + double e, r; + Jdata *J; + + n = 0; + e = r = 0; + while (infoq.rd_avail ()) + { + J = infoq.rd_datap (); + if (J->_state == Jackclient::TERM) + { + printf ("Fatal error condition, terminating.\n"); + stop = true; + return; + } + else if (J->_state == Jackclient::WAIT) + { + printf ("Detected excessive timing errors, waiting 15 seconds.\n"); + printf ("This may happen with current Jack1 after freewheeling.\n"); + n = 0; + } + else if (J->_state == Jackclient::SYNC0) + { + printf ("Starting synchronisation.\n"); + } + else if (v_opt) + { + n++; + e += J->_error; + r += J->_ratio; + } + infoq.rd_commit (); + } + if (n) printf ("%8.3lf %10.6lf\n", e / n, r / n); +} + + +static Alsa_pcmi *A = 0; +static Alsathread *C = 0; +static Jackclient *J = 0; + +extern "C" { + +int +jack_initialize (jack_client_t* client, const char* load_init) +{ + int k, k_del, opts; + double t_jack; + double t_alsa; + double t_del; + + if (parse_options (load_init)) { + fprintf (stderr, "parse options failed\n"); + return 1; + } + + if (device == 0) help (); + if (rqual < 16) rqual = 16; + if (rqual > 96) rqual = 96; + if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1)) + { + fprintf (stderr, "Illegal parameter value(s).\n"); + return 1; + } + + opts = 0; + if (v_opt) opts |= Alsa_pcmi::DEBUG_ALL; + if (L_opt) opts |= Alsa_pcmi::FORCE_16B | Alsa_pcmi::FORCE_2CH; + A = new Alsa_pcmi (0, device, 0, fsamp, bsize, nfrag, opts); + if (A->state ()) + { + fprintf (stderr, "Can't open ALSA capture device '%s'.\n", device); + return 1; + } + if (v_opt) A->printinfo (); + if (nchan > A->ncapt ()) + { + nchan = A->ncapt (); + fprintf (stderr, "Warning: only %d channels are available.\n", nchan); + } + C = new Alsathread (A, Alsathread::CAPT); + J = new Jackclient (client, 0, Jackclient::CAPT, nchan); + usleep (100000); + + t_alsa = (double) bsize / fsamp; + if (t_alsa < 1e-3) t_alsa = 1e-3; + t_jack = (double) J->bsize () / J->fsamp (); + t_del = 1.5 * t_alsa + t_jack; + k_del = (int)(t_del * fsamp); + for (k = 256; k < k_del + J->bsize (); k *= 2); + audioq = new Lfq_audio (k, nchan); + + C->start (audioq, &commq, &alsaq, J->rprio () + 10); + J->start (audioq, &commq, &alsaq, &infoq, J->fsamp () / (double) fsamp, k_del, ltcor, rqual); + + return 0; +} + +void jack_finish (void* arg) +{ + + commq.wr_int32 (Alsathread::TERM); + usleep (100000); + delete C; + delete A; + delete J; + delete audioq; +} + +} /* extern "C" */ diff --git a/zalsa/zita-j2a.cc b/zalsa/zita-j2a.cc new file mode 100644 index 0000000..73688ab --- /dev/null +++ b/zalsa/zita-j2a.cc @@ -0,0 +1,265 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (C) 2012 Fons Adriaensen +// +// 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 3 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, see . +// +// ---------------------------------------------------------------------------- +#include + +#include +#include +#include +#include +#include +#include "alsathread.h" +#include "jackclient.h" +#include "lfqueue.h" + + +static Lfq_int32 commq (16); +static Lfq_adata alsaq (256); +static Lfq_jdata infoq (256); +static Lfq_audio *audioq = 0; +static bool stop = false; + +static const char *clopt = "hvLj:d:r:p:n:c:Q:O:"; +static bool v_opt = false; +static bool L_opt = false; +static const char *jname = APPNAME; +static const char *device = 0; +static int fsamp = 48000; +static int bsize = 256; +static int nfrag = 2; +static int nchan = 2; +static int rqual = 48; +static int ltcor = 0; + + +static void help (void) +{ + fprintf (stderr, "\n%s-%s\n", APPNAME, VERSION); + fprintf (stderr, "(C) 2012-2013 Fons Adriaensen \n"); + fprintf (stderr, "Use ALSA playback device as a Jack client, with resampling.\n\n"); + fprintf (stderr, "Usage: %s \n", APPNAME); + fprintf (stderr, "Options:\n"); + fprintf (stderr, " -h Display this text\n"); + fprintf (stderr, " -j Name as Jack client [%s]\n", APPNAME); + fprintf (stderr, " -d ALSA playback device [none]\n"); + fprintf (stderr, " -r Sample rate [48000]\n"); + fprintf (stderr, " -p Period size [256]\n"); + fprintf (stderr, " -n Number of fragments [2]\n"); + fprintf (stderr, " -c Number of channels [2]\n"); + fprintf (stderr, " -Q Resampling quality [48]\n"); + fprintf (stderr, " -O Latency adjustment[0]\n"); + fprintf (stderr, " -L Force 16-bit and 2 channels [off]\n"); + fprintf (stderr, " -v Print tracing information [off]\n"); + exit (1); +} + +static int procoptions (int ac, const char *av []) +{ + int k; + + optind = 1; + opterr = 0; + while ((k = getopt (ac, (char **) av, (char *) clopt)) != -1) + { + if (optarg && (*optarg == '-')) + { + fprintf (stderr, " Missing argument for '-%c' option.\n", k); + fprintf (stderr, " Use '-h' to see all options.\n"); + exit (1); + } + switch (k) + { + case 'h' : help (); exit (0); + case 'v' : v_opt = true; break; + case 'L' : L_opt = true; break; + case 'j' : jname = optarg; break; + case 'd' : device = optarg; break; + case 'r' : fsamp = atoi (optarg); break; + case 'p' : bsize = atoi (optarg); break; + case 'n' : nfrag = atoi (optarg); break; + case 'c' : nchan = atoi (optarg); break; + case 'Q' : rqual = atoi (optarg); break; + case 'O' : ltcor = atoi (optarg); break; + case '?': + if (optopt != ':' && strchr (clopt, optopt)) + { + fprintf (stderr, " Missing argument for '-%c' option.\n", optopt); + } + else if (isprint (optopt)) + { + fprintf (stderr, " Unknown option '-%c'.\n", optopt); + } + else + { + fprintf (stderr, " Unknown option character '0x%02x'.\n", optopt & 255); + } + fprintf (stderr, " Use '-h' to see all options.\n"); + return 1; + default: + return 1; + } + } + + return 0; +} + +static int parse_options (const char* load_init) +{ + int argsz; + int argc = 0; + const char** argv; + char* args = strdup (load_init); + char* token; + char* ptr = args; + char* savep; + + if (!load_init) { + return 0; + } + + argsz = 8; /* random guess at "maxargs" */ + argv = (const char **) malloc (sizeof (char *) * argsz); + + argv[argc++] = APPNAME; + + while (1) { + + if ((token = strtok_r (ptr, " ", &savep)) == NULL) { + break; + } + + if (argc == argsz) { + argsz *= 2; + argv = (const char **) realloc (argv, sizeof (char *) * argsz); + } + + argv[argc++] = token; + ptr = NULL; + } + + return procoptions (argc, argv); +} + +static void printinfo (void) +{ + int n; + double e, r; + Jdata *J; + + n = 0; + e = r = 0; + while (infoq.rd_avail ()) + { + J = infoq.rd_datap (); + if (J->_state == Jackclient::TERM) + { + printf ("Fatal error condition, terminating.\n"); + stop = true; + return; + } + else if (J->_state == Jackclient::WAIT) + { + printf ("Detected excessive timing errors, waiting 15 seconds.\n"); + printf ("This may happen with current Jack1 after freewheeling.\n"); + n = 0; + } + else if (J->_state == Jackclient::SYNC0) + { + printf ("Starting synchronisation.\n"); + } + else if (v_opt) + { + n++; + e += J->_error; + r += J->_ratio; + } + infoq.rd_commit (); + } + if (n) printf ("%8.3lf %10.6lf\n", e / n, r / n); +} + +Alsa_pcmi *A = 0; +Alsathread *P = 0; +Jackclient *J = 0; + +extern "C" { + +int jack_initialize (jack_client_t* client, const char* load_init) +{ + int k, k_del, opts; + double t_jack; + double t_alsa; + double t_del; + + if (parse_options (load_init)) { + return 1; + } + + if (device == 0) help (); + if (rqual < 16) rqual = 16; + if (rqual > 96) rqual = 96; + if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1)) + { + fprintf (stderr, "Illegal parameter value(s).\n"); + return 1; + } + + opts = 0; + if (v_opt) opts |= Alsa_pcmi::DEBUG_ALL; + if (L_opt) opts |= Alsa_pcmi::FORCE_16B | Alsa_pcmi::FORCE_2CH; + A = new Alsa_pcmi (device, 0, 0, fsamp, bsize, nfrag, opts); + if (A->state ()) + { + fprintf (stderr, "Can't open ALSA playback device '%s'.\n", device); + return 1; + } + if (v_opt) A->printinfo (); + if (nchan > A->nplay ()) + { + nchan = A->nplay (); + fprintf (stderr, "Warning: only %d channels are available.\n", nchan); + } + P = new Alsathread (A, Alsathread::PLAY); + J = new Jackclient (client, 0, Jackclient::PLAY, nchan); + usleep (100000); + + t_alsa = (double) bsize / fsamp; + if (t_alsa < 1e-3) t_alsa = 1e-3; + t_jack = (double) J->bsize () / J->fsamp (); + t_del = 1.5 * t_alsa + t_jack; + k_del = (int)(t_del * fsamp); + for (k = 256; k < k_del + J->bsize (); k *= 2); + audioq = new Lfq_audio (k, nchan); + + P->start (audioq, &commq, &alsaq, J->rprio () + 10); + J->start (audioq, &commq, &alsaq, &infoq, (double) fsamp / J->fsamp (), k_del, ltcor, rqual); + + return 0; +} + +void jack_finish (void* arg) +{ + commq.wr_int32 (Alsathread::TERM); + usleep (100000); + delete P; + delete A; + delete J; + delete audioq; +} + +} /* extern "C" */