Move vari-speed into backend (resample ports)
authorRobin Gareus <robin@gareus.org>
Sun, 29 Oct 2017 17:30:18 +0000 (18:30 +0100)
committerRobin Gareus <robin@gareus.org>
Sun, 29 Oct 2017 19:04:00 +0000 (20:04 +0100)
Previously Ardour used a /local/ per track vari-speed mechanism.
Now that the disk-reader is a latency-compensated processor, the speed
of each disk-reader would need to be maintained locally, offset by each
disk-reader's output latency. Furthermore each disk-reader may
produce a different number of samples, depending on its global alignment.

This commit introduces port-data resampling directly at the engine-level:
Up/down-sample all input ports at the beginning, and down/up-sample output
port-data using the inverse ratio at the end of the session's process
cycle.
The session itself is unaware of the speed-change, and only needs to
handle transport speeds {-1, 0, +1}.

This also allows for aligned cue-monitoring and vari-speed recording,
and also pitch-shifts synthesized MIDI along.

libs/ardour/ardour/audio_port.h
libs/ardour/ardour/port.h
libs/ardour/ardour/session.h
libs/ardour/audio_port.cc
libs/ardour/audioengine.cc
libs/ardour/midi_port.cc
libs/ardour/port.cc
libs/ardour/port_manager.cc
libs/ardour/session.cc
libs/ardour/wscript

index 384de3ef5d0bcd8aad241547780c1e63e34e366b..4224a88d558bbb15f1009c9390c5b90468e10324 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef __ardour_audio_port_h__
 #define __ardour_audio_port_h__
 
+#include "zita-resampler/vmresampler.h"
+
 #include "ardour/port.h"
 #include "ardour/audio_buffer.h"
 
@@ -49,12 +51,14 @@ class LIBARDOUR_API AudioPort : public Port
        friend class PortManager;
        AudioPort (std::string const &, PortFlags);
 
-        /* special access for PortManager only (hah, C++) */
-        Sample* engine_get_whole_audio_buffer ();
+       /* special access for PortManager only (hah, C++) */
+       Sample* engine_get_whole_audio_buffer ();
 
   private:
-       AudioBuffer* _buffer;
-        bool         _buf_valid;
+       AudioBuffer*            _buffer;
+       ArdourZita::VMResampler _src;
+       Sample*                 _data;
+       bool                    _buf_valid;
 };
 
 } // namespace ARDOUR
index e59df0cb03693a1bb6b3bcccc6d99c25bb43c051..18d56dd8df3ffcd1c0c2366d56e418f1b1ff1796 100644 (file)
@@ -106,7 +106,7 @@ public:
        void set_private_latency_range (LatencyRange& range, bool playback);
        const LatencyRange&  private_latency_range (bool playback) const;
 
-       void set_public_latency_range (LatencyRange& range, bool playback) const;
+       void set_public_latency_range (LatencyRange const& range, bool playback) const;
        LatencyRange public_latency_range (bool playback) const;
 
        virtual void reset ();
@@ -121,15 +121,16 @@ public:
        virtual void realtime_locate () {}
 
        bool physically_connected () const;
+       bool externally_connected () const;
 
        PBD::Signal1<void,bool> MonitorInputChanged;
        static PBD::Signal2<void,boost::shared_ptr<Port>,boost::shared_ptr<Port> > PostDisconnect;
        static PBD::Signal0<void> PortDrop;
        static PBD::Signal0<void> PortSignalDrop;
 
