Exemplo n.º 1
0
bool EffectReverse::ProcessOneClip(int count, WaveTrack *track,
                               sampleCount start, sampleCount len,
                               sampleCount originalStart, sampleCount originalEnd)
{
   bool rc = true;
   // keep track of two blocks whose data we will swap
   auto first = start;

   auto blockSize = track->GetMaxBlockSize();
   float tmp;
   Floats buffer1{ blockSize };
   Floats buffer2{ blockSize };

   auto originalLen = originalEnd - originalStart;

   while (len > 1) {
      auto block =
         limitSampleBufferSize( track->GetBestBlockSize(first), len / 2 );
      auto second = first + (len - block);

      track->Get((samplePtr)buffer1.get(), floatSample, first, block);
      track->Get((samplePtr)buffer2.get(), floatSample, second, block);
      for (decltype(block) i = 0; i < block; i++) {
         tmp = buffer1[i];
         buffer1[i] = buffer2[block-i-1];
         buffer2[block-i-1] = tmp;
      }
      track->Set((samplePtr)buffer1.get(), floatSample, first, block);
      track->Set((samplePtr)buffer2.get(), floatSample, second, block);

      len -= 2 * block;
      first += block;

      if( TrackProgress(count, 2 * ( first - originalStart ).as_double() /
                        originalLen.as_double() ) ) {
         rc = false;
         break;
      }
   }

   return rc;
}
Exemplo n.º 2
0
size_t EffectDtmf::ProcessBlock(float **WXUNUSED(inbuf), float **outbuf, size_t size)
{
   float *buffer = outbuf[0];
   decltype(size) processed = 0;

   // for the whole dtmf sequence, we will be generating either tone or silence
   // according to a bool value, and this might be done in small chunks of size
   // 'block', as a single tone might sometimes be larger than the block
   // tone and silence generally have different duration, thus two generation blocks
   //
   // Note: to overcome a 'clicking' noise introduced by the abrupt transition from/to
   // silence, I added a fade in/out of 1/250th of a second (4ms). This can still be
   // tweaked but gives excellent results at 44.1kHz: I haven't tried other freqs.
   // A problem might be if the tone duration is very short (<10ms)... (?)
   //
   // One more problem is to deal with the approximations done when calculating the duration
   // of both tone and silence: in some cases the final sum might not be same as the initial
   // duration. So, to overcome this, we had a redistribution block up, and now we will spread
   // the remaining samples in every bin in order to achieve the full duration: test case was
   // to generate an 11 tone DTMF sequence, in 4 seconds, and with DutyCycle=75%: after generation
   // you ended up with 3.999s or in other units: 3 seconds and 44097 samples.
   //
   while (size)
   {
      if (numRemaining == 0)
      {
         isTone = !isTone;

         if (isTone)
         {
            curSeqPos++;
            numRemaining = numSamplesTone;
            curTonePos = 0;
         }
         else
         {
            numRemaining = numSamplesSilence;
         }

         // the statement takes care of extracting one sample from the diff bin and
         // adding it into the current block until depletion
         numRemaining += (diff-- > 0 ? 1 : 0);         
      }

      const auto len = limitSampleBufferSize( size, numRemaining );

      if (isTone)
      {
         // generate the tone and append
         MakeDtmfTone(buffer, len, mSampleRate, dtmfSequence[curSeqPos], curTonePos, numSamplesTone, dtmfAmplitude);
         curTonePos += len;
      }
      else
      {
         memset(buffer, 0, sizeof(float) * len);
      }

      numRemaining -= len;

      buffer += len;
      size -= len;
      processed += len;
   }

   return processed;
}
Exemplo n.º 3
0
// ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
// and calls libsamplerate code on these blocks.
bool EffectChangeSpeed::ProcessOne(WaveTrack * track,
                           sampleCount start, sampleCount end)
{
   if (track == NULL)
      return false;

   // initialization, per examples of Mixer::Mixer and
   // EffectSoundTouch::ProcessOne

   auto outputTrack = mFactory->NewWaveTrack(track->GetSampleFormat(),
                                                    track->GetRate());

   //Get the length of the selection (as double). len is
   //used simple to calculate a progress meter, so it is easier
   //to make it a double now than it is to do it later
   auto len = (end - start).as_double();

   // Initiate processing buffers, most likely shorter than
   // the length of the selection being processed.
   auto inBufferSize = track->GetMaxBlockSize();

   Floats inBuffer{ inBufferSize };

   // mFactor is at most 100-fold so this shouldn't overflow size_t
   auto outBufferSize = size_t( mFactor * inBufferSize + 10 );
   Floats outBuffer{ outBufferSize };

   // Set up the resampling stuff for this track.
   Resample resample(true, mFactor, mFactor); // constant rate resampling

   //Go through the track one buffer at a time. samplePos counts which
   //sample the current buffer starts at.
   bool bResult = true;
   auto samplePos = start;
   while (samplePos < end) {
      //Get a blockSize of samples (smaller than the size of the buffer)
      auto blockSize = limitSampleBufferSize(
         track->GetBestBlockSize(samplePos),
         end - samplePos
      );

      //Get the samples from the track and put them in the buffer
      track->Get((samplePtr) inBuffer.get(), floatSample, samplePos, blockSize);

      const auto results = resample.Process(mFactor,
                                    inBuffer.get(),
                                    blockSize,
                                    ((samplePos + blockSize) >= end),
                                    outBuffer.get(),
                                    outBufferSize);
      const auto outgen = results.second;

      if (outgen > 0)
         outputTrack->Append((samplePtr)outBuffer.get(), floatSample,
                             outgen);

      // Increment samplePos
      samplePos += results.first;

      // Update the Progress meter
      if (TrackProgress(mCurTrackNum, (samplePos - start).as_double() / len)) {
         bResult = false;
         break;
      }
   }

   // Flush the output WaveTrack (since it's buffered, too)
   outputTrack->Flush();

   // Take the output track and insert it in place of the original
   // sample data
   double newLength = outputTrack->GetEndTime();
   if (bResult)
   {
      LinearTimeWarper warper { mCurT0, mCurT0, mCurT1, mCurT0 + newLength };
      bResult = track->ClearAndPaste(
         mCurT0, mCurT1, outputTrack.get(), true, false, &warper);
   }

   if (newLength > mMaxNewLength)
      mMaxNewLength = newLength;

   return bResult;
}
Exemplo n.º 4
0
bool EffectTruncSilence::Analyze(RegionList& silenceList,
                                 RegionList& trackSilences,
                                 const WaveTrack *wt,
                                 sampleCount* silentFrame,
                                 sampleCount* index,
                                 int whichTrack,
                                 double* inputLength /*= NULL*/,
                                 double* minInputLength /*= NULL*/)
{
   // Smallest silent region to detect in frames
   auto minSilenceFrames = sampleCount(std::max( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());

   double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex];
   auto blockLen = wt->GetMaxBlockSize();
   auto start = wt->TimeToLongSamples(mT0);
   auto end = wt->TimeToLongSamples(mT1);
   sampleCount outLength = 0;

   double previewLength;
   gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLength, 6.0);
   // Minimum required length in samples.
   const sampleCount previewLen( previewLength * wt->GetRate() );

   // Keep position in overall silences list for optimization
   RegionList::iterator rit(silenceList.begin());

   // Allocate buffer
   Floats buffer{ blockLen };

   // Loop through current track
   while (*index < end) {
      if (inputLength && ((outLength >= previewLen) || (*index - start > wt->TimeToLongSamples(*minInputLength)))) {
         *inputLength = std::min<double>(*inputLength, *minInputLength);
         if (outLength >= previewLen) {
            *minInputLength = *inputLength;
         }
         return true;
      }

      if (!inputLength) {
         // Show progress dialog, test for cancellation
         bool cancelled = TotalProgress(
               detectFrac * (whichTrack +
                             (*index - start).as_double() /
                             (end - start).as_double()) /
                             (double)GetNumWaveTracks());
         if (cancelled)
            return false;
      }

      // Optimization: if not in a silent region skip ahead to the next one

      double curTime = wt->LongSamplesToTime(*index);
      for ( ; rit != silenceList.end(); ++rit) {
         // Find the first silent region ending after current time
         if (rit->end >= curTime) {
            break;
         }
      }

      if (rit == silenceList.end()) {
         // No more regions -- no need to process the rest of the track
         if (inputLength) {
            // Add available samples up to previewLength.
            auto remainingTrackSamples = wt->TimeToLongSamples(wt->GetEndTime()) - *index;
            auto requiredTrackSamples = previewLen - outLength;
            outLength += (remainingTrackSamples > requiredTrackSamples)? requiredTrackSamples : remainingTrackSamples;
         }

         break;
      }
      else if (rit->start > curTime) {
         // End current silent region, skip ahead
         if (*silentFrame >= minSilenceFrames)  {
            trackSilences.push_back(Region(
               wt->LongSamplesToTime(*index - *silentFrame),
               wt->LongSamplesToTime(*index)
            ));
         }
         *silentFrame = 0;
         auto newIndex = wt->TimeToLongSamples(rit->start);
         if (inputLength) {
            auto requiredTrackSamples = previewLen - outLength;
            // Add non-silent sample to outLength
            outLength += ((newIndex - *index) > requiredTrackSamples)? requiredTrackSamples : newIndex - *index;
         }

         *index = newIndex;
      }
      // End of optimization

      // Limit size of current block if we've reached the end
      auto count = limitSampleBufferSize( blockLen, end - *index );

      // Fill buffer
      wt->Get((samplePtr)(buffer.get()), floatSample, *index, count);

      // Look for silenceList in current block
      for (decltype(count) i = 0; i < count; ++i) {
         if (inputLength && ((outLength >= previewLen) || (outLength > wt->TimeToLongSamples(*minInputLength)))) {
            *inputLength = wt->LongSamplesToTime(*index + i) - wt->LongSamplesToTime(start);
            break;
         }

         if (fabs(buffer[i]) < truncDbSilenceThreshold) {
            (*silentFrame)++;
         }
         else {
            sampleCount allowed = 0;
            if (*silentFrame >= minSilenceFrames) {
               if (inputLength) {
                  switch (mActionIndex) {
                     case kTruncate:
                        outLength += wt->TimeToLongSamples(mTruncLongestAllowedSilence);
                        break;
                     case kCompress:
                        allowed = wt->TimeToLongSamples(mInitialAllowedSilence);
                        outLength += sampleCount(
                           allowed.as_double() +
                              (*silentFrame - allowed).as_double()
                                 * mSilenceCompressPercent / 100.0
                        );
                        break;
                     // default: // Not currently used.
                  }
               }

               // Record the silent region
               trackSilences.push_back(Region(
                  wt->LongSamplesToTime(*index + i - *silentFrame),
                  wt->LongSamplesToTime(*index + i)
               ));
            }
            else if (inputLength) {   // included as part of non-silence
               outLength += *silentFrame;
            }
            *silentFrame = 0;
            if (inputLength) {
                ++outLength;   // Add non-silent sample to outLength
            }
         }
      }
      // Next block
      *index += count;
   }

   if (inputLength) {
      *inputLength = std::min<double>(*inputLength, *minInputLength);
      if (outLength >= previewLen) {
         *minInputLength = *inputLength;
      }
   }

   return true;
}
Exemplo n.º 5
0
//ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
//and executes TwoBufferProcessPass1 or TwoBufferProcessPass2 on these blocks
bool EffectTwoPassSimpleMono::ProcessOne(WaveTrack * track,
                                         sampleCount start, sampleCount end)
{
   bool ret;
   float *tmpfloat;

   //Get the length of the buffer (as double). len is
   //used simple to calculate a progress meter, so it is easier
   //to make it a double now than it is to do it later
   auto len = (end - start).as_double();
   auto maxblock = track->GetMaxBlockSize();

   //Initiate a processing buffer.  This buffer will (most likely)
   //be shorter than the length of the track being processed.
   float *buffer1 = new float[maxblock];
   float *buffer2 = new float[maxblock];
   auto samples1 =  limitSampleBufferSize(
      std::min( maxblock, track->GetBestBlockSize(start) ), end - start );

   //Get the samples from the track and put them in the buffer
   track->Get((samplePtr) buffer1, floatSample, start, samples1);

   // Process the first buffer with a NULL previous buffer
   if (mPass == 0)
      ret = TwoBufferProcessPass1(NULL, 0, buffer1, samples1);
   else
      ret = TwoBufferProcessPass2(NULL, 0, buffer1, samples1);
   if (!ret) {
      delete[]buffer1;
      delete[]buffer2;

      //Return false because the effect failed.
      return false;
   }

   //Go through the track one buffer at a time. s counts which
   //sample the current buffer starts at.
   auto s = start + samples1;
   while (s < end) {
      //Get a block of samples (smaller than the size of the buffer)
      //Adjust the block size if it is the final block in the track
      auto samples2 = limitSampleBufferSize(
         std::min( track->GetBestBlockSize(s), maxblock ), end - s
      );

      //Get the samples from the track and put them in the buffer
      track->Get((samplePtr) buffer2, floatSample, s, samples2);

      //Process the buffer.  If it fails, clean up and exit.
      if (mPass == 0)
         ret = TwoBufferProcessPass1(buffer1, samples1, buffer2, samples2);
      else
         ret = TwoBufferProcessPass2(buffer1, samples1, buffer2, samples2);
      if (!ret) {
         delete[]buffer1;
         delete[]buffer2;
         //Return false because the effect failed.
         return false;
      }

      //Processing succeeded. copy the newly-changed samples back
      //onto the track.
      track->Set((samplePtr) buffer1, floatSample, s-samples1, samples1);

      //Increment s one blockfull of samples
      s += samples2;

      //Update the Progress meter
      if (mSecondPassDisabled)
         ret = TotalProgress(
            (mCurTrackNum + (s-start).as_double()/len) /
            GetNumWaveTracks());
      else
         ret = TotalProgress(
            (mCurTrackNum + (s-start).as_double()/len + GetNumWaveTracks()*mPass) /
            (GetNumWaveTracks()*2));
      if (ret) {
         delete[]buffer1;
         delete[]buffer2;
         //Return false because the effect failed.
         return false;
      }

      // Rotate the buffers
      tmpfloat = buffer1;
      buffer1 = buffer2;
      buffer2 = tmpfloat;

      std::swap(samples1, samples2);
   }

   // Send the last buffer with a NULL pointer for the current buffer
   if (mPass == 0)
      ret = TwoBufferProcessPass1(buffer1, samples1, NULL, 0);
   else
      ret = TwoBufferProcessPass2(buffer1, samples1, NULL, 0);

   if (!ret) {
      delete[]buffer1;
      delete[]buffer2;
      //Return false because the effect failed.
      return false;
   }

   //Processing succeeded. copy the newly-changed samples back
   //onto the track.
   track->Set((samplePtr) buffer1, floatSample, s-samples1, samples1);

   //Clean up the buffer
   delete[]buffer1;
   delete[]buffer2;

   //Return true because the effect processing succeeded.
   return true;
}
Exemplo n.º 6
0
bool VampEffect::Process()
{
   if (!mPlugin)
   {
      return false;
   }

   int count = 0;

   bool multiple = false;
   unsigned prevTrackChannels = 0;

   if (GetNumWaveGroups() > 1)
   {
      // if there is another track beyond this one and any linked one,
      // then we're processing more than one track.  That means we
      // should use the originating track name in each NEW label
      // track's name, to make clear which is which
      multiple = true;
   }

   std::vector<std::shared_ptr<Effect::AddedAnalysisTrack>> addedTracks;

   for (auto leader : inputTracks()->Leaders<const WaveTrack>())
   {
      auto channelGroup = TrackList::Channels(leader);
      auto left = *channelGroup.first++;

      sampleCount lstart, rstart = 0;
      sampleCount len;
      GetSamples(left, &lstart, &len);

      unsigned channels = 1;

      // channelGroup now contains all but the first channel
      const WaveTrack *right =
         channelGroup.size() ? *channelGroup.first++ : nullptr;
      if (right)
      {
         channels = 2;
         GetSamples(right, &rstart, &len);
      }

      // TODO: more-than-two-channels

      size_t step = mPlugin->getPreferredStepSize();
      size_t block = mPlugin->getPreferredBlockSize();

      bool initialiseRequired = true;

      if (block == 0)
      {
         if (step != 0)
         {
            block = step;
         }
         else
         {
            block = 1024;
         }
      }

      if (step == 0)
      {
         step = block;
      }

      if (prevTrackChannels > 0)
      {
         // Plugin has already been initialised, so if the number of
         // channels remains the same, we only need to do a reset.
         // Otherwise we need to re-construct the whole plugin,
         // because a Vamp plugin can't be re-initialised.
         if (prevTrackChannels == channels)
         {
            mPlugin->reset();
            initialiseRequired = false;
         }
         else
         {
            //!!! todo: retain parameters previously set
            Init();
         }
      }

      if (initialiseRequired)
      {
         if (!mPlugin->initialise(channels, step, block))
         {
            Effect::MessageBox(_("Sorry, Vamp Plug-in failed to initialize."));
            return false;
         }
      }

      const auto effectName = GetSymbol().Translation();
      addedTracks.push_back(AddAnalysisTrack(
         multiple
         ? wxString::Format( _("%s: %s"), left->GetName(), effectName )
         : effectName
      ));
      LabelTrack *ltrack = addedTracks.back()->get();

      FloatBuffers data{ channels, block };

      auto originalLen = len;
      auto ls = lstart;
      auto rs = rstart;

      while (len != 0)
      {
         const auto request = limitSampleBufferSize( block, len );

         if (left)
         {
            left->Get((samplePtr)data[0].get(), floatSample, ls, request);
         }

         if (right)
         {
            right->Get((samplePtr)data[1].get(), floatSample, rs, request);
         }

         if (request < block)
         {
            for (unsigned int c = 0; c < channels; ++c)
            {
               for (decltype(block) i = request; i < block; ++i)
               {
                  data[c][i] = 0.f;
               }
            }
         }

         // UNSAFE_SAMPLE_COUNT_TRUNCATION
         // Truncation in case of very long tracks!
         Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime(
            long( ls.as_long_long() ),
            (int)(mRate + 0.5)
         );

         Vamp::Plugin::FeatureSet features = mPlugin->process(
            reinterpret_cast< float** >( data.get() ), timestamp);
         AddFeatures(ltrack, features);

         if (len > (int)step)
         {
            len -= step;
         }
         else
         {
            len = 0;
         }

         ls += step;
         rs += step;

         if (channels > 1)
         {
            if (TrackGroupProgress(count,
                  (ls - lstart).as_double() /
                  originalLen.as_double() ))
            {
               return false;
            }
         }
         else
         {
            if (TrackProgress(count,
                  (ls - lstart).as_double() /
                  originalLen.as_double() ))
            {
               return false;
            }
         }
      }

      Vamp::Plugin::FeatureSet features = mPlugin->getRemainingFeatures();
      AddFeatures(ltrack, features);

      prevTrackChannels = channels;
   }

   // All completed without cancellation, so commit the addition of tracks now
   for (auto &addedTrack : addedTracks)
      addedTrack->Commit();

   return true;
}
Exemplo n.º 7
0
// this currently does an exponential fade
bool EffectAutoDuck::ApplyDuckFade(int trackNum, WaveTrack* t,
                                   double t0, double t1)
{
   bool cancel = false;

   auto start = t->TimeToLongSamples(t0);
   auto end = t->TimeToLongSamples(t1);

   Floats buf{ kBufSize };
   auto pos = start;

   auto fadeDownSamples = t->TimeToLongSamples(
      mOuterFadeDownLen + mInnerFadeDownLen);
   if (fadeDownSamples < 1)
      fadeDownSamples = 1;

   auto fadeUpSamples = t->TimeToLongSamples(
      mOuterFadeUpLen + mInnerFadeUpLen);
   if (fadeUpSamples < 1)
      fadeUpSamples = 1;

   float fadeDownStep = mDuckAmountDb / fadeDownSamples.as_double();
   float fadeUpStep = mDuckAmountDb / fadeUpSamples.as_double();

   while (pos < end)
   {
      const auto len = limitSampleBufferSize( kBufSize, end - pos );

      t->Get((samplePtr)buf.get(), floatSample, pos, len);

      for (auto i = pos; i < pos + len; i++)
      {
         float gainDown = fadeDownStep * (i - start).as_float();
         float gainUp = fadeUpStep * (end - i).as_float();

         float gain;
         if (gainDown > gainUp)
            gain = gainDown;
         else
            gain = gainUp;
         if (gain < mDuckAmountDb)
            gain = mDuckAmountDb;

         // i - pos is bounded by len:
         buf[ ( i - pos ).as_size_t() ] *= DB_TO_LINEAR(gain);
      }

      t->Set((samplePtr)buf.get(), floatSample, pos, len);

      pos += len;

      float curTime = t->LongSamplesToTime(pos);
      float fractionFinished = (curTime - mT0) / (mT1 - mT0);
      if (TotalProgress( (trackNum + 1 + fractionFinished) /
                         (GetNumWaveTracks() + 1) ))
      {
         cancel = true;
         break;
      }
   }

   return cancel;
}
Exemplo n.º 8
0
bool EffectAutoDuck::Process()
{
   if (GetNumWaveTracks() == 0 || !mControlTrack)
      return false;

   bool cancel = false;

   auto start =
      mControlTrack->TimeToLongSamples(mT0 + mOuterFadeDownLen);
   auto end =
      mControlTrack->TimeToLongSamples(mT1 - mOuterFadeUpLen);

   if (end <= start)
      return false;

   // the minimum number of samples we have to wait until the maximum
   // pause has been exceeded
   double maxPause = mMaximumPause;

   // We don't fade in until we have time enough to actually fade out again
   if (maxPause < mOuterFadeDownLen + mOuterFadeUpLen)
      maxPause = mOuterFadeDownLen + mOuterFadeUpLen;

   auto minSamplesPause =
      mControlTrack->TimeToLongSamples(maxPause);

   double threshold = DB_TO_LINEAR(mThresholdDb);

   // adjust the threshold so we can compare it to the rmsSum value
   threshold = threshold * threshold * kRMSWindowSize;

   int rmsPos = 0;
   float rmsSum = 0;
   // to make the progress bar appear more natural, we first look for all
   // duck regions and apply them all at once afterwards
   std::vector<AutoDuckRegion> regions;
   bool inDuckRegion = false;
   {
      Floats rmsWindow{ kRMSWindowSize, true };

      Floats buf{ kBufSize };

      // initialize the following two variables to prevent compiler warning
      double duckRegionStart = 0;
      sampleCount curSamplesPause = 0;

      auto pos = start;

      while (pos < end)
      {
         const auto len = limitSampleBufferSize( kBufSize, end - pos );
         
         mControlTrack->Get((samplePtr)buf.get(), floatSample, pos, len);

         for (auto i = pos; i < pos + len; i++)
         {
            rmsSum -= rmsWindow[rmsPos];
            // i - pos is bounded by len:
            auto index = ( i - pos ).as_size_t();
            rmsWindow[rmsPos] = buf[ index ] * buf[ index ];
            rmsSum += rmsWindow[rmsPos];
            rmsPos = (rmsPos + 1) % kRMSWindowSize;

            bool thresholdExceeded = rmsSum > threshold;

            if (thresholdExceeded)
            {
               // everytime the threshold is exceeded, reset our count for
               // the number of pause samples
               curSamplesPause = 0;

               if (!inDuckRegion)
               {
                  // the threshold has been exceeded for the first time, so
                  // let the duck region begin here
                  inDuckRegion = true;
                  duckRegionStart = mControlTrack->LongSamplesToTime(i);
               }
            }

            if (!thresholdExceeded && inDuckRegion)
            {
               // the threshold has not been exceeded and we are in a duck
               // region, but only fade in if the maximum pause has been
               // exceeded
               curSamplesPause += 1;

               if (curSamplesPause >= minSamplesPause)
               {
                  // do the actual duck fade and reset all values
                  double duckRegionEnd =
                     mControlTrack->LongSamplesToTime(i - curSamplesPause);

                  regions.push_back(AutoDuckRegion(
                     duckRegionStart - mOuterFadeDownLen,
                     duckRegionEnd + mOuterFadeUpLen));

                  inDuckRegion = false;
               }
            }
         }

         pos += len;

         if (TotalProgress(
            (pos - start).as_double() /
            (end - start).as_double() /
            (GetNumWaveTracks() + 1)
         ))
         {
            cancel = true;
            break;
         }
      }

      // apply last duck fade, if any
      if (inDuckRegion)
      {
         double duckRegionEnd =
            mControlTrack->LongSamplesToTime(end - curSamplesPause);
         regions.push_back(AutoDuckRegion(
            duckRegionStart - mOuterFadeDownLen,
            duckRegionEnd + mOuterFadeUpLen));
      }
   }

   if (!cancel)
   {
      CopyInputTracks(); // Set up mOutputTracks.
      SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks.get());
      Track *iterTrack = iter.First();

      int trackNum = 0;

      while (iterTrack)
      {
         WaveTrack* t = (WaveTrack*)iterTrack;

         for (size_t i = 0; i < regions.size(); i++)
         {
            const AutoDuckRegion& region = regions[i];
            if (ApplyDuckFade(trackNum, t, region.t0, region.t1))
            {
               cancel = true;
               break;
            }
         }

         if (cancel)
            break;

         iterTrack = iter.Next();
         trackNum++;
      }
   }

   ReplaceProcessedTracks(!cancel);
   return !cancel;
}