Merge some cases to avoid duplicated logic
[ardour.git] / libs / ardour / audioregion.cc
index 6b549b3d13fc9c7ff7926d9652fd47fc1cf91cc0..873d4119d45e505c0dbb36ce1c4381e418ec20f5 100644 (file)
@@ -55,7 +55,7 @@
 #include "ardour/coreaudiosource.h"
 #endif // HAVE_COREAUDIO
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -213,7 +213,7 @@ AudioRegion::register_properties ()
        , _fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_fade_in.val()))) \
        , _inverse_fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_inverse_fade_in.val()))) \
        , _fade_out (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_fade_out.val()))) \
-       , _inverse_fade_out (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_inverse_fade_out.val())))
+       , _inverse_fade_out (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_inverse_fade_out.val()))) \
 /* a Session will reset these to its chosen defaults by calling AudioRegion::set_default_fade() */
 
 void
@@ -279,8 +279,8 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other)
        assert (_sources.size() == _master_sources.size());
 }
 
-AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, framecnt_t offset)
-       : Region (other, offset)
+AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, framecnt_t offset, const int32_t sub_num)
+       : Region (other, offset, sub_num)
        , AUDIOREGION_COPY_STATE (other)
          /* As far as I can see, the _envelope's times are relative to region position, and have nothing
             to do with sources (and hence _start).  So when we copy the envelope, we just use the supplied offset.
@@ -372,7 +372,7 @@ void
 AudioRegion::connect_to_analysis_changed ()
 {
        for (SourceList::const_iterator i = _sources.begin(); i != _sources.end(); ++i) {
-               (*i)->AnalysisChanged.connect_same_thread (*this, boost::bind (&AudioRegion::invalidate_transients, this));
+               (*i)->AnalysisChanged.connect_same_thread (*this, boost::bind (&AudioRegion::maybe_invalidate_transients, this));
        }
 }
 
@@ -771,7 +771,7 @@ AudioRegion::get_basic_state ()
 {
        XMLNode& node (Region::state ());
        char buf[64];
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
 
        snprintf (buf, sizeof (buf), "%u", (uint32_t) _sources.size());
        node.add_property ("channels", buf);
@@ -784,7 +784,7 @@ AudioRegion::state ()
 {
        XMLNode& node (get_basic_state());
        XMLNode *child;
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
 
        child = node.add_child ("Envelope");
 
@@ -840,8 +840,8 @@ int
 AudioRegion::_set_state (const XMLNode& node, int version, PropertyChange& what_changed, bool send)
 {
        const XMLNodeList& nlist = node.children();
-       const XMLProperty *prop;
-       LocaleGuard lg (X_("C"));
+       XMLProperty const * prop;
+       LocaleGuard lg;
        boost::shared_ptr<Playlist> the_playlist (_playlist.lock());
 
        suspend_property_changes ();
@@ -871,7 +871,7 @@ AudioRegion::_set_state (const XMLNode& node, int version, PropertyChange& what_
 
        for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) {
                XMLNode *child;
-               XMLProperty *prop;
+               XMLProperty const * prop;
 
                child = (*niter);
 
@@ -1402,9 +1402,6 @@ AudioRegion::set_scale_amplitude (gain_t g)
        send_change (PropertyChange (Properties::scale_amplitude));
 }
 
-/** @return the maximum (linear) amplitude of the region, or a -ve
- *  number if the Progress object reports that the process was cancelled.
- */
 double
 AudioRegion::maximum_amplitude (Progress* p) const
 {
@@ -1444,6 +1441,45 @@ AudioRegion::maximum_amplitude (Progress* p) const
        return maxamp;
 }
 