-       static void set_cycle_samplecnt (pframes_t n) {
-               _cycle_nframes = n;
-       }
+       static void set_speed_ratio (double s);
+       static void set_cycle_samplecnt (pframes_t n);
+
        static samplecnt_t port_offset() { return _global_port_buffer_offset; }
        static void set_global_port_buffer_offset (pframes_t off) {
                _global_port_buffer_offset = off;
@@ -145,21 +146,26 @@ public:
 
        static std::string state_node_name;
 
+       static pframes_t cycle_nframes () { return _cycle_nframes; }
+
 protected:
 
        Port (std::string const &, DataType, PortFlags);
 
-        PortEngine::PortHandle _port_handle;
+       PortEngine::PortHandle _port_handle;
 
-       static bool       _connecting_blocked;
-       static pframes_t  _global_port_buffer_offset;   /* access only from process() tree */
+       static bool           _connecting_blocked;
        static pframes_t  _cycle_nframes; /* access only from process() tree */
 
-       samplecnt_t _port_buffer_offset; /* access only from process() tree */
+       static pframes_t  _global_port_buffer_offset; /* access only from process() tree */
+       samplecnt_t       _port_buffer_offset; /* access only from process() tree */
 
        LatencyRange _private_playback_latency;
        LatencyRange _private_capture_latency;
 
+       static double _speed_ratio;
+       static const uint32_t _resampler_quality; /* also latency of the resampler */
+
 private:
        std::string _name;  ///< port short name
        PortFlags   _flags; ///< flags
index db3f345db610f4f2002fc3c246d640401cd066a3..b0e133f4c699c6def90a08430ac45e1b48706342 100644 (file)
@@ -728,6 +728,12 @@ public:
        bool   synced_to_mtc () const { return config.get_external_sync() && Config->get_sync_source() == MTC && g_atomic_int_get (const_cast<gint*>(&_mtc_active)); }
        bool   synced_to_ltc () const { return config.get_external_sync() && Config->get_sync_source() == LTC && g_atomic_int_get (const_cast<gint*>(&_ltc_active)); }
 
+       double engine_speed() const { return _engine_speed; }
+       double actual_speed() const {
+               if (_transport_speed > 0) return _engine_speed;
+               if (_transport_speed < 0) return - _engine_speed;
+               return 0;
+       }
        double transport_speed() const { return _count_in_samples > 0 ? 0. : _transport_speed; }
        bool   transport_stopped() const { return _transport_speed == 0.0; }
        bool   transport_rolling() const { return _transport_speed != 0.0 && _count_in_samples == 0 && _remaining_latency_preroll == 0; }
@@ -1255,6 +1261,7 @@ private:
        samplecnt_t             _remaining_latency_preroll;
 
        // varispeed playback
+       double                  _engine_speed;
        double                  _transport_speed;
        double                  _default_transport_speed;
        double                  _last_transport_speed;
index 70943e35a308ef47a475372cb08b86d4fad821ef..a9d41afce268bda665cadae55325a0b5d5b21cfc 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <cassert>
 
+#include "pbd/malign.h"
 #include "pbd/stacktrace.h"
 
 #include "ardour/audio_buffer.h"
@@ -36,10 +37,14 @@ AudioPort::AudioPort (const std::string& name, PortFlags flags)
        , _buffer (new AudioBuffer (0))
 {
        assert (name.find_first_of (':') == string::npos);
+       cache_aligned_malloc ((void**) &_data, sizeof (Sample) * 8192);
+       _src.setup (_resampler_quality);
+       _src.set_rrfilt (10);
 }
 
 AudioPort::~AudioPort ()
 {
+       cache_aligned_free (_data);
        delete _buffer;
 }
 
@@ -47,11 +52,32 @@ void
 AudioPort::cycle_start (pframes_t nframes)
 {
        /* caller must hold process lock */
-
-        Port::cycle_start (nframes);
+       Port::cycle_start (nframes);
 
        if (sends_output()) {
                _buffer->prepare ();
+       } else if (!externally_connected ()) {
+               /* ardour internal port, just silence input, don't resample */
+               // TODO reset resampler only once
+               _src.reset ();
+               memset (_data, 0, _cycle_nframes * sizeof (float));
+       } else {
+               _src.inp_data  = (float*)port_engine.get_buffer (_port_handle, nframes);
+               _src.inp_count = nframes;
+               _src.out_count = _cycle_nframes;
+               _src.set_rratio (_cycle_nframes / (double)nframes);
+               _src.out_data  = _data;
+               _src.process ();
+#ifndef NDEBUG
+               if (_src.inp_count != 0 || _src.out_count != 0) {
+                       printf ("AudioPort::cycle_start x-flow: %d/%d\n", _src.inp_count, _src.out_count);
+               }
+#endif
+               while (_src.out_count > 0) {
+                       *_src.out_data =  _src.out_data[-1];
+                       ++_src.out_data;
+                       --_src.out_count;
+               }
        }
 }
 
@@ -66,6 +92,33 @@ AudioPort::cycle_end (pframes_t nframes)
                        _buffer->silence (nframes);
                }
        }
