summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipe Coelho <falktx@falktx.com>2021-04-14 16:16:11 +0100
committerGitHub <noreply@github.com>2021-04-14 16:16:11 +0100
commit8f4880518b2d1aeed7381feb84ccffea8800b4f4 (patch)
tree82170e2354bed86af0a5f8cd8241c7cafc2a6436
parent62b07487dc76b5d94d118e7926d216a2cbb8bb5d (diff)
parent5a1c0f98d55c8c97a178f21fa6617034411c38af (diff)
downloadjack2-8f4880518b2d1aeed7381feb84ccffea8800b4f4.tar.gz
Merge pull request #747 from jackaudio/zalsa
Add zita-a2j/j2a as internal client
-rw-r--r--tools/wscript28
-rw-r--r--tools/zalsa/alsathread.cc218
-rw-r--r--tools/zalsa/alsathread.h68
-rw-r--r--tools/zalsa/jackclient.cc549
-rw-r--r--tools/zalsa/jackclient.h120
-rw-r--r--tools/zalsa/lfqueue.cc89
-rw-r--r--tools/zalsa/lfqueue.h182
-rw-r--r--tools/zalsa/pxthread.cc78
-rw-r--r--tools/zalsa/pxthread.h52
-rw-r--r--tools/zalsa/timers.h53
-rw-r--r--tools/zalsa/zita-a2j.cc329
-rw-r--r--tools/zalsa/zita-j2a.cc327
-rw-r--r--wscript5
13 files changed, 2094 insertions, 4 deletions
diff --git a/tools/wscript b/tools/wscript
index 0fc90289..304b26d8 100644
--- a/tools/wscript
+++ b/tools/wscript
@@ -19,8 +19,9 @@ example_tools = {
}
def configure(conf):
- conf.env['BUILD_EXAMPLE_ALSA_IO'] = conf.env['SAMPLERATE'] and conf.env['BUILD_DRIVER_ALSA']
- conf.env['BUILD_EXAMPLE_CLIENT_TRANSPORT'] = conf.env['READLINE']
+ conf.env['BUILD_TOOL_ALSA_IO'] = conf.env['SAMPLERATE'] and conf.env['BUILD_DRIVER_ALSA']
+ conf.env['BUILD_TOOL_CLIENT_TRANSPORT'] = conf.env['READLINE']
+ conf.env['BUILD_TOOL_ZALSA'] = conf.env['ZALSA']
def build(bld):
if bld.env['IS_LINUX']:
@@ -50,7 +51,7 @@ def build(bld):
prog.target = example_tool
- if bld.env['BUILD_EXAMPLE_CLIENT_TRANSPORT']:
+ if bld.env['BUILD_TOOL_CLIENT_TRANSPORT']:
prog = bld(features = 'c cprogram')
prog.includes = os_incdir + ['../common/jack', '../common']
prog.source = 'transport.c'
@@ -74,7 +75,7 @@ def build(bld):
prog.target = 'jack_netsource'
prog.defines = ['HAVE_CONFIG_H']
- if bld.env['IS_LINUX'] and bld.env['BUILD_EXAMPLE_ALSA_IO']:
+ if bld.env['IS_LINUX'] and bld.env['BUILD_TOOL_ALSA_IO']:
prog = bld(features = 'c cprogram')
prog.includes = os_incdir + ['../common/jack', '../common']
prog.source = ['alsa_in.c', '../common/memops.c']
@@ -89,6 +90,25 @@ def build(bld):
prog.use = ['clientlib', 'ALSA', 'SAMPLERATE', 'M']
prog.target = 'alsa_out'
+ if bld.env['IS_LINUX'] and bld.env['BUILD_TOOL_ZALSA']:
+ prog = bld(features = ['cxx', 'cxxshlib'])
+ prog.defines = ['HAVE_CONFIG_H','SERVER_SIDE','APPNAME="zalsa_in"','VERSION="0.4.0"']
+ prog.install_path = '${ADDON_DIR}/'
+ prog.includes = os_incdir + ['../common/jack', '../common', 'zalsa']
+ prog.source = ['zalsa/zita-a2j.cc', 'zalsa/alsathread.cc', 'zalsa/jackclient.cc', 'zalsa/pxthread.cc', 'zalsa/lfqueue.cc']
+ prog.target = 'zita-a2j'
+ prog.use = ['ZITA-ALSA-PCMI', 'ZITA-RESAMPLER', 'ALSA', 'M', 'RT', 'serverlib']
+ prog.env['cxxshlib_PATTERN'] = '%s.so'
+
+ prog = bld(features = ['cxx', 'cxxshlib'])
+ prog.defines = ['HAVE_CONFIG_H','SERVER_SIDE','APPNAME="zalsa_out"','VERSION="0.4.0"']
+ prog.install_path = '${ADDON_DIR}/'
+ prog.includes = os_incdir + ['../common/jack', '../common', 'zalsa']
+ prog.source = ['zalsa/zita-j2a.cc', 'zalsa/alsathread.cc', 'zalsa/jackclient.cc', 'zalsa/pxthread.cc', 'zalsa/lfqueue.cc']
+ prog.target = 'zita-j2a'
+ prog.use = ['ZITA-ALSA-PCMI', 'ZITA-RESAMPLER', 'ALSA', 'M', 'RT', 'serverlib']
+ prog.env['cxxshlib_PATTERN'] = '%s.so'
+
if not bld.env['IS_WINDOWS']:
bld.symlink_as('${PREFIX}/bin/jack_disconnect', 'jack_connect')
bld.install_files('${PREFIX}/bin', 'jack_control', chmod=0o755)
diff --git a/tools/zalsa/alsathread.cc b/tools/zalsa/alsathread.cc
new file mode 100644
index 00000000..1ce5dd93
--- /dev/null
+++ b/tools/zalsa/alsathread.cc
@@ -0,0 +1,218 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include "alsathread.h"
+#include "timers.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;
+}
+
+
+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 = tjack (jack_get_time ());
+ // Check for errors - requires restart.
+ if (_alsadev->state () && (na == 0))
+ {
+ _state = WAIT;
+ send (0, 0);
+ usleep (10000);
+ 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;
+ _dt = (double) _fsize / _alsadev->fsamp ();
+ _t0 = tw;
+ _t1 = tw + _dt;
+ }
+ else
+ {
+ // Update the DLL.
+ // If we have more than one period, use
+ // the time error only for the last one.
+ if (na >= _fsize) er = 0;
+ else er = tjack_diff (tw, _t1);
+ _t0 = _t1;
+ _t1 = tjack_diff (_t1 + _dt + _w1 * er, 0.0);
+ _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/tools/zalsa/alsathread.h b/tools/zalsa/alsathread.h
new file mode 100644
index 00000000..f9cb761c
--- /dev/null
+++ b/tools/zalsa/alsathread.h
@@ -0,0 +1,68 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#ifndef __ALSATHREAD_H
+#define __ALSATHREAD_H
+
+
+#include <zita-alsa-pcmi.h>
+#include "jack/jack.h"
+#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 _nfail;
+ int _fsize;
+ Lfq_audio *_audioq;
+ Lfq_int32 *_commq;
+ Lfq_adata *_alsaq;
+ bool _first;
+// double _jtmod;
+ double _t0;
+ double _t1;
+ double _dt;
+ double _w1;
+ double _w2;
+};
+
+
+#endif
diff --git a/tools/zalsa/jackclient.cc b/tools/zalsa/jackclient.cc
new file mode 100644
index 00000000..826f706e
--- /dev/null
+++ b/tools/zalsa/jackclient.cc
@@ -0,0 +1,549 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#include <stdio.h>
+#include <math.h>
+#include "jackclient.h"
+#include "alsathread.h"
+#include "timers.h"
+
+
+Jackclient::Jackclient (jack_client_t* cl, const char *jserv, int mode, int nchan, bool sync, void *arg) :
+ _client (cl),
+ _arg (arg),
+ _mode (mode),
+ _nchan (nchan),
+ _state (INIT),
+ _freew (false),
+ _resamp (0)
+{
+ init (jserv);
+ if (!sync) _resamp = new VResampler ();
+}
+
+
+Jackclient::~Jackclient (void)
+{
+ fini ();
+}
+
+
+bool Jackclient::init (const char *jserv)
+{
+ int i, spol, flags;
+ char s [64];
+ struct sched_param spar;
+
+ if (_client == 0)
+ {
+ fprintf (stderr, "Can't connect to Jack, is the server running ?\n");
+ return false;
+ }
+ 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;
+ if (jack_activate (_client))
+ {
+ fprintf(stderr, "Can't activate Jack");
+ return false;
+ }
+ _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, "USB_Audio_Playback_%d", i + 1);
+ _ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE,
+ flags | JackPortIsInput, 0);
+ }
+ else
+ {
+ sprintf (s, "USB_Audio_Capture_%d", i + 1);
+ _ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE,
+ flags | JackPortIsOutput, 0);
+ }
+ }
+ pthread_getschedparam (jack_client_thread_id (_client), &spol, &spar);
+ _rprio = spar.sched_priority - sched_get_priority_max (spol);
+ _buff = new float [_bsize * _nchan];
+ return true;
+}
+
+
+void Jackclient::fini (void)
+{
+ delete[] _buff;
+ delete _resamp;
+}
+
+
+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;
+ _ratio = ratio;
+ _delay = delay;
+ _rcorr = 1.0;
+ if (_resamp)
+ {
+ _resamp->setup (_ratio, _nchan, rqual);
+ _resamp->set_rrfilt (100);
+ d = _resamp->inpsize () / 2.0;
+ if (_mode == PLAY) d *= _ratio;
+ _delay += d;
+ }
+ _ltcor = ltcor;
+ _ppsec = (_fsamp + _bsize / 2) / _bsize;
+ 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 ();
+ if (_resamp)
+ {
+ // Reset and prefill the resampler.
+ _resamp->reset ();
+ _resamp->inp_count = _resamp->inpsize () / 2 - 1;
+ _resamp->out_count = 99999;
+ _resamp->process ();
+ }
+ // Initiliase state variables.
+ _t_a0 = _t_a1 = 0;
+ _k_a0 = _k_a1 = 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.28 * bw * _bsize / _fsamp;
+ _w0 = 1.0 - exp (-20.0 * w);
+ _w1 = w * 2 / _bsize;
+ _w2 = w / 2;
+ if (_mode == PLAY) _w1 /= _ratio;
+ else _w1 *= _ratio;
+}
+
+
+void Jackclient::playback (int nframes)
+{
+ int i, j, n;
+ float *p, *q;
+ float *inp [MAXCHAN];
+
+ _bstat = _audioq->rd_avail ();
+ for (i = 0; i < _nchan; i++)
+ {
+ inp [i] = (float *)(jack_port_get_buffer (_ports [i], nframes));
+ }
+ if (_resamp)
+ {
+ // Interleave inputs into _buff.
+ for (i = 0; i < _nchan; i++)
+ {
+ p = inp [i];
+ 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);
+ }
+ }
+ else
+ {
+ // Interleave inputs into audio queue.
+ // The while loop takes care of wraparound.
+ while (nframes)
+ {
+ q = _audioq->wr_datap ();
+ n = _audioq->wr_linav ();
+ if (n > nframes) n = nframes;
+ for (i = 0; i < _nchan; i++)
+ {
+ p = inp [i];
+ for (j = 0; j < n; j++) q [j * _nchan] = p [j];
+ inp [i] += n;
+ q += 1;
+ }
+ _audioq->wr_commit (n);
+ nframes -= n;
+ }
+ }
+}
+
+
+void Jackclient::capture (int nframes)
+{
+ int i, j, n;
+ float *p, *q;
+ float *out [MAXCHAN];
+
+ for (i = 0; i < _nchan; i++)
+ {
+ out [i] = (float *)(jack_port_get_buffer (_ports [i], nframes));
+ }
+ if (_resamp)
+ {
+ // 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);
+ }
+ // Deinterleave _buff to outputs.
+ for (i = 0; i < _nchan; i++)
+ {
+ p = _buff + i;
+ q = out [i];
+ for (j = 0; j < _bsize; j++) q [j] = p [j * _nchan];
+ }
+ }
+ else
+ {
+ // Deinterleave audio queue to outputs.
+ // The while loop takes care of wraparound.
+ while (nframes)
+ {
+ p = _audioq->rd_datap ();
+ n = _audioq->rd_linav ();
+ if (n > nframes) n = nframes;
+ for (i = 0; i < _nchan; i++)
+ {
+ q = out [i];
+ for (j = 0; j < n; j++) q [j] = p [j * _nchan];
+ out [i] += n;
+ p += 1;
+ }
+ _audioq->rd_commit (n);
+ nframes -= n;
+ }
+ }
+ _bstat = _audioq->rd_avail ();
+}
+
+
+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;
+ J->_bstat = _bstat;
+ _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_time_t t0, t1;
+ jack_nframes_t ft;
+ float us;
+ double tj, err, d1, d2, rd;
+
+ // 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;
+ }
+
+ // Get the start time of the current cycle.
+ jack_get_cycle_times (_client, &ft, &t0, &t1, &us);
+ tj = tjack (t0);
+
+ // Check for any skipped cycles.
+ if (_state >= SYNC1)
+ {
+ dk = ft - _ft - _bsize;
+ if (_mode == PLAY)
+ {
+ dk = (int)(dk * _ratio + 0.5);
+ _audioq->wr_commit (dk);
+ }
+ else
+ {
+ dk = (int)(dk / _ratio + 0.5);
+ _audioq->rd_commit (dk);
+ }
+ }
+ _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 = tjack_diff (tj, _t_a0);
+ d2 = tjack_diff (_t_a1, _t_a0);
+ rd = _resamp ? _resamp->inpdist () : 0.0;
+
+ if (_mode == PLAY)
+ {
+ n = _audioq->nwr () - _k_a0; // Must be done as integer as both terms will overflow.
+ err = n - (_k_a1 - _k_a0) * d1 / d2 + rd * _ratio - _delay;
+ }
+ else
+ {
+ n = _k_a0 - _audioq->nrd (); // Must be done as integer as both terms will overflow.
+ err = n + (_k_a1 - _k_a0) * d1 / d2 + rd - _delay ;
+ }
+ n = (int)(floor (err + 0.5));
+ if (_state == SYNC2)
+ {
+ // We have the first delay error value. Adjust the audio
+ // queue to obtain the actually wanted delay, and start
+ // tracking.
+ if (_mode == PLAY) _audioq->wr_commit (-n);
+ else _audioq->rd_commit (n);
+ err -= n;
+ setloop (1.0);
+ _state = PROC1;
+ }
+ }
+
+ // Switch to lower bandwidth after 4 seconds.
+ if ((_state == PROC1) && (++_count == 4 * _ppsec))
+ {
+ _state = PROC2;
+ setloop (0.05);
+ }
+
+ if (_state >= PROC1)
+ {
+ _z1 += _w0 * (_w1 * err - _z1);
+ _z2 += _w0 * (_z1 - _z2);
+ _z3 += _w2 * _z2;
+ // Check error conditions.
+ if (fabs (_z3) > 0.05)
+ {
+ // Something is really wrong, wait 10 seconds then restart.
+ initwait (10 * _ppsec);
+ return 0;
+ }
+ // Run loop filter and set resample ratio.
+ if (_resamp)
+ {
+ _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/tools/zalsa/jackclient.h b/tools/zalsa/jackclient.h
new file mode 100644
index 00000000..52f3ddfd
--- /dev/null
+++ b/tools/zalsa/jackclient.h
@@ -0,0 +1,120 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#ifndef __JACKCLIENT_H
+#define __JACKCLIENT_H
+
+
+#include <zita-resampler/vresampler.h>
+#include "jack/jack.h"
+#include "lfqueue.h"
+
+
+class Jackclient
+{
+public:
+
+ Jackclient (jack_client_t*, const char *jserv, int mode, int nchan, bool sync, void *arg);
+ virtual ~Jackclient (void);
+
+ enum { PLAY, CAPT, MAXCHAN = 64 };
+ 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; }
+ void *getarg(void) const { return _arg; }
+
+private:
+
+ bool init (const char *jserv);
+ void fini (void);
+ 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 [MAXCHAN];
+ void *_arg;
+ 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;
+ int _ppsec;
+ int _bstat;
+
+ jack_nframes_t _ft;
+ double _t_a0;
+ double _t_a1;
+ int _k_a0;
+ int _k_a1;
+ 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/tools/zalsa/lfqueue.cc b/tools/zalsa/lfqueue.cc
new file mode 100644
index 00000000..f66d07e3
--- /dev/null
+++ b/tools/zalsa/lfqueue.cc
@@ -0,0 +1,89 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#include <assert.h>
+#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/tools/zalsa/lfqueue.h b/tools/zalsa/lfqueue.h
new file mode 100644
index 00000000..3943d401
--- /dev/null
+++ b/tools/zalsa/lfqueue.h
@@ -0,0 +1,182 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#ifndef __LFQUEUE_H
+#define __LFQUEUE_H
+
+
+#include <stdint.h>
+#include <string.h>
+
+
+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;
+ int _bstat;
+};
+
+
+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/tools/zalsa/pxthread.cc b/tools/zalsa/pxthread.cc
new file mode 100644
index 00000000..e3e88499
--- /dev/null
+++ b/tools/zalsa/pxthread.cc
@@ -0,0 +1,78 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#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/tools/zalsa/pxthread.h b/tools/zalsa/pxthread.h
new file mode 100644
index 00000000..32d115fb
--- /dev/null
+++ b/tools/zalsa/pxthread.h
@@ -0,0 +1,52 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#ifndef __PXTHREAD_H
+#define __PXTHREAD_H
+
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+
+
+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/tools/zalsa/timers.h b/tools/zalsa/timers.h
new file mode 100644
index 00000000..267afa61
--- /dev/null
+++ b/tools/zalsa/timers.h
@@ -0,0 +1,53 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#ifndef __TIMERS_H
+#define __TIMERS_H
+
+
+#include <math.h>
+#include <sys/time.h>
+#include <jack/jack.h>
+
+
+#define tjack_mod ldexp (1e-6f, 32)
+
+
+
+inline double tjack_diff (double a, double b)
+{
+ double d, m;
+
+ d = a - b;
+ m = tjack_mod;
+ while (d < -m / 2) d += m;
+ while (d >= m / 2) d -= m;
+ return d;
+}
+
+
+inline double tjack (jack_time_t t, double dt = 0)
+{
+ int32_t u = (int32_t)(t & 0xFFFFFFFFLL);
+ return 1e-6 * u;
+}
+
+
+#endif
diff --git a/tools/zalsa/zita-a2j.cc b/tools/zalsa/zita-a2j.cc
new file mode 100644
index 00000000..7fa56c39
--- /dev/null
+++ b/tools/zalsa/zita-a2j.cc
@@ -0,0 +1,329 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <signal.h>
+#include "alsathread.h"
+#include "jackclient.h"
+#include "lfqueue.h"
+
+static const char *clopt = "hvLSj:d:r:p:n:c:Q:I:";
+
+static void help (void)
+{
+ fprintf (stderr, "\n%s-%s\n", APPNAME, VERSION);
+ fprintf (stderr, "(C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>\n");
+ fprintf (stderr, "Use ALSA capture device as a Jack client.\n\n");
+ fprintf (stderr, "Usage: %s <options>\n", APPNAME);
+ fprintf (stderr, "Options:\n");
+ fprintf (stderr, " -h Display this text\n");
+ fprintf (stderr, " -j <jackname> Name as Jack client [%s]\n", APPNAME);
+ fprintf (stderr, " -d <device> ALSA capture device [none]\n");
+ fprintf (stderr, " -r <rate> Sample rate [48000]\n");
+ fprintf (stderr, " -p <period> Period size [256]\n");
+ fprintf (stderr, " -n <nfrags> Number of fragments [2]\n");
+ fprintf (stderr, " -c <nchannels> Number of channels [2]\n");
+ fprintf (stderr, " -S Word clock sync, no resampling\n");
+ fprintf (stderr, " -Q <quality> Resampling quality, 16..96 [auto]\n");
+ fprintf (stderr, " -I <samples> Latency adjustment [0]\n");
+ fprintf (stderr, " -L Force 16-bit and 2 channels [off]\n");
+ fprintf (stderr, " -v Print tracing information [off]\n");
+}
+
+class zita_a2j
+{
+ Lfq_int32 *commq;
+ Lfq_adata *alsaq;
+ Lfq_jdata *infoq;
+ Lfq_audio *audioq;
+ bool stop;
+ bool v_opt;
+ bool L_opt;
+ bool S_opt;
+ char *jname;
+ char *device;
+ int fsamp;
+ int bsize;
+ int nfrag;
+ int nchan;
+ int rqual;
+ int ltcor;
+
+public:
+
+ zita_a2j()
+ {
+ commq = new Lfq_int32(16);
+ alsaq = new Lfq_adata(256);
+ infoq = new Lfq_jdata(256);
+ audioq = 0;
+ stop = false;
+ v_opt = false;
+ L_opt = false;
+ S_opt = false;
+ jname = strdup(APPNAME);
+ device = 0;
+ fsamp = 48000;
+ bsize = 128;
+ nfrag = 2;
+ nchan = 2;
+ rqual = 0;
+ ltcor = 0;
+ A = 0;
+ C = 0;
+ J = 0;
+ }
+
+private:
+
+ 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");
+ return 1;
+ }
+ switch (k)
+ {
+ case 'h' : help (); return 1;
+ case 'v' : v_opt = true; break;
+ case 'L' : L_opt = true; break;
+ case 'S' : S_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;
+ }
+
+ 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);
+ }
+
+ void printinfo (void)
+ {
+ int n, k;
+ double e, r;
+ Jdata *J;
+
+ n = 0;
+ k = 99999;
+ 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 10 seconds.\n");
+ n = 0;
+ }
+ else if (J->_state == Jackclient::SYNC0)
+ {
+ printf ("Starting synchronisation.\n");
+ }
+ else if (v_opt)
+ {
+ n++;
+ e += J->_error;
+ r += J->_ratio;
+ if (J->_bstat < k) k = J->_bstat;
+ }
+ infoq->rd_commit ();
+ }
+ if (n) printf ("%8.3lf %10.6lf %5d\n", e / n, r / n, k);
+ }
+
+
+ Alsa_pcmi *A;
+ Alsathread *C;
+ Jackclient *J;
+
+public:
+
+ 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 ();
+ return 1;
+ }
+ 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, S_opt, this);
+ 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 = t_alsa + t_jack;
+ k_del = (int)(t_del * fsamp);
+ for (k = 256; k < 2 * k_del; k *= 2);
+ audioq = new Lfq_audio (k, nchan);
+
+ if (rqual == 0)
+ {
+ k = (fsamp < J->fsamp ()) ? fsamp : J->fsamp ();
+ if (k < 44100) k = 44100;
+ rqual = (int)((6.7 * k) / (k - 38000));
+ }
+ if (rqual < 16) rqual = 16;
+ if (rqual > 96) rqual = 96;
+
+ 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" {
+
+int
+jack_initialize (jack_client_t* client, const char* load_init)
+{
+ zita_a2j *c = new zita_a2j();
+ c->jack_initialize(client, load_init);
+ return 0;
+}
+
+void jack_finish (void* arg)
+{
+ Jackclient *J = (Jackclient *)arg;
+ zita_a2j *c = (zita_a2j *)J->getarg();
+ c->jack_finish(arg);
+ delete c;
+}
+
+} /* extern "C" */
diff --git a/tools/zalsa/zita-j2a.cc b/tools/zalsa/zita-j2a.cc
new file mode 100644
index 00000000..28b41bf1
--- /dev/null
+++ b/tools/zalsa/zita-j2a.cc
@@ -0,0 +1,327 @@
+// ----------------------------------------------------------------------------
+//
+// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+// ----------------------------------------------------------------------------
+#include <iostream>
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <signal.h>
+#include "alsathread.h"
+#include "jackclient.h"
+#include "lfqueue.h"
+
+static const char *clopt = "hvLSj:d:r:p:n:c:Q:O:";
+
+static void help (void)
+{
+ fprintf (stderr, "\n%s-%s\n", APPNAME, VERSION);
+ fprintf (stderr, "(C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>\n");
+ fprintf (stderr, "Use ALSA playback device as a Jack client.\n\n");
+ fprintf (stderr, "Usage: %s <options>\n", APPNAME);
+ fprintf (stderr, "Options:\n");
+ fprintf (stderr, " -h Display this text\n");
+ fprintf (stderr, " -j <jackname> Name as Jack client [%s]\n", APPNAME);
+ fprintf (stderr, " -d <device> ALSA playback device [none]\n");
+ fprintf (stderr, " -r <rate> Sample rate [48000]\n");
+ fprintf (stderr, " -p <period> Period size [256]\n");
+ fprintf (stderr, " -n <nfrags> Number of fragments [2]\n");
+ fprintf (stderr, " -c <nchannels> Number of channels [2]\n");
+ fprintf (stderr, " -S Word clock sync, no resampling\n");
+ fprintf (stderr, " -Q <quality> Resampling quality, 16..96 [auto]\n");
+ fprintf (stderr, " -O <samples> Latency adjustment [0]\n");
+ fprintf (stderr, " -L Force 16-bit and 2 channels [off]\n");
+ fprintf (stderr, " -v Print tracing information [off]\n");
+}
+
+class zita_j2a
+{
+ Lfq_int32 *commq;
+ Lfq_adata *alsaq;
+ Lfq_jdata *infoq;
+ Lfq_audio *audioq;
+ bool stop;
+ bool v_opt;
+ bool L_opt;
+ bool S_opt;
+ char *jname;
+ char *device;
+ int fsamp;
+ int bsize;
+ int nfrag;
+ int nchan;
+ int rqual;
+ int ltcor;
+
+public:
+
+ zita_j2a()
+ {
+ commq = new Lfq_int32(16);
+ alsaq = new Lfq_adata(256);
+ infoq = new Lfq_jdata(256);
+ audioq = 0;
+ stop = false;
+ v_opt = false;
+ L_opt = false;
+ S_opt = false;
+ jname = strdup(APPNAME);
+ device = 0;
+ fsamp = 48000;
+ bsize = 128;
+ nfrag = 2;
+ nchan = 2;
+ rqual = 0;
+ ltcor = 0;
+ A = 0;
+ P = 0;
+ J = 0;
+ }
+
+private:
+
+ 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");
+ return 1;
+ }
+ switch (k)
+ {
+ case 'h' : help (); return 1;
+ case 'v' : v_opt = true; break;
+ case 'L' : L_opt = true; break;
+ case 'S' : S_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;
+ }
+
+ 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);
+ }
+
+ void printinfo (void)
+ {
+ int n, k;
+ double e, r;
+ Jdata *J;
+
+ n = 0;
+ k = 99999;
+ 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 10 seconds.\n");
+ n = 0;
+ }
+ else if (J->_state == Jackclient::SYNC0)
+ {
+ printf ("Starting synchronisation.\n");
+ }
+ else if (v_opt)
+ {
+ n++;
+ e += J->_error;
+ r += J->_ratio;
+ if (J->_bstat < k) k = J->_bstat;
+ }
+ infoq->rd_commit ();
+ }
+ if (n) printf ("%8.3lf %10.6lf %5d\n", e / n, r / n, k);
+ }
+
+ Alsa_pcmi *A;
+ Alsathread *P;
+ Jackclient *J;
+
+public:
+
+ 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 ();
+ return 1;
+ }
+ 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, S_opt, this);
+ 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 = t_alsa + t_jack;
+ k_del = (int)(t_del * fsamp);
+ for (k = 256; k < 2 * k_del; k *= 2);
+ audioq = new Lfq_audio (k, nchan);
+
+ if (rqual == 0)
+ {
+ k = (fsamp < J->fsamp ()) ? fsamp : J->fsamp ();
+ if (k < 44100) k = 44100;
+ rqual = (int)((6.7 * k) / (k - 38000));
+ }
+ if (rqual < 16) rqual = 16;
+ if (rqual > 96) rqual = 96;
+
+ 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" {
+
+int
+jack_initialize (jack_client_t* client, const char* load_init)
+{
+ zita_j2a *c = new zita_j2a();
+ c->jack_initialize(client, load_init);
+ return 0;
+}
+
+void jack_finish (void* arg)
+{
+ Jackclient *J = (Jackclient *)arg;
+ zita_j2a *c = (zita_j2a *)J->getarg();
+ c->jack_finish(arg);
+ delete c;
+}
+
+} /* extern "C" */
diff --git a/wscript b/wscript
index dfd02f4b..86a42302 100644
--- a/wscript
+++ b/wscript
@@ -174,6 +174,11 @@ def options(opt):
help='Use Berkeley DB (metadata)')
db.check(header_name='db.h')
db.check(lib='db')
+ zalsa = opt.add_auto_option(
+ 'zalsa',
+ help='Build internal zita-a2j/j2a client')
+ zalsa.check(lib='zita-alsa-pcmi')
+ zalsa.check(lib='zita-resampler')
# dbus options
opt.recurse('dbus')