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