fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / rb_effect.cc
index 0de5a5bf4a402730903f1c6fa947ff367e29d2b6..bc127e2d816f025727653ebf4e163dbe881212f7 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2004-2007 Paul Davis 
+    Copyright (C) 2004-2007 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
 #include <algorithm>
 #include <cmath>
 
-#include <pbd/error.h>
 #include <rubberband/RubberBandStretcher.h>
 
-#include <ardour/types.h>
-#include <ardour/stretch.h>
-#include <ardour/pitch.h>
-#include <ardour/audiofilesource.h>
-#include <ardour/session.h>
-#include <ardour/audioregion.h>
+#include "pbd/error.h"
 
-#include "i18n.h"
+#include "ardour/audioregion.h"
+#include "ardour/audiosource.h"
+#include "ardour/pitch.h"
+#include "ardour/progress.h"
+#include "ardour/session.h"
+#include "ardour/stretch.h"
+#include "ardour/types.h"
+
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -42,7 +44,7 @@ Pitch::Pitch (Session& s, TimeFXRequest& req)
 {
 }
 
-Stretch::Stretch (Session& s, TimeFXRequest& req)
+RBStretch::RBStretch (Session& s, TimeFXRequest& req)
        : RBEffect (s, req)
 {
 }
@@ -52,7 +54,7 @@ RBEffect::RBEffect (Session& s, TimeFXRequest& req)
        , tsr (req)
 
 {
-       tsr.progress = 0.0f;
+
 }
 
 RBEffect::~RBEffect ()
@@ -60,49 +62,129 @@ RBEffect::~RBEffect ()
 }
 
 int