+
+       if (sends_output() && _port_handle) {
+
+               if (!externally_connected ()) {
+                       /* ardour internal port, data goes nowhere, skip resampling */
+                       // TODO reset resampler only once
+                       _src.reset ();
+                       return;
+               }
+
+               _src.inp_count = _cycle_nframes;
+               _src.out_count = nframes;
+               _src.set_rratio (nframes / (double)_cycle_nframes);
+               _src.inp_data  = _data;
+               _src.out_data  = (float*)port_engine.get_buffer (_port_handle, nframes);
+               _src.process ();
+#ifndef NDEBUG
+               if (_src.inp_count != 0 || _src.out_count != 0) {
+                       printf ("AudioPort::cycle_end x-flow: %d/%d\n", _src.inp_count, _src.out_count);
+               }
+#endif
+               while (_src.out_count > 0) {
+                       *_src.out_data =  _src.out_data[-1];
+                       ++_src.out_data;
+                       --_src.out_count;
+               }
+       }
 }
 
 void
@@ -78,8 +131,12 @@ AudioPort::get_audio_buffer (pframes_t nframes)
 {
        /* caller must hold process lock */
        assert (_port_handle);
-       _buffer->set_data ((Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes) +
-                          _global_port_buffer_offset + _port_buffer_offset, nframes);
+       if (!externally_connected ()) {
+               _buffer->set_data ((Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes) +
+                               _global_port_buffer_offset + _port_buffer_offset, nframes);
+       } else {
+               _buffer->set_data (&_data[_global_port_buffer_offset + _port_buffer_offset], nframes);
+       }
        return *_buffer;
 }
 
@@ -90,7 +147,3 @@ AudioPort::engine_get_whole_audio_buffer ()
        assert (_port_handle);
        return (Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes);
 }
-
-
-
-
index 03c13e38fd61a6114b650d4e28844b4a1a0cd85c..da512cc51dc9748784a3921041063a98dfbbfca9 100644 (file)
@@ -190,6 +190,7 @@ int
 AudioEngine::process_callback (pframes_t nframes)
 {
        Glib::Threads::Mutex::Lock tm (_process_lock, Glib::Threads::TRY_LOCK);
+       Port::set_speed_ratio (1.0);
 
        PT_TIMING_REF;
        PT_TIMING_CHECK (1);
@@ -357,6 +358,14 @@ AudioEngine::process_callback (pframes_t nframes)
                return 0;
        }
 
+       if (!_freewheeling || Freewheel.empty()) {
+               // run a list of slaves here
+               // - multiple slaves (ow_many_dsp_threads() in paralell)
+               // - session can pick one (ask for position & speed)
+               // - GUI can display all
+               Port::set_speed_ratio (_session->engine_speed ());
+       }
+
        /* tell all relevant objects that we're starting a new cycle */
 
        InternalSend::CycleStart (nframes);
@@ -373,7 +382,19 @@ AudioEngine::process_callback (pframes_t nframes)
        if (_freewheeling && !Freewheel.empty()) {
                Freewheel (nframes);
        } else {
-               _session->process (nframes);
+               if (Port::cycle_nframes () <= nframes) {
+                       _session->process (Port::cycle_nframes ());
+               } else {
+                       pframes_t remain = Port::cycle_nframes ();
+                       while (remain > 0) {
+                               pframes_t nf = std::min (remain, nframes);
+                               _session->process (nf);
+                               remain -= nf;
+                               if (remain > 0) {
+                                       split_cycle (nf);
+                               }
+                       }
+               }
        }
 
        if (_freewheeling) {
index a6553b7fb679c1eefe73e40f048f4a79761fd475..24dc9945354197f1908126942dc0e13304f22f2d 100644 (file)
@@ -138,9 +138,15 @@ MidiPort::get_midi_buffer (pframes_t nframes)
                                        continue;
                                }
 
