redesign of declicking and fades around loop boundaries
authorPaul Davis <paul@linuxaudiosystems.com>
Sat, 23 Nov 2019 06:41:56 +0000 (23:41 -0700)
committerPaul Davis <paul@linuxaudiosystems.com>
Sat, 23 Nov 2019 06:41:56 +0000 (23:41 -0700)
16 files changed:
gtk2_ardour/rc_option_editor.cc
libs/ardour/ardour/disk_reader.h
libs/ardour/ardour/rc_configuration_vars.h
libs/ardour/ardour/route.h
libs/ardour/ardour/session.h
libs/ardour/ardour/track.h
libs/ardour/ardour/transport_fsm.h
libs/ardour/ardour/types.h
libs/ardour/ardour/types_convert.h
libs/ardour/disk_reader.cc
libs/ardour/enums.cc
libs/ardour/route.cc
libs/ardour/session.cc
libs/ardour/session_transport.cc
libs/ardour/track.cc
libs/ardour/transport_fsm.cc

index 261537d8ff8f20f98d164bf9dbb5bacef7316c58..d6e221f328ddc7a5f170dadda3c9cfd40fd183e9 100644 (file)
@@ -3176,8 +3176,24 @@ RCOptionEditor::RCOptionEditor ()
        Gtkmm2ext::UI::instance()->set_tip (bo->tip_widget(),
                                            (_("<b>When enabled</b> the loop button does not start playback but forces playback to always play the loop\n\n"
                                               "<b>When disabled</b> the loop button starts playing the loop, but stop then cancels loop playback")));
+
+
        add_option (_("Transport"), bo);
 
+
+       ComboOption<LoopFadeChoice>* lca = new ComboOption<LoopFadeChoice> (
+                    "loop-fade-choice",
+                    _("Loop Fades"),
+                    sigc::mem_fun (*_rc_config, &RCConfiguration::get_loop_fade_choice),
+                    sigc::mem_fun (*_rc_config, &RCConfiguration::set_loop_fade_choice)
+                    );
+       lca->add (NoLoopFade, _("No fades at loop boundaries"));
+       lca->add (EndLoopFade, _("Fade out at loop end"));
+       lca->add (BothLoopFade, _("Fade in at loop start & Fade out at loop end"));
+       lca->add (XFadeLoop, _("Cross-fade loop end and start"));
+       add_option (_("Transport"), lca);
+       Gtkmm2ext::UI::instance()->set_tip (lca->tip_widget(), _("Options for fades/crossfades at loop boundaries"));
+
        add_option (_("Transport"), new OptionEditorHeading (_("Dropout (xrun) Handling")));
        bo = new BoolOption (
                     "stop-recording-on-xrun",
index 427a6503d25622b9fc7fb51832a3755e25c45a0a..970295b6c273e29f2f8a3a2cdbdfe80023341928 100644 (file)
@@ -22,6 +22,8 @@
 
 #include "pbd/i18n.h"
 
+#include "evoral/Curve.h"
+
 #include "ardour/disk_io.h"
 #include "ardour/midi_buffer.h"
 #include "ardour/midi_state_tracker.h"
@@ -53,6 +55,7 @@ public:
        void realtime_locate (bool);
        bool overwrite_existing_buffers ();
        void set_pending_overwrite ();
+       void set_loop (Location *);
 
        int set_state (const XMLNode&, int version);
 
@@ -96,6 +99,7 @@ public:
        void reset_tracker ();
 
        bool declick_in_progress () const;
+       void reload_loop ();
 
        static void set_midi_readahead_samples (samplecnt_t samples_ahead) { midi_readahead = samples_ahead; }
 
@@ -110,18 +114,30 @@ public:
        static void inc_no_disk_output ();
        static void dec_no_disk_output();
        static bool no_disk_output () { return g_atomic_int_get (&_no_disk_output); }
+       static void reset_loop_declick (Location*, samplecnt_t sample_rate);
+       static void alloc_loop_declick (samplecnt_t sample_rate);
 
 protected:
        friend class Track;
        friend class MidiTrack;
 
-       struct ReaderChannelInfo : public DiskIOProcessor::ChannelInfo {
-               ReaderChannelInfo (samplecnt_t buffer_size)
+       struct ReaderChannelInfo : public DiskIOProcessor::ChannelInfo
+       {
+               ReaderChannelInfo (samplecnt_t buffer_size, samplecnt_t preloop_size)
                        : DiskIOProcessor::ChannelInfo (buffer_size)
+                       , pre_loop_buffer (0)
+                       , pre_loop_buffer_size (0)
                {
                        resize (buffer_size);
+                       resize_preloop (preloop_size);
                }
+               ~ReaderChannelInfo() { delete [] pre_loop_buffer; }
+
                void resize (samplecnt_t);
+               void resize_preloop (samplecnt_t);
+
+               Sample* pre_loop_buffer;
+               samplecnt_t pre_loop_buffer_size;
        };
 
        XMLNode& state ();
@@ -138,7 +154,7 @@ protected:
                public:
                        DeclickAmp (samplecnt_t sample_rate);
 
-                       void apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target);
+               void apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target, sampleoffset_t buffer_offset = 0);
 
                        float gain () const { return _g; }
                        void set_gain (float g) { _g = g; }