+double
+AudioRegion::rms (Progress* p) const
+{
+       framepos_t fpos = _start;
+       framepos_t const fend = _start + _length;
+       uint32_t const n_chan = n_channels ();
+       double rms = 0;
+
+       framecnt_t const blocksize = 64 * 1024;
+       Sample buf[blocksize];
+
+       framecnt_t total = 0;
+
+       if (n_chan == 0) {
+               return 0;
+       }
+
+       while (fpos < fend) {
+               framecnt_t const to_read = min (fend - fpos, blocksize);
+               total += to_read;
+               for (uint32_t c = 0; c < n_chan; ++c) {
+                       if (read_raw_internal (buf, fpos, to_read, c) != to_read) {
+                               return 0;
+                       }
+                       for (framepos_t i = 0; i < to_read; ++i) {
+                               rms += buf[i] * buf[i];
+                       }
+                       fpos += to_read;
+                       if (p) {
+                               p->set_progress (float (fpos - _start) / _length);
+                               if (p->cancelled ()) {
+                                       return -1;
+                               }
+                       }
+               }
+       }
+       return sqrt (rms / (double)(total * n_chan));
+}
+
 /** Normalize using a given maximum amplitude and target, so that region
  *  _scale_amplitude becomes target / max_amplitude.
  */
@@ -1594,93 +1630,136 @@ AudioRegion::get_related_audio_file_channel_count () const
     return chan_count;
 }
 
-int
-AudioRegion::adjust_transients (frameoffset_t delta)
+void
+AudioRegion::clear_transients () // yet unused
 {
-       for (AnalysisFeatureList::iterator x = _transients.begin(); x != _transients.end(); ++x) {
-               (*x) = (*x) + delta;
-       }
-
+       _user_transients.clear ();
+       _valid_transients = false;
        send_change (PropertyChange (Properties::valid_transients));
-
-       return 0;
 }
 