+                               timestamp = floor (timestamp * _speed_ratio);
+
                                /* check that the event is in the acceptable time range */
                                if ((timestamp <  (_global_port_buffer_offset + _port_buffer_offset)) ||
                                    (timestamp >= (_global_port_buffer_offset + _port_buffer_offset + nframes))) {
+                                       // XXX this is normal after a split cycles:
+                                       // The engine buffer contains the data for the complete cycle, but
+                                       // only the part after _global_port_buffer_offset is needed.
+#ifndef NDEBUG
                                        cerr << "Dropping incoming MIDI at time " << timestamp << "; offset="
                                                << _global_port_buffer_offset << " limit="
                                                << (_global_port_buffer_offset + _port_buffer_offset + nframes)
@@ -148,9 +154,14 @@ MidiPort::get_midi_buffer (pframes_t nframes)
                                                << " + " << _port_buffer_offset
                                                << " + " << nframes
                                                << ")\n";
+#endif
                                        continue;
                                }
 
+                               /* adjust timestamp to match current cycle */
+                               timestamp -= _global_port_buffer_offset + _port_buffer_offset;
+                               assert (timestamp >= 0 && timestamp < nframes);
+
                                if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
                                        /* normalize note on with velocity 0 to proper note off */
                                        uint8_t ev[3];