@@ -149,6 +165,22 @@ protected:
                        float _g;
        };
 
+       class Declicker {
+          public:
+               Declicker ();
+               ~Declicker ();
+
+               void alloc (samplecnt_t sr, bool fadein);
+
+               void run (Sample* buf, samplepos_t start, samplepos_t end);
+               void reset (samplepos_t start, samplepos_t end, bool fadein, samplecnt_t sr);
+
+               samplepos_t fade_start;
+               samplepos_t fade_end;
+               samplecnt_t fade_length;
+               Sample* vec;
+       };
+
 private:
        /** The number of samples by which this diskstream's output should be delayed
            with respect to the transport sample.  This is used for latency compensation.
@@ -170,12 +202,18 @@ private:
        static samplecnt_t midi_readahead;
        static gint       _no_disk_output;
 
+       static Declicker loop_declick_in;
+       static Declicker loop_declick_out;
+       static samplecnt_t loop_fade_length;
+
        int audio_read (PBD::PlaybackBuffer<Sample>*,
                        Sample* sum_buffer,
                        Sample* mixdown_buffer,
                        float*  gain_buffer,
                        samplepos_t& start, samplecnt_t cnt,
-                       int channel, bool reversed);
+                       ReaderChannelInfo* rci,
+                       int channel,
+                       bool reversed);
 
        static Sample* _sum_buffer;
        static Sample* _mixdown_buffer;
@@ -189,6 +227,7 @@ private:
        RTMidiBuffer* rt_midibuffer();
 
        void get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, samplepos_t end_sample, MonitorState, BufferSet&, double speed, samplecnt_t distance);
+       void maybe_xfade_loop (Sample*, samplepos_t read_start, samplepos_t read_end, ReaderChannelInfo*);
 };
 
 } // namespace
index 6938531b5e6c83dedb293d3ba0a9c77e232ee494..b572bec865ecd4086018b9b456d9beab283c259f 100644 (file)
@@ -162,6 +162,7 @@ CONFIG_VARIABLE (bool, create_xrun_marker, "create-xrun-marker", true)
 CONFIG_VARIABLE (bool, stop_at_session_end, "stop-at-session-end", false)
 CONFIG_VARIABLE (float, preroll_seconds, "preroll-seconds", -2.0f)
 CONFIG_VARIABLE (bool, loop_is_mode, "loop-is-mode", false)
+CONFIG_VARIABLE (LoopFadeChoice, loop_fade_choice, "loop-fade-choice", XFadeLoop)
 CONFIG_VARIABLE (samplecnt_t, preroll, "preroll", 0)
 CONFIG_VARIABLE (samplecnt_t, postroll, "postroll", 0)
 CONFIG_VARIABLE (float, shuttle_speed_factor, "shuttle-speed-factor", 1.0f) // used for MMC shuttle
index efc93a7ee9de38c5e4d774c89acb7c4ad8a786e0..5c2c4ad48d0289ea0c5e42dca052ccc270eddbaf 100644 (file)
@@ -360,6 +360,8 @@ public:
        PBD::Signal0<void> denormal_protection_changed;
        PBD::Signal0<void> comment_changed;
 
+       virtual void reload_loop();
+
        bool is_track();
 
        /** track numbers - assigned by session
index 4e74a1dc5af9ff0062480088bd55d0f1ee374aed..3a3f16dfdf0ddde01b2f0ece503043f60a526d56 100644 (file)
@@ -1486,7 +1486,6 @@ private:
 
        PBD::ScopedConnectionList loop_connections;
        void             auto_loop_changed (Location *);
-       void             auto_loop_declick_range (Location *, samplepos_t &, samplepos_t &);
 
        void pre_engine_init (std::string path);
        int  post_engine_init ();
index 90d5574b6553b2c150a2cfb5ca8e3d13b0ca77db..0b6462fbc3205e79c53cabc5e6e0633ca658c351 100644 (file)
@@ -166,6 +166,7 @@ public:
        }
        void adjust_playback_buffering ();
        void adjust_capture_buffering ();
+       void reload_loop ();
 
        PBD::Signal0<void> FreezeChange;
        PBD::Signal0<void> PlaylistChanged;
index 7eafccead1aa520d4e8642753929bcace9f544f6..26ccb787657970330ac9ae02d5da9c3ee3ab1d54 100644 (file)
@@ -54,7 +54,7 @@ struct TransportFSM
                };
                /* for locate */
                samplepos_t target;
