diff --git a/libs/ardour/ardour/audio_port.h b/libs/ardour/ardour/audio_port.h index 384de3ef5d..4224a88d55 100644 --- a/libs/ardour/ardour/audio_port.h +++ b/libs/ardour/ardour/audio_port.h @@ -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 diff --git a/libs/ardour/ardour/port.h b/libs/ardour/ardour/port.h index e59df0cb03..18d56dd8df 100644 --- a/libs/ardour/ardour/port.h +++ b/libs/ardour/ardour/port.h @@ -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 MonitorInputChanged; static PBD::Signal2,boost::shared_ptr > PostDisconnect; static PBD::Signal0 PortDrop; static PBD::Signal0 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 diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index db3f345db6..b0e133f4c6 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -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(&_mtc_active)); } bool synced_to_ltc () const { return config.get_external_sync() && Config->get_sync_source() == LTC && g_atomic_int_get (const_cast(&_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; diff --git a/libs/ardour/audio_port.cc b/libs/ardour/audio_port.cc index 70943e35a3..a9d41afce2 100644 --- a/libs/ardour/audio_port.cc +++ b/libs/ardour/audio_port.cc @@ -18,6 +18,7 @@ #include +#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); } - - - - diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 03c13e38fd..da512cc51d 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -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) { diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc index a6553b7fb6..24dc994535 100644 --- a/libs/ardour/midi_port.cc +++ b/libs/ardour/midi_port.cc @@ -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; diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc index 81290aa021..b469194ea5 100644 --- a/libs/ardour/port.cc +++ b/libs/ardour/port.cc @@ -43,7 +43,9 @@ PBD::Signal0 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); +} diff --git a/libs/ardour/port_manager.cc b/libs/ardour/port_manager.cc index dfc1c64421..6d3336cabb 100644 --- a/libs/ardour/port_manager.cc +++ b/libs/ardour/port_manager.cc @@ -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); } diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index aad0fefdd9..0bfc5d97ca 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -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) diff --git a/libs/ardour/wscript b/libs/ardour/wscript index df28255d5e..1604803884 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -396,6 +396,7 @@ def build(bld): 'libaudiographer', 'libtemporal', 'liblua', + 'zita-resampler', ] if bld.env['build_target'] != 'mingw': obj.uselib += ['DL']