-int
-AudioRegion::update_transient (framepos_t old_position, framepos_t new_position)
+void
+AudioRegion::add_transient (framepos_t where)
 {
-       for (AnalysisFeatureList::iterator x = _transients.begin(); x != _transients.end(); ++x) {
-               if ((*x) == old_position) {
-                       (*x) = new_position;
-                       send_change (PropertyChange (Properties::valid_transients));
+       if (where < first_frame () || where >= last_frame ()) {
+               return;
+       }
+       where -= _position;
 
-                       break;
+       if (!_valid_transients) {
+               _transient_user_start = _start;
+               _valid_transients = true;
+       }
+       frameoffset_t offset = _transient_user_start - _start;
+
+       if (where < offset) {
+               if (offset <= 0) {
+                       return;
                }
+               // region start changed (extend to front), shift points and offset
+               for (AnalysisFeatureList::iterator x = _transients.begin(); x != _transients.end(); ++x) {
+                       (*x) += offset;
+               }
+               _transient_user_start -= offset;
+               offset = 0;
        }
 
-       return 0;
+       const framepos_t p = where - offset;
+       _user_transients.push_back(p);
+       send_change (PropertyChange (Properties::valid_transients));
 }
 
 void
-AudioRegion::add_transient (framepos_t where)
+AudioRegion::update_transient (framepos_t old_position, framepos_t new_position)
 {
-       _transients.push_back(where);
-       _valid_transients = true;
+       bool changed = false;
+       if (!_onsets.empty ()) {
+               const framepos_t p = old_position - _position;
+               AnalysisFeatureList::iterator x = std::find (_onsets.begin (), _onsets.end (), p);
+               if (x != _transients.end ()) {
+                       (*x) = new_position - _position;
+                       changed = true;
+               }
+       }
 
-       send_change (PropertyChange (Properties::valid_transients));
+       if (_valid_transients) {
+               const frameoffset_t offset = _position + _transient_user_start - _start;
+               const framepos_t p = old_position - offset;
+               AnalysisFeatureList::iterator x = std::find (_user_transients.begin (), _user_transients.end (), p);
+               if (x != _transients.end ()) {
+                       (*x) = new_position - offset;
+                       changed = true;
+               }
+       }
+
+       if (changed) {
+               send_change (PropertyChange (Properties::valid_transients));
+       }
 }
 
 void
 AudioRegion::remove_transient (framepos_t where)
 {
-       _transients.remove(where);
-       _valid_transients = true;
+       bool changed = false;
+       if (!_onsets.empty ()) {
+               const framepos_t p = where - _position;
+               AnalysisFeatureList::iterator i = std::find (_onsets.begin (), _onsets.end (), p);
+               if (i != _transients.end ()) {
+                       _onsets.erase (i);
+                       changed = true;
+               }
+       }
 
-       send_change (PropertyChange (Properties::valid_transients));
+       if (_valid_transients) {
+               const framepos_t p = where - (_position + _transient_user_start - _start);
+               AnalysisFeatureList::iterator i = std::find (_user_transients.begin (), _user_transients.end (), p);
+               if (i != _transients.end ()) {
+                       _transients.erase (i);
+                       changed = true;
+               }
+       }
+
+       if (changed) {
+               send_change (PropertyChange (Properties::valid_transients));
+       }
 }
 
-int
-AudioRegion::set_transients (AnalysisFeatureList& results)
+void
+AudioRegion::set_onsets (AnalysisFeatureList& results)
 {
-       _transients.clear();
-       _transients = results;
-       _valid_transients = true;
-
+       _onsets.clear();
+       _onsets = results;
        send_change (PropertyChange (Properties::valid_transients));
-
-       return 0;
 }
 
-int
-AudioRegion::get_transients (AnalysisFeatureList& results, bool force_new)
+void
+AudioRegion::build_transients ()
 {
+       _transients.clear ();
+       _transient_analysis_start = _transient_analysis_end = 0;
+
        boost::shared_ptr<Playlist> pl = playlist();
 
        if (!pl) {
-               return -1;
-       }
-
-       if (_valid_transients && !force_new) {
-               results = _transients;
-               return 0;
+               return;
        }
 
+       /* check analyzed sources first */
        SourceList::iterator s;
-
        for (s = _sources.begin() ; s != _sources.end(); ++s) {
                if (!(*s)->has_been_analysed()) {
+#ifndef NDEBUG
                        cerr << "For " << name() << " source " << (*s)->name() << " has not been analyzed\n";
+#endif
                        break;
                }
        }
 
        if (s == _sources.end()) {
                /* all sources are analyzed, merge data from each one */
-
                for (s = _sources.begin() ; s != _sources.end(); ++s) {
 
                        /* find the set of transients within the bounds of this region */
-
                        AnalysisFeatureList::iterator low = lower_bound ((*s)->transients.begin(),
                                                                         (*s)->transients.end(),
                                                                         _start);
@@ -1690,23 +1769,19 @@ AudioRegion::get_transients (AnalysisFeatureList& results, bool force_new)
                                                                          _start + _length);
 
                        /* and add them */
-
-                       results.insert (results.end(), low, high);
+                       _transients.insert (_transients.end(), low, high);
                }
 
-               TransientDetector::cleanup_transients (results, pl->session().frame_rate(), 3.0);
+               TransientDetector::cleanup_transients (_transients, pl->session().frame_rate(), 3.0);
 
                /* translate all transients to current position */
-
-               for (AnalysisFeatureList::iterator x = results.begin(); x != results.end(); ++x) {
+               for (AnalysisFeatureList::iterator x = _transients.begin(); x != _transients.end(); ++x) {
                        (*x) -= _start;
-                       (*x) += _position;
                }
 
-               _transients = results;
-               _valid_transients = true;
-
-               return 0;
+               _transient_analysis_start = _start;
+               _transient_analysis_end = _start + _length;
+               return;
        }
 
        /* no existing/complete transient info */
@@ -1721,7 +1796,7 @@ You currently have \"auto-analyse-audio\" disabled, which means \
 that transient data must be generated every time it is required.\n\n\
 If you are doing work that will require transient data on a \
 regular basis, you should probably enable \"auto-analyse-audio\" \
-then quit %1 and restart.\n\n\
+in Preferences > Audio > Regions, then quit %1 and restart.\n\n\
 This dialog will not display again.  But you may notice a slight delay \
 in this and future transient-detection operations.\n\
 "), PROGRAM_NAME));
