X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fcrossfade.cc;h=d2271030a5933c4c0c5c051c9a8106c885e3ac0f;hb=b6f1f02131d99392899cd394f1fb408438b1dd58;hp=379c9333b04ff088e30022ace8259284a72e8a96;hpb=7d2b7e72fd72379aa379d568032bf0fa251a7a85;p=ardour.git diff --git a/libs/ardour/crossfade.cc b/libs/ardour/crossfade.cc index 379c9333b0..d2271030a5 100644 --- a/libs/ardour/crossfade.cc +++ b/libs/ardour/crossfade.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2003 Paul Davis + Copyright (C) 2003-2006 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,18 +15,21 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ -#include -#include -#include -#include -#include -#include -#include -#include +#include "pbd/stacktrace.h" + +#include "ardour/debug.h" +#include "ardour/types.h" +#include "ardour/crossfade.h" +#include "ardour/crossfade_compare.h" +#include "ardour/audioregion.h" +#include "ardour/playlist.h" +#include "ardour/utils.h" +#include "ardour/session.h" +#include "ardour/source.h" +#include "ardour/region_factory.h" #include "i18n.h" #include @@ -35,8 +38,7 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -jack_nframes_t Crossfade::_short_xfade_length = 0; -Change Crossfade::ActiveChanged = ARDOUR::new_change(); +framecnt_t Crossfade::_short_xfade_length = 0; /* XXX if and when we ever implement parallel processing of the process() callback, these will need to be handled on a per-thread basis. @@ -45,18 +47,33 @@ Change Crossfade::ActiveChanged = ARDOUR::new_change(); Sample* Crossfade::crossfade_buffer_out = 0; Sample* Crossfade::crossfade_buffer_in = 0; + +#define CROSSFADE_DEFAULT_PROPERTIES \ + _active (Properties::active, _session.config.get_xfades_active ()) \ + , _follow_overlap (Properties::follow_overlap, false) + + +namespace ARDOUR { + namespace Properties { + PropertyDescriptor follow_overlap; + } +} + void -Crossfade::set_buffer_size (jack_nframes_t sz) +Crossfade::make_property_quarks () { - if (crossfade_buffer_out) { - delete [] crossfade_buffer_out; - crossfade_buffer_out = 0; - } + Properties::follow_overlap.property_id = g_quark_from_static_string (X_("follow-overlap")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for follow-overlap = %1\n", Properties::follow_overlap.property_id)); +} - if (crossfade_buffer_in) { - delete [] crossfade_buffer_in; - crossfade_buffer_in = 0; - } +void +Crossfade::set_buffer_size (framecnt_t sz) +{ + delete [] crossfade_buffer_out; + crossfade_buffer_out = 0; + + delete [] crossfade_buffer_in; + crossfade_buffer_in = 0; if (sz) { crossfade_buffer_out = new Sample[sz]; @@ -70,31 +87,34 @@ Crossfade::operator== (const Crossfade& other) return (_in == other._in) && (_out == other._out); } -Crossfade::Crossfade (ARDOUR::AudioRegion& in, ARDOUR::AudioRegion& out, - jack_nframes_t length, - jack_nframes_t position, +Crossfade::Crossfade (boost::shared_ptr in, boost::shared_ptr out, + framecnt_t length, + framepos_t position, AnchorPoint ap) - : _fade_in (0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB - _fade_out (0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB + : AudioRegion (in->session(), position, length, in->name() + string ("<>") + out->name()) + , CROSSFADE_DEFAULT_PROPERTIES + , _fade_in (Evoral::Parameter(FadeInAutomation)) // linear (gain coefficient) => -inf..+6dB + , _fade_out (Evoral::Parameter(FadeOutAutomation)) // linear (gain coefficient) => -inf..+6dB + { - _in = ∈ - _out = &out; - _length = length; - _position = position; + _in = in; + _out = out; _anchor_point = ap; - _follow_overlap = false; - _active = true; _fixed = true; - + _follow_overlap = false; + initialize (); } -Crossfade::Crossfade (ARDOUR::AudioRegion& a, ARDOUR::AudioRegion& b, CrossfadeModel model, bool act) - : _fade_in (0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB - _fade_out (0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB +Crossfade::Crossfade (boost::shared_ptr a, boost::shared_ptr b, CrossfadeModel model, bool act) + : AudioRegion (a->session(), 0, 0, a->name() + string ("<>") + b->name()) + , CROSSFADE_DEFAULT_PROPERTIES + , _fade_in (Evoral::Parameter(FadeInAutomation)) // linear (gain coefficient) => -inf..+6dB + , _fade_out (Evoral::Parameter(FadeOutAutomation)) // linear (gain coefficient) => -inf..+6dB { _in_update = false; _fixed = false; + _follow_overlap = false; if (compute (a, b, model)) { throw failed_constructor(); @@ -103,15 +123,17 @@ Crossfade::Crossfade (ARDOUR::AudioRegion& a, ARDOUR::AudioRegion& b, CrossfadeM _active = act; initialize (); - } -Crossfade::Crossfade (const Playlist& playlist, XMLNode& node) - : _fade_in (0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB - _fade_out (0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB +Crossfade::Crossfade (const Playlist& playlist, XMLNode const & node) + : AudioRegion (playlist.session(), 0, 0, "unnamed crossfade") + , CROSSFADE_DEFAULT_PROPERTIES + , _fade_in (Evoral::Parameter(FadeInAutomation)) // linear (gain coefficient) => -inf..+6dB + , _fade_out (Evoral::Parameter(FadeOutAutomation)) // linear (gain coefficient) => -inf..+6dB + { - Region* r; - XMLProperty* prop; + boost::shared_ptr r; + XMLProperty const * prop; LocaleGuard lg (X_("POSIX")); /* we have to find the in/out regions before we can do anything else */ @@ -120,16 +142,25 @@ Crossfade::Crossfade (const Playlist& playlist, XMLNode& node) error << _("Crossfade: no \"in\" region in state") << endmsg; throw failed_constructor(); } - + PBD::ID id (prop->value()); - if ((r = playlist.find_region (id)) == 0) { - error << string_compose (_("Crossfade: no \"in\" region %1 found in playlist %2"), id, playlist.name()) + r = playlist.find_region (id); + + if (!r) { + /* the `in' region is not in a playlist, which probably means that this crossfade + is in the undo record, so we have to find the region in the global region map. + */ + r = RegionFactory::region_by_id (id); + } + + if (!r) { + error << string_compose (_("Crossfade: no \"in\" region %1 found in playlist %2 nor in region map"), id, playlist.name()) << endmsg; throw failed_constructor(); } - - if ((_in = dynamic_cast (r)) == 0) { + + if ((_in = boost::dynamic_pointer_cast (r)) == 0) { throw failed_constructor(); } @@ -140,273 +171,169 @@ Crossfade::Crossfade (const Playlist& playlist, XMLNode& node) PBD::ID id2 (prop->value()); - if ((r = playlist.find_region (id2)) == 0) { - error << string_compose (_("Crossfade: no \"out\" region %1 found in playlist %2"), id2, playlist.name()) + r = playlist.find_region (id2); + + if (!r) { + r = RegionFactory::region_by_id (id2); + } + + if (!r) { + error << string_compose (_("Crossfade: no \"out\" region %1 found in playlist %2 nor in region map"), id2, playlist.name()) << endmsg; throw failed_constructor(); } - - if ((_out = dynamic_cast (r)) == 0) { + + if ((_out = boost::dynamic_pointer_cast (r)) == 0) { throw failed_constructor(); } _length = 0; - initialize(false); - - if (set_state (node)) { + initialize(); + _active = true; + + if (set_state (node, Stateful::loading_state_version)) { throw failed_constructor(); } - - save_state ("initial"); } -Crossfade::Crossfade (const Crossfade &orig, ARDOUR::AudioRegion *newin, ARDOUR::AudioRegion *newout) - : _fade_in(orig._fade_in), - _fade_out(orig._fade_out) +Crossfade::Crossfade (boost::shared_ptr orig, boost::shared_ptr newin, boost::shared_ptr newout) + : AudioRegion (boost::dynamic_pointer_cast (orig), 0, true) + , CROSSFADE_DEFAULT_PROPERTIES + , _fade_in (orig->_fade_in) + , _fade_out (orig->_fade_out) { - _active = orig._active; - _in_update = orig._in_update; - _length = orig._length; - _position = orig._position; - _anchor_point = orig._anchor_point; - _follow_overlap = orig._follow_overlap; - _fixed = orig._fixed; - _follow_overlap = orig._follow_overlap; - _short_xfade_length = orig._short_xfade_length; - + _active = orig->_active; + _in_update = orig->_in_update; + _anchor_point = orig->_anchor_point; + _follow_overlap = orig->_follow_overlap; + _fixed = orig->_fixed; + _in = newin; _out = newout; // copied from Crossfade::initialize() _in_update = false; - + _out->suspend_fade_out (); _in->suspend_fade_in (); overlap_type = _in->coverage (_out->position(), _out->last_frame()); + layer_relation = (int32_t) (_in->layer() - _out->layer()); // Let's make sure the fade isn't too long - set_length(_length); + set_xfade_length(_length); } Crossfade::~Crossfade () { - for (StateMap::iterator i = states.begin(); i != states.end(); ++i) { - delete *i; - } } void -Crossfade::initialize (bool savestate) +Crossfade::initialize () { + /* merge source lists from regions */ + + _sources = _in->sources(); + _sources.insert (_sources.end(), _out->sources().begin(), _out->sources().end()); + + for (SourceList::iterator i = _sources.begin(); i != _sources.end(); ++i) { + (*i)->inc_use_count (); + } + + _master_sources = _in->master_sources(); + _master_sources.insert(_master_sources.end(), _out->master_sources().begin(), _out->master_sources().end()); + + for (SourceList::iterator i = _master_sources.begin(); i != _master_sources.end(); ++i) { + (*i)->inc_use_count (); + } + _in_update = false; - + _out->suspend_fade_out (); _in->suspend_fade_in (); _fade_out.freeze (); _fade_out.clear (); - _fade_out.add (0.0, 1.0); - _fade_out.add ((_length * 0.1), 0.99); - _fade_out.add ((_length * 0.2), 0.97); - _fade_out.add ((_length * 0.8), 0.03); - _fade_out.add ((_length * 0.9), 0.01); - _fade_out.add (_length, 0.0); - _fade_out.thaw (); - - _fade_in.freeze (); - _fade_in.clear (); - _fade_in.add (0.0, 0.0); - _fade_in.add ((_length * 0.1), 0.01); - _fade_in.add ((_length * 0.2), 0.03); - _fade_in.add ((_length * 0.8), 0.97); - _fade_in.add ((_length * 0.9), 0.99); - _fade_in.add (_length, 1.0); - _fade_in.thaw (); -// _in->StateChanged.connect (slot (*this, &Crossfade::member_changed)); -// _out->StateChanged.connect (slot (*this, &Crossfade::member_changed)); +#define EQUAL_POWER_MINUS_3DB +#ifdef EQUAL_POWER_MINUS_3DB - overlap_type = _in->coverage (_out->position(), _out->last_frame()); + _fade_out.add ((_length * 0.000000), 1.000000); + _fade_out.add ((_length * 0.166667), 0.948859); + _fade_out.add ((_length * 0.333333), 0.851507); + _fade_out.add ((_length * 0.500000), 0.707946); + _fade_out.add ((_length * 0.666667), 0.518174); + _fade_out.add ((_length * 0.833333), 0.282192); + _fade_out.add ((_length * 1.000000), 0.000000); - if (savestate) { - save_state ("initial"); - } -} +#else // EQUAL_POWER_MINUS_6DB -int -Crossfade::compute (AudioRegion& a, AudioRegion& b, CrossfadeModel model) -{ - AudioRegion* top; - AudioRegion* bottom; - jack_nframes_t short_xfade_length; + _fade_out.add ((_length * 0.000000), 1.000000); + _fade_out.add ((_length * 0.166667), 0.833033); + _fade_out.add ((_length * 0.333333), 0.666186); + _fade_out.add ((_length * 0.500000), 0.499459); + _fade_out.add ((_length * 0.666667), 0.332853); + _fade_out.add ((_length * 0.833333), 0.166366); + _fade_out.add ((_length * 1.000000), 0.000000); +#endif - short_xfade_length = _short_xfade_length; + _fade_out.thaw (); - if (a.layer() < b.layer()) { - top = &b; - bottom = &a; - } else { - top = &a; - bottom = &b; - } - - /* first check for matching ends */ - - if (top->first_frame() == bottom->first_frame()) { - - /* Both regions start at the same point */ - - if (top->last_frame() < bottom->last_frame()) { - - /* top ends before bottom, so put an xfade - in at the end of top. - */ - - /* [-------- top ---------- ] - * {====== bottom =====================} - */ + _fade_in.freeze (); + _fade_in.clear (); - _in = bottom; - _out = top; +#define EQUAL_POWER_MINUS_3DB +#ifdef EQUAL_POWER_MINUS_3DB - if (top->last_frame() < short_xfade_length) { - _position = 0; - } else { - _position = top->last_frame() - short_xfade_length; - } - - _length = min (short_xfade_length, top->length()); - _follow_overlap = false; - _anchor_point = EndOfIn; - _active = true; - _fixed = true; + _fade_in.add ((_length * 0.000000), 0.000000); + _fade_in.add ((_length * 0.166667), 0.282192); + _fade_in.add ((_length * 0.333333), 0.518174); + _fade_in.add ((_length * 0.500000), 0.707946); + _fade_in.add ((_length * 0.666667), 0.851507); + _fade_in.add ((_length * 0.833333), 0.948859); + _fade_in.add ((_length * 1.000000), 1.000000); - } else { - /* top ends after (or same time) as bottom - no xfade - */ - - /* [-------- top ------------------------ ] - * {====== bottom =====================} - */ +#else // EQUAL_POWER_MINUS_SIX_DB - throw NoCrossfadeHere(); - } - - } else if (top->last_frame() == bottom->last_frame()) { - - /* Both regions end at the same point */ - - if (top->first_frame() > bottom->first_frame()) { - - /* top starts after bottom, put an xfade in at the - start of top - */ - - /* [-------- top ---------- ] - * {====== bottom =====================} - */ + _fade_in.add ((_length * 0.000000), 0.000000); + _fade_in.add ((_length * 0.166667), 0.166366); + _fade_in.add ((_length * 0.333333), 0.332853); + _fade_in.add ((_length * 0.500000), 0.499459); + _fade_in.add ((_length * 0.666667), 0.666186); + _fade_in.add ((_length * 0.833333), 0.833033); + _fade_in.add ((_length * 1.000000), 1.000000); - _in = top; - _out = bottom; - _position = top->first_frame(); - _length = min (short_xfade_length, top->length()); - _follow_overlap = false; - _anchor_point = StartOfIn; - _active = true; - _fixed = true; - - } else { - /* top starts before bottom - no xfade - */ +#endif - /* [-------- top ------------------------ ] - * {====== bottom =====================} - */ + _fade_in.thaw (); - throw NoCrossfadeHere(); - } + overlap_type = _in->coverage (_out->position(), _out->last_frame()); + layer_relation = (int32_t) (_in->layer() - _out->layer()); +} - } else { +framecnt_t +Crossfade::read_raw_internal (Sample* buf, framecnt_t start, framecnt_t cnt, int channel) const +{ + Sample* mixdown = new Sample[cnt]; + float* gain = new float[cnt]; + framecnt_t ret; - /* OK, time to do more regular overlapping */ - - OverlapType ot = top->coverage (bottom->first_frame(), bottom->last_frame()); - - switch (ot) { - case OverlapNone: - /* should be NOTREACHED as a precondition of creating - a new crossfade, but we need to handle it here. - */ - throw NoCrossfadeHere(); - break; - - case OverlapInternal: - case OverlapExternal: - /* should be NOTREACHED because of tests above */ - throw NoCrossfadeHere(); - break; - - case OverlapEnd: /* top covers start of bottom but ends within it */ - - /* [---- top ------------------------] - * { ==== bottom ============ } - */ - - _in = bottom; - _out = top; - _position = bottom->first_frame(); - _anchor_point = StartOfIn; - - if (model == FullCrossfade) { - _length = _out->first_frame() + _out->length() - _in->first_frame(); - /* leave active alone */ - _follow_overlap = true; - } else { - _length = min (short_xfade_length, top->length()); - _active = true; - _follow_overlap = false; - - } - break; - - case OverlapStart: /* top starts within bottom but covers bottom's end */ + ret = read_at (buf, mixdown, gain, start, cnt, channel, cnt); - /* { ==== top ============ } - * [---- bottom -------------------] - */ + delete [] mixdown; + delete [] gain; - _in = top; - _out = bottom; - _position = top->first_frame(); - _anchor_point = StartOfIn; - - if (model == FullCrossfade) { - _length = _out->first_frame() + _out->length() - _in->first_frame(); - /* leave active alone */ - _follow_overlap = true; - } else { - _length = min (short_xfade_length, top->length()); - _active = true; - _follow_overlap = false; - - } - - break; - } - } - - return 0; + return ret; } -jack_nframes_t -Crossfade::read_at (Sample *buf, Sample *mixdown_buffer, - float *gain_buffer, jack_nframes_t start, jack_nframes_t cnt, uint32_t chan_n, - jack_nframes_t read_frames, jack_nframes_t skip_frames) +framecnt_t +Crossfade::read_at (Sample *buf, Sample *mixdown_buffer, + float *gain_buffer, framepos_t start, framecnt_t cnt, uint32_t chan_n, + framecnt_t read_frames, framecnt_t skip_frames) const { - jack_nframes_t offset; - jack_nframes_t to_write; + frameoffset_t offset; + framecnt_t to_write; if (!_active) { return 0; @@ -425,35 +352,42 @@ Crossfade::read_at (Sample *buf, Sample *mixdown_buffer, } else { return 0; } - + start = _position; buf += offset; - to_write = min (_length, cnt); + to_write = min (_length.val(), cnt); } else { - - to_write = min (_length - (start - _position), cnt); - + + to_write = min ((_length - (start - _position)), cnt); + } offset = start - _position; + /* Prevent data from piling up inthe crossfade buffers when reading a transparent region */ + if (!(_out->opaque())) { + memset (crossfade_buffer_out, 0, sizeof (Sample) * to_write); + } else if (!(_in->opaque())) { + memset (crossfade_buffer_in, 0, sizeof (Sample) * to_write); + } + _out->read_at (crossfade_buffer_out, mixdown_buffer, gain_buffer, start, to_write, chan_n, read_frames, skip_frames); _in->read_at (crossfade_buffer_in, mixdown_buffer, gain_buffer, start, to_write, chan_n, read_frames, skip_frames); float* fiv = new float[to_write]; float* fov = new float[to_write]; - _fade_in.get_vector (offset, offset+to_write, fiv, to_write); - _fade_out.get_vector (offset, offset+to_write, fov, to_write); + _fade_in.curve().get_vector (offset, offset+to_write, fiv, to_write); + _fade_out.curve().get_vector (offset, offset+to_write, fov, to_write); /* note: although we have not explicitly taken into account the return values from _out->read_at() or _in->read_at(), the length() function does this implicitly. why? because it computes a value based on the in+out regions' - position and length, and so we know precisely how much data they could return. + position and length, and so we know precisely how much data they could return. */ - for (jack_nframes_t n = 0; n < to_write; ++n) { + for (framecnt_t n = 0; n < to_write; ++n) { buf[n] = (crossfade_buffer_out[n] * fov[n]) + (crossfade_buffer_in[n] * fiv[n]); } @@ -461,12 +395,12 @@ Crossfade::read_at (Sample *buf, Sample *mixdown_buffer, delete [] fiv; return to_write; -} +} -OverlapType -Crossfade::coverage (jack_nframes_t start, jack_nframes_t end) const +OverlapType +Crossfade::coverage (framepos_t start, framepos_t end) const { - jack_nframes_t my_end = _position + _length; + framepos_t my_end = _position + _length; if ((start >= _position) && (end <= my_end)) { return OverlapInternal; @@ -488,8 +422,7 @@ Crossfade::set_active (bool yn) { if (_active != yn) { _active = yn; - save_state (_("active changed")); - send_state_changed (ActiveChanged); + PropertyChanged (PropertyChange (Properties::active)); } } @@ -497,45 +430,79 @@ bool Crossfade::refresh () { /* crossfades must be between non-muted regions */ - + if (_out->muted() || _in->muted()) { - Invalidated (this); + Invalidated (shared_from_this ()); return false; } - /* overlap type must be Start, End or External */ + /* Top layer shouldn't be transparent */ - OverlapType ot; - - ot = _in->coverage (_out->first_frame(), _out->last_frame()); - - switch (ot) { - case OverlapNone: - case OverlapInternal: - Invalidated (this); + if (!((layer_relation > 0 ? _in : _out)->opaque())) { + Invalidated (shared_from_this()); return false; - - default: - break; } - - /* overlap type must not have altered */ - - if (ot != overlap_type) { - Invalidated (this); + + /* layer ordering cannot change */ + + int32_t new_layer_relation = (int32_t) (_in->layer() - _out->layer()); + + if (new_layer_relation * layer_relation < 0) { // different sign, layers rotated + Invalidated (shared_from_this ()); + return false; + } + + OverlapType ot = _in->coverage (_out->first_frame(), _out->last_frame()); + + if (ot == OverlapNone) { + Invalidated (shared_from_this ()); return false; - } + } + + bool send_signal; + + if (ot != overlap_type) { + + if (_follow_overlap) { + + try { + compute (_in, _out, _session.config.get_xfade_model()); + } + + catch (NoCrossfadeHere& err) { + Invalidated (shared_from_this ()); + return false; + } + + send_signal = true; + + } else { + Invalidated (shared_from_this ()); + return false; + } - /* time to update */ + } else { + + send_signal = update (); + } + + if (send_signal) { + PropertyChange bounds; + bounds.add (Properties::start); + bounds.add (Properties::position); + bounds.add (Properties::length); + PropertyChanged (bounds); /* EMIT SIGNAL */ + } + + _in_update = false; - return update (false); + return true; } bool -Crossfade::update (bool force) +Crossfade::update () { - jack_nframes_t newlen; - bool save = false; + framecnt_t newlen; if (_follow_overlap) { newlen = _out->first_frame() + _out->length() - _in->first_frame(); @@ -544,153 +511,226 @@ Crossfade::update (bool force) } if (newlen == 0) { - Invalidated (this); + Invalidated (shared_from_this ()); return false; } _in_update = true; - if (force || (_follow_overlap && newlen != _length) || (_length > newlen)) { + if ((_follow_overlap && newlen != _length) || (_length > newlen)) { double factor = newlen / (double) _length; - + _fade_out.x_scale (factor); _fade_in.x_scale (factor); - - _length = newlen; - - save = true; - } + _length = newlen; + } switch (_anchor_point) { case StartOfIn: - if (_position != _in->first_frame()) { - _position = _in->first_frame(); - save = true; - } + _position = _in->first_frame(); break; case EndOfIn: - if (_position != _in->last_frame() - _length) { - _position = _in->last_frame() - _length; - save = true; - } + _position = _in->last_frame() - _length; break; case EndOfOut: - if (_position != _out->last_frame() - _length) { - _position = _out->last_frame() - _length; - save = true; - } + _position = _out->last_frame() - _length; } - if (save) { - save_state ("updated"); - } - - /* UI's may need to know that the overlap changed even - though the xfade length did not. - */ - - send_state_changed (BoundsChanged); /* EMIT SIGNAL */ - - _in_update = false; - return true; } -void -Crossfade::member_changed (Change what_changed) +int +Crossfade::compute (boost::shared_ptr a, boost::shared_ptr b, CrossfadeModel model) { - Change what_we_care_about = Change (Region::MuteChanged| - Region::LayerChanged| - ARDOUR::BoundsChanged); + boost::shared_ptr top; + boost::shared_ptr bottom; + framecnt_t short_xfade_length; + + short_xfade_length = _short_xfade_length; - if (what_changed & what_we_care_about) { - refresh (); + if (a->layer() < b->layer()) { + top = b; + bottom = a; + } else { + top = a; + bottom = b; } -} -Change -Crossfade::restore_state (StateManager::State& state) -{ - CrossfadeState* xfstate = dynamic_cast (&state); - Change what_changed = Change (0); + /* first check for matching ends */ - _in_update = true; - - xfstate->fade_in_memento (); - xfstate->fade_out_memento (); + if (top->first_frame() == bottom->first_frame()) { - if (_length != xfstate->length) { - what_changed = Change (what_changed|LengthChanged); - _length = xfstate->length; - } - if (_active != xfstate->active) { - what_changed = Change (what_changed|ActiveChanged); - _active = xfstate->active; - } - if (_position != xfstate->position) { - what_changed = Change (what_changed|PositionChanged); - _position = xfstate->position; - } + /* Both regions start at the same point */ - /* XXX what to do about notifications for these? I don't - think (G)UI cares about them because they are - implicit in the bounds. - */ + if (top->last_frame() < bottom->last_frame()) { - _follow_overlap = xfstate->follow_overlap; - _anchor_point = xfstate->anchor_point; + /* top ends before bottom, so put an xfade + in at the end of top. + */ - _in_update = false; + /* [-------- top ---------- ] + * {====== bottom =====================} + */ - return Change (what_changed); -} + _in = bottom; + _out = top; -StateManager::State* -Crossfade::state_factory (std::string why) const -{ - CrossfadeState* state = new CrossfadeState (why); + if (top->last_frame() < short_xfade_length) { + _position = 0; + } else { + _position = top->last_frame() - short_xfade_length; + } + + _length = min (short_xfade_length, top->length()); + _follow_overlap = false; + _anchor_point = EndOfIn; + _active = true; + _fixed = true; - state->fade_in_memento = _fade_in.get_memento (); - state->fade_out_memento = _fade_out.get_memento (); - state->active = _active; - state->length = _length; - state->position = _position; - state->follow_overlap = _follow_overlap; - state->anchor_point = _anchor_point; + } else { + /* top ends after (or same time) as bottom - no xfade + */ - return state; -} - -UndoAction -Crossfade::get_memento() const -{ - return sigc::bind (mem_fun (*(const_cast (this)), &StateManager::use_state), _current_state_id); + /* [-------- top ------------------------ ] + * {====== bottom =====================} + */ + + throw NoCrossfadeHere(); + } + + } else if (top->last_frame() == bottom->last_frame()) { + + /* Both regions end at the same point */ + + if (top->first_frame() > bottom->first_frame()) { + + /* top starts after bottom, put an xfade in at the + start of top + */ + + /* [-------- top ---------- ] + * {====== bottom =====================} + */ + + _in = top; + _out = bottom; + _position = top->first_frame(); + _length = min (short_xfade_length, top->length()); + _follow_overlap = false; + _anchor_point = StartOfIn; + _active = true; + _fixed = true; + + } else { + /* top starts before bottom - no xfade + */ + + /* [-------- top ------------------------ ] + * {====== bottom =====================} + */ + + throw NoCrossfadeHere(); + } + + } else { + + /* OK, time to do more regular overlapping */ + + OverlapType ot = top->coverage (bottom->first_frame(), bottom->last_frame()); + + switch (ot) { + case OverlapNone: + /* should be NOTREACHED as a precondition of creating + a new crossfade, but we need to handle it here. + */ + throw NoCrossfadeHere(); + break; + + case OverlapInternal: + case OverlapExternal: + /* should be NOTREACHED because of tests above */ + throw NoCrossfadeHere(); + break; + + case OverlapEnd: /* top covers start of bottom but ends within it */ + + /* [---- top ------------------------] + * { ==== bottom ============ } + */ + + _in = bottom; + _out = top; + _anchor_point = EndOfOut; + + if (model == FullCrossfade) { + _position = bottom->first_frame(); // "{" + _length = _out->first_frame() + _out->length() - _in->first_frame(); + /* leave active alone */ + _follow_overlap = true; + } else { + _length = min (short_xfade_length, top->length()); + _position = top->last_frame() - _length; // "]" - length + _active = true; + _follow_overlap = false; + + } + break; + + case OverlapStart: /* top starts within bottom but covers bottom's end */ + + /* { ==== top ============ } + * [---- bottom -------------------] + */ + + _in = top; + _out = bottom; + _position = top->first_frame(); + _anchor_point = StartOfIn; + + if (model == FullCrossfade) { + _length = _out->first_frame() + _out->length() - _in->first_frame(); + /* leave active alone */ + _follow_overlap = true; + } else { + _length = min (short_xfade_length, top->length()); + _active = true; + _follow_overlap = false; + + } + + break; + } + } + + return 0; } XMLNode& -Crossfade::get_state () +Crossfade::get_state () { XMLNode* node = new XMLNode (X_("Crossfade")); XMLNode* child; char buf[64]; LocaleGuard lg (X_("POSIX")); - _out->id().print (buf); + id().print (buf, sizeof (buf)); + node->add_property ("id", buf); + _out->id().print (buf, sizeof (buf)); node->add_property ("out", buf); - _in->id().print (buf); + _in->id().print (buf, sizeof (buf)); node->add_property ("in", buf); node->add_property ("active", (_active ? "yes" : "no")); node->add_property ("follow-overlap", (_follow_overlap ? "yes" : "no")); node->add_property ("fixed", (_fixed ? "yes" : "no")); - snprintf (buf, sizeof(buf), "%" PRIu32, _length); + snprintf (buf, sizeof(buf), "%" PRId64, _length.val()); node->add_property ("length", buf); snprintf (buf, sizeof(buf), "%" PRIu32, (uint32_t) _anchor_point); node->add_property ("anchor-point", buf); - snprintf (buf, sizeof(buf), "%" PRIu32, (uint32_t) _position); + snprintf (buf, sizeof(buf), "%" PRId64, _position.val()); node->add_property ("position", buf); child = node->add_child ("FadeIn"); @@ -700,7 +740,7 @@ Crossfade::get_state () pnode = new XMLNode ("point"); - snprintf (buf, sizeof (buf), "%" PRIu32, (jack_nframes_t) floor ((*ii)->when)); + snprintf (buf, sizeof (buf), "%" PRId64, (framepos_t) floor ((*ii)->when)); pnode->add_property ("x", buf); snprintf (buf, sizeof (buf), "%.12g", (*ii)->value); pnode->add_property ("y", buf); @@ -714,7 +754,7 @@ Crossfade::get_state () pnode = new XMLNode ("point"); - snprintf (buf, sizeof (buf), "%" PRIu32, (jack_nframes_t) floor ((*ii)->when)); + snprintf (buf, sizeof (buf), "%" PRId64, (framepos_t) floor ((*ii)->when)); pnode->add_property ("x", buf); snprintf (buf, sizeof (buf), "%.12g", (*ii)->value); pnode->add_property ("y", buf); @@ -725,7 +765,7 @@ Crossfade::get_state () } int -Crossfade::set_state (const XMLNode& node) +Crossfade::set_state (const XMLNode& node, int /*version*/) { XMLNodeConstIterator i; XMLNodeList children; @@ -733,28 +773,42 @@ Crossfade::set_state (const XMLNode& node) XMLNode* fo; const XMLProperty* prop; LocaleGuard lg (X_("POSIX")); + PropertyChange what_changed; + framepos_t val; + + if ((prop = node.property (X_("id")))) { + _id = prop->value(); + } if ((prop = node.property ("position")) != 0) { - _position = atoi (prop->value().c_str()); + sscanf (prop->value().c_str(), "%" PRId64, &val); + if (val != _position) { + _position = val; + what_changed.add (Properties::position); + } } else { warning << _("old-style crossfade information - no position information") << endmsg; _position = _in->first_frame(); } if ((prop = node.property ("active")) != 0) { - _active = (prop->value() == "yes"); + bool x = string_is_affirmative (prop->value()); + if (x != _active) { + _active = x; + what_changed.add (Properties::active); + } } else { _active = true; } if ((prop = node.property ("follow-overlap")) != 0) { - _follow_overlap = (prop->value() == "yes"); + _follow_overlap = string_is_affirmative (prop->value()); } else { _follow_overlap = false; } if ((prop = node.property ("fixed")) != 0) { - _fixed = (prop->value() == "yes"); + _fixed = string_is_affirmative (prop->value()); } else { _fixed = false; } @@ -767,14 +821,18 @@ Crossfade::set_state (const XMLNode& node) if ((prop = node.property ("length")) != 0) { - _length = atol (prop->value().c_str()); + sscanf (prop->value().c_str(), "%" PRId64, &val); + if (val != _length) { + _length = val; + what_changed.add (Properties::length); + } } else { - + /* XXX this branch is legacy code from before the point where we stored xfade lengths. */ - + if ((_length = overlap_length()) == 0) { throw failed_constructor(); } @@ -783,54 +841,69 @@ Crossfade::set_state (const XMLNode& node) if ((fi = find_named_node (node, "FadeIn")) == 0) { return -1; } - + if ((fo = find_named_node (node, "FadeOut")) == 0) { return -1; } /* fade in */ - + + _fade_in.freeze (); _fade_in.clear (); - + children = fi->children(); - + for (i = children.begin(); i != children.end(); ++i) { if ((*i)->name() == "point") { - jack_nframes_t x; + framepos_t x; float y; - + prop = (*i)->property ("x"); - sscanf (prop->value().c_str(), "%" PRIu32, &x); - + sscanf (prop->value().c_str(), "%" PRId64, &x); + prop = (*i)->property ("y"); sscanf (prop->value().c_str(), "%f", &y); _fade_in.add (x, y); } } - + + _fade_in.front()->value = 0.0; + _fade_in.back()->value = 1.0; + + _fade_in.thaw (); + /* fade out */ - + + _fade_out.freeze (); _fade_out.clear (); children = fo->children(); - + for (i = children.begin(); i != children.end(); ++i) { if ((*i)->name() == "point") { - jack_nframes_t x; + framepos_t x; float y; XMLProperty* prop; prop = (*i)->property ("x"); - sscanf (prop->value().c_str(), "%" PRIu32, &x); + sscanf (prop->value().c_str(), "%" PRId64, &x); prop = (*i)->property ("y"); sscanf (prop->value().c_str(), "%f", &y); - + _fade_out.add (x, y); } } + _fade_out.front()->value = 1.0; + _fade_out.back()->value = 0.0; + + _fade_out.thaw (); + + PropertyChanged (what_changed); /* EMIT SIGNAL */ + FadesChanged (); /* EMIT SIGNAL */ + return 0; } @@ -850,16 +923,18 @@ Crossfade::set_follow_overlap (bool yn) _follow_overlap = yn; if (!yn) { - set_length (_short_xfade_length); + set_xfade_length (_short_xfade_length); } else { - set_length (_out->first_frame() + _out->length() - _in->first_frame()); + set_xfade_length (_out->first_frame() + _out->length() - _in->first_frame()); } + + PropertyChanged (PropertyChange (Properties::follow_overlap)); } -jack_nframes_t -Crossfade::set_length (jack_nframes_t len) +framecnt_t +Crossfade::set_xfade_length (framecnt_t len) { - jack_nframes_t limit; + framecnt_t limit = 0; switch (_anchor_point) { case StartOfIn: @@ -873,7 +948,7 @@ Crossfade::set_length (jack_nframes_t len) case EndOfOut: limit = _out->length(); break; - + } len = min (limit, len); @@ -884,17 +959,15 @@ Crossfade::set_length (jack_nframes_t len) _fade_out.x_scale (factor); _fade_in.x_scale (factor); _in_update = false; - - _length = len; - save_state ("length changed"); - - send_state_changed (LengthChanged); + _length = len; + PropertyChanged (PropertyChange (Properties::length)); + return len; } -jack_nframes_t +framecnt_t Crossfade::overlap_length () const { if (_fixed) { @@ -904,7 +977,7 @@ Crossfade::overlap_length () const } void -Crossfade::set_short_xfade_length (jack_nframes_t n) +Crossfade::set_short_xfade_length (framecnt_t n) { _short_xfade_length = n; }