@@ -196,19 +207,20 @@ MidiPort::resolve_notes (void* port_buffer, MidiBuffer::TimeType when)
        for (uint8_t channel = 0; channel <= 0xF; channel++) {
 
                uint8_t ev[3] = { ((uint8_t) (MIDI_CMD_CONTROL | channel)), MIDI_CTL_SUSTAIN, 0 };
+               pframes_t tme = floor (when / _speed_ratio);
 
                /* we need to send all notes off AND turn the
                 * sustain/damper pedal off to handle synths
                 * that prioritize sustain over AllNotesOff
                 */
 
-               if (port_engine.midi_event_put (port_buffer, when, ev, 3) != 0) {
+               if (port_engine.midi_event_put (port_buffer, tme, ev, 3) != 0) {
                        cerr << "failed to deliver sustain-zero on channel " << (int)channel << " on port " << name() << endl;
                }
 
                ev[1] = MIDI_CTL_ALL_NOTES_OFF;
 
-               if (port_engine.midi_event_put (port_buffer, when, ev, 3) != 0) {
+               if (port_engine.midi_event_put (port_buffer, tme, ev, 3) != 0) {
                        cerr << "failed to deliver ALL NOTES OFF on channel " << (int)channel << " on port " << name() << endl;
                }
        }
@@ -279,7 +291,8 @@ MidiPort::flush_buffers (pframes_t nframes)
                        assert (ev.time() < (nframes + _global_port_buffer_offset));
 
                        if (ev.time() >= _global_port_buffer_offset) {
-                               if (port_engine.midi_event_put (port_buffer, (pframes_t) ev.time() + _port_buffer_offset, ev.buffer(), ev.size()) != 0) {
+                               pframes_t tme = floor ((ev.time() + _port_buffer_offset) / _speed_ratio);
+                               if (port_engine.midi_event_put (port_buffer, tme, ev.buffer(), ev.size()) != 0) {
                                        cerr << "write failed, dropped event, time "
                                             << ev.time() << " + " << _port_buffer_offset
                                                         << " > " << _global_port_buffer_offset << endl;
index 81290aa021187d3a4d7b62ee51e9f46223a43edb..b469194ea5cdf702b1136712e0dd484771e5cb2a 100644 (file)
@@ -43,7 +43,9 @@ PBD::Signal0<void> Port::PortSignalDrop;
 bool         Port::_connecting_blocked = false;
 pframes_t    Port::_global_port_buffer_offset = 0;
 pframes_t    Port::_cycle_nframes = 0;
+double       Port::_speed_ratio = 1.0;
 std::string  Port::state_node_name = X_("Port");
+const uint32_t Port::_resampler_quality = 12;
 
 /* a handy define to shorten what would otherwise be a needlessly verbose
  * repeated phrase
@@ -350,7 +352,7 @@ Port::increment_port_buffer_offset (pframes_t nframes)
 }
 
 void
-Port::set_public_latency_range (LatencyRange& range, bool playback) const
+Port::set_public_latency_range (LatencyRange const& range, bool playback) const
 {
        /* this sets the visible latency that the rest of the port system
           sees. because we do latency compensation, all (most) of our visible
@@ -363,7 +365,16 @@ Port::set_public_latency_range (LatencyRange& range, bool playback) const
                                     (playback ? "PLAYBACK" : "CAPTURE")));;
 
        if (_port_handle) {
-               port_engine.set_latency_range (_port_handle, playback, range);
+               LatencyRange r (range);
+               if (externally_connected ()) {
+#if 0
+                       r.min *= _speed_ratio;
+                       r.max *= _speed_ratio;
+#endif
+                       r.min += (_resampler_quality - 1);
+                       r.max += (_resampler_quality - 1);
+               }
+               port_engine.set_latency_range (_port_handle, playback, r);
        }
 }
 
@@ -419,6 +430,14 @@ Port::public_latency_range (bool /*playback*/) const
 
        if (_port_handle) {
                r = port_engine.get_latency_range (_port_handle, sends_output() ? true : false);
+               if (externally_connected ()) {
+#if 0
+                       r.min /= _speed_ratio;
+                       r.max /= _speed_ratio;
+#endif
+                       r.min += (_resampler_quality - 1);
+                       r.max += (_resampler_quality - 1);
+               }
 
                DEBUG_TRACE (DEBUG::Latency, string_compose (
                                     "GET PORT %1: %4 PUBLIC latency range %2 .. %3\n",
@@ -458,6 +477,14 @@ Port::get_connected_latency_range (LatencyRange& range, bool playback) const
 
                                 if (remote_port) {
                                         lr = port_engine.get_latency_range (remote_port, playback);
+                                        if (externally_connected ()) {
+#if 0
+                                          lr.min /= _speed_ratio;
+                                          lr.max /= _speed_ratio;
+#endif
+                                          lr.min += (_resampler_quality - 1);
+                                          lr.max += (_resampler_quality - 1);
+                                        }
 
                                         DEBUG_TRACE (DEBUG::Latency, string_compose (
                                                              "\t%1 <-> %2 : latter has latency range %3 .. %4\n",
@@ -564,6 +591,19 @@ Port::physically_connected () const
        return port_engine.physically_connected (_port_handle);
 }
 
+bool
+Port::externally_connected () const
+{
+       if (!_port_handle) {
+               return false;
+       }
+
+       // TODO: When used with JACK, check if this port
+       // is connected to any non-ardour ports.
+
+       return port_engine.physically_connected (_port_handle);
+}
+
 XMLNode&
 Port::get_state () const
 {
@@ -621,3 +661,14 @@ Port::set_state (const XMLNode& node, int)
 
        return 0;
 }
+
+/*static*/ void
+Port::set_speed_ratio (double s) {
+       /* see VMResampler::set_rratio() for min/max range */
+       _speed_ratio = std::min (16.0, std::max (0.5, s));
+}
+
+/*static*/ void
+Port::set_cycle_samplecnt (pframes_t n) {
+       _cycle_nframes = floor (n * _speed_ratio);
+}
index dfc1c6442107c4aefdf5c59356d0b92813f0a514..6d3336cabbc909dec0b294c9520d810fc7840884 100644 (file)
@@ -749,10 +749,11 @@ void
 PortManager::cycle_start (pframes_t nframes)
 {
        Port::set_global_port_buffer_offset (0);
-        Port::set_cycle_samplecnt (nframes);
+       Port::set_cycle_samplecnt (nframes);
 
        _cycle_ports = ports.reader ();
 
+       // TODO parallelize
        for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) {
                p->second->cycle_start (nframes);
        }
index aad0fefdd9f697789bfdfa05386c5e8280526743..0bfc5d97ca51a91e78517b8eea1318dbb4281f60 100644 (file)
@@ -187,6 +187,7 @@ Session::Session (AudioEngine &eng,
        , _slave (0)
        , _silent (false)
        , _remaining_latency_preroll (0)
+       , _engine_speed (1.0)
        , _transport_speed (0)
        , _default_transport_speed (1.0)
        , _last_transport_speed (0)
index df28255d5e7f2667cbda9a325b031e07d08d2ae0..16048038845e93bba406e67805ad0b92e86258a3 100644 (file)
@@ -396,6 +396,7 @@ def build(bld):
                         'libaudiographer',
                         'libtemporal',
                         'liblua',
+                        'zita-resampler',
                         ]
     if bld.env['build_target'] != 'mingw':
         obj.uselib += ['DL']