@@ -1729,64 +1804,74 @@ in this and future transient-detection operations.\n\
                }
        }
 
-       bool existing_results = !results.empty();
-
        try {
-
                TransientDetector t (pl->session().frame_rate());
-
-               _transients.clear ();
-               _valid_transients = false;
-
                for (uint32_t i = 0; i < n_channels(); ++i) {
 
                        AnalysisFeatureList these_results;
 
                        t.reset ();
 
+                       /* this produces analysis result relative to current position
+                        * ::read() sample 0 is at _position */
                        if (t.run ("", this, i, these_results)) {
-                               return -1;
-                       }
-
-                       /* translate all transients to give absolute position */
-
-                       for (AnalysisFeatureList::iterator i = these_results.begin(); i != these_results.end(); ++i) {
-                               (*i) += _position;
+                               return;
                        }
 
                        /* merge */
-
                        _transients.insert (_transients.end(), these_results.begin(), these_results.end());
                }
        } catch (...) {
                error << string_compose(_("Transient Analysis failed for %1."), _("Audio Region")) << endmsg;
-               return -1;
+               return;
        }
 
-       if (!results.empty()) {
-               if (existing_results) {
-
-                       /* merge our transients into the existing ones, then clean up
-                          those.
-                       */
-
-                       results.insert (results.end(), _transients.begin(), _transients.end());
-                       TransientDetector::cleanup_transients (results, pl->session().frame_rate(), 3.0);
-               }
-
-               /* make sure ours are clean too */
+       TransientDetector::cleanup_transients (_transients, pl->session().frame_rate(), 3.0);
+       _transient_analysis_start = _start;
+       _transient_analysis_end = _start + _length;
+}
 
-               TransientDetector::cleanup_transients (_transients, pl->session().frame_rate(), 3.0);
+/* Transient analysis uses ::read() which is relative to _start,
+ * at the time of analysis and spans _length samples.
+ *
+ * This is true for RhythmFerret::run_analysis and the
+ * TransientDetector here.
+ *
+ * We store _start and length in _transient_analysis_start,
+ * _transient_analysis_end in case the region is trimmed or split after analysis.
+ *
+ * Various methods (most notably Playlist::find_next_transient and
+ * RhythmFerret::do_split_action) span multiple regions and *merge/combine*
+ * Analysis results.
+ * We therefore need to translate the analysis timestamps to absolute session-time
+ * and include the _position of the region.
+ *
+ * Note: we should special case the AudioRegionView. The region-view itself
+ * is located at _position (currently ARV subtracts _position again)
+ */
+void
+AudioRegion::get_transients (AnalysisFeatureList& results)
+{
+       boost::shared_ptr<Playlist> pl = playlist();
+       if (!playlist ()) {
+               return;
+       }
 
-       } else {
+       Region::merge_features (results, _user_transients, _position + _transient_user_start - _start);
 
-               TransientDetector::cleanup_transients (_transients, pl->session().frame_rate(), 3.0);
-               results = _transients;
+       if (!_onsets.empty ()) {
+               // onsets are invalidated when start or length changes
+               merge_features (results, _onsets, _position);
+               return;
        }
 
-       _valid_transients = true;
+       if ((_transient_analysis_start == _transient_analysis_end)
+                       || _transient_analysis_start > _start
+                       || _transient_analysis_end < _start + _length) {
+               build_transients ();
+       }
 
-       return 0;
+       merge_features (results, _transients, _position + _transient_analysis_start - _start);
 }
 
 /** Find areas of `silence' within a region.