diff --git a/gtk2_ardour/rc_option_editor.cc b/gtk2_ardour/rc_option_editor.cc index 261537d8ff..d6e221f328 100644 --- a/gtk2_ardour/rc_option_editor.cc +++ b/gtk2_ardour/rc_option_editor.cc @@ -3176,8 +3176,24 @@ RCOptionEditor::RCOptionEditor () Gtkmm2ext::UI::instance()->set_tip (bo->tip_widget(), (_("When enabled the loop button does not start playback but forces playback to always play the loop\n\n" "When disabled the loop button starts playing the loop, but stop then cancels loop playback"))); + + add_option (_("Transport"), bo); + + ComboOption* lca = new ComboOption ( + "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", diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h index 427a6503d2..970295b6c2 100644 --- a/libs/ardour/ardour/disk_reader.h +++ b/libs/ardour/ardour/disk_reader.h @@ -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* 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 diff --git a/libs/ardour/ardour/rc_configuration_vars.h b/libs/ardour/ardour/rc_configuration_vars.h index 6938531b5e..b572bec865 100644 --- a/libs/ardour/ardour/rc_configuration_vars.h +++ b/libs/ardour/ardour/rc_configuration_vars.h @@ -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 diff --git a/libs/ardour/ardour/route.h b/libs/ardour/ardour/route.h index efc93a7ee9..5c2c4ad48d 100644 --- a/libs/ardour/ardour/route.h +++ b/libs/ardour/ardour/route.h @@ -360,6 +360,8 @@ public: PBD::Signal0 denormal_protection_changed; PBD::Signal0 comment_changed; + virtual void reload_loop(); + bool is_track(); /** track numbers - assigned by session diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 4e74a1dc5a..3a3f16dfdf 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -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 (); diff --git a/libs/ardour/ardour/track.h b/libs/ardour/ardour/track.h index 90d5574b65..0b6462fbc3 100644 --- a/libs/ardour/ardour/track.h +++ b/libs/ardour/ardour/track.h @@ -166,6 +166,7 @@ public: } void adjust_playback_buffering (); void adjust_capture_buffering (); + void reload_loop (); PBD::Signal0 FreezeChange; PBD::Signal0 PlaylistChanged; diff --git a/libs/ardour/ardour/transport_fsm.h b/libs/ardour/ardour/transport_fsm.h index 7eafccead1..26ccb78765 100644 --- a/libs/ardour/ardour/transport_fsm.h +++ b/libs/ardour/ardour/transport_fsm.h @@ -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); diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index ea96c7fd6d..7cd3759a88 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -785,6 +785,13 @@ struct CaptureInfo { samplecnt_t samples; }; +enum LoopFadeChoice { + NoLoopFade, + EndLoopFade, + BothLoopFade, + XFadeLoop, +}; + typedef std::vector CaptureInfos; } // namespace ARDOUR diff --git a/libs/ardour/ardour/types_convert.h b/libs/ardour/ardour/types_convert.h index ae48c7602d..9e46d51982 100644 --- a/libs/ardour/ardour/types_convert.h +++ b/libs/ardour/ardour/types_convert.h @@ -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) diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc index b62fe04ecd..b4ffba834e 100644 --- a/libs/ardour/disk_reader.cc +++ b/libs/ardour/disk_reader.cc @@ -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 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 (*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*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*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 (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 mix_buf (new Sample [loop_fade_length]); + boost::scoped_array gain_buf (new Sample [loop_fade_length]); + + boost::shared_ptr c = channels.reader(); + uint32_t channel = 0; + + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) { + + ReaderChannelInfo* rci = dynamic_cast (*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); + } + + } +} diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index f374d70e75..67337ab9e5 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -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 */ diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index b48b18b305..15a609dbfd 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -6051,3 +6051,8 @@ Route::monitoring_state () const return get_auto_monitoring_state(); } + +void +Route::reload_loop () +{ +} diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 073ad07961..79bf3b31e2 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -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 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); diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 116091db18..e61e8fee59 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -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 diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc index 9e43635a24..e9c29ffa83 100644 --- a/libs/ardour/track.cc +++ b/libs/ardour/track.cc @@ -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 () { diff --git a/libs/ardour/transport_fsm.cc b/libs/ardour/transport_fsm.cc index d1a56cf101..5466192b33 100644 --- a/libs/ardour/transport_fsm.cc +++ b/libs/ardour/transport_fsm.cc @@ -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 (); } }