1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   WaveClip.cpp
6 
7   ?? Dominic Mazzoni
8   ?? Markus Meyer
9 
10 *******************************************************************//**
11 
12 \class WaveClip
13 \brief This allows multiple clips to be a part of one WaveTrack.
14 
15 *//****************************************************************//**
16 
17 \class WaveCache
18 \brief Cache used with WaveClip to cache wave information (for drawing).
19 
20 *//*******************************************************************/
21 
22 #include "WaveClip.h"
23 
24 
25 
26 #include <math.h>
27 #include <vector>
28 #include <wx/log.h>
29 
30 #include "Sequence.h"
31 #include "Spectrum.h"
32 #include "Prefs.h"
33 #include "Envelope.h"
34 #include "Resample.h"
35 #include "WaveTrack.h"
36 #include "Profiler.h"
37 #include "InconsistencyException.h"
38 #include "UserException.h"
39 
40 #include "prefs/SpectrogramSettings.h"
41 #include "widgets/ProgressDialog.h"
42 
43 #ifdef _OPENMP
44 #include <omp.h>
45 #endif
46 
47 class WaveCache {
48 public:
WaveCache()49    WaveCache()
50       : dirty(-1)
51       , start(-1)
52       , pps(0)
53       , rate(-1)
54       , where(0)
55       , min(0)
56       , max(0)
57       , rms(0)
58       , bl(0)
59    {
60    }
61 
WaveCache(size_t len_,double pixelsPerSecond,double rate_,double t0,int dirty_)62    WaveCache(size_t len_, double pixelsPerSecond, double rate_, double t0, int dirty_)
63       : dirty(dirty_)
64       , len(len_)
65       , start(t0)
66       , pps(pixelsPerSecond)
67       , rate(rate_)
68       , where(1 + len)
69       , min(len)
70       , max(len)
71       , rms(len)
72       , bl(len)
73    {
74    }
75 
~WaveCache()76    ~WaveCache()
77    {
78    }
79 
80    int          dirty;
81    const size_t len { 0 }; // counts pixels, not samples
82    const double start;
83    const double pps;
84    const int    rate;
85    std::vector<sampleCount> where;
86    std::vector<float> min;
87    std::vector<float> max;
88    std::vector<float> rms;
89    std::vector<int> bl;
90 };
91 
ComputeSpectrumUsingRealFFTf(float * __restrict buffer,const FFTParam * hFFT,const float * __restrict window,size_t len,float * __restrict out)92 static void ComputeSpectrumUsingRealFFTf
93    (float * __restrict buffer, const FFTParam *hFFT,
94     const float * __restrict window, size_t len, float * __restrict out)
95 {
96    size_t i;
97    if(len > hFFT->Points * 2)
98       len = hFFT->Points * 2;
99    for(i = 0; i < len; i++)
100       buffer[i] *= window[i];
101    for( ; i < (hFFT->Points * 2); i++)
102       buffer[i] = 0; // zero pad as needed
103    RealFFTf(buffer, hFFT);
104    // Handle the (real-only) DC
105    float power = buffer[0] * buffer[0];
106    if(power <= 0)
107       out[0] = -160.0;
108    else
109       out[0] = 10.0 * log10f(power);
110    for(i = 1; i < hFFT->Points; i++) {
111       const int index = hFFT->BitReversed[i];
112       const float re = buffer[index], im = buffer[index + 1];
113       power = re * re + im * im;
114       if(power <= 0)
115          out[i] = -160.0;
116       else
117          out[i] = 10.0*log10f(power);
118    }
119 }
120 
WaveClip(const SampleBlockFactoryPtr & factory,sampleFormat format,int rate,int colourIndex)121 WaveClip::WaveClip(const SampleBlockFactoryPtr &factory,
122                    sampleFormat format, int rate, int colourIndex)
123 {
124    mRate = rate;
125    mColourIndex = colourIndex;
126    mSequence = std::make_unique<Sequence>(factory, format);
127 
128    mEnvelope = std::make_unique<Envelope>(true, 1e-7, 2.0, 1.0);
129 
130    mWaveCache = std::make_unique<WaveCache>();
131    mSpecCache = std::make_unique<SpecCache>();
132    mSpecPxCache = std::make_unique<SpecPxCache>(1);
133 }
134 
WaveClip(const WaveClip & orig,const SampleBlockFactoryPtr & factory,bool copyCutlines)135 WaveClip::WaveClip(const WaveClip& orig,
136                    const SampleBlockFactoryPtr &factory,
137                    bool copyCutlines)
138 {
139    // essentially a copy constructor - but you must pass in the
140    // current sample block factory, because we might be copying
141    // from one project to another
142 
143    mSequenceOffset = orig.mSequenceOffset;
144    mTrimLeft = orig.mTrimLeft;
145    mTrimRight = orig.mTrimRight;
146    mRate = orig.mRate;
147    mColourIndex = orig.mColourIndex;
148    mSequence = std::make_unique<Sequence>(*orig.mSequence, factory);
149 
150    mEnvelope = std::make_unique<Envelope>(*orig.mEnvelope);
151 
152    mWaveCache = std::make_unique<WaveCache>();
153    mSpecCache = std::make_unique<SpecCache>();
154    mSpecPxCache = std::make_unique<SpecPxCache>(1);
155 
156    mName = orig.mName;
157 
158    if ( copyCutlines )
159       for (const auto &clip: orig.mCutLines)
160          mCutLines.push_back
161             ( std::make_unique<WaveClip>( *clip, factory, true ) );
162 
163    mIsPlaceholder = orig.GetIsPlaceholder();
164 }
165 
WaveClip(const WaveClip & orig,const SampleBlockFactoryPtr & factory,bool copyCutlines,double t0,double t1)166 WaveClip::WaveClip(const WaveClip& orig,
167                    const SampleBlockFactoryPtr &factory,
168                    bool copyCutlines,
169                    double t0, double t1)
170 {
171    // Copy only a range of the other WaveClip
172 
173    mSequenceOffset = orig.mSequenceOffset;
174 
175    mRate = orig.mRate;
176    mColourIndex = orig.mColourIndex;
177 
178    mWaveCache = std::make_unique<WaveCache>();
179    mSpecCache = std::make_unique<SpecCache>();
180    mSpecPxCache = std::make_unique<SpecPxCache>(1);
181 
182    mIsPlaceholder = orig.GetIsPlaceholder();
183 
184    auto s0 = orig.TimeToSequenceSamples(t0);
185    auto s1 = orig.TimeToSequenceSamples(t1);
186 
187    mSequence = orig.mSequence->Copy(factory, s0, s1);
188 
189    mEnvelope = std::make_unique<Envelope>(
190       *orig.mEnvelope,
191       GetSequenceStartTime() + s0.as_double()/mRate,
192       GetSequenceStartTime() + s1.as_double()/mRate
193    );
194 
195    if ( copyCutlines )
196       // Copy cutline clips that fall in the range
197       for (const auto &ppClip : orig.mCutLines)
198       {
199          const WaveClip* clip = ppClip.get();
200          double cutlinePosition = orig.GetSequenceStartTime() + clip->GetSequenceStartTime();
201          if (cutlinePosition >= t0 && cutlinePosition <= t1)
202          {
203             auto newCutLine =
204                std::make_unique< WaveClip >( *clip, factory, true );
205             newCutLine->SetSequenceStartTime( cutlinePosition - t0 );
206             mCutLines.push_back(std::move(newCutLine));
207          }
208       }
209 }
210 
211 
~WaveClip()212 WaveClip::~WaveClip()
213 {
214 }
215 
GetSamples(samplePtr buffer,sampleFormat format,sampleCount start,size_t len,bool mayThrow) const216 bool WaveClip::GetSamples(samplePtr buffer, sampleFormat format,
217                    sampleCount start, size_t len, bool mayThrow) const
218 {
219    return mSequence->Get(buffer, format, start + TimeToSamples(mTrimLeft), len, mayThrow);
220 }
221 
222 /*! @excsafety{Strong} */
SetSamples(constSamplePtr buffer,sampleFormat format,sampleCount start,size_t len)223 void WaveClip::SetSamples(constSamplePtr buffer, sampleFormat format,
224                    sampleCount start, size_t len)
225 {
226    // use Strong-guarantee
227    mSequence->SetSamples(buffer, format, start + TimeToSamples(mTrimLeft), len);
228 
229    // use No-fail-guarantee
230    MarkChanged();
231 }
232 
GetSequenceBlockArray()233 BlockArray* WaveClip::GetSequenceBlockArray()
234 {
235    return &mSequence->GetBlockArray();
236 }
237 
GetSequenceBlockArray() const238 const BlockArray* WaveClip::GetSequenceBlockArray() const
239 {
240    return &mSequence->GetBlockArray();
241 }
242 
243 ///Delete the wave cache - force redraw.  Thread-safe
ClearWaveCache()244 void WaveClip::ClearWaveCache()
245 {
246    mWaveCache = std::make_unique<WaveCache>();
247 }
248 
249 namespace {
250 
251 inline
findCorrection(const std::vector<sampleCount> & oldWhere,size_t oldLen,size_t newLen,double t0,double rate,double samplesPerPixel,int & oldX0,double & correction)252 void findCorrection(const std::vector<sampleCount> &oldWhere, size_t oldLen,
253          size_t newLen,
254          double t0, double rate, double samplesPerPixel,
255          int &oldX0, double &correction)
256 {
257    // Mitigate the accumulation of location errors
258    // in copies of copies of ... of caches.
259    // Look at the loop that populates "where" below to understand this.
260 
261    // Find the sample position that is the origin in the old cache.
262    const double oldWhere0 = oldWhere[1].as_double() - samplesPerPixel;
263    const double oldWhereLast = oldWhere0 + oldLen * samplesPerPixel;
264    // Find the length in samples of the old cache.
265    const double denom = oldWhereLast - oldWhere0;
266 
267    // What sample would go in where[0] with no correction?
268    const double guessWhere0 = t0 * rate;
269 
270    if ( // Skip if old and NEW are disjoint:
271       oldWhereLast <= guessWhere0 ||
272       guessWhere0 + newLen * samplesPerPixel <= oldWhere0 ||
273       // Skip unless denom rounds off to at least 1.
274       denom < 0.5)
275    {
276       // The computation of oldX0 in the other branch
277       // may underflow and the assertion would be violated.
278       oldX0 =  oldLen;
279       correction = 0.0;
280    }
281    else
282    {
283       // What integer position in the old cache array does that map to?
284       // (even if it is out of bounds)
285       oldX0 = floor(0.5 + oldLen * (guessWhere0 - oldWhere0) / denom);
286       // What sample count would the old cache have put there?
287       const double where0 = oldWhere0 + double(oldX0) * samplesPerPixel;
288       // What correction is needed to align the NEW cache with the old?
289       const double correction0 = where0 - guessWhere0;
290       correction = std::max(-samplesPerPixel, std::min(samplesPerPixel, correction0));
291       wxASSERT(correction == correction0);
292    }
293 }
294 
295 inline void
fillWhere(std::vector<sampleCount> & where,size_t len,double bias,double correction,double t0,double rate,double samplesPerPixel)296 fillWhere(std::vector<sampleCount> &where, size_t len, double bias, double correction,
297           double t0, double rate, double samplesPerPixel)
298 {
299    // Be careful to make the first value non-negative
300    const double w0 = 0.5 + correction + bias + t0 * rate;
301    where[0] = sampleCount( std::max(0.0, floor(w0)) );
302    for (decltype(len) x = 1; x < len + 1; x++)
303       where[x] = sampleCount( floor(w0 + double(x) * samplesPerPixel) );
304 }
305 
306 }
307 
308 //
309 // Getting high-level data from the track for screen display and
310 // clipping calculations
311 //
312 
GetWaveDisplay(WaveDisplay & display,double t0,double pixelsPerSecond) const313 bool WaveClip::GetWaveDisplay(WaveDisplay &display, double t0,
314                                double pixelsPerSecond) const
315 {
316    t0 += GetTrimLeft();
317 
318    const bool allocated = (display.where != 0);
319 
320    const size_t numPixels = (int)display.width;
321 
322    size_t p0 = 0;         // least column requiring computation
323    size_t p1 = numPixels; // greatest column requiring computation, plus one
324 
325    float *min;
326    float *max;
327    float *rms;
328    int *bl;
329    std::vector<sampleCount> *pWhere;
330 
331    if (allocated) {
332       // assume ownWhere is filled.
333       min = &display.min[0];
334       max = &display.max[0];
335       rms = &display.rms[0];
336       bl = &display.bl[0];
337       pWhere = &display.ownWhere;
338    }
339    else {
340       const double tstep = 1.0 / pixelsPerSecond;
341       const double samplesPerPixel = mRate * tstep;
342 
343       // Make a tolerant comparison of the pps values in this wise:
344       // accumulated difference of times over the number of pixels is less than
345       // a sample period.
346       const bool ppsMatch = mWaveCache &&
347          (fabs(tstep - 1.0 / mWaveCache->pps) * numPixels < (1.0 / mRate));
348 
349       const bool match =
350          mWaveCache &&
351          ppsMatch &&
352          mWaveCache->len > 0 &&
353          mWaveCache->dirty == mDirty;
354 
355       if (match &&
356          mWaveCache->start == t0 &&
357          mWaveCache->len >= numPixels) {
358 
359          // Satisfy the request completely from the cache
360          display.min = &mWaveCache->min[0];
361          display.max = &mWaveCache->max[0];
362          display.rms = &mWaveCache->rms[0];
363          display.bl = &mWaveCache->bl[0];
364          display.where = &mWaveCache->where[0];
365          return true;
366       }
367 
368       std::unique_ptr<WaveCache> oldCache(std::move(mWaveCache));
369 
370       int oldX0 = 0;
371       double correction = 0.0;
372       size_t copyBegin = 0, copyEnd = 0;
373       if (match) {
374          findCorrection(oldCache->where, oldCache->len, numPixels,
375             t0, mRate, samplesPerPixel,
376             oldX0, correction);
377          // Remember our first pixel maps to oldX0 in the old cache,
378          // possibly out of bounds.
379          // For what range of pixels can data be copied?
380          copyBegin = std::min<size_t>(numPixels, std::max(0, -oldX0));
381          copyEnd = std::min<size_t>(numPixels, std::max(0,
382             (int)oldCache->len - oldX0
383          ));
384       }
385       if (!(copyEnd > copyBegin))
386          oldCache.reset(0);
387 
388       mWaveCache = std::make_unique<WaveCache>(numPixels, pixelsPerSecond, mRate, t0, mDirty);
389       min = &mWaveCache->min[0];
390       max = &mWaveCache->max[0];
391       rms = &mWaveCache->rms[0];
392       bl = &mWaveCache->bl[0];
393       pWhere = &mWaveCache->where;
394 
395       fillWhere(*pWhere, numPixels, 0.0, correction,
396          t0, mRate, samplesPerPixel);
397 
398       // The range of pixels we must fetch from the Sequence:
399       p0 = (copyBegin > 0) ? 0 : copyEnd;
400       p1 = (copyEnd >= numPixels) ? copyBegin : numPixels;
401 
402       // Optimization: if the old cache is good and overlaps
403       // with the current one, re-use as much of the cache as
404       // possible
405 
406       if (oldCache) {
407 
408          // Copy what we can from the old cache.
409          const int length = copyEnd - copyBegin;
410          const size_t sizeFloats = length * sizeof(float);
411          const int srcIdx = (int)copyBegin + oldX0;
412          memcpy(&min[copyBegin], &oldCache->min[srcIdx], sizeFloats);
413          memcpy(&max[copyBegin], &oldCache->max[srcIdx], sizeFloats);
414          memcpy(&rms[copyBegin], &oldCache->rms[srcIdx], sizeFloats);
415          memcpy(&bl[copyBegin], &oldCache->bl[srcIdx], length * sizeof(int));
416       }
417    }
418 
419    if (p1 > p0) {
420       // Cache was not used or did not satisfy the whole request
421       std::vector<sampleCount> &where = *pWhere;
422 
423       /* handle values in the append buffer */
424 
425       auto numSamples = mSequence->GetNumSamples();
426       auto a = p0;
427 
428       // Not all of the required columns might be in the sequence.
429       // Some might be in the append buffer.
430       for (; a < p1; ++a) {
431          if (where[a + 1] > numSamples)
432             break;
433       }
434 
435       // Handle the columns that land in the append buffer.
436       //compute the values that are outside the overlap from scratch.
437       if (a < p1) {
438          sampleFormat seqFormat = mSequence->GetSampleFormat();
439          bool didUpdate = false;
440          for(auto i = a; i < p1; i++) {
441             auto left = std::max(sampleCount{ 0 },
442                                  where[i] - numSamples);
443             auto right = std::min(sampleCount{ mAppendBufferLen },
444                                   where[i + 1] - numSamples);
445 
446             //wxCriticalSectionLocker locker(mAppendCriticalSection);
447 
448             if (right > left) {
449                Floats b;
450                float *pb{};
451                // left is nonnegative and at most mAppendBufferLen:
452                auto sLeft = left.as_size_t();
453                // The difference is at most mAppendBufferLen:
454                size_t len = ( right - left ).as_size_t();
455 
456                if (seqFormat == floatSample)
457                   pb = &((float *)mAppendBuffer.ptr())[sLeft];
458                else {
459                   b.reinit(len);
460                   pb = b.get();
461                   SamplesToFloats(
462                      mAppendBuffer.ptr() + sLeft * SAMPLE_SIZE(seqFormat),
463                      seqFormat, pb, len);
464                }
465 
466                float theMax, theMin, sumsq;
467                {
468                   const float val = pb[0];
469                   theMax = theMin = val;
470                   sumsq = val * val;
471                }
472                for(decltype(len) j = 1; j < len; j++) {
473                   const float val = pb[j];
474                   theMax = std::max(theMax, val);
475                   theMin = std::min(theMin, val);
476                   sumsq += val * val;
477                }
478 
479                min[i] = theMin;
480                max[i] = theMax;
481                rms[i] = (float)sqrt(sumsq / len);
482                bl[i] = 1; //for now just fake it.
483 
484                didUpdate=true;
485             }
486          }
487 
488          // Shrink the right end of the range to fetch from Sequence
489          if(didUpdate)
490             p1 = a;
491       }
492 
493       // Done with append buffer, now fetch the rest of the cache miss
494       // from the sequence
495       if (p1 > p0) {
496          if (!mSequence->GetWaveDisplay(&min[p0],
497                                         &max[p0],
498                                         &rms[p0],
499                                         &bl[p0],
500                                         p1-p0,
501                                         &where[p0]))
502          {
503             return false;
504          }
505       }
506    }
507 
508    if (!allocated) {
509       // Now report the results
510       display.min = min;
511       display.max = max;
512       display.rms = rms;
513       display.bl = bl;
514       display.where = &(*pWhere)[0];
515    }
516 
517    return true;
518 }
519 
520 namespace {
521 
ComputeSpectrogramGainFactors(size_t fftLen,double rate,int frequencyGain,std::vector<float> & gainFactors)522 void ComputeSpectrogramGainFactors
523    (size_t fftLen, double rate, int frequencyGain, std::vector<float> &gainFactors)
524 {
525    if (frequencyGain > 0) {
526       // Compute a frequency-dependent gain factor
527       // scaled such that 1000 Hz gets a gain of 0dB
528 
529       // This is the reciprocal of the bin number of 1000 Hz:
530       const double factor = ((double)rate / (double)fftLen) / 1000.0;
531 
532       auto half = fftLen / 2;
533       gainFactors.reserve(half);
534       // Don't take logarithm of zero!  Let bin 0 replicate the gain factor for bin 1.
535       gainFactors.push_back(frequencyGain*log10(factor));
536       for (decltype(half) x = 1; x < half; x++) {
537          gainFactors.push_back(frequencyGain*log10(factor * x));
538       }
539    }
540 }
541 
542 }
543 
Matches(int dirty_,double pixelsPerSecond,const SpectrogramSettings & settings,double rate) const544 bool SpecCache::Matches
545    (int dirty_, double pixelsPerSecond,
546     const SpectrogramSettings &settings, double rate) const
547 {
548    // Make a tolerant comparison of the pps values in this wise:
549    // accumulated difference of times over the number of pixels is less than
550    // a sample period.
551    const double tstep = 1.0 / pixelsPerSecond;
552    const bool ppsMatch =
553       (fabs(tstep - 1.0 / pps) * len < (1.0 / rate));
554 
555    return
556       ppsMatch &&
557       dirty == dirty_ &&
558       windowType == settings.windowType &&
559       windowSize == settings.WindowSize() &&
560       zeroPaddingFactor == settings.ZeroPaddingFactor() &&
561       frequencyGain == settings.frequencyGain &&
562       algorithm == settings.algorithm;
563 }
564 
CalculateOneSpectrum(const SpectrogramSettings & settings,WaveTrackCache & waveTrackCache,const int xx,const sampleCount numSamples,double offset,double rate,double pixelsPerSecond,int lowerBoundX,int upperBoundX,const std::vector<float> & gainFactors,float * __restrict scratch,float * __restrict out) const565 bool SpecCache::CalculateOneSpectrum
566    (const SpectrogramSettings &settings,
567     WaveTrackCache &waveTrackCache,
568     const int xx, const sampleCount numSamples,
569     double offset, double rate, double pixelsPerSecond,
570     int lowerBoundX, int upperBoundX,
571     const std::vector<float> &gainFactors,
572     float* __restrict scratch, float* __restrict out) const
573 {
574    bool result = false;
575    const bool reassignment =
576       (settings.algorithm == SpectrogramSettings::algReassignment);
577    const size_t windowSizeSetting = settings.WindowSize();
578 
579    sampleCount from;
580 
581    // xx may be for a column that is out of the visible bounds, but only
582    // when we are calculating reassignment contributions that may cross into
583    // the visible area.
584 
585    if (xx < 0)
586       from = sampleCount(
587          where[0].as_double() + xx * (rate / pixelsPerSecond)
588       );
589    else if (xx > (int)len)
590       from = sampleCount(
591          where[len].as_double() + (xx - len) * (rate / pixelsPerSecond)
592       );
593    else
594       from = where[xx];
595 
596    const bool autocorrelation =
597       settings.algorithm == SpectrogramSettings::algPitchEAC;
598    const size_t zeroPaddingFactorSetting = settings.ZeroPaddingFactor();
599    const size_t padding = (windowSizeSetting * (zeroPaddingFactorSetting - 1)) / 2;
600    const size_t fftLen = windowSizeSetting * zeroPaddingFactorSetting;
601    auto nBins = settings.NBins();
602 
603    if (from < 0 || from >= numSamples) {
604       if (xx >= 0 && xx < (int)len) {
605          // Pixel column is out of bounds of the clip!  Should not happen.
606          float *const results = &out[nBins * xx];
607          std::fill(results, results + nBins, 0.0f);
608       }
609    }
610    else {
611 
612 
613       // We can avoid copying memory when ComputeSpectrum is used below
614       bool copy = !autocorrelation || (padding > 0) || reassignment;
615       float *useBuffer = 0;
616       float *adj = scratch + padding;
617 
618       {
619          auto myLen = windowSizeSetting;
620          // Take a window of the track centered at this sample.
621          from -= windowSizeSetting >> 1;
622          if (from < 0) {
623             // Near the start of the clip, pad left with zeroes as needed.
624             // from is at least -windowSize / 2
625             for (auto ii = from; ii < 0; ++ii)
626                *adj++ = 0;
627             myLen += from.as_long_long(); // add a negative
628             from = 0;
629             copy = true;
630          }
631 
632          if (from + myLen >= numSamples) {
633             // Near the end of the clip, pad right with zeroes as needed.
634             // newlen is bounded by myLen:
635             auto newlen = ( numSamples - from ).as_size_t();
636             for (decltype(myLen) ii = newlen; ii < myLen; ++ii)
637                adj[ii] = 0;
638             myLen = newlen;
639             copy = true;
640          }
641 
642          if (myLen > 0) {
643             useBuffer = (float*)(waveTrackCache.GetFloats(
644                sampleCount(
645                   floor(0.5 + from.as_double() + offset * rate)
646                ),
647                myLen,
648                // Don't throw in this drawing operation
649                false)
650             );
651 
652             if (copy) {
653                if (useBuffer)
654                   memcpy(adj, useBuffer, myLen * sizeof(float));
655                else
656                   memset(adj, 0, myLen * sizeof(float));
657             }
658          }
659       }
660 
661       if (copy || !useBuffer)
662          useBuffer = scratch;
663 
664       if (autocorrelation) {
665          // not reassignment, xx is surely within bounds.
666          wxASSERT(xx >= 0);
667          float *const results = &out[nBins * xx];
668          // This function does not mutate useBuffer
669          ComputeSpectrum(useBuffer, windowSizeSetting, windowSizeSetting,
670             rate, results,
671             autocorrelation, settings.windowType);
672       }
673       else if (reassignment) {
674          static const double epsilon = 1e-16;
675          const auto hFFT = settings.hFFT.get();
676 
677          float *const scratch2 = scratch + fftLen;
678          std::copy(scratch, scratch2, scratch2);
679 
680          float *const scratch3 = scratch + 2 * fftLen;
681          std::copy(scratch, scratch2, scratch3);
682 
683          {
684             const float *const window = settings.window.get();
685             for (size_t ii = 0; ii < fftLen; ++ii)
686                scratch[ii] *= window[ii];
687             RealFFTf(scratch, hFFT);
688          }
689 
690          {
691             const float *const dWindow = settings.dWindow.get();
692             for (size_t ii = 0; ii < fftLen; ++ii)
693                scratch2[ii] *= dWindow[ii];
694             RealFFTf(scratch2, hFFT);
695          }
696 
697          {
698             const float *const tWindow = settings.tWindow.get();
699             for (size_t ii = 0; ii < fftLen; ++ii)
700                scratch3[ii] *= tWindow[ii];
701             RealFFTf(scratch3, hFFT);
702          }
703 
704          for (size_t ii = 0; ii < hFFT->Points; ++ii) {
705             const int index = hFFT->BitReversed[ii];
706             const float
707                denomRe = scratch[index],
708                denomIm = ii == 0 ? 0 : scratch[index + 1];
709             const double power = denomRe * denomRe + denomIm * denomIm;
710             if (power < epsilon)
711                // Avoid dividing by near-zero below
712                continue;
713 
714             double freqCorrection;
715             {
716                const double multiplier = -(fftLen / (2.0f * M_PI));
717                const float
718                   numRe = scratch2[index],
719                   numIm = ii == 0 ? 0 : scratch2[index + 1];
720                // Find complex quotient --
721                // Which means, multiply numerator by conjugate of denominator,
722                // then divide by norm squared of denominator --
723                // Then just take its imaginary part.
724                const double
725                   quotIm = (-numRe * denomIm + numIm * denomRe) / power;
726                // With appropriate multiplier, that becomes the correction of
727                // the frequency bin.
728                freqCorrection = multiplier * quotIm;
729             }
730 
731             const int bin = (int)((int)ii + freqCorrection + 0.5f);
732             // Must check if correction takes bin out of bounds, above or below!
733             // bin is signed!
734             if (bin >= 0 && bin < (int)hFFT->Points) {
735                double timeCorrection;
736                {
737                   const float
738                      numRe = scratch3[index],
739                      numIm = ii == 0 ? 0 : scratch3[index + 1];
740                   // Find another complex quotient --
741                   // Then just take its real part.
742                   // The result has sample interval as unit.
743                   timeCorrection =
744                      (numRe * denomRe + numIm * denomIm) / power;
745                }
746 
747                int correctedX = (floor(0.5 + xx + timeCorrection * pixelsPerSecond / rate));
748                if (correctedX >= lowerBoundX && correctedX < upperBoundX)
749                {
750                   result = true;
751 
752                   // This is non-negative, because bin and correctedX are
753                   auto ind = (int)nBins * correctedX + bin;
754 #ifdef _OPENMP
755                   // This assignment can race if index reaches into another thread's bins.
756                   // The probability of a race very low, so this carries little overhead,
757                   // about 5% slower vs allowing it to race.
758                   #pragma omp atomic update
759 #endif
760                   out[ind] += power;
761                }
762             }
763          }
764       }
765       else {
766          // not reassignment, xx is surely within bounds.
767          wxASSERT(xx >= 0);
768          float *const results = &out[nBins * xx];
769 
770          // Do the FFT.  Note that useBuffer is multiplied by the window,
771          // and the window is initialized with leading and trailing zeroes
772          // when there is padding.  Therefore we did not need to reinitialize
773          // the part of useBuffer in the padding zones.
774 
775          // This function mutates useBuffer
776          ComputeSpectrumUsingRealFFTf
777             (useBuffer, settings.hFFT.get(), settings.window.get(), fftLen, results);
778          if (!gainFactors.empty()) {
779             // Apply a frequency-dependent gain factor
780             for (size_t ii = 0; ii < nBins; ++ii)
781                results[ii] += gainFactors[ii];
782          }
783       }
784    }
785 
786    return result;
787 }
788 
Grow(size_t len_,const SpectrogramSettings & settings,double pixelsPerSecond,double start_)789 void SpecCache::Grow(size_t len_, const SpectrogramSettings& settings,
790                        double pixelsPerSecond, double start_)
791 {
792    settings.CacheWindows();
793 
794    // len columns, and so many rows, column-major.
795    // Don't take column literally -- this isn't pixel data yet, it's the
796    // raw data to be mapped onto the display.
797    freq.resize(len_ * settings.NBins());
798 
799    // Sample counts corresponding to the columns, and to one past the end.
800    where.resize(len_ + 1);
801 
802    len = len_;
803    algorithm = settings.algorithm;
804    pps = pixelsPerSecond;
805    start = start_;
806    windowType = settings.windowType;
807    windowSize = settings.WindowSize();
808    zeroPaddingFactor = settings.ZeroPaddingFactor();
809    frequencyGain = settings.frequencyGain;
810 }
811 
Populate(const SpectrogramSettings & settings,WaveTrackCache & waveTrackCache,int copyBegin,int copyEnd,size_t numPixels,sampleCount numSamples,double offset,double rate,double pixelsPerSecond)812 void SpecCache::Populate
813    (const SpectrogramSettings &settings, WaveTrackCache &waveTrackCache,
814     int copyBegin, int copyEnd, size_t numPixels,
815     sampleCount numSamples,
816     double offset, double rate, double pixelsPerSecond)
817 {
818    const int &frequencyGainSetting = settings.frequencyGain;
819    const size_t windowSizeSetting = settings.WindowSize();
820    const bool autocorrelation =
821       settings.algorithm == SpectrogramSettings::algPitchEAC;
822    const bool reassignment =
823       settings.algorithm == SpectrogramSettings::algReassignment;
824    const size_t zeroPaddingFactorSetting = settings.ZeroPaddingFactor();
825 
826    // FFT length may be longer than the window of samples that affect results
827    // because of zero padding done for increased frequency resolution
828    const size_t fftLen = windowSizeSetting * zeroPaddingFactorSetting;
829    const auto nBins = settings.NBins();
830 
831    const size_t bufferSize = fftLen;
832    const size_t scratchSize = reassignment ? 3 * bufferSize : bufferSize;
833    std::vector<float> scratch(scratchSize);
834 
835    std::vector<float> gainFactors;
836    if (!autocorrelation)
837       ComputeSpectrogramGainFactors(fftLen, rate, frequencyGainSetting, gainFactors);
838 
839    // Loop over the ranges before and after the copied portion and compute anew.
840    // One of the ranges may be empty.
841    for (int jj = 0; jj < 2; ++jj) {
842       const int lowerBoundX = jj == 0 ? 0 : copyEnd;
843       const int upperBoundX = jj == 0 ? copyBegin : numPixels;
844 
845 #ifdef _OPENMP
846       // Storage for mutable per-thread data.
847       // private clause ensures one copy per thread
848       struct ThreadLocalStorage {
849          ThreadLocalStorage()  { }
850          ~ThreadLocalStorage() { }
851 
852          void init(WaveTrackCache &waveTrackCache, size_t scratchSize) {
853             if (!cache) {
854                cache = std::make_unique<WaveTrackCache>(waveTrackCache.GetTrack());
855                scratch.resize(scratchSize);
856             }
857          }
858          std::unique_ptr<WaveTrackCache> cache;
859          std::vector<float> scratch;
860       } tls;
861 
862       #pragma omp parallel for private(tls)
863 #endif
864       for (auto xx = lowerBoundX; xx < upperBoundX; ++xx)
865       {
866 #ifdef _OPENMP
867          tls.init(waveTrackCache, scratchSize);
868          WaveTrackCache& cache = *tls.cache;
869          float* buffer = &tls.scratch[0];
870 #else
871          WaveTrackCache& cache = waveTrackCache;
872          float* buffer = &scratch[0];
873 #endif
874          CalculateOneSpectrum(
875             settings, cache, xx, numSamples,
876             offset, rate, pixelsPerSecond,
877             lowerBoundX, upperBoundX,
878             gainFactors, buffer, &freq[0]);
879       }
880 
881       if (reassignment) {
882          // Need to look beyond the edges of the range to accumulate more
883          // time reassignments.
884          // I'm not sure what's a good stopping criterion?
885          auto xx = lowerBoundX;
886          const double pixelsPerSample = pixelsPerSecond / rate;
887          const int limit = std::min((int)(0.5 + fftLen * pixelsPerSample), 100);
888          for (int ii = 0; ii < limit; ++ii)
889          {
890             const bool result =
891                CalculateOneSpectrum(
892                   settings, waveTrackCache, --xx, numSamples,
893                   offset, rate, pixelsPerSecond,
894                   lowerBoundX, upperBoundX,
895                   gainFactors, &scratch[0], &freq[0]);
896             if (!result)
897                break;
898          }
899 
900          xx = upperBoundX;
901          for (int ii = 0; ii < limit; ++ii)
902          {
903             const bool result =
904                CalculateOneSpectrum(
905                   settings, waveTrackCache, xx++, numSamples,
906                   offset, rate, pixelsPerSecond,
907                   lowerBoundX, upperBoundX,
908                   gainFactors, &scratch[0], &freq[0]);
909             if (!result)
910                break;
911          }
912 
913          // Now Convert to dB terms.  Do this only after accumulating
914          // power values, which may cross columns with the time correction.
915 #ifdef _OPENMP
916          #pragma omp parallel for
917 #endif
918          for (xx = lowerBoundX; xx < upperBoundX; ++xx) {
919             float *const results = &freq[nBins * xx];
920             for (size_t ii = 0; ii < nBins; ++ii) {
921                float &power = results[ii];
922                if (power <= 0)
923                   power = -160.0;
924                else
925                   power = 10.0*log10f(power);
926             }
927             if (!gainFactors.empty()) {
928                // Apply a frequency-dependent gain factor
929                for (size_t ii = 0; ii < nBins; ++ii)
930                   results[ii] += gainFactors[ii];
931             }
932          }
933       }
934    }
935 }
936 
GetSpectrogram(WaveTrackCache & waveTrackCache,const float * & spectrogram,const sampleCount * & where,size_t numPixels,double t0,double pixelsPerSecond) const937 bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache,
938                               const float *& spectrogram,
939                               const sampleCount *& where,
940                               size_t numPixels,
941                               double t0, double pixelsPerSecond) const
942 {
943    t0 += GetTrimLeft();
944 
945    const WaveTrack *const track = waveTrackCache.GetTrack().get();
946    const SpectrogramSettings &settings = track->GetSpectrogramSettings();
947 
948    //Trim offset comparison failure forces spectrogram cache rebuild
949    //and skip copying "unchanged" data after clip border was trimmed.
950    bool match =
951       mSpecCache &&
952       mSpecCache->leftTrim == GetTrimLeft() &&
953       mSpecCache->rightTrim == GetTrimRight() &&
954       mSpecCache->len > 0 &&
955       mSpecCache->Matches
956       (mDirty, pixelsPerSecond, settings, mRate);
957 
958    if (match &&
959        mSpecCache->start == t0 &&
960        mSpecCache->len >= numPixels) {
961       spectrogram = &mSpecCache->freq[0];
962       where = &mSpecCache->where[0];
963 
964       return false;  //hit cache completely
965    }
966 
967    // Caching is not implemented for reassignment, unless for
968    // a complete hit, because of the complications of time reassignment
969    if (settings.algorithm == SpectrogramSettings::algReassignment)
970       match = false;
971 
972    // Free the cache when it won't cause a major stutter.
973    // If the window size changed, we know there is nothing to be copied
974    // If we zoomed out, or resized, we can give up memory. But not too much -
975    // up to 2x extra is needed at the end of the clip to prevent stutter.
976    if (mSpecCache->freq.capacity() > 2.1 * mSpecCache->freq.size() ||
977        mSpecCache->windowSize*mSpecCache->zeroPaddingFactor <
978        settings.WindowSize()*settings.ZeroPaddingFactor())
979    {
980       match = false;
981       mSpecCache = std::make_unique<SpecCache>();
982    }
983 
984    const double tstep = 1.0 / pixelsPerSecond;
985    const double samplesPerPixel = mRate * tstep;
986 
987    int oldX0 = 0;
988    double correction = 0.0;
989 
990    int copyBegin = 0, copyEnd = 0;
991    if (match) {
992       findCorrection(mSpecCache->where, mSpecCache->len, numPixels,
993          t0, mRate, samplesPerPixel,
994          oldX0, correction);
995       // Remember our first pixel maps to oldX0 in the old cache,
996       // possibly out of bounds.
997       // For what range of pixels can data be copied?
998       copyBegin = std::min((int)numPixels, std::max(0, -oldX0));
999       copyEnd = std::min((int)numPixels, std::max(0,
1000          (int)mSpecCache->len - oldX0
1001       ));
1002    }
1003 
1004    // Resize the cache, keep the contents unchanged.
1005    mSpecCache->Grow(numPixels, settings, pixelsPerSecond, t0);
1006    mSpecCache->leftTrim = GetTrimLeft();
1007    mSpecCache->rightTrim = GetTrimRight();
1008    auto nBins = settings.NBins();
1009 
1010    // Optimization: if the old cache is good and overlaps
1011    // with the current one, re-use as much of the cache as
1012    // possible
1013    if (copyEnd > copyBegin)
1014    {
1015       // memmove is required since dst/src overlap
1016       memmove(&mSpecCache->freq[nBins * copyBegin],
1017                &mSpecCache->freq[nBins * (copyBegin + oldX0)],
1018                nBins * (copyEnd - copyBegin) * sizeof(float));
1019    }
1020 
1021    // Reassignment accumulates, so it needs a zeroed buffer
1022    if (settings.algorithm == SpectrogramSettings::algReassignment)
1023    {
1024       // The cache could theoretically copy from the middle, resulting
1025       // in two regions to update. This won't happen in zoom, since
1026       // old cache doesn't match. It won't happen in resize, since the
1027       // spectrum view is pinned to left side of window.
1028       wxASSERT(
1029          (copyBegin >= 0 && copyEnd == (int)numPixels) || // copied the end
1030          (copyBegin == 0 && copyEnd <= (int)numPixels)    // copied the beginning
1031       );
1032 
1033       int zeroBegin = copyBegin > 0 ? 0 : copyEnd-copyBegin;
1034       int zeroEnd = copyBegin > 0 ? copyBegin : numPixels;
1035 
1036       memset(&mSpecCache->freq[nBins*zeroBegin], 0, nBins*(zeroEnd-zeroBegin)*sizeof(float));
1037    }
1038 
1039    // purposely offset the display 1/2 sample to the left (as compared
1040    // to waveform display) to properly center response of the FFT
1041    fillWhere(mSpecCache->where, numPixels, 0.5, correction,
1042       t0, mRate, samplesPerPixel);
1043 
1044    mSpecCache->Populate
1045       (settings, waveTrackCache, copyBegin, copyEnd, numPixels,
1046        GetSequenceSamplesCount(),
1047        GetSequenceStartTime(), mRate, pixelsPerSecond);
1048 
1049    mSpecCache->dirty = mDirty;
1050    spectrogram = &mSpecCache->freq[0];
1051    where = &mSpecCache->where[0];
1052 
1053    return true;
1054 }
1055 
GetMinMax(double t0,double t1,bool mayThrow) const1056 std::pair<float, float> WaveClip::GetMinMax(
1057    double t0, double t1, bool mayThrow) const
1058 {
1059    if (t0 > t1) {
1060       if (mayThrow)
1061          THROW_INCONSISTENCY_EXCEPTION;
1062       return {
1063          0.f,  // harmless, but unused since Sequence::GetMinMax does not use these values
1064          0.f   // harmless, but unused since Sequence::GetMinMax does not use these values
1065       };
1066    }
1067 
1068    if (t0 == t1)
1069       return{ 0.f, 0.f };
1070 
1071    auto s0 = TimeToSequenceSamples(t0);
1072    auto s1 = TimeToSequenceSamples(t1);
1073 
1074    return mSequence->GetMinMax(s0, s1-s0, mayThrow);
1075 }
1076 
GetRMS(double t0,double t1,bool mayThrow) const1077 float WaveClip::GetRMS(double t0, double t1, bool mayThrow) const
1078 {
1079    if (t0 > t1) {
1080       if (mayThrow)
1081          THROW_INCONSISTENCY_EXCEPTION;
1082       return 0.f;
1083    }
1084 
1085    if (t0 == t1)
1086       return 0.f;
1087 
1088    auto s0 = TimeToSequenceSamples(t0);
1089    auto s1 = TimeToSequenceSamples(t1);
1090 
1091    return mSequence->GetRMS(s0, s1-s0, mayThrow);
1092 }
1093 
ConvertToSampleFormat(sampleFormat format,const std::function<void (size_t)> & progressReport)1094 void WaveClip::ConvertToSampleFormat(sampleFormat format,
1095    const std::function<void(size_t)> & progressReport)
1096 {
1097    // Note:  it is not necessary to do this recursively to cutlines.
1098    // They get converted as needed when they are expanded.
1099 
1100    auto bChanged = mSequence->ConvertToSampleFormat(format, progressReport);
1101    if (bChanged)
1102       MarkChanged();
1103 }
1104 
1105 /*! @excsafety{No-fail} */
UpdateEnvelopeTrackLen()1106 void WaveClip::UpdateEnvelopeTrackLen()
1107 {
1108    auto len = (mSequence->GetNumSamples().as_double()) / mRate;
1109    if (len != mEnvelope->GetTrackLen())
1110       mEnvelope->SetTrackLen(len, 1.0 / GetRate());
1111 }
1112 
1113 /*! @excsafety{Strong} */
AppendNewBlock(samplePtr buffer,sampleFormat format,size_t len)1114 std::shared_ptr<SampleBlock> WaveClip::AppendNewBlock(
1115    samplePtr buffer, sampleFormat format, size_t len)
1116 {
1117    return mSequence->AppendNewBlock( buffer, format, len );
1118 }
1119 
1120 /*! @excsafety{Strong} */
AppendSharedBlock(const std::shared_ptr<SampleBlock> & pBlock)1121 void WaveClip::AppendSharedBlock(const std::shared_ptr<SampleBlock> &pBlock)
1122 {
1123    mSequence->AppendSharedBlock( pBlock );
1124 }
1125 
1126 /*! @excsafety{Partial}
1127  -- Some prefix (maybe none) of the buffer is appended,
1128 and no content already flushed to disk is lost. */
Append(constSamplePtr buffer,sampleFormat format,size_t len,unsigned int stride)1129 bool WaveClip::Append(constSamplePtr buffer, sampleFormat format,
1130                       size_t len, unsigned int stride)
1131 {
1132    //wxLogDebug(wxT("Append: len=%lli"), (long long) len);
1133    bool result = false;
1134 
1135    auto maxBlockSize = mSequence->GetMaxBlockSize();
1136    auto blockSize = mSequence->GetIdealAppendLen();
1137    sampleFormat seqFormat = mSequence->GetSampleFormat();
1138 
1139    if (!mAppendBuffer.ptr())
1140       mAppendBuffer.Allocate(maxBlockSize, seqFormat);
1141 
1142    auto cleanup = finally( [&] {
1143       // use No-fail-guarantee
1144       UpdateEnvelopeTrackLen();
1145       MarkChanged();
1146    } );
1147 
1148    for(;;) {
1149       if (mAppendBufferLen >= blockSize) {
1150          // flush some previously appended contents
1151          // use Strong-guarantee
1152          mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize);
1153          result = true;
1154 
1155          // use No-fail-guarantee for rest of this "if"
1156          memmove(mAppendBuffer.ptr(),
1157                  mAppendBuffer.ptr() + blockSize * SAMPLE_SIZE(seqFormat),
1158                  (mAppendBufferLen - blockSize) * SAMPLE_SIZE(seqFormat));
1159          mAppendBufferLen -= blockSize;
1160          blockSize = mSequence->GetIdealAppendLen();
1161       }
1162 
1163       if (len == 0)
1164          break;
1165 
1166       // use No-fail-guarantee for rest of this "for"
1167       wxASSERT(mAppendBufferLen <= maxBlockSize);
1168       auto toCopy = std::min(len, maxBlockSize - mAppendBufferLen);
1169 
1170       CopySamples(buffer, format,
1171                   mAppendBuffer.ptr() + mAppendBufferLen * SAMPLE_SIZE(seqFormat),
1172                   seqFormat,
1173                   toCopy,
1174                   gHighQualityDither,
1175                   stride);
1176 
1177       mAppendBufferLen += toCopy;
1178       buffer += toCopy * SAMPLE_SIZE(format) * stride;
1179       len -= toCopy;
1180    }
1181 
1182    return result;
1183 }
1184 
1185 /*! @excsafety{Mixed} */
1186 /*! @excsafety{No-fail} -- The clip will be in a flushed state. */
1187 /*! @excsafety{Partial}
1188 -- Some initial portion (maybe none) of the append buffer of the
1189 clip gets appended; no previously flushed contents are lost. */
Flush()1190 void WaveClip::Flush()
1191 {
1192    //wxLogDebug(wxT("WaveClip::Flush"));
1193    //wxLogDebug(wxT("   mAppendBufferLen=%lli"), (long long) mAppendBufferLen);
1194    //wxLogDebug(wxT("   previous sample count %lli"), (long long) mSequence->GetNumSamples());
1195 
1196    if (mAppendBufferLen > 0) {
1197 
1198       auto cleanup = finally( [&] {
1199          // Blow away the append buffer even in case of failure.  May lose some
1200          // data but don't leave the track in an un-flushed state.
1201 
1202          // Use No-fail-guarantee of these steps.
1203          mAppendBufferLen = 0;
1204          UpdateEnvelopeTrackLen();
1205          MarkChanged();
1206       } );
1207 
1208       mSequence->Append(mAppendBuffer.ptr(), mSequence->GetSampleFormat(),
1209          mAppendBufferLen);
1210    }
1211 
1212    //wxLogDebug(wxT("now sample count %lli"), (long long) mSequence->GetNumSamples());
1213 }
1214 
HandleXMLTag(const std::string_view & tag,const AttributesList & attrs)1215 bool WaveClip::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
1216 {
1217    if (tag == "waveclip")
1218    {
1219       double dblValue;
1220       long longValue;
1221       for (auto pair : attrs)
1222       {
1223          auto attr = pair.first;
1224          auto value = pair.second;
1225 
1226          if (attr == "offset")
1227          {
1228             if (!value.TryGet(dblValue))
1229                return false;
1230             SetSequenceStartTime(dblValue);
1231          }
1232          else if (attr == "trimLeft")
1233          {
1234             if (!value.TryGet(dblValue))
1235                return false;
1236             SetTrimLeft(dblValue);
1237          }
1238          else if (attr == "trimRight")
1239          {
1240             if (!value.TryGet(dblValue))
1241                return false;
1242             SetTrimRight(dblValue);
1243          }
1244          else if (attr == "name")
1245          {
1246             if(value.IsStringView())
1247                SetName(value.ToWString());
1248          }
1249          else if (attr == "colorindex")
1250          {
1251             if (!value.TryGet(longValue))
1252                return false;
1253             SetColourIndex(longValue);
1254          }
1255       }
1256       return true;
1257    }
1258 
1259    return false;
1260 }
1261 
HandleXMLEndTag(const std::string_view & tag)1262 void WaveClip::HandleXMLEndTag(const std::string_view& tag)
1263 {
1264    if (tag == "waveclip")
1265       UpdateEnvelopeTrackLen();
1266 }
1267 
HandleXMLChild(const std::string_view & tag)1268 XMLTagHandler *WaveClip::HandleXMLChild(const std::string_view& tag)
1269 {
1270    if (tag == "sequence")
1271       return mSequence.get();
1272    else if (tag == "envelope")
1273       return mEnvelope.get();
1274    else if (tag == "waveclip")
1275    {
1276       // Nested wave clips are cut lines
1277       mCutLines.push_back(
1278          std::make_unique<WaveClip>(mSequence->GetFactory(),
1279             mSequence->GetSampleFormat(), mRate, 0 /*colourindex*/));
1280       return mCutLines.back().get();
1281    }
1282    else
1283       return NULL;
1284 }
1285 
WriteXML(XMLWriter & xmlFile) const1286 void WaveClip::WriteXML(XMLWriter &xmlFile) const
1287 // may throw
1288 {
1289    xmlFile.StartTag(wxT("waveclip"));
1290    xmlFile.WriteAttr(wxT("offset"), mSequenceOffset, 8);
1291    xmlFile.WriteAttr(wxT("trimLeft"), mTrimLeft, 8);
1292    xmlFile.WriteAttr(wxT("trimRight"), mTrimRight, 8);
1293    xmlFile.WriteAttr(wxT("name"), mName);
1294    xmlFile.WriteAttr(wxT("colorindex"), mColourIndex );
1295 
1296    mSequence->WriteXML(xmlFile);
1297    mEnvelope->WriteXML(xmlFile);
1298 
1299    for (const auto &clip: mCutLines)
1300       clip->WriteXML(xmlFile);
1301 
1302    xmlFile.EndTag(wxT("waveclip"));
1303 }
1304 
1305 /*! @excsafety{Strong} */
Paste(double t0,const WaveClip * other)1306 void WaveClip::Paste(double t0, const WaveClip* other)
1307 {
1308    const bool clipNeedsResampling = other->mRate != mRate;
1309    const bool clipNeedsNewFormat =
1310       other->mSequence->GetSampleFormat() != mSequence->GetSampleFormat();
1311    std::unique_ptr<WaveClip> newClip;
1312 
1313    t0 = std::clamp(t0, GetPlayStartTime(), GetPlayEndTime());
1314 
1315    //seems like edge cases cannot happen, see WaveTrack::PasteWaveTrack
1316    if (t0 == GetPlayStartTime())
1317    {
1318        ClearSequence(GetSequenceStartTime(), t0);
1319        SetTrimLeft(other->GetTrimLeft());
1320 
1321        auto copy = std::make_unique<WaveClip>(*other, mSequence->GetFactory(), true);
1322        copy->ClearSequence(copy->GetPlayEndTime(), copy->GetSequenceEndTime());
1323        newClip = std::move(copy);
1324    }
1325    else if (t0 == GetPlayEndTime())
1326    {
1327        ClearSequence(GetPlayEndTime(), GetSequenceEndTime());
1328        SetTrimRight(other->GetTrimRight());
1329 
1330        auto copy = std::make_unique<WaveClip>(*other, mSequence->GetFactory(), true);
1331        copy->ClearSequence(copy->GetSequenceStartTime(), copy->GetPlayStartTime());
1332        newClip = std::move(copy);
1333    }
1334    else
1335    {
1336        newClip = std::make_unique<WaveClip>(*other, mSequence->GetFactory(), true,
1337            other->GetPlayStartTime(), other->GetPlayEndTime());
1338    }
1339 
1340    if (clipNeedsResampling || clipNeedsNewFormat)
1341    {
1342       auto copy = std::make_unique<WaveClip>(*newClip.get(), mSequence->GetFactory(), true);
1343       if (clipNeedsResampling)
1344          // The other clip's rate is different from ours, so resample
1345           copy->Resample(mRate);
1346       if (clipNeedsNewFormat)
1347          // Force sample formats to match.
1348           copy->ConvertToSampleFormat(mSequence->GetSampleFormat());
1349       newClip = std::move(copy);
1350    }
1351 
1352    // Paste cut lines contained in pasted clip
1353    WaveClipHolders newCutlines;
1354    for (const auto &cutline: newClip->mCutLines)
1355    {
1356       auto cutlineCopy = std::make_unique<WaveClip>(*cutline, mSequence->GetFactory(),
1357          // Recursively copy cutlines of cutlines.  They don't need
1358          // their offsets adjusted.
1359          true);
1360       cutlineCopy->Offset(t0 - GetSequenceStartTime());
1361       newCutlines.push_back(std::move(cutlineCopy));
1362    }
1363 
1364    sampleCount s0 = TimeToSequenceSamples(t0);
1365 
1366    // Assume Strong-guarantee from Sequence::Paste
1367    mSequence->Paste(s0, newClip->mSequence.get());
1368 
1369    // Assume No-fail-guarantee in the remaining
1370    MarkChanged();
1371    auto sampleTime = 1.0 / GetRate();
1372    mEnvelope->PasteEnvelope
1373       (s0.as_double()/mRate + GetSequenceStartTime(), newClip->mEnvelope.get(), sampleTime);
1374    OffsetCutLines(t0, newClip->GetPlayEndTime() - newClip->GetPlayStartTime());
1375 
1376    for (auto &holder : newCutlines)
1377       mCutLines.push_back(std::move(holder));
1378 }
1379 
1380 /*! @excsafety{Strong} */
InsertSilence(double t,double len,double * pEnvelopeValue)1381 void WaveClip::InsertSilence( double t, double len, double *pEnvelopeValue )
1382 {
1383    if (t == GetPlayStartTime() && t > GetSequenceStartTime())
1384       ClearSequence(GetSequenceStartTime(), t);
1385    else if (t == GetPlayEndTime() && t < GetSequenceEndTime()) {
1386       ClearSequence(t, GetSequenceEndTime());
1387       SetTrimRight(.0);
1388    }
1389 
1390    auto s0 = TimeToSequenceSamples(t);
1391    auto slen = (sampleCount)floor(len * mRate + 0.5);
1392 
1393    // use Strong-guarantee
1394    GetSequence()->InsertSilence(s0, slen);
1395 
1396    // use No-fail-guarantee
1397    OffsetCutLines(t, len);
1398 
1399    const auto sampleTime = 1.0 / GetRate();
1400    auto pEnvelope = GetEnvelope();
1401    if ( pEnvelopeValue ) {
1402 
1403       // Preserve limit value at the end
1404       auto oldLen = pEnvelope->GetTrackLen();
1405       auto newLen = oldLen + len;
1406       pEnvelope->Cap( sampleTime );
1407 
1408       // Ramp across the silence to the given value
1409       pEnvelope->SetTrackLen( newLen, sampleTime );
1410       pEnvelope->InsertOrReplace
1411          ( pEnvelope->GetOffset() + newLen, *pEnvelopeValue );
1412    }
1413    else
1414       pEnvelope->InsertSpace( t, len );
1415 
1416    MarkChanged();
1417 }
1418 
1419 /*! @excsafety{Strong} */
AppendSilence(double len,double envelopeValue)1420 void WaveClip::AppendSilence( double len, double envelopeValue )
1421 {
1422    auto t = GetPlayEndTime();
1423    InsertSilence( t, len, &envelopeValue );
1424 }
1425 
1426 /*! @excsafety{Strong} */
Clear(double t0,double t1)1427 void WaveClip::Clear(double t0, double t1)
1428 {
1429     auto st0 = t0;
1430     auto st1 = t1;
1431     auto offset = .0;
1432     if (st0 <= GetPlayStartTime())
1433     {
1434         offset = (t0 - GetPlayStartTime()) + GetTrimLeft();
1435         st0 = GetSequenceStartTime();
1436 
1437         SetTrimLeft(.0);
1438     }
1439     if (st1 >= GetPlayEndTime())
1440     {
1441         st1 = GetSequenceEndTime();
1442         SetTrimRight(.0);
1443     }
1444     ClearSequence(st0, st1);
1445 
1446     if (offset != .0)
1447         Offset(offset);
1448 }
1449 
ClearLeft(double t)1450 void WaveClip::ClearLeft(double t)
1451 {
1452    if (t > GetPlayStartTime() && t < GetPlayEndTime())
1453    {
1454       ClearSequence(GetSequenceStartTime(), t);
1455       SetTrimLeft(.0);
1456       SetSequenceStartTime(t);
1457    }
1458 }
1459 
ClearRight(double t)1460 void WaveClip::ClearRight(double t)
1461 {
1462    if (t > GetPlayStartTime() && t < GetPlayEndTime())
1463    {
1464       ClearSequence(t, GetSequenceEndTime());
1465       SetTrimRight(.0);
1466    }
1467 }
1468 
ClearSequence(double t0,double t1)1469 void WaveClip::ClearSequence(double t0, double t1)
1470 {
1471     auto clip_t0 = std::max(t0, GetSequenceStartTime());
1472     auto clip_t1 = std::min(t1, GetSequenceEndTime());
1473 
1474     auto s0 = TimeToSequenceSamples(clip_t0);
1475     auto s1 = TimeToSequenceSamples(clip_t1);
1476 
1477     if (s0 != s1)
1478     {
1479         // use Strong-guarantee
1480         GetSequence()->Delete(s0, s1 - s0);
1481 
1482         // use No-fail-guarantee in the remaining
1483 
1484         // msmeyer
1485         //
1486         // Delete all cutlines that are within the given area, if any.
1487         //
1488         // Note that when cutlines are active, two functions are used:
1489         // Clear() and ClearAndAddCutLine(). ClearAndAddCutLine() is called
1490         // whenever the user directly calls a command that removes some audio, e.g.
1491         // "Cut" or "Clear" from the menu. This command takes care about recursive
1492         // preserving of cutlines within clips. Clear() is called when internal
1493         // operations want to remove audio. In the latter case, it is the right
1494         // thing to just remove all cutlines within the area.
1495         //
1496 
1497         // May DELETE as we iterate, so don't use range-for
1498         for (auto it = mCutLines.begin(); it != mCutLines.end();)
1499         {
1500             WaveClip* clip = it->get();
1501             double cutlinePosition = GetSequenceStartTime() + clip->GetSequenceStartTime();
1502             if (cutlinePosition >= t0 && cutlinePosition <= t1)
1503             {
1504                 // This cutline is within the area, DELETE it
1505                 it = mCutLines.erase(it);
1506             }
1507             else
1508             {
1509                 if (cutlinePosition >= t1)
1510                 {
1511                     clip->Offset(clip_t0 - clip_t1);
1512                 }
1513                 ++it;
1514             }
1515         }
1516 
1517         // Collapse envelope
1518         auto sampleTime = 1.0 / GetRate();
1519         GetEnvelope()->CollapseRegion(t0, t1, sampleTime);
1520     }
1521 
1522 
1523     MarkChanged();
1524 }
1525 
1526 /*! @excsafety{Weak}
1527 -- This WaveClip remains destructible in case of AudacityException.
1528 But some cutlines may be deleted */
ClearAndAddCutLine(double t0,double t1)1529 void WaveClip::ClearAndAddCutLine(double t0, double t1)
1530 {
1531    if (t0 > GetPlayEndTime() || t1 < GetPlayStartTime())
1532       return; // time out of bounds
1533 
1534    const double clip_t0 = std::max( t0, GetPlayStartTime() );
1535    const double clip_t1 = std::min( t1, GetPlayEndTime() );
1536 
1537    auto newClip = std::make_unique< WaveClip >
1538       (*this, mSequence->GetFactory(), true, clip_t0, clip_t1);
1539 
1540    newClip->SetSequenceStartTime( clip_t0 - GetSequenceStartTime() );
1541 
1542    // Remove cutlines from this clip that were in the selection, shift
1543    // left those that were after the selection
1544    // May DELETE as we iterate, so don't use range-for
1545    for (auto it = mCutLines.begin(); it != mCutLines.end();)
1546    {
1547       WaveClip* clip = it->get();
1548       double cutlinePosition = GetSequenceStartTime() + clip->GetSequenceStartTime();
1549       if (cutlinePosition >= t0 && cutlinePosition <= t1)
1550          it = mCutLines.erase(it);
1551       else
1552       {
1553          if (cutlinePosition >= t1)
1554          {
1555             clip->Offset(clip_t0 - clip_t1);
1556          }
1557          ++it;
1558       }
1559    }
1560 
1561    // Clear actual audio data
1562    auto s0 = TimeToSequenceSamples(t0);
1563    auto s1 = TimeToSequenceSamples(t1);
1564 
1565    // use Weak-guarantee
1566    GetSequence()->Delete(s0, s1-s0);
1567 
1568    // Collapse envelope
1569    auto sampleTime = 1.0 / GetRate();
1570    GetEnvelope()->CollapseRegion( t0, t1, sampleTime );
1571 
1572    MarkChanged();
1573 
1574    mCutLines.push_back(std::move(newClip));
1575 }
1576 
FindCutLine(double cutLinePosition,double * cutlineStart,double * cutlineEnd) const1577 bool WaveClip::FindCutLine(double cutLinePosition,
1578                            double* cutlineStart /* = NULL */,
1579                            double* cutlineEnd /* = NULL */) const
1580 {
1581    for (const auto &cutline: mCutLines)
1582    {
1583       if (fabs(GetSequenceStartTime() + cutline->GetSequenceStartTime() - cutLinePosition) < 0.0001)
1584       {
1585          if (cutlineStart)
1586             *cutlineStart = GetSequenceStartTime() + cutline->GetSequenceStartTime();
1587          if (cutlineEnd)
1588             *cutlineEnd = GetSequenceStartTime() + cutline->GetSequenceEndTime();
1589          return true;
1590       }
1591    }
1592 
1593    return false;
1594 }
1595 
1596 /*! @excsafety{Strong} */
ExpandCutLine(double cutLinePosition)1597 void WaveClip::ExpandCutLine(double cutLinePosition)
1598 {
1599    auto end = mCutLines.end();
1600    auto it = std::find_if( mCutLines.begin(), end,
1601       [&](const WaveClipHolder &cutline) {
1602          return fabs(GetSequenceStartTime() + cutline->GetSequenceStartTime() - cutLinePosition) < 0.0001;
1603       } );
1604 
1605    if ( it != end ) {
1606       auto cutline = it->get();
1607       // assume Strong-guarantee from Paste
1608 
1609       // Envelope::Paste takes offset into account, WaveClip::Paste doesn't!
1610       // Do this to get the right result:
1611       cutline->mEnvelope->SetOffset(0);
1612 
1613       Paste(GetSequenceStartTime()+cutline->GetSequenceStartTime(), cutline);
1614       // Now erase the cutline,
1615       // but be careful to find it again, because Paste above may
1616       // have modified the array of cutlines (if our cutline contained
1617       // another cutline!), invalidating the iterator we had.
1618       end = mCutLines.end();
1619       it = std::find_if(mCutLines.begin(), end,
1620          [=](const WaveClipHolder &p) { return p.get() == cutline; });
1621       if (it != end)
1622          mCutLines.erase(it); // deletes cutline!
1623       else {
1624          wxASSERT(false);
1625       }
1626    }
1627 }
1628 
RemoveCutLine(double cutLinePosition)1629 bool WaveClip::RemoveCutLine(double cutLinePosition)
1630 {
1631    for (auto it = mCutLines.begin(); it != mCutLines.end(); ++it)
1632    {
1633       const auto &cutline = *it;
1634       //std::numeric_limits<double>::epsilon() or (1.0 / static_cast<double>(mRate))?
1635       if (fabs(GetSequenceStartTime() + cutline->GetSequenceStartTime() - cutLinePosition) < 0.0001)
1636       {
1637          mCutLines.erase(it); // deletes cutline!
1638          return true;
1639       }
1640    }
1641 
1642    return false;
1643 }
1644 
1645 /*! @excsafety{No-fail} */
OffsetCutLines(double t0,double len)1646 void WaveClip::OffsetCutLines(double t0, double len)
1647 {
1648    for (const auto &cutLine : mCutLines)
1649    {
1650       if (GetSequenceStartTime() + cutLine->GetSequenceStartTime() >= t0)
1651          cutLine->Offset(len);
1652    }
1653 }
1654 
CloseLock()1655 void WaveClip::CloseLock()
1656 {
1657    GetSequence()->CloseLock();
1658    for (const auto &cutline: mCutLines)
1659       cutline->CloseLock();
1660 }
1661 
SetRate(int rate)1662 void WaveClip::SetRate(int rate)
1663 {
1664    mRate = rate;
1665    auto newLength = mSequence->GetNumSamples().as_double() / mRate;
1666    mEnvelope->RescaleTimes( newLength );
1667    MarkChanged();
1668 }
1669 
1670 /*! @excsafety{Strong} */
Resample(int rate,ProgressDialog * progress)1671 void WaveClip::Resample(int rate, ProgressDialog *progress)
1672 {
1673    // Note:  it is not necessary to do this recursively to cutlines.
1674    // They get resampled as needed when they are expanded.
1675 
1676    if (rate == mRate)
1677       return; // Nothing to do
1678 
1679    double factor = (double)rate / (double)mRate;
1680    ::Resample resample(true, factor, factor); // constant rate resampling
1681 
1682    const size_t bufsize = 65536;
1683    Floats inBuffer{ bufsize };
1684    Floats outBuffer{ bufsize };
1685    sampleCount pos = 0;
1686    bool error = false;
1687    int outGenerated = 0;
1688    auto numSamples = mSequence->GetNumSamples();
1689 
1690    auto newSequence =
1691       std::make_unique<Sequence>(mSequence->GetFactory(), mSequence->GetSampleFormat());
1692 
1693    /**
1694     * We want to keep going as long as we have something to feed the resampler
1695     * with OR as long as the resampler spews out samples (which could continue
1696     * for a few iterations after we stop feeding it)
1697     */
1698    while (pos < numSamples || outGenerated > 0)
1699    {
1700       const auto inLen = limitSampleBufferSize( bufsize, numSamples - pos );
1701 
1702       bool isLast = ((pos + inLen) == numSamples);
1703 
1704       if (!mSequence->Get((samplePtr)inBuffer.get(), floatSample, pos, inLen, true))
1705       {
1706          error = true;
1707          break;
1708       }
1709 
1710       const auto results = resample.Process(factor, inBuffer.get(), inLen, isLast,
1711                                             outBuffer.get(), bufsize);
1712       outGenerated = results.second;
1713 
1714       pos += results.first;
1715 
1716       if (outGenerated < 0)
1717       {
1718          error = true;
1719          break;
1720       }
1721 
1722       newSequence->Append((samplePtr)outBuffer.get(), floatSample,
1723                           outGenerated);
1724 
1725       if (progress)
1726       {
1727          auto updateResult = progress->Update(
1728             pos.as_long_long(),
1729             numSamples.as_long_long()
1730          );
1731          error = (updateResult != ProgressResult::Success);
1732          if (error)
1733             throw UserException{};
1734       }
1735    }
1736 
1737    if (error)
1738       throw SimpleMessageBoxException{
1739          ExceptionType::Internal,
1740          XO("Resampling failed."),
1741          XO("Warning"),
1742          "Error:_Resampling"
1743       };
1744    else
1745    {
1746       // Use No-fail-guarantee in these steps
1747 
1748       // Invalidate wave display cache
1749       mWaveCache = std::make_unique<WaveCache>();
1750       // Invalidate the spectrum display cache
1751       mSpecCache = std::make_unique<SpecCache>();
1752 
1753       mSequence = std::move(newSequence);
1754       mRate = rate;
1755    }
1756 }
1757 
1758 // Used by commands which interact with clips using the keyboard.
1759 // When two clips are immediately next to each other, the GetPlayEndTime()
1760 // of the first clip and the GetPlayStartTime() of the second clip may not
1761 // be exactly equal due to rounding errors.
SharesBoundaryWithNextClip(const WaveClip * next) const1762 bool WaveClip::SharesBoundaryWithNextClip(const WaveClip* next) const
1763 {
1764    double endThis = GetRate() * GetPlayStartTime() + GetPlaySamplesCount().as_double();
1765    double startNext = next->GetRate() * next->GetPlayStartTime();
1766 
1767    // given that a double has about 15 significant digits, using a criterion
1768    // of half a sample should be safe in all normal usage.
1769    return fabs(startNext - endThis) < 0.5;
1770 }
1771 
SetName(const wxString & name)1772 void WaveClip::SetName(const wxString& name)
1773 {
1774    mName = name;
1775 }
1776 
GetName() const1777 const wxString& WaveClip::GetName() const
1778 {
1779    return mName;
1780 }
1781 
TimeToSamples(double time) const1782 sampleCount WaveClip::TimeToSamples(double time) const noexcept
1783 {
1784     return sampleCount(floor(time * mRate + 0.5));
1785 }
1786 
SamplesToTime(sampleCount s) const1787 double WaveClip::SamplesToTime(sampleCount s) const noexcept
1788 {
1789     return s.as_double() / mRate;
1790 }
1791 
SetSilence(sampleCount offset,sampleCount length)1792 void WaveClip::SetSilence(sampleCount offset, sampleCount length)
1793 {
1794     GetSequence()->SetSilence(TimeToSamples(GetTrimLeft()) + offset, length);
1795     MarkChanged();
1796 }
1797 
GetSequenceSamplesCount() const1798 sampleCount WaveClip::GetSequenceSamplesCount() const
1799 {
1800     return mSequence->GetNumSamples();
1801 }
1802 
GetPlayStartTime() const1803 double WaveClip::GetPlayStartTime() const noexcept
1804 {
1805     return mSequenceOffset + SamplesToTime(TimeToSamples(mTrimLeft));
1806 }
1807 
SetPlayStartTime(double time)1808 void WaveClip::SetPlayStartTime(double time)
1809 {
1810     SetSequenceStartTime(time - mTrimLeft);
1811 }
1812 
GetPlayEndTime() const1813 double WaveClip::GetPlayEndTime() const
1814 {
1815     auto numSamples = mSequence->GetNumSamples();
1816 
1817     double maxLen = GetSequenceStartTime() + ((numSamples + mAppendBufferLen).as_double()) / mRate
1818        - SamplesToTime(TimeToSamples(mTrimRight));
1819     // JS: calculated value is not the length;
1820     // it is a maximum value and can be negative; no clipping to 0
1821 
1822     return maxLen;
1823 }
1824 
GetPlayStartSample() const1825 sampleCount WaveClip::GetPlayStartSample() const
1826 {
1827     return TimeToSamples(GetPlayStartTime());
1828 }
1829 
GetPlayEndSample() const1830 sampleCount WaveClip::GetPlayEndSample() const
1831 {
1832     return GetPlayStartSample() + GetPlaySamplesCount();
1833 }
1834 
GetPlaySamplesCount() const1835 sampleCount WaveClip::GetPlaySamplesCount() const
1836 {
1837     return mSequence->GetNumSamples()
1838        - TimeToSamples(mTrimRight) - TimeToSamples(mTrimLeft);
1839 }
1840 
SetTrimLeft(double trim)1841 void WaveClip::SetTrimLeft(double trim)
1842 {
1843     mTrimLeft = std::max(.0, trim);
1844 }
1845 
GetTrimLeft() const1846 double WaveClip::GetTrimLeft() const noexcept
1847 {
1848     return mTrimLeft;
1849 }
1850 
SetTrimRight(double trim)1851 void WaveClip::SetTrimRight(double trim)
1852 {
1853     mTrimRight = std::max(.0, trim);
1854 }
1855 
GetTrimRight() const1856 double WaveClip::GetTrimRight() const noexcept
1857 {
1858     return mTrimRight;
1859 }
1860 
TrimLeft(double deltaTime)1861 void WaveClip::TrimLeft(double deltaTime)
1862 {
1863     mTrimLeft += deltaTime;
1864 }
1865 
TrimRight(double deltaTime)1866 void WaveClip::TrimRight(double deltaTime)
1867 {
1868     mTrimRight += deltaTime;
1869 }
1870 
TrimLeftTo(double to)1871 void WaveClip::TrimLeftTo(double to)
1872 {
1873     mTrimLeft = std::clamp(to, GetSequenceStartTime(), GetPlayEndTime()) - GetSequenceStartTime();
1874 }
1875 
TrimRightTo(double to)1876 void WaveClip::TrimRightTo(double to)
1877 {
1878     mTrimRight = GetSequenceEndTime() - std::clamp(to, GetPlayStartTime(), GetSequenceEndTime());
1879 }
1880 
GetSequenceStartTime() const1881 double WaveClip::GetSequenceStartTime() const noexcept
1882 {
1883     // JS: mSequenceOffset is the minimum value and it is returned; no clipping to 0
1884     return mSequenceOffset;
1885 }
1886 
SetSequenceStartTime(double startTime)1887 void WaveClip::SetSequenceStartTime(double startTime)
1888 {
1889     mSequenceOffset = startTime;
1890     mEnvelope->SetOffset(startTime);
1891 }
1892 
GetSequenceEndTime() const1893 double WaveClip::GetSequenceEndTime() const
1894 {
1895     auto numSamples = mSequence->GetNumSamples();
1896 
1897     double maxLen = GetSequenceStartTime() + (numSamples + mAppendBufferLen).as_double() / mRate;
1898     // JS: calculated value is not the length;
1899     // it is a maximum value and can be negative; no clipping to 0
1900 
1901     return maxLen;
1902 }
1903 
GetSequenceStartSample() const1904 sampleCount WaveClip::GetSequenceStartSample() const
1905 {
1906     return TimeToSamples(mSequenceOffset);
1907 }
1908 
GetSequenceEndSample() const1909 sampleCount WaveClip::GetSequenceEndSample() const
1910 {
1911     return GetSequenceStartSample() + mSequence->GetNumSamples();
1912 }
1913 
Offset(double delta)1914 void WaveClip::Offset(double delta) noexcept
1915 {
1916     SetSequenceStartTime(GetSequenceStartTime() + delta);
1917 }
1918 
1919 // Bug 2288 allowed overlapping clips.
1920 // This was a classic fencepost error.
1921 // We are within the clip if start < t <= end.
1922 // Note that BeforeClip and AfterClip must be consistent
1923 // with this definition.
WithinPlayRegion(double t) const1924 bool WaveClip::WithinPlayRegion(double t) const
1925 {
1926     auto ts = TimeToSamples(t);
1927     return ts > GetPlayStartSample() && ts < GetPlayEndSample() + mAppendBufferLen;
1928 }
1929 
BeforePlayStartTime(double t) const1930 bool WaveClip::BeforePlayStartTime(double t) const
1931 {
1932     auto ts = TimeToSamples(t);
1933     return ts <= GetPlayStartSample();
1934 }
1935 
AfterPlayEndTime(double t) const1936 bool WaveClip::AfterPlayEndTime(double t) const
1937 {
1938     auto ts = TimeToSamples(t);
1939     return ts >= GetPlayEndSample() + mAppendBufferLen;
1940 }
1941 
TimeToSequenceSamples(double t) const1942 sampleCount WaveClip::TimeToSequenceSamples(double t) const
1943 {
1944     if (t < GetSequenceStartTime())
1945         return 0;
1946     else if (t > GetSequenceEndTime())
1947         return mSequence->GetNumSamples();
1948     return TimeToSamples(t - GetSequenceStartTime());
1949 }
1950 
ToSequenceSamples(sampleCount s) const1951 sampleCount WaveClip::ToSequenceSamples(sampleCount s) const
1952 {
1953     return s - GetSequenceStartSample();
1954 }
1955