-               bool with_loop;
+               bool for_loop_end;
                bool force;
 
                Event (EventType t)
@@ -62,7 +62,7 @@ struct TransportFSM
                        , with_roll (false)
                        , with_flush (false)
                        , target (0)
-                       , with_loop (false)
+                       , for_loop_end (false)
                        , force (false)
                {}
                Event (EventType t, bool ab, bool cl)
@@ -77,7 +77,7 @@ struct TransportFSM
                        , with_roll (r)
                        , with_flush (fl)
                        , target (pos)
-                       , with_loop (lp)
+                       , for_loop_end (lp)
                        , force (f4c)
                {
                        assert (t == Locate);
index ea96c7fd6dc9ff25711739f6db591d6dcd80dd74..7cd3759a8839e609a4e6d77d4e8287ad9c693f01 100644 (file)
@@ -785,6 +785,13 @@ struct CaptureInfo {
        samplecnt_t samples;
 };
 
+enum LoopFadeChoice {
+       NoLoopFade,
+       EndLoopFade,
+       BothLoopFade,
+       XFadeLoop,
+};
+
 typedef std::vector<CaptureInfo*> CaptureInfos;
 
 } // namespace ARDOUR
index ae48c7602dd0077aab463e0d26cb8445e833c2f3..9e46d51982ab3289474bde37a5c5ed05701f9fdc 100644 (file)
@@ -75,6 +75,7 @@ DEFINE_ENUM_CONVERT(ARDOUR::VUMeterStandard)
 DEFINE_ENUM_CONVERT(ARDOUR::MeterLineUp)
 DEFINE_ENUM_CONVERT(ARDOUR::MidiPortFlags)
 DEFINE_ENUM_CONVERT(ARDOUR::TransportRequestType)
+DEFINE_ENUM_CONVERT(ARDOUR::LoopFadeChoice)
 
 DEFINE_ENUM_CONVERT(MusicalMode::Type)
 
index b62fe04ecd847919e23648308c6c73ec42f000ae..b4ffba834e40895f14b2e66c69491a3880f038e1 100644 (file)
@@ -23,6 +23,8 @@
 #include "pbd/memento_command.h"
 #include "pbd/playback_buffer.h"
 
+#include "evoral/Range.h"
+
 #include "ardour/amp.h"
 #include "ardour/audioengine.h"
 #include "ardour/audioplaylist.h"
@@ -52,6 +54,9 @@ Sample* DiskReader::_mixdown_buffer = 0;
 gain_t* DiskReader::_gain_buffer = 0;
 samplecnt_t DiskReader::midi_readahead = 4096;
 gint DiskReader::_no_disk_output (0);
+DiskReader::Declicker DiskReader::loop_declick_in;
+DiskReader::Declicker DiskReader::loop_declick_out;
+samplecnt_t DiskReader::loop_fade_length (0);
 
 DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
        : DiskIOProcessor (s, str, f)
@@ -80,11 +85,25 @@ DiskReader::ReaderChannelInfo::resize (samplecnt_t bufsize)
        memset (rbuf->buffer(), 0, sizeof (Sample) * rbuf->bufsize());
 }
 
