From 361b2f55479c7444ff7b8f7bd0895aa1cbc1e1b1 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 14 Apr 2021 15:38:57 +0100 Subject: add zita internal client code Signed-off-by: falkTX --- tools/zalsa/alsathread.cc | 218 ++++++++++++++++++ tools/zalsa/alsathread.h | 68 ++++++ tools/zalsa/jackclient.cc | 549 ++++++++++++++++++++++++++++++++++++++++++++++ tools/zalsa/jackclient.h | 120 ++++++++++ tools/zalsa/lfqueue.cc | 89 ++++++++ tools/zalsa/lfqueue.h | 182 +++++++++++++++ tools/zalsa/pxthread.cc | 78 +++++++ tools/zalsa/pxthread.h | 52 +++++ tools/zalsa/timers.h | 53 +++++ tools/zalsa/zita-a2j.cc | 329 +++++++++++++++++++++++++++ tools/zalsa/zita-j2a.cc | 327 +++++++++++++++++++++++++++ 11 files changed, 2065 insertions(+) create mode 100644 tools/zalsa/alsathread.cc create mode 100644 tools/zalsa/alsathread.h create mode 100644 tools/zalsa/jackclient.cc create mode 100644 tools/zalsa/jackclient.h create mode 100644 tools/zalsa/lfqueue.cc create mode 100644 tools/zalsa/lfqueue.h create mode 100644 tools/zalsa/pxthread.cc create mode 100644 tools/zalsa/pxthread.h create mode 100644 tools/zalsa/timers.h create mode 100644 tools/zalsa/zita-a2j.cc create mode 100644 tools/zalsa/zita-j2a.cc 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 +// +// 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 "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 +// +// 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 "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 +// +// 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 "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 +// +// 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 "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 +// +// 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/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 +// +// 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; + 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 +// +// 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/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 +// +// 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/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 +// +// 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 __TIMERS_H +#define __TIMERS_H + + +#include +#include +#include + + +#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 +// +// 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 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 \n"); + fprintf (stderr, "Use ALSA capture device as a Jack client.\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, " -S Word clock sync, no resampling\n"); + fprintf (stderr, " -Q Resampling quality, 16..96 [auto]\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"); +} + +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 +// +// 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 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 \n"); + fprintf (stderr, "Use ALSA playback device as a Jack client.\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, " -S Word clock sync, no resampling\n"); + fprintf (stderr, " -Q Resampling quality, 16..96 [auto]\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"); +} + +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" */ -- cgit v1.2.1