-RBEffect::run (boost::shared_ptr<AudioRegion> region)
+RBEffect::run (boost::shared_ptr<Region> r, Progress* progress)
 {
+       boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (r);
+
+       if (!region) {
+               error << "RBEffect::run() passed a non-audio region! WTF?" << endmsg;
+               return -1;
+       }
+
        SourceList nsrcs;
-       nframes_t done;
        int ret = -1;
-       const nframes_t bufsize = 256;
+       const framecnt_t bufsize = 256;
        gain_t* gain_buffer = 0;
        Sample** buffers = 0;
        char suffix[32];
        string new_name;
        string::size_type at;
-       nframes_t pos = 0;
-       int avail = 0;
-
-       RubberBandStretcher stretcher (session.frame_rate(), region->n_channels(),
-                                      RubberBandStretcher::DefaultOptions,
-                                      tsr.time_fraction, tsr.pitch_fraction);
-       
-       stretcher.setExpectedInputDuration(region->length());
-       stretcher.setDebugLevel(1);
+       boost::shared_ptr<AudioRegion> result;
+
+       cerr << "RBEffect: source region: position = " << region->position()
+            << ", start = " << region->start()
+            << ", length = " << region->length()
+            << ", ancestral_start = " << region->ancestral_start()
+            << ", ancestral_length = " << region->ancestral_length()
+            << ", stretch " << region->stretch()
+            << ", shift " << region->shift() << endl;
+
+       /*
+          We have two cases to consider:
+
+          1. The region has not been stretched before.
+
+          In this case, we just want to read region->length() frames
+          from region->start().
+
+          We will create a new region of region->length() *
+          tsr.time_fraction frames.  The new region will have its
+          start set to 0 (because it has a new audio file that begins
+          at the start of the stretched area) and its ancestral_start
+          set to region->start() (so that we know where to begin
+          reading if we want to stretch it again).
+
+          2. The region has been stretched before.
+
+          The region starts at region->start() frames into its
+          (possibly previously stretched) source file.  But we don't
+          want to read from its source file; we want to read from the
+          file it was originally stretched from.
+
+          The region's source begins at region->ancestral_start()
+          frames into its master source file.  Thus, we need to start
+          reading at region->ancestral_start() + (region->start() /
+          region->stretch()) frames into the master source.  This
+          value will also become the ancestral_start for the new
+          region.
+
+          We cannot use region->ancestral_length() to establish how
+          many frames to read, because it won't be up to date if the
+          region has been trimmed since it was last stretched.  We
+          must read region->length() / region->stretch() frames and
+          stretch them by tsr.time_fraction * region->stretch(), for
+          a new region of region->length() * tsr.time_fraction
+          frames.
+
+          Case 1 is of course a special case of 2, where
+          region->ancestral_start() == 0 and region->stretch() == 1.
+
+          When we ask to read from a region, we supply a position on
+          the global timeline.  The read function calculates the
+          offset into the source as (position - region->position()) +
+          region->start().  This calculation is used regardless of
+          whether we are reading from a master or
+          previously-stretched region.  In order to read from a point
+          n frames into the master source, we need to provide n -
+          region->start() + region->position() as our position
+          argument to master_read_at().
+
+          Note that region->ancestral_length() is not used.
+
+          I hope this is clear.
+       */
 
-       tsr.progress = 0.0f;
-       tsr.done = false;
+       double stretch = region->stretch() * tsr.time_fraction;
+       double shift = region->shift() * tsr.pitch_fraction;
+
+       framecnt_t read_start = region->ancestral_start() +
+               framecnt_t(region->start() / (double)region->stretch());
+
+       framecnt_t read_duration =
+               framecnt_t(region->length() / (double)region->stretch());
 
        uint32_t channels = region->n_channels();
-       nframes_t duration = region->length();
+
+       RubberBandStretcher stretcher
+               (session.frame_rate(), channels,
+                (RubberBandStretcher::Options) tsr.opts, stretch, shift);
+
+       progress->set_progress (0);
+       tsr.done = false;
+
+       stretcher.setExpectedInputDuration(read_duration);
+       stretcher.setDebugLevel(1);
 
        /* the name doesn't need to be super-precise, but allow for 2 fractional
           digits just to disambiguate close but not identical FX
        */
 
-       if (tsr.time_fraction == 1.0) {
-               snprintf (suffix, sizeof (suffix), "@%d", (int) floor (tsr.pitch_fraction * 100.0f));
-       } else if (tsr.pitch_fraction == 1.0) {
-               snprintf (suffix, sizeof (suffix), "@%d", (int) floor (tsr.time_fraction * 100.0f));
+       if (stretch == 1.0) {
+               snprintf (suffix, sizeof (suffix), "@%d", (int) floor (shift * 100.0f));
+       } else if (shift == 1.0) {
+               snprintf (suffix, sizeof (suffix), "@%d", (int) floor (stretch * 100.0f));
        } else {
-               snprintf (suffix, sizeof (suffix), "@%d-%d", 
-                         (int) floor (tsr.time_fraction * 100.0f),
-                         (int) floor (tsr.pitch_fraction * 100.0f));
+               snprintf (suffix, sizeof (suffix), "@%d-%d",
+                         (int) floor (stretch * 100.0f),
+                         (int) floor (shift * 100.0f));
        }
 
        /* create new sources */
 
+       framepos_t pos   = 0;
+       framecnt_t avail = 0;
+       framecnt_t done  = 0;
+
        if (make_new_sources (region, nsrcs, suffix)) {
                goto out;
        }
@@ -120,72 +202,72 @@ RBEffect::run (boost::shared_ptr<AudioRegion> region)
 
        /* study first, process afterwards. */
 
-       pos = 0;
-       avail = 0;
-       done = 0;
+       try {
+               while (pos < read_duration && !tsr.cancel) {
+
+                       framecnt_t this_read = 0;
 
-       try { 
-               while (pos < duration && !tsr.cancel) {
-                       
-                       nframes_t this_read = 0;
-                       
                        for (uint32_t i = 0; i < channels; ++i) {
-                               
-                               this_read = 0;
-                               nframes_t this_time;
-                               
-                               this_time = min(bufsize, duration - pos);
-                               
+
+                               framepos_t this_time;
+                               this_time = min(bufsize, read_duration - pos);
+
+                               framepos_t this_position;
+                               this_position = read_start + pos -
+                                       region->start() + region->position();
+
                                this_read = region->master_read_at
                                        (buffers[i],
                                         buffers[i],
                                         gain_buffer,
-                                        pos + region->position(),
+                                        this_position,
                                         this_time,
                                         i);
-                               
+
                                if (this_read != this_time) {
                                        error << string_compose
-                                               (_("tempoize: error reading data from %1"),
-                                                nsrcs[i]->name()) << endmsg;
+                                               (_("tempoize: error reading data from %1 at %2 (wanted %3, got %4)"),
+                                                region->name(), this_position, this_time, this_read) << endmsg;
                                        goto out;
                                }
                        }
-                       
+
                        pos += this_read;
                        done += this_read;
 
-                       tsr.progress = ((float) done / duration) * 0.75;
+                       progress->set_progress (((float) done / read_duration) * 0.25);
 
-                       stretcher.study(buffers, this_read, pos == duration);
+                       stretcher.study(buffers, this_read, pos == read_duration);
                }
-               
+
                done = 0;
                pos = 0;
 
-               while (pos < duration && !tsr.cancel) {
-                       
-                       nframes_t this_read = 0;
-                       
+               while (pos < read_duration && !tsr.cancel) {
+
+                       framecnt_t this_read = 0;
+
                        for (uint32_t i = 0; i < channels; ++i) {
-                               
-                               this_read = 0;
-                               nframes_t this_time;
-                               
-                               this_time = min(bufsize, duration - pos);
-                               
+
+                               framepos_t this_time;
+                               this_time = min(bufsize, read_duration - pos);
+
+                               framepos_t this_position;
+                               this_position = read_start + pos -
+                                       region->start() + region->position();
+
                                this_read = region->master_read_at
                                        (buffers[i],
                                         buffers[i],
                                         gain_buffer,
-                                        pos + region->position(),
+                                        this_position,
                                         this_time,
                                         i);
-                               
+
                                if (this_read != this_time) {
                                        error << string_compose
-                                               (_("tempoize: error reading data from %1"),
-                                                nsrcs[i]->name()) << endmsg;
+                                               (_("tempoize: error reading data from %1 at %2 (wanted %3, got %4)"),
+                                                region->name(), pos + region->position(), this_time, this_read) << endmsg;
                                        goto out;
                                }
                        }
@@ -193,22 +275,26 @@ RBEffect::run (boost::shared_ptr<AudioRegion> region)
                        pos += this_read;
                        done += this_read;
 
-                       tsr.progress = 0.75 + ((float) done / duration) * 0.25;
+                       progress->set_progress (0.25 + ((float) done / read_duration) * 0.75);
 
-                       stretcher.process(buffers, this_read, pos == duration);
+                       stretcher.process(buffers, this_read, pos == read_duration);
 
-                       int avail = 0;
+                       framecnt_t avail = 0;
 
                        while ((avail = stretcher.available()) > 0) {
 
-                               this_read = min(bufsize, uint32_t(avail));
+                               this_read = min (bufsize, avail);
 
                                stretcher.retrieve(buffers, this_read);
-                       
+
                                for (uint32_t i = 0; i < nsrcs.size(); ++i) {
 
-                                       if (nsrcs[i]->write(buffers[i], this_read) !=
-                                           this_read) {
+                                       boost::shared_ptr<AudioSource> asrc = boost::dynamic_pointer_cast<AudioSource>(nsrcs[i]);
+                                       if (!asrc) {
+                                               continue;
+                                       }
+
+                                       if (asrc->write(buffers[i], this_read) != this_read) {
                                                error << string_compose (_("error writing tempo-adjusted data to %1"), nsrcs[i]->name()) << endmsg;
                                                goto out;
                                        }
@@ -218,13 +304,18 @@ RBEffect::run (boost::shared_ptr<AudioRegion> region)
 
                while ((avail = stretcher.available()) >= 0) {
 
-                       uint32_t this_read = min(bufsize, uint32_t(avail));
+                       framecnt_t this_read = min (bufsize, avail);
 
                        stretcher.retrieve(buffers, this_read);
 
                        for (uint32_t i = 0; i < nsrcs.size(); ++i) {
 
-                               if (nsrcs[i]->write(buffers[i], this_read) !=
+                               boost::shared_ptr<AudioSource> asrc = boost::dynamic_pointer_cast<AudioSource>(nsrcs[i]);
+                               if (!asrc) {
+                                       continue;
+                               }
+
+                               if (asrc->write(buffers[i], this_read) !=
                                    this_read) {
                                        error << string_compose (_("error writing tempo-adjusted data to %1"), nsrcs[i]->name()) << endmsg;
                                        goto out;
@@ -233,7 +324,7 @@ RBEffect::run (boost::shared_ptr<AudioRegion> region)
                }
 
        } catch (runtime_error& err) {
-               error << _("timefx code failure. please notify ardour-developers.") << endmsg;
+               error << string_compose (_("programming error: %1"), X_("timefx code failure")) << endmsg;
                error << err.what() << endmsg;
                goto out;
        }
@@ -253,30 +344,31 @@ RBEffect::run (boost::shared_ptr<AudioRegion> region)
 
        /* now reset ancestral data for each new region */
 
-       for (vector<boost::shared_ptr<AudioRegion> >::iterator x = results.begin(); x != results.end(); ++x) {
-               nframes64_t astart = (*x)->ancestral_start();
-               nframes64_t alength = (*x)->ancestral_length();
-               nframes_t start;
-               nframes_t length;
-
-               // note: tsr.time_fraction is a percentage of original length. 100 = no change, 
-               // 50 is half as long, 200 is twice as long, etc.
-               
-               
-               float stretch = (*x)->stretch() * (tsr.time_fraction/100.0);
-               float shift = (*x)->shift() * tsr.pitch_fraction;
+       for (vector<boost::shared_ptr<Region> >::iterator x = results.begin(); x != results.end(); ++x) {
+
+               (*x)->set_ancestral_data (read_start,
+                                         read_duration,
+                                         stretch,
+                                         shift);
+               (*x)->set_master_sources (region->master_sources());
+               /* multiply the old (possibly previously stretched) region length by the extra
+                  stretch this time around to get its new length. this is a non-music based edit atm.
+               */
+               (*x)->set_length ((*x)->length() * tsr.time_fraction, 0);
+       }
 
-               start = (nframes_t) floor (astart + ((astart - (*x)->start()) / stretch));
-               length = (nframes_t) floor (alength / stretch);
+       /* stretch region gain envelope */
+       /* XXX: assuming we've only processed one input region into one result here */
 
-               (*x)->set_ancestral_data (start, length, stretch, shift);
+       if (tsr.time_fraction != 1) {
+               result = boost::dynamic_pointer_cast<AudioRegion> (results.front());
+               assert (result);
+               result->envelope()->x_scale (tsr.time_fraction);
        }
 
   out:
 
-       if (gain_buffer) {
-               delete [] gain_buffer;
-       }
+       delete [] gain_buffer;
 
        if (buffers) {
                for (uint32_t i = 0; i < channels; ++i) {
@@ -290,8 +382,6 @@ RBEffect::run (boost::shared_ptr<AudioRegion> region)
                        (*si)->mark_for_remove ();
                }
        }
-       
-       tsr.done = true;
 
        return ret;
 }
@@ -299,4 +389,4 @@ RBEffect::run (boost::shared_ptr<AudioRegion> region)
 
 
 
-    
+