+void
+DiskReader::ReaderChannelInfo::resize_preloop (samplecnt_t bufsize)
+{
+       if (bufsize == 0) {
+               return;
+       }
+
+       if (bufsize > pre_loop_buffer_size) {
+               delete [] pre_loop_buffer;
+               pre_loop_buffer = new Sample[bufsize];
+               pre_loop_buffer_size = bufsize;
+       }
+}
+
 int
 DiskReader::add_channel_to (boost::shared_ptr<ChannelList> c, uint32_t how_many)
 {
        while (how_many--) {
-               c->push_back (new ReaderChannelInfo (_session.butler()->audio_diskstream_playback_buffer_size()));
+               c->push_back (new ReaderChannelInfo (_session.butler()->audio_diskstream_playback_buffer_size(), loop_fade_length));
                DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: new reader channel, write space = %2 read = %3\n",
                                                            name(),
                                                            c->back()->rbuf->write_space(),
@@ -540,7 +559,9 @@ DiskReader::overwrite_existing_buffers ()
                        samplepos_t start = overwrite_sample;
                        samplecnt_t to_read = size;
 
-                       if (audio_read ((*chan)->rbuf, sum_buffer.get(), mixdown_buffer.get(), gain_buffer.get(), start, to_read, n, reversed)) {
+                       ReaderChannelInfo* rci = dynamic_cast<ReaderChannelInfo*> (*chan);
+
+                       if (audio_read ((*chan)->rbuf, sum_buffer.get(), mixdown_buffer.get(), gain_buffer.get(), start, to_read, rci, n, reversed)) {
                                error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), size, overwrite_sample) << endmsg;
                                goto midi;
                        }
@@ -689,6 +710,7 @@ DiskReader::audio_read (PBD::PlaybackBuffer<Sample>*rb,
                         Sample* mixdown_buffer,
                         float* gain_buffer,
                         samplepos_t& start, samplecnt_t cnt,
+                        ReaderChannelInfo* rci,
                         int channel, bool reversed)
 {
        samplecnt_t this_read = 0;
@@ -758,11 +780,36 @@ DiskReader::audio_read (PBD::PlaybackBuffer<Sample>*rb,
 
                this_read = min (cnt, this_read);
 
+               /* note that the mixdown and gain buffers are purely for the
+                * internal use of the playlist, and cannot be considered
+                * useful after the return from AudioPlayback::read()
+                */
+
                if (audio_playlist()->read (sum_buffer, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) {
                        error << string_compose(_("DiskReader %1: cannot read %2 from playlist at sample %3"), id(), this_read, start) << endmsg;
                        return -1;
                }
 
+               if (loc) {
+
+                       /* Looping: do something (maybe) about the loop boundaries */
+
+                       switch (Config->get_loop_fade_choice()) {
+                       case NoLoopFade:
+                               break;
+                       case BothLoopFade:
+                               loop_declick_in.run (sum_buffer, start, start + this_read);
+                               loop_declick_out.run (sum_buffer, start, start + this_read);
+                               break;
+                       case EndLoopFade:
+                               loop_declick_out.run (sum_buffer, start, start + this_read);
+                               break;
+                       case XFadeLoop:
+                               maybe_xfade_loop (sum_buffer, start, start + this_read, rci);
+                               break;
+                       }
+               }
+
                if (reversed) {
 
                        swap_by_ptr (sum_buffer, sum_buffer + this_read - 1);
@@ -968,7 +1015,8 @@ DiskReader::refill_audio (Sample* sum_buffer, Sample* mixdown_buffer, float* gai
                // cerr << owner()->name() << " to-read: " << to_read << endl;
 
                if (to_read) {
-                       if (audio_read (chan->rbuf, sum_buffer, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) {
+                       ReaderChannelInfo* rci = dynamic_cast<ReaderChannelInfo*> (chan);
+                       if (audio_read (chan->rbuf, sum_buffer, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, rci, chan_n, reversed)) {
                                error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), to_read, ffa) << endmsg;
                                ret = -1;
                                goto out;
@@ -1236,7 +1284,7 @@ DiskReader::DeclickAmp::DeclickAmp (samplecnt_t sample_rate)
 }
 
 void
-DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target)
+DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target, sampleoffset_t buffer_offset)
 {
        if (n_samples == 0) {
                return;
@@ -1244,6 +1292,7 @@ DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, con
        float g = _g;
 
        if (g == target) {
+               assert (buffer_offset == 0);
                Amp::apply_simple_gain (buf, n_samples, target, 0);
                return;
        }
@@ -1253,7 +1302,7 @@ DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, con
 
        const int max_nproc = 16;
        uint32_t remain = n_samples;
-       uint32_t offset = 0;
+       uint32_t offset = buffer_offset;
 
        while (remain > 0) {
                uint32_t n_proc = remain > max_nproc ? max_nproc : remain;
@@ -1280,6 +1329,232 @@ DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, con
        }
 }
 
+DiskReader::Declicker::Declicker ()
+       : fade_start (0)
+       , fade_end (0)
+       , fade_length (0)
+       , vec (0)
+{
+}
+
+DiskReader::Declicker::~Declicker ()
+{
+       delete vec;
+}
+
+void
+DiskReader::Declicker::alloc (samplecnt_t sr, bool fadein)
+{
+       delete [] vec;
+       vec = new Sample[loop_fade_length];
+
+       const float a = 1024.0f / sr;
+
+       /* build a psuedo-exponential (linear-volume) shape for the fade */
+
+       samplecnt_t n;
+
+#define GAIN_COEFF_DELTA (1e-5)
+
+       if (fadein) {
+               gain_t g = 0.0;
+               for (n = 0; (n < sr) && ((1.0 - g) > GAIN_COEFF_DELTA); ++n) {
+                       vec[n] = g;
+                       g += a * (1.0 - g);
+               }
+       } else {
+               gain_t g = 1.0;
+               for (n = 0; (n < sr) && (g > GAIN_COEFF_DELTA); ++n) {
+                       vec[n] = g;
+                       g += a * -g;
+               }
+       }
+
+       fade_length = n;
+
+       /* zero out the rest just to be safe */
+
+       memset (&vec[n], 0, sizeof (gain_t) * (loop_fade_length - n));
+
+#undef GAIN_COEFF_DELTA
+}
+
+void
+DiskReader::Declicker::reset (samplepos_t loop_start, samplepos_t loop_end, bool fadein, samplecnt_t sr)
+{
+       if (loop_start == loop_end) {
+               fade_start = 0;
+               fade_end = 0;
+               return;
+       }
+
+       /* adjust the position of the fade (this is absolute (global) timeline units) */
+
+       if (fadein) {
+               fade_start = loop_start;
+               fade_end = loop_start + fade_length;
+       } else {
+               fade_start = loop_end - fade_length;
+               fade_end = loop_end;
+       }
+
+}
+
+void
+DiskReader::Declicker::run (Sample* buf, samplepos_t read_start, samplepos_t read_end)
+{
+       samplecnt_t n;     /* how many samples to process */
+       sampleoffset_t bo; /* offset into buffer */
+       sampleoffset_t vo; /* offset into gain vector */
+
+       if (fade_start == fade_end) {
+               return;
+       }
+
+       /* Determine how the read range overlaps with the fade range, so we can determine which part of the fade gain vector
+          to apply to which part of the buffer.
+       */
+
+       switch (Evoral::coverage (fade_start, fade_end, read_start, read_end)) {
+
+       case Evoral::OverlapInternal:
+               /* note: start and end points cannot coincide (see evoral/Range.h)
+                *
+                * read range is entirely within fade range
+                */
+               bo = 0;
+               vo = read_start - fade_start;
+               n = read_end - read_start;
+               break;
+
+       case Evoral::OverlapExternal:
+               /* read range extends on either side of fade range
+                *
+                * External allows coincidental start & end points, so check for that
+                */
+               if (fade_start == read_start && fade_end == read_end) {
+                       /* fade entire read ... this is SO unlikely ! */
+                       bo = 0;
+                       vo = 0;
+                       n = fade_end - fade_start;
+               } else {
+                       bo = fade_start - read_start;
+                       vo = 0;
+                       n = fade_end - fade_start;
+               }
+               break;
+
+       case Evoral::OverlapStart:
+               /* read range starts before and ends within fade or at same end as fade */
+               n = fade_end - read_start;
+               vo = 0;
+               bo = fade_start - read_start;
+               break;
+
+       case Evoral::OverlapEnd:
+               /* read range starts within fade range, but possibly at it's end, so check */
+               if (read_start == fade_end) {
+                       /* nothing to do */
+                       return;
+               }
+               bo = 0;
+               vo = read_start - fade_start;
+               n = fade_end - read_start;
+               break;
+
+       case Evoral::OverlapNone:
+               /* no overlap ... nothing to do */
+               return;
+       }
+
+       Sample* b = &buf[bo];
+       gain_t* g = &vec[vo];
+
+       for (sampleoffset_t i = 0; i < n; ++i) {
+               b[i] *= g[i];
+       }
+}
+
+void
+DiskReader::maybe_xfade_loop (Sample* buf, samplepos_t read_start, samplepos_t read_end, ReaderChannelInfo* chan)
+{
+       samplecnt_t n;     /* how many samples to process */
+       sampleoffset_t bo; /* offset into buffer */
+       sampleoffset_t vo; /* offset into gain vector */
+
+       const samplepos_t fade_start = loop_declick_out.fade_start;
+       const samplepos_t fade_end = loop_declick_out.fade_end;
+
+       if (fade_start == fade_end) {
+               return;
+       }
+
+       /* Determine how the read range overlaps with the fade range, so we can determine which part of the fade gain vector
+          to apply to which part of the buffer.
+       */
+
+       switch (Evoral::coverage (fade_start, fade_end, read_start, read_end)) {
+
+       case Evoral::OverlapInternal:
+               /* note: start and end points cannot coincide (see evoral/Range.h)
+                *
+                * read range is entirely within fade range
+                */
+               bo = 0;
+               vo = read_start - fade_start;
+               n = read_end - read_start;
+               break;
+
+       case Evoral::OverlapExternal:
+               /* read range extends on either side of fade range
+                *
+                * External allows coincidental start & end points, so check for that
+                */
+               if (fade_start == read_start && fade_end == read_end) {
+                       /* fade entire read ... this is SO unlikely ! */
+                       bo = 0;
+                       vo = 0;
+                       n = fade_end - fade_start;
+               } else {
+                       bo = fade_start - read_start;
+                       vo = 0;
+                       n = fade_end - fade_start;
+               }
+               break;
+
+       case Evoral::OverlapStart:
+               /* read range starts before and ends within fade or at same end as fade */
+               n = fade_end - read_start;
+               vo = 0;
+               bo = fade_start - read_start;
+               break;
+
+       case Evoral::OverlapEnd:
+               /* read range starts within fade range, but possibly at it's end, so check */
+               if (read_start == fade_end) {
+                       /* nothing to do */
+                       return;
+               }
+               bo = 0;
+               vo = read_start - fade_start;
+               n = fade_end - read_start;
+               break;
+
+       case Evoral::OverlapNone:
+               /* no overlap ... nothing to do */
+               return;
+       }
+
+       Sample* b    = &buf[bo];                   /* data to be faded out */
+       Sample* sbuf = &chan->pre_loop_buffer[vo]; /* pre-loop (maybe silence) to be faded in */
+       gain_t* og   = &loop_declick_out.vec[vo];  /* fade out gain vector */
+       gain_t* ig   = &loop_declick_in.vec[vo];   /* fade in gain vector */
+
+       for (sampleoffset_t i = 0; i < n; ++i) {
+               b[i] = (b[i] * og[i]) + (sbuf[i] * ig[i]);
+       }
+}
+
 RTMidiBuffer*
 DiskReader::rt_midibuffer ()
 {
@@ -1298,3 +1573,60 @@ DiskReader::rt_midibuffer ()
 
        return mpl->rendered();
 }
+
+void
+DiskReader::alloc_loop_declick (samplecnt_t sr)
+{
+       loop_fade_length = lrintf (ceil (-log (1e-5) / (1024.f/sr)));
+       loop_declick_in.alloc (sr, true);
+       loop_declick_out.alloc (sr, false);
+}
+
+void
+DiskReader::reset_loop_declick (Location* loc, samplecnt_t sr)
+{
+       if (loc) {
+               loop_declick_in.reset (loc->start(), loc->end(), true, sr);
+               loop_declick_out.reset (loc->start(), loc->end(), false, sr);
+       } else {
+               loop_declick_in.reset (0, 0, true, sr);
+               loop_declick_out.reset (0, 0, false, sr);
+       }
+}
+
+void
+DiskReader::set_loop (Location* loc)
+{
+       Processor::set_loop (loc);
+
+       if (!loc) {
+               return;
+       }
+
+       reload_loop ();
+}
+
+void
+DiskReader::reload_loop ()
+{
+       Location* loc = _loop_location;
+       boost::scoped_array<Sample> mix_buf (new Sample [loop_fade_length]);
+       boost::scoped_array<Sample> gain_buf (new Sample [loop_fade_length]);
+
+       boost::shared_ptr<ChannelList> c = channels.reader();
+       uint32_t channel = 0;
+
+       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) {
+
+               ReaderChannelInfo* rci = dynamic_cast<ReaderChannelInfo*> (*chan);
+
+               rci->resize_preloop (loop_fade_length);
+
+               if (loc->start() > loop_fade_length) {
+                       audio_playlist()->read (rci->pre_loop_buffer, mix_buf.get(), gain_buf.get(), loc->start() - loop_declick_out.fade_length, loop_declick_out.fade_length, channel);
+               } else {
+                       memset (rci->pre_loop_buffer, 0, sizeof (Sample) * loop_fade_length);
+               }
+
+       }
+}
index f374d70e759cba0b39bc2e3fbd2f097d16568742..67337ab9e5724c449ef2296df219622d1547018b 100644 (file)
@@ -155,7 +155,8 @@ setup_enum_writer ()
        TransportFSM::EventType _TransportFSM_EventType;
        TransportFSM::MotionState _TransportFSM_MotionState;
        TransportFSM::ButlerState _TransportFSM_ButlerState;
-
+       LoopFadeChoice _LoopFadeChooice;
+       
 #define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
 #define REGISTER_BITS(e) enum_writer.register_bits (typeid(e).name(), i, s); i.clear(); s.clear()
 #define REGISTER_ENUM(e) i.push_back (e); s.push_back (#e)
@@ -819,6 +820,12 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (TransportFSM, NotWaitingForButler);
        REGISTER_CLASS_ENUM (TransportFSM, WaitingForButler);
        REGISTER (_TransportFSM_ButlerState);
+
+       REGISTER_ENUM (NoLoopFade);
+       REGISTER_ENUM (EndLoopFade);
+       REGISTER_ENUM (BothLoopFade);
+       REGISTER_ENUM (XFadeLoop);
+       REGISTER (_LoopFadeChooice);
 }
 
 } /* namespace ARDOUR */
index b48b18b305f8fe33f3f72df40fa0d19c013ad161..15a609dbfd3af91774b582d9a5f30a662a9e59dc 100644 (file)
@@ -6051,3 +6051,8 @@ Route::monitoring_state () const
 
        return get_auto_monitoring_state();
 }
+
+void
+Route::reload_loop ()
+{
+}
index 073ad079611c145459053f541eb85dc185710d7b..79bf3b31e2a42a60671167e6388ae981db5891fa 100644 (file)
@@ -1408,17 +1408,6 @@ Session::auto_punch_changed (Location* location)
        auto_punch_end_changed (location);
 }
 
-/** @param loc A loop location.
- *  @param pos Filled in with the start time of the required fade-out (in session samples).
- *  @param length Filled in with the length of the required fade-out.
- */
-void
-Session::auto_loop_declick_range (Location* loc, samplepos_t & pos, samplepos_t & length)
-{
-       pos = max (loc->start(), loc->end() - 64);
-       length = loc->end() - pos;
-}
-
 void
 Session::auto_loop_changed (Location* location)
 {
@@ -1430,6 +1419,12 @@ Session::auto_loop_changed (Location* location)
 
                if (play_loop) {
 
+                       boost::shared_ptr<RouteList> r = routes.reader ();
+
+                       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+                               (*i)->reload_loop ();
+                       }
+
                        if (_transport_sample < location->start() || _transport_sample > location->end()) {
                                // new loop range excludes current transport
                                // sample => relocate to beginning of loop and roll.
@@ -1534,9 +1529,6 @@ Session::set_auto_loop_location (Location* location)
                loop_connections.drop_connections ();
                existing->set_auto_loop (false, this);
                remove_event (existing->end(), SessionEvent::AutoLoop);
-               samplepos_t dcp;
-               samplecnt_t dcl;
-               auto_loop_declick_range (existing, dcp, dcl);
                auto_loop_location_changed (0);
        }
 
@@ -1972,6 +1964,10 @@ Session::set_sample_rate (samplecnt_t frames_per_second)
        clear_clicks ();
        reset_write_sources (false);
 
+       DiskReader::alloc_loop_declick (nominal_sample_rate());
+       Location* loc = _locations->auto_loop_location ();
+       DiskReader::reset_loop_declick (loc, nominal_sample_rate());
+
        // XXX we need some equivalent to this, somehow
        // SndFileSource::setup_standard_crossfades (frames_per_second);
 
index 116091db18567a5e2aa5265b0e5bdc5cca97d643..e61e8fee59be98d9b6a571525fbf3b859a7f2464 100644 (file)
@@ -1675,6 +1675,8 @@ Session::set_track_loop (bool yn)
                        (*i)->set_loop (yn ? loc : 0);
                }
        }
+
+       DiskReader::reset_loop_declick (loc, nominal_sample_rate());
 }
 
 samplecnt_t
index 9e43635a2464936101a209cf8e3767a5b3d4d776..e9c29ffa835a4500aff05a38148423ebd7c51988 100644 (file)
@@ -572,6 +572,12 @@ Track::set_slaved (bool s)
        _disk_writer->set_slaved (s);
 }
 
+void
+Track::reload_loop ()
+{
+       _disk_reader->reload_loop ();
+}
+
 ChanCount
 Track::n_channels ()
 {
index d1a56cf10131914655e3e0ee39a4d301b14de1cb..5466192b33ad8ffb8186216a183d850e8145fa33 100644 (file)
@@ -151,8 +151,8 @@ a_row < Stopped,             locate,          WaitingForLocate,  &T::start_locat
 g_row < WaitingForLocate,    locate_done,     Stopped,                                  &T::should_not_roll_after_locate >,
 _row  < Rolling,             butler_done,     Rolling                                                                    >,
 _row  < Rolling,             start_transport, Rolling                                                                    >,
-a_row < Rolling,             stop_transport,  DeclickToStop,     &T::start_declick_for_stop                              >,
-a_row < DeclickToStop,       declick_done,    Stopped,           &T::stop_playback                                       >,
+a_row < Rolling,             stop_transport,  DeclickToStop,     &T::stop_playback                                       >,
+a_row < DeclickToStop,       declick_done,    Stopped,                                                                   >,
 a_row < Rolling,             locate,          DeclickToLocate,   &T::start_declick_for_locate                            >,
 a_row < DeclickToLocate,     declick_done,    WaitingForLocate,  &T::start_locate_after_declick                          >,
 row   < WaitingForLocate,    locate_done,     Rolling,           &T::roll_after_locate, &T::should_roll_after_locate     >,
@@ -253,7 +253,7 @@ TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
                                                                ev.with_roll,
                                                                ev.with_flush,
                                                                ev.target,
-                                                               ev.with_loop,
+                                                               ev.for_loop_end,
                                                                ev.force));
                switch (_motion_state) {
                case Stopped:
@@ -261,7 +261,7 @@ TransportFSM::process_event (Event& ev, bool already_deferred, bool& deferred)
                        start_locate_while_stopped (ev);
                        break;
                case Rolling:
-                       if (ev.with_loop) {
+                       if (ev.for_loop_end) {
                                /* we will finish the locate synchronously, so
                                 * that after returning from
                                 * ::locate_for_loop() we will already have
@@ -407,17 +407,17 @@ TransportFSM::start_locate_while_stopped (Event const & l) const
 
        set_roll_after (l.with_roll);
 
-       api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.with_loop, l.force);
+       api->locate (l.target, current_roll_after_locate_status.get(), l.with_flush, l.for_loop_end, l.force);
 }
 
 void
 TransportFSM::locate_for_loop (Event const & l)
 {
        assert (l.type == Locate);
-       DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.with_loop));
+       DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("locate_for_loop, wl = %1\n", l.for_loop_end));
        set_roll_after (l.with_roll);
        _last_locate = l;
-       api->locate (l.target, l.with_roll, l.with_flush, l.with_loop, l.force);
+       api->locate (l.target, l.with_roll, l.with_flush, l.for_loop_end, l.force);
 }
 
 void
@@ -427,7 +427,7 @@ TransportFSM::start_locate_after_declick () const
                                                        current_roll_after_locate_status ? current_roll_after_locate_status.get() : _last_locate.with_roll));
 
        const bool roll = current_roll_after_locate_status ? current_roll_after_locate_status.get() : _last_locate.with_roll;
-       api->locate (_last_locate.target, roll, _last_locate.with_flush, _last_locate.with_loop, _last_locate.force);
+       api->locate (_last_locate.target, roll, _last_locate.with_flush, _last_locate.for_loop_end, _last_locate.force);
 }
 
 void
@@ -455,7 +455,7 @@ TransportFSM::interrupt_locate (Event const & l) const
        /* maintain original "with-roll" choice of initial locate, even though
         * we are interrupting the locate to start a new one.
         */
-       api->locate (l.target, false, l.with_flush, l.with_loop, l.force);
+       api->locate (l.target, false, l.with_flush, l.for_loop_end, l.force);
 }
 
 void
@@ -482,9 +482,9 @@ TransportFSM::should_roll_after_locate () const
 void
 TransportFSM::roll_after_locate () const
 {
-       DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("rolling after locate, was for_loop ? %1\n", _last_locate.with_loop));
+       DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("rolling after locate, was for_loop ? %1\n", _last_locate.for_loop_end));
        current_roll_after_locate_status = boost::none;
-       if (!_last_locate.with_loop) {
+       if (!_last_locate.for_loop_end) {
                api->start_transport ();
        }
 }