1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   WaveTrack.cpp
6 
7   Dominic Mazzoni
8 
9 *******************************************************************//**
10 
11 \class WaveTrack
12 \brief A Track that contains audio waveform data.
13 
14 *//****************************************************************//**
15 
16 \class WaveTrack::Location
17 \brief Used only by WaveTrack, a special way to hold location that
18 can accommodate merged regions.
19 
20 *//****************************************************************/
21 
22 /*!
23 @class WaveTrackFactory
24 @brief Used to create or clone a WaveTrack, with appropriate context
25 from the project that will own the track.
26 */
27 
28 
29 #include "WaveTrack.h"
30 
31 
32 
33 #include "WaveClip.h"
34 
35 #include <wx/defs.h>
36 #include <wx/intl.h>
37 #include <wx/debug.h>
38 #include <wx/log.h>
39 
40 #include <float.h>
41 #include <math.h>
42 #include <algorithm>
43 #include <optional>
44 
45 #include "float_cast.h"
46 
47 #include "Envelope.h"
48 #include "Sequence.h"
49 
50 #include "Project.h"
51 #include "ProjectRate.h"
52 
53 #include "Prefs.h"
54 
55 #include "effects/TimeWarper.h"
56 #include "QualitySettings.h"
57 #include "prefs/SpectrogramSettings.h"
58 #include "prefs/TracksPrefs.h"
59 #include "prefs/TracksBehaviorsPrefs.h"
60 #include "prefs/WaveformSettings.h"
61 
62 #include "InconsistencyException.h"
63 
64 #include "tracks/ui/TrackView.h"
65 #include "tracks/ui/TrackControls.h"
66 
67 #include "ProjectFormatExtensionsRegistry.h"
68 
69 using std::max;
70 
71 namespace {
72 
AreAligned(const WaveClipPointers & a,const WaveClipPointers & b)73 bool AreAligned(const WaveClipPointers& a, const WaveClipPointers& b)
74 {
75    if (a.size() != b.size())
76       return false;
77 
78    const auto compare = [](const WaveClip* a, const WaveClip* b) {
79       // clips are aligned if both sequence start/end
80       // points and play start/end points of the first clip match
81       // the corresponding points of the other clip
82       return a->GetPlayStartTime() == b->GetPlayStartTime() &&
83          a->GetSequenceStartTime() == b->GetSequenceStartTime() &&
84          a->GetPlayEndTime() == b->GetPlayEndTime() &&
85          a->GetSequenceEndTime() == b->GetSequenceEndTime();
86    };
87 
88    return std::mismatch(a.begin(), a.end(), b.begin(), compare).first == a.end();
89 }
90 
91 //Handles possible future file values
ToLinkType(int value)92 Track::LinkType ToLinkType(int value)
93 {
94    if (value < 0)
95       return Track::LinkType::None;
96    else if (value > 3)
97       return Track::LinkType::Group;
98    return static_cast<Track::LinkType>(value);
99 }
100 
101 }
102 
103 static ProjectFileIORegistry::ObjectReaderEntry readerEntry{
104    "wavetrack",
__anon8aaf33e20302( )105    []( AudacityProject &project ){
106       auto &trackFactory = WaveTrackFactory::Get( project );
107       auto &tracks = TrackList::Get( project );
108       auto result = tracks.Add(trackFactory.NewWaveTrack());
109       TrackView::Get( *result );
110       TrackControls::Get( *result );
111       return result;
112    }
113 };
114 
DuplicateWaveTrack(const WaveTrack & orig)115 WaveTrack::Holder WaveTrackFactory::DuplicateWaveTrack(const WaveTrack &orig)
116 {
117    auto waveTrack = std::static_pointer_cast<WaveTrack>( orig.Duplicate() );
118 
119    return waveTrack;
120 }
121 
122 
NewWaveTrack(sampleFormat format,double rate)123 WaveTrack::Holder WaveTrackFactory::NewWaveTrack(sampleFormat format, double rate)
124 {
125    if (format == (sampleFormat)0)
126       format = QualitySettings::SampleFormatChoice();
127    if (rate == 0)
128       rate = mRate.GetRate();
129 
130    auto waveTrack = std::make_shared<WaveTrack> ( mpFactory, format, rate );
131 
132    return waveTrack;
133 }
134 
WaveTrack(const SampleBlockFactoryPtr & pFactory,sampleFormat format,double rate)135 WaveTrack::WaveTrack( const SampleBlockFactoryPtr &pFactory,
136    sampleFormat format, double rate )
137    : PlayableTrack()
138    , mpFactory(pFactory)
139 {
140    mLegacyProjectFileOffset = 0;
141 
142    mFormat = format;
143    mRate = (int) rate;
144    mGain = 1.0;
145    mPan = 0.0;
146    mOldGain[0] = 0.0;
147    mOldGain[1] = 0.0;
148    mWaveColorIndex = 0;
149    SetDefaultName(TracksPrefs::GetDefaultAudioTrackNamePreference());
150    SetName(GetDefaultName());
151    mDisplayMin = -1.0;
152    mDisplayMax = 1.0;
153    mSpectrumMin = mSpectrumMax = -1; // so values will default to settings
154    mLastScaleType = -1;
155    mLastdBRange = -1;
156 }
157 
WaveTrack(const WaveTrack & orig)158 WaveTrack::WaveTrack(const WaveTrack &orig):
159    PlayableTrack(orig)
160    , mpFactory( orig.mpFactory )
161    , mpSpectrumSettings(orig.mpSpectrumSettings
162       ? std::make_unique<SpectrogramSettings>(*orig.mpSpectrumSettings)
163       : nullptr
164    )
165    , mpWaveformSettings(orig.mpWaveformSettings
166       ? std::make_unique<WaveformSettings>(*orig.mpWaveformSettings)
167       : nullptr
168    )
169 {
170    mLastScaleType = -1;
171    mLastdBRange = -1;
172 
173    mLegacyProjectFileOffset = 0;
174 
175    Init(orig);
176 
177    for (const auto &clip : orig.mClips)
178       mClips.push_back
179          ( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
180 }
181 
182 // Copy the track metadata but not the contents.
Init(const WaveTrack & orig)183 void WaveTrack::Init(const WaveTrack &orig)
184 {
185    PlayableTrack::Init(orig);
186    mpFactory = orig.mpFactory;
187 
188    mFormat = orig.mFormat;
189    mWaveColorIndex = orig.mWaveColorIndex;
190    mRate = orig.mRate;
191    mGain = orig.mGain;
192    mPan = orig.mPan;
193    mOldGain[0] = 0.0;
194    mOldGain[1] = 0.0;
195    SetDefaultName(orig.GetDefaultName());
196    SetName(orig.GetName());
197    mDisplayMin = orig.mDisplayMin;
198    mDisplayMax = orig.mDisplayMax;
199    mSpectrumMin = orig.mSpectrumMin;
200    mSpectrumMax = orig.mSpectrumMax;
201    mDisplayLocationsCache.clear();
202 }
203 
Reinit(const WaveTrack & orig)204 void WaveTrack::Reinit(const WaveTrack &orig)
205 {
206    Init(orig);
207 
208    {
209       auto &settings = orig.mpSpectrumSettings;
210       if (settings)
211          mpSpectrumSettings = std::make_unique<SpectrogramSettings>(*settings);
212       else
213          mpSpectrumSettings.reset();
214    }
215 
216    {
217       auto &settings = orig.mpWaveformSettings;
218       if (settings)
219          mpWaveformSettings = std::make_unique<WaveformSettings>(*settings);
220       else
221          mpWaveformSettings.reset();
222    }
223 }
224 
Merge(const Track & orig)225 void WaveTrack::Merge(const Track &orig)
226 {
227    orig.TypeSwitch( [&](const WaveTrack *pwt) {
228       const WaveTrack &wt = *pwt;
229       mGain    = wt.mGain;
230       mPan     = wt.mPan;
231       mDisplayMin = wt.mDisplayMin;
232       mDisplayMax = wt.mDisplayMax;
233       SetSpectrogramSettings(wt.mpSpectrumSettings
234          ? std::make_unique<SpectrogramSettings>(*wt.mpSpectrumSettings) : nullptr);
235       SetWaveformSettings
236          (wt.mpWaveformSettings ? std::make_unique<WaveformSettings>(*wt.mpWaveformSettings) : nullptr);
237    });
238    PlayableTrack::Merge(orig);
239 }
240 
~WaveTrack()241 WaveTrack::~WaveTrack()
242 {
243 }
244 
GetOffset() const245 double WaveTrack::GetOffset() const
246 {
247    return GetStartTime();
248 }
249 
250 /*! @excsafety{No-fail} */
SetOffset(double o)251 void WaveTrack::SetOffset(double o)
252 {
253    double delta = o - GetOffset();
254 
255    for (const auto &clip : mClips)
256       // assume No-fail-guarantee
257       clip->Offset(delta);
258 
259    mOffset = o;
260 }
261 
GetChannelIgnoringPan() const262 auto WaveTrack::GetChannelIgnoringPan() const -> ChannelType {
263    return mChannel;
264 }
265 
GetChannel() const266 auto WaveTrack::GetChannel() const -> ChannelType
267 {
268    if( mChannel != Track::MonoChannel )
269       return mChannel;
270    auto pan = GetPan();
271    if( pan < -0.99 )
272       return Track::LeftChannel;
273    if( pan >  0.99 )
274       return Track::RightChannel;
275    return mChannel;
276 }
277 
SetPanFromChannelType()278 void WaveTrack::SetPanFromChannelType()
279 {
280    if( mChannel == Track::LeftChannel )
281       SetPan( -1.0f );
282    else if( mChannel == Track::RightChannel )
283       SetPan( 1.0f );
284 }
285 
LinkConsistencyCheck()286 bool WaveTrack::LinkConsistencyCheck()
287 {
288    auto err = PlayableTrack::LinkConsistencyCheck();
289 
290    auto linkType = GetLinkType();
291    if (static_cast<int>(linkType) == 1 || //Comes from old audacity version
292        linkType == LinkType::Aligned)
293    {
294       auto next = dynamic_cast<WaveTrack*>(*std::next(GetOwner()->Find(this)));
295       if (next == nullptr)
296       {
297          //next track is not a wave track, fix and report error
298           wxLogWarning(
299              wxT("Right track %s is expected to be a WaveTrack.\n Removing link from left wave track %s."),
300              next->GetName(), GetName());
301          SetLinkType(LinkType::None);
302          SetChannel(MonoChannel);
303          err = true;
304       }
305       else
306       {
307          auto newLinkType = AreAligned(SortedClipArray(), next->SortedClipArray())
308             ? LinkType::Aligned : LinkType::Group;
309          //not an error
310          if (newLinkType != linkType)
311             SetLinkType(newLinkType);
312       }
313    }
314    return !err;
315 }
316 
317 
SetLastScaleType() const318 void WaveTrack::SetLastScaleType() const
319 {
320    mLastScaleType = GetWaveformSettings().scaleType;
321 }
322 
SetLastdBRange() const323 void WaveTrack::SetLastdBRange() const
324 {
325    mLastdBRange = GetWaveformSettings().dBRange;
326 }
327 
GetDisplayBounds(float * min,float * max) const328 void WaveTrack::GetDisplayBounds(float *min, float *max) const
329 {
330    *min = mDisplayMin;
331    *max = mDisplayMax;
332 }
333 
SetDisplayBounds(float min,float max) const334 void WaveTrack::SetDisplayBounds(float min, float max) const
335 {
336    mDisplayMin = min;
337    mDisplayMax = max;
338 }
339 
GetSpectrumBounds(float * min,float * max) const340 void WaveTrack::GetSpectrumBounds(float *min, float *max) const
341 {
342    const double rate = GetRate();
343 
344    const SpectrogramSettings &settings = GetSpectrogramSettings();
345    const SpectrogramSettings::ScaleType type = settings.scaleType;
346 
347    const float top = (rate / 2.);
348 
349    float bottom;
350    if (type == SpectrogramSettings::stLinear)
351       bottom = 0.0f;
352    else if (type == SpectrogramSettings::stPeriod) {
353       // special case
354       const auto half = settings.GetFFTLength() / 2;
355       // EAC returns no data for below this frequency:
356       const float bin2 = rate / half;
357       bottom = bin2;
358    }
359    else
360       // logarithmic, etc.
361       bottom = 1.0f;
362 
363    {
364       float spectrumMax = mSpectrumMax;
365       if (spectrumMax < 0)
366          spectrumMax = settings.maxFreq;
367       if (spectrumMax < 0)
368          *max = top;
369       else
370          *max = std::max(bottom, std::min(top, spectrumMax));
371    }
372 
373    {
374       float spectrumMin = mSpectrumMin;
375       if (spectrumMin < 0)
376          spectrumMin = settings.minFreq;
377       if (spectrumMin < 0)
378          *min = std::max(bottom, top / 1000.0f);
379       else
380          *min = std::max(bottom, std::min(top, spectrumMin));
381    }
382 }
383 
SetSpectrumBounds(float min,float max) const384 void WaveTrack::SetSpectrumBounds(float min, float max) const
385 {
386    mSpectrumMin = min;
387    mSpectrumMax = max;
388 }
389 
ZeroLevelYCoordinate(wxRect rect) const390 int WaveTrack::ZeroLevelYCoordinate(wxRect rect) const
391 {
392    return rect.GetTop() +
393       (int)((mDisplayMax / (mDisplayMax - mDisplayMin)) * rect.height);
394 }
395 
396 template< typename Container >
MakeIntervals(const std::vector<WaveClipHolder> & clips)397 static Container MakeIntervals(const std::vector<WaveClipHolder> &clips)
398 {
399    Container result;
400    for (const auto &clip: clips) {
401       result.emplace_back( clip->GetPlayStartTime(), clip->GetPlayEndTime(),
402          std::make_unique<WaveTrack::IntervalData>( clip ) );
403    }
404    return result;
405 }
406 
PasteInto(AudacityProject & project) const407 Track::Holder WaveTrack::PasteInto( AudacityProject &project ) const
408 {
409    auto &trackFactory = WaveTrackFactory::Get( project );
410    auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
411    auto pNewTrack = EmptyCopy( pSampleBlockFactory );
412    pNewTrack->Paste(0.0, this);
413    return pNewTrack;
414 }
415 
GetIntervals() const416 auto WaveTrack::GetIntervals() const -> ConstIntervals
417 {
418    return MakeIntervals<ConstIntervals>( mClips );
419 }
420 
GetIntervals()421 auto WaveTrack::GetIntervals() -> Intervals
422 {
423    return MakeIntervals<Intervals>( mClips );
424 }
425 
FindClipByName(const wxString & name) const426 const WaveClip* WaveTrack::FindClipByName(const wxString& name) const
427 {
428    for (const auto& clip : mClips)
429    {
430       if (clip->GetName() == name)
431          return clip.get();
432    }
433    return nullptr;
434 }
435 
Clone() const436 Track::Holder WaveTrack::Clone() const
437 {
438    return std::make_shared<WaveTrack>( *this );
439 }
440 
MakeClipCopyName(const wxString & originalName) const441 wxString WaveTrack::MakeClipCopyName(const wxString& originalName) const
442 {
443    auto name = originalName;
444    for (auto i = 1;; ++i)
445    {
446       if (FindClipByName(name) == nullptr)
447          return name;
448       //i18n-hint Template for clip name generation on copy-paste
449       name = XC("%s.%i", "clip name template").Format(originalName, i).Translation();
450    }
451 }
452 
MakeNewClipName() const453 wxString WaveTrack::MakeNewClipName() const
454 {
455    auto name = GetName();
456    for (auto i = 1;; ++i)
457    {
458       if (FindClipByName(name) == nullptr)
459          return name;
460       //i18n-hint Template for clip name generation on inserting new empty clip
461       name = XC("%s %i", "clip name template").Format(GetName(), i).Translation();
462    }
463 }
464 
GetRate() const465 double WaveTrack::GetRate() const
466 {
467    return mRate;
468 }
469 
SetRate(double newRate)470 void WaveTrack::SetRate(double newRate)
471 {
472    wxASSERT( newRate > 0 );
473    newRate = std::max( 1.0, newRate );
474    auto ratio = mRate / newRate;
475    mRate = (int) newRate;
476    for (const auto &clip : mClips) {
477       clip->SetRate((int)newRate);
478       clip->SetSequenceStartTime( clip->GetSequenceStartTime() * ratio );
479    }
480 }
481 
GetGain() const482 float WaveTrack::GetGain() const
483 {
484    return mGain;
485 }
486 
SetGain(float newGain)487 void WaveTrack::SetGain(float newGain)
488 {
489    if (mGain != newGain) {
490       mGain = newGain;
491       Notify();
492    }
493 }
494 
GetPan() const495 float WaveTrack::GetPan() const
496 {
497    return mPan;
498 }
499 
SetPan(float newPan)500 void WaveTrack::SetPan(float newPan)
501 {
502    if (newPan > 1.0)
503       newPan = 1.0;
504    else if (newPan < -1.0)
505       newPan = -1.0;
506 
507    if ( mPan != newPan ) {
508       mPan = newPan;
509       Notify();
510    }
511 }
512 
GetChannelGain(int channel) const513 float WaveTrack::GetChannelGain(int channel) const
514 {
515    float left = 1.0;
516    float right = 1.0;
517 
518    if (mPan < 0)
519       right = (mPan + 1.0);
520    else if (mPan > 0)
521       left = 1.0 - mPan;
522 
523    if ((channel%2) == 0)
524       return left*mGain;
525    else
526       return right*mGain;
527 }
528 
GetOldChannelGain(int channel) const529 float WaveTrack::GetOldChannelGain(int channel) const
530 {
531    return mOldGain[channel%2];
532 }
533 
SetOldChannelGain(int channel,float gain)534 void WaveTrack::SetOldChannelGain(int channel, float gain)
535 {
536    mOldGain[channel % 2] = gain;
537 }
538 
539 
540 
541 /*! @excsafety{Strong} */
SetWaveColorIndex(int colorIndex)542 void WaveTrack::SetWaveColorIndex(int colorIndex)
543 {
544    for (const auto &clip : mClips)
545       clip->SetColourIndex( colorIndex );
546    mWaveColorIndex = colorIndex;
547 }
548 
GetPlaySamplesCount() const549 sampleCount WaveTrack::GetPlaySamplesCount() const
550 {
551     sampleCount result{ 0 };
552 
553     for (const auto& clip : mClips)
554         result += clip->GetPlaySamplesCount();
555 
556     return result;
557 }
558 
GetSequenceSamplesCount() const559 sampleCount WaveTrack::GetSequenceSamplesCount() const
560 {
561    sampleCount result{ 0 };
562 
563    for (const auto& clip : mClips)
564       result += clip->GetSequenceSamplesCount();
565 
566    return result;
567 }
568 
569 /*! @excsafety{Weak} -- Might complete on only some clips */
ConvertToSampleFormat(sampleFormat format,const std::function<void (size_t)> & progressReport)570 void WaveTrack::ConvertToSampleFormat(sampleFormat format,
571    const std::function<void(size_t)> & progressReport)
572 {
573    for (const auto& clip : mClips)
574       clip->ConvertToSampleFormat(format, progressReport);
575    mFormat = format;
576 }
577 
578 
IsEmpty(double t0,double t1) const579 bool WaveTrack::IsEmpty(double t0, double t1) const
580 {
581    if (t0 > t1)
582       return true;
583 
584    //wxPrintf("Searching for overlap in %.6f...%.6f\n", t0, t1);
585    for (const auto &clip : mClips)
586    {
587       if (!clip->BeforePlayStartTime(t1) && !clip->AfterPlayEndTime(t0)) {
588          //wxPrintf("Overlapping clip: %.6f...%.6f\n",
589          //       clip->GetStartTime(),
590          //       clip->GetEndTime());
591          // We found a clip that overlaps this region
592          return false;
593       }
594    }
595    //wxPrintf("No overlap found\n");
596 
597    // Otherwise, no clips overlap this region
598    return true;
599 }
600 
Cut(double t0,double t1)601 Track::Holder WaveTrack::Cut(double t0, double t1)
602 {
603    if (t1 < t0)
604       THROW_INCONSISTENCY_EXCEPTION;
605 
606    auto tmp = Copy(t0, t1);
607 
608    Clear(t0, t1);
609 
610    return tmp;
611 }
612 
613 /*! @excsafety{Strong} */
SplitCut(double t0,double t1)614 Track::Holder WaveTrack::SplitCut(double t0, double t1)
615 {
616    if (t1 < t0)
617       THROW_INCONSISTENCY_EXCEPTION;
618 
619    // SplitCut is the same as 'Copy', then 'SplitDelete'
620    auto tmp = Copy(t0, t1);
621 
622    SplitDelete(t0, t1);
623 
624    return tmp;
625 }
626 
627 #if 0
628 Track::Holder WaveTrack::CutAndAddCutLine(double t0, double t1)
629 {
630    if (t1 < t0)
631       THROW_INCONSISTENCY_EXCEPTION;
632 
633    // Cut is the same as 'Copy', then 'Delete'
634    auto tmp = Copy(t0, t1);
635 
636    ClearAndAddCutLine(t0, t1);
637 
638    return tmp;
639 }
640 #endif
641 
642 
643 
644 //Trim trims within a clip, rather than trimming everything.
645 //If a bound is outside a clip, it trims everything.
646 /*! @excsafety{Weak} */
Trim(double t0,double t1)647 void WaveTrack::Trim (double t0, double t1)
648 {
649    bool inside0 = false;
650    bool inside1 = false;
651 
652    for (const auto &clip : mClips)
653    {
654       if(t1 > clip->GetPlayStartTime() && t1 < clip->GetPlayEndTime())
655       {
656          clip->SetTrimRight(clip->GetTrimRight() + clip->GetPlayEndTime() - t1);
657          inside1 = true;
658       }
659 
660       if(t0 > clip->GetPlayStartTime() && t0 < clip->GetPlayEndTime())
661       {
662          clip->SetTrimLeft(clip->GetTrimLeft() + t0 - clip->GetPlayStartTime());
663          inside0 = true;
664       }
665    }
666 
667    //if inside0 is false, then the left selector was between
668    //clips, so DELETE everything to its left.
669    if(!inside1 && t1 < GetEndTime())
670       Clear(t1,GetEndTime());
671 
672    if(!inside0 && t0 > GetStartTime())
673       SplitDelete(GetStartTime(), t0);
674 }
675 
676 
677 
678 
EmptyCopy(const SampleBlockFactoryPtr & pFactory) const679 WaveTrack::Holder WaveTrack::EmptyCopy(
680    const SampleBlockFactoryPtr &pFactory ) const
681 {
682    auto result = std::make_shared<WaveTrack>( pFactory, mFormat, mRate );
683    result->Init(*this);
684    result->mpFactory = pFactory ? pFactory : mpFactory;
685    return result;
686 }
687 
Copy(double t0,double t1,bool forClipboard) const688 Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
689 {
690    if (t1 < t0)
691       THROW_INCONSISTENCY_EXCEPTION;
692 
693    auto result = EmptyCopy();
694    WaveTrack *newTrack = result.get();
695 
696    // PRL:  Why shouldn't cutlines be copied and pasted too?  I don't know, but
697    // that was the old behavior.  But this function is also used by the
698    // Duplicate command and I changed its behavior in that case.
699 
700    for (const auto &clip : mClips)
701    {
702       if (t0 <= clip->GetPlayStartTime() && t1 >= clip->GetPlayEndTime())
703       {
704          // Whole clip is in copy region
705          //wxPrintf("copy: clip %i is in copy region\n", (int)clip);
706 
707          newTrack->mClips.push_back
708             (std::make_unique<WaveClip>(*clip, mpFactory, ! forClipboard));
709          WaveClip *const newClip = newTrack->mClips.back().get();
710          newClip->Offset(-t0);
711       }
712       else if (t1 > clip->GetPlayStartTime() && t0 < clip->GetPlayEndTime())
713       {
714          // Clip is affected by command
715          //wxPrintf("copy: clip %i is affected by command\n", (int)clip);
716 
717          const double clip_t0 = std::max(t0, clip->GetPlayStartTime());
718          const double clip_t1 = std::min(t1, clip->GetPlayEndTime());
719 
720          auto newClip = std::make_unique<WaveClip>
721             (*clip, mpFactory, ! forClipboard, clip_t0, clip_t1);
722          newClip->SetName(clip->GetName());
723 
724          //wxPrintf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1);
725 
726          newClip->Offset(-t0);
727          if (newClip->GetPlayStartTime() < 0)
728             newClip->SetPlayStartTime(0);
729 
730          newTrack->mClips.push_back(std::move(newClip)); // transfer ownership
731       }
732    }
733 
734    // AWD, Oct 2009: If the selection ends in whitespace, create a placeholder
735    // clip representing that whitespace
736    // PRL:  Only if we want the track for pasting into other tracks.  Not if it
737    // goes directly into a project as in the Duplicate command.
738    if (forClipboard &&
739        newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0)
740    {
741       auto placeholder = std::make_unique<WaveClip>(mpFactory,
742             newTrack->GetSampleFormat(),
743             static_cast<int>(newTrack->GetRate()),
744             0 /*colourindex*/);
745       placeholder->SetIsPlaceholder(true);
746       placeholder->InsertSilence(0, (t1 - t0) - newTrack->GetEndTime());
747       placeholder->Offset(newTrack->GetEndTime());
748       newTrack->mClips.push_back(std::move(placeholder)); // transfer ownership
749    }
750 
751    return result;
752 }
753 
CopyNonconst(double t0,double t1)754 Track::Holder WaveTrack::CopyNonconst(double t0, double t1)
755 {
756    return Copy(t0, t1);
757 }
758 
759 /*! @excsafety{Strong} */
Clear(double t0,double t1)760 void WaveTrack::Clear(double t0, double t1)
761 {
762    HandleClear(t0, t1, false, false);
763 }
764 
765 /*! @excsafety{Strong} */
ClearAndAddCutLine(double t0,double t1)766 void WaveTrack::ClearAndAddCutLine(double t0, double t1)
767 {
768    HandleClear(t0, t1, true, false);
769 }
770 
GetSpectrogramSettings() const771 const SpectrogramSettings &WaveTrack::GetSpectrogramSettings() const
772 {
773    if (mpSpectrumSettings)
774       return *mpSpectrumSettings;
775    else
776       return SpectrogramSettings::defaults();
777 }
778 
GetSpectrogramSettings()779 SpectrogramSettings &WaveTrack::GetSpectrogramSettings()
780 {
781    if (mpSpectrumSettings)
782       return *mpSpectrumSettings;
783    else
784       return SpectrogramSettings::defaults();
785 }
786 
GetIndependentSpectrogramSettings()787 SpectrogramSettings &WaveTrack::GetIndependentSpectrogramSettings()
788 {
789    if (!mpSpectrumSettings)
790       mpSpectrumSettings =
791       std::make_unique<SpectrogramSettings>(SpectrogramSettings::defaults());
792    return *mpSpectrumSettings;
793 }
794 
SetSpectrogramSettings(std::unique_ptr<SpectrogramSettings> && pSettings)795 void WaveTrack::SetSpectrogramSettings(std::unique_ptr<SpectrogramSettings> &&pSettings)
796 {
797    if (mpSpectrumSettings != pSettings) {
798       mpSpectrumSettings = std::move(pSettings);
799    }
800 }
801 
UseSpectralPrefs(bool bUse)802 void WaveTrack::UseSpectralPrefs( bool bUse )
803 {
804    if( bUse ){
805       if( !mpSpectrumSettings )
806          return;
807       // reset it, and next we will be getting the defaults.
808       mpSpectrumSettings.reset();
809    }
810    else {
811       if( mpSpectrumSettings )
812          return;
813       GetIndependentSpectrogramSettings();
814    }
815 }
816 
817 
818 
GetWaveformSettings() const819 const WaveformSettings &WaveTrack::GetWaveformSettings() const
820 {
821    // Create on demand
822    return const_cast<WaveTrack*>(this)->GetWaveformSettings();
823 }
824 
GetWaveformSettings()825 WaveformSettings &WaveTrack::GetWaveformSettings()
826 {
827    // Create on demand
828    if (!mpWaveformSettings)
829       mpWaveformSettings = std::make_unique<WaveformSettings>(WaveformSettings::defaults());
830    return *mpWaveformSettings;
831 }
832 
SetWaveformSettings(std::unique_ptr<WaveformSettings> && pSettings)833 void WaveTrack::SetWaveformSettings(std::unique_ptr<WaveformSettings> &&pSettings)
834 {
835    if (mpWaveformSettings != pSettings) {
836       mpWaveformSettings = std::move(pSettings);
837    }
838 }
839 
840 namespace {
841 
842    //Internal structure, which is supposed to contain
843    //data related to clip boundaries, and used during
844    //ClearAndPaste to restore old splits after new
845    //data is pasted
846    struct SplitInfo
847    {
848       //Time, where boundary is located
849       double time;
850       //Contains trimmed data, which should be re-appended to
851       //the clip to the left from the boundary, may be null
852       std::shared_ptr<WaveClip> left;
853       //Contains trimmed data, which should be re-appended to
854       //the clip to the right from the boundary, may be null
855       std::shared_ptr<WaveClip> right;
856       //Contains clip name next to the left from the boundary,
857       //if present, that needs to be re-assigned to the matching
858       //clip after split
859       std::optional<wxString> leftClipName;
860       //Contains clip name next to the right from the boundary,
861       //if present, that needs to be re-assigned to the matching
862       //clip after split
863       std::optional<wxString> rightClipName;
864    };
865 
866 }
867 
868 //
869 // ClearAndPaste() is a specialized version of HandleClear()
870 // followed by Paste() and is used mostly by effects that
871 // can't replace track data directly using Get()/Set().
872 //
873 // HandleClear() removes any cut/split lines with the
874 // cleared range, but, in most cases, effects want to preserve
875 // the existing cut/split lines, trimmed data and clip names,
876 // so they are saved before the HandleClear()/Paste() and restored after.
877 // When pasted track has split lines with hidden data at same places
878 // as the target one, then only targets hidden data is preserved, and
879 // hidden data from pasted track is discarded.
880 //
881 // If the pasted track overlaps two or more clips, then it will
882 // be pasted with visible split lines.  Normally, effects do not
883 // want these extra lines, so they may be merged out.
884 //
885 /*! @excsafety{Weak} -- This WaveTrack remains destructible in case of AudacityException.
886 But some of its cutline clips may have been destroyed. */
ClearAndPaste(double t0,double t1,const Track * src,bool preserve,bool merge,const TimeWarper * effectWarper)887 void WaveTrack::ClearAndPaste(double t0, // Start of time to clear
888                               double t1, // End of time to clear
889                               const Track *src, // What to paste
890                               bool preserve, // Whether to reinsert splits/cuts
891                               bool merge, // Whether to remove 'extra' splits
892                               const TimeWarper *effectWarper // How does time change
893                               )
894 {
895    double dur = std::min(t1 - t0, src->GetEndTime());
896 
897    // If duration is 0, then it's just a plain paste
898    if (dur == 0.0) {
899       // use Weak-guarantee
900       Paste(t0, src);
901       return;
902    }
903 
904    std::vector<SplitInfo> splits;
905    WaveClipHolders cuts;
906 
907    //helper routine, that finds SplitInfo by time value,
908    //or creates a new one if no one exists yet
909    auto get_split = [&](double time) {
910       auto it = std::find_if(splits.begin(), splits.end(), [time](const SplitInfo& split) {
911          return split.time == time;
912       });
913       if(it == splits.end())
914          it = splits.insert(
915             splits.end(),
916             { time, nullptr, nullptr, std::nullopt, std::nullopt }
917          );
918       return it;
919    };
920 
921    // If provided time warper was NULL, use a default one that does nothing
922    IdentityTimeWarper localWarper;
923    const TimeWarper *warper = (effectWarper ? effectWarper : &localWarper);
924 
925    // Align to a sample
926    t0 = LongSamplesToTime(TimeToLongSamples(t0));
927    t1 = LongSamplesToTime(TimeToLongSamples(t1));
928 
929    // Save the cut/split lines whether preserving or not since merging
930    // needs to know if a clip boundary is being crossed since Paste()
931    // will add split lines around the pasted clip if so.
932    for (const auto &clip : mClips) {
933       double st;
934 
935       // Remember clip boundaries as locations to split
936       // we need to copy clips, trims and names, because the original ones
937       // could be changed later during Clear/Paste routines
938       st = LongSamplesToTime(TimeToLongSamples(clip->GetPlayStartTime()));
939       if (st >= t0 && st <= t1) {
940          auto it = get_split(st);
941          if (clip->GetTrimLeft() != 0)
942             it->right = std::make_shared<WaveClip>(*clip, mpFactory, false, clip->GetSequenceStartTime(), st);
943          it->rightClipName = clip->GetName();
944       }
945 
946       st = LongSamplesToTime(TimeToLongSamples(clip->GetPlayEndTime()));
947       if (st >= t0 && st <= t1) {
948          auto it = get_split(st);
949          if (clip->GetTrimRight() != 0)
950             it->left = std::make_shared<WaveClip>(*clip, mpFactory, false, st, clip->GetSequenceEndTime());
951          it->leftClipName = clip->GetName();
952       }
953 
954       // Search for cut lines
955       auto &cutlines = clip->GetCutLines();
956       // May erase from cutlines, so don't use range-for
957       for (auto it = cutlines.begin(); it != cutlines.end(); ) {
958          WaveClip *cut = it->get();
959          double cs = LongSamplesToTime(TimeToLongSamples(clip->GetSequenceStartTime() +
960                                                          cut->GetSequenceStartTime()));
961 
962          // Remember cut point
963          if (cs >= t0 && cs <= t1) {
964 
965             // Remember the absolute offset and add to our cuts array.
966             cut->SetSequenceStartTime(cs);
967             cuts.push_back(std::move(*it)); // transfer ownership!
968             it = cutlines.erase(it);
969          }
970          else
971             ++it;
972       }
973    }
974 
975    const auto tolerance = 2.0 / GetRate();
976 
977    // Now, clear the selection
978    HandleClear(t0, t1, false, false);
979    {
980 
981       // And paste in the NEW data
982       Paste(t0, src);
983       {
984          // First, merge the NEW clip(s) in with the existing clips
985          if (merge && splits.size() > 0)
986          {
987             // Now t1 represents the absolute end of the pasted data.
988             t1 = t0 + src->GetEndTime();
989 
990             // Get a sorted array of the clips
991             auto clips = SortedClipArray();
992 
993             // Scan the sorted clips for the first clip whose start time
994             // exceeds the pasted regions end time.
995             {
996                WaveClip *prev = nullptr;
997                for (const auto clip : clips) {
998                   // Merge this clip and the previous clip if the end time
999                   // falls within it and this isn't the first clip in the track.
1000                   if (fabs(t1 - clip->GetPlayStartTime()) < tolerance) {
1001                      if (prev)
1002                         MergeClips(GetClipIndex(prev), GetClipIndex(clip));
1003                      break;
1004                   }
1005                   prev = clip;
1006                }
1007             }
1008          }
1009 
1010          // Refill the array since clips have changed.
1011          auto clips = SortedClipArray();
1012 
1013          {
1014             // Scan the sorted clips to look for the start of the pasted
1015             // region.
1016             WaveClip *prev = nullptr;
1017             for (const auto clip : clips) {
1018                if (prev) {
1019                   // It must be that clip is what was pasted and it begins where
1020                   // prev ends.
1021                   // use Weak-guarantee
1022                   MergeClips(GetClipIndex(prev), GetClipIndex(clip));
1023                   break;
1024                }
1025                if (fabs(t0 - clip->GetPlayEndTime()) < tolerance)
1026                   // Merge this clip and the next clip if the start time
1027                   // falls within it and this isn't the last clip in the track.
1028                   prev = clip;
1029                else
1030                   prev = nullptr;
1031             }
1032          }
1033       }
1034 
1035       // Restore cut/split lines
1036       if (preserve) {
1037 
1038          auto attachLeft = [](WaveClip* target, WaveClip* src)
1039          {
1040             wxASSERT(target->GetTrimLeft() == 0);
1041             if (target->GetTrimLeft() != 0)
1042                return;
1043 
1044             auto trim = src->GetPlayEndTime() - src->GetPlayStartTime();
1045             target->Paste(target->GetPlayStartTime(), src);
1046             target->SetTrimLeft(trim);
1047             //Play start time needs to be ajusted after
1048             //prepending data to the sequence
1049             target->Offset(-trim);
1050          };
1051 
1052          auto attachRight = [](WaveClip* target, WaveClip* src)
1053          {
1054             wxASSERT(target->GetTrimRight() == 0);
1055             if (target->GetTrimRight() != 0)
1056                return;
1057 
1058             auto trim = src->GetPlayEndTime() - src->GetPlayStartTime();
1059             target->Paste(target->GetPlayEndTime(), src);
1060             target->SetTrimRight(trim);
1061          };
1062 
1063          // Restore the split lines and trims, transforming the position appropriately
1064          for (const auto& split: splits) {
1065             auto at = LongSamplesToTime(TimeToLongSamples(warper->Warp(split.time)));
1066             for (const auto& clip : GetClips())
1067             {
1068                if (clip->WithinPlayRegion(at))//strictly inside
1069                {
1070                   auto newClip = std::make_unique<WaveClip>(*clip, mpFactory, true);
1071 
1072                   clip->ClearRight(at);
1073                   newClip->ClearLeft(at);
1074                   if (split.left)
1075                      attachRight(clip.get(), split.left.get());
1076                   if (split.right)
1077                      attachLeft(newClip.get(), split.right.get());
1078                   AddClip(std::move(newClip));
1079                   break;
1080                }
1081                else if (clip->GetPlayStartSample() == TimeToLongSamples(at) && split.right)
1082                {
1083                   attachLeft(clip.get(), split.right.get());
1084                   break;
1085                }
1086                else if (clip->GetPlayEndSample() == TimeToLongSamples(at) && split.left)
1087                {
1088                   attachRight(clip.get(), split.left.get());
1089                   break;
1090                }
1091             }
1092          }
1093 
1094          //Restore clip names
1095          for (const auto& split : splits)
1096          {
1097             auto s = TimeToLongSamples(warper->Warp(split.time));
1098             for (auto& clip : GetClips())
1099             {
1100                if (split.rightClipName.has_value() && clip->GetPlayStartSample() == s)
1101                   clip->SetName(*split.rightClipName);
1102                else if (split.leftClipName.has_value() && clip->GetPlayEndSample() == s)
1103                   clip->SetName(*split.leftClipName);
1104             }
1105          }
1106 
1107          // Restore the saved cut lines, also transforming if time altered
1108          for (const auto &clip : mClips) {
1109             double st;
1110             double et;
1111 
1112             st = clip->GetPlayStartTime();
1113             et = clip->GetPlayEndTime();
1114 
1115             // Scan the cuts for any that live within this clip
1116             for (auto it = cuts.begin(); it != cuts.end();) {
1117                WaveClip *cut = it->get();
1118                //cutlines in this array were orphaned previously
1119                double cs = cut->GetSequenceStartTime();
1120 
1121                // Offset the cut from the start of the clip and add it to
1122                // this clips cutlines.
1123                if (cs >= st && cs <= et) {
1124                   cut->SetSequenceStartTime(warper->Warp(cs) - st);
1125                   clip->GetCutLines().push_back( std::move(*it) ); // transfer ownership!
1126                   it = cuts.erase(it);
1127                }
1128                else
1129                   ++it;
1130             }
1131          }
1132       }
1133    }
1134 }
1135 
1136 /*! @excsafety{Strong} */
SplitDelete(double t0,double t1)1137 void WaveTrack::SplitDelete(double t0, double t1)
1138 {
1139    bool addCutLines = false;
1140    bool split = true;
1141    HandleClear(t0, t1, addCutLines, split);
1142 }
1143 
1144 namespace
1145 {
1146    WaveClipHolders::const_iterator
FindClip(const WaveClipHolders & list,const WaveClip * clip,int * distance=nullptr)1147       FindClip(const WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
1148    {
1149       if (distance)
1150          *distance = 0;
1151       auto it = list.begin();
1152       for (const auto end = list.end(); it != end; ++it)
1153       {
1154          if (it->get() == clip)
1155             break;
1156          if (distance)
1157             ++*distance;
1158       }
1159       return it;
1160    }
1161 
1162    WaveClipHolders::iterator
FindClip(WaveClipHolders & list,const WaveClip * clip,int * distance=nullptr)1163       FindClip(WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
1164    {
1165       if (distance)
1166          *distance = 0;
1167       auto it = list.begin();
1168       for (const auto end = list.end(); it != end; ++it)
1169       {
1170          if (it->get() == clip)
1171             break;
1172          if (distance)
1173             ++*distance;
1174       }
1175       return it;
1176    }
1177 }
1178 
RemoveAndReturnClip(WaveClip * clip)1179 std::shared_ptr<WaveClip> WaveTrack::RemoveAndReturnClip(WaveClip* clip)
1180 {
1181    // Be clear about who owns the clip!!
1182    auto it = FindClip(mClips, clip);
1183    if (it != mClips.end()) {
1184       auto result = std::move(*it); // Array stops owning the clip, before we shrink it
1185       mClips.erase(it);
1186       return result;
1187    }
1188    else
1189       return {};
1190 }
1191 
AddClip(const std::shared_ptr<WaveClip> & clip)1192 bool WaveTrack::AddClip(const std::shared_ptr<WaveClip> &clip)
1193 {
1194    if (clip->GetSequence()->GetFactory() != this->mpFactory)
1195       return false;
1196 
1197    // Uncomment the following line after we correct the problem of zero-length clips
1198    //if (CanInsertClip(clip))
1199       mClips.push_back(clip); // transfer ownership
1200 
1201    return true;
1202 }
1203 
1204 /*! @excsafety{Strong} */
HandleClear(double t0,double t1,bool addCutLines,bool split)1205 void WaveTrack::HandleClear(double t0, double t1,
1206                             bool addCutLines, bool split)
1207 {
1208    // For debugging, use an ASSERT so that we stop
1209    // closer to the problem.
1210    wxASSERT( t1 >= t0 );
1211    if (t1 < t0)
1212       THROW_INCONSISTENCY_EXCEPTION;
1213 
1214    bool editClipCanMove = GetEditClipsCanMove();
1215 
1216    WaveClipPointers clipsToDelete;
1217    WaveClipHolders clipsToAdd;
1218 
1219    // We only add cut lines when deleting in the middle of a single clip
1220    // The cut line code is not really prepared to handle other situations
1221    if (addCutLines)
1222    {
1223       for (const auto &clip : mClips)
1224       {
1225          if (!clip->BeforePlayStartTime(t1) && !clip->AfterPlayEndTime(t0) &&
1226                (clip->BeforePlayStartTime(t0) || clip->AfterPlayEndTime(t1)))
1227          {
1228             addCutLines = false;
1229             break;
1230          }
1231       }
1232    }
1233 
1234    for (const auto &clip : mClips)
1235    {
1236       if (clip->BeforePlayStartTime(t0) && clip->AfterPlayEndTime(t1))
1237       {
1238          // Whole clip must be deleted - remember this
1239          clipsToDelete.push_back(clip.get());
1240       }
1241       else if (!clip->BeforePlayStartTime(t1) && !clip->AfterPlayEndTime(t0))
1242       {
1243          // Clip data is affected by command
1244          if (addCutLines)
1245          {
1246             // Don't modify this clip in place, because we want a strong
1247             // guarantee, and might modify another clip
1248             clipsToDelete.push_back( clip.get() );
1249             auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1250             newClip->ClearAndAddCutLine( t0, t1 );
1251             clipsToAdd.push_back( std::move( newClip ) );
1252          }
1253          else
1254          {
1255             if (split) {
1256                // Three cases:
1257 
1258                if (clip->BeforePlayStartTime(t0)) {
1259                   // Delete from the left edge
1260 
1261                   // Don't modify this clip in place, because we want a strong
1262                   // guarantee, and might modify another clip
1263                   clipsToDelete.push_back( clip.get() );
1264                   auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1265                   newClip->TrimLeft(t1 - clip->GetPlayStartTime());
1266                   clipsToAdd.push_back( std::move( newClip ) );
1267                }
1268                else if (clip->AfterPlayEndTime(t1)) {
1269                   // Delete to right edge
1270 
1271                   // Don't modify this clip in place, because we want a strong
1272                   // guarantee, and might modify another clip
1273                   clipsToDelete.push_back( clip.get() );
1274                   auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1275                   newClip->TrimRight(clip->GetPlayEndTime() - t0);
1276 
1277                   clipsToAdd.push_back( std::move( newClip ) );
1278                }
1279                else {
1280                   // Delete in the middle of the clip...we actually create two
1281                   // NEW clips out of the left and right halves...
1282 
1283                   auto leftClip = std::make_unique<WaveClip>(*clip, mpFactory, true);
1284                   leftClip->TrimRight(clip->GetPlayEndTime() - t0);
1285                   clipsToAdd.push_back(std::move(leftClip));
1286 
1287                   auto rightClip = std::make_unique<WaveClip>(*clip, mpFactory, true);
1288                   rightClip->TrimLeft(t1 - rightClip->GetPlayStartTime());
1289                   clipsToAdd.push_back(std::move(rightClip));
1290 
1291                   clipsToDelete.push_back(clip.get());
1292                }
1293             }
1294             else {
1295                // (We are not doing a split cut)
1296 
1297                // Don't modify this clip in place, because we want a strong
1298                // guarantee, and might modify another clip
1299                clipsToDelete.push_back( clip.get() );
1300                auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1301 
1302                // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
1303                newClip->Clear(t0,t1);
1304 
1305                clipsToAdd.push_back( std::move( newClip ) );
1306             }
1307          }
1308       }
1309    }
1310 
1311    // Only now, change the contents of this track
1312    // use No-fail-guarantee for the rest
1313 
1314    if (!split && editClipCanMove)
1315    {
1316       // Clip is "behind" the region -- offset it unless we're splitting
1317       // or we're using the "don't move other clips" mode
1318       for (const auto& clip : mClips)
1319       {
1320          if (clip->BeforePlayStartTime(t1))
1321             clip->Offset(-(t1 - t0));
1322       }
1323    }
1324 
1325    for (const auto &clip: clipsToDelete)
1326    {
1327       auto myIt = FindClip(mClips, clip);
1328       if (myIt != mClips.end())
1329          mClips.erase(myIt); // deletes the clip!
1330       else
1331          wxASSERT(false);
1332    }
1333 
1334    for (auto &clip: clipsToAdd)
1335       mClips.push_back(std::move(clip)); // transfer ownership
1336 }
1337 
SyncLockAdjust(double oldT1,double newT1)1338 void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
1339 {
1340    if (newT1 > oldT1) {
1341       // Insert space within the track
1342 
1343       // JKC: This is a rare case where using >= rather than > on a float matters.
1344       // GetEndTime() looks through the clips and may give us EXACTLY the same
1345       // value as T1, when T1 was set to be at the end of one of those clips.
1346       if (oldT1 >= GetEndTime())
1347          return;
1348 
1349       // If track is empty at oldT1 insert whitespace; otherwise, silence
1350       if (IsEmpty(oldT1, oldT1))
1351       {
1352          // Check if clips can move
1353          bool clipsCanMove = true;
1354          gPrefs->Read(wxT("/GUI/EditClipCanMove"), &clipsCanMove);
1355          if (clipsCanMove) {
1356             auto tmp = Cut (oldT1, GetEndTime() + 1.0/GetRate());
1357 
1358             Paste(newT1, tmp.get());
1359          }
1360          return;
1361       }
1362       else {
1363          // AWD: Could just use InsertSilence() on its own here, but it doesn't
1364          // follow EditClipCanMove rules (Paste() does it right)
1365          auto tmp = std::make_shared<WaveTrack>(
1366             mpFactory, GetSampleFormat(), GetRate() );
1367 
1368          tmp->InsertSilence(0.0, newT1 - oldT1);
1369          tmp->Flush();
1370          Paste(oldT1, tmp.get());
1371       }
1372    }
1373    else if (newT1 < oldT1) {
1374       Clear(newT1, oldT1);
1375    }
1376 }
1377 
PasteWaveTrack(double t0,const WaveTrack * other)1378 void WaveTrack::PasteWaveTrack(double t0, const WaveTrack* other)
1379 {
1380     //
1381     // Pasting is a bit complicated, because with the existence of multiclip mode,
1382     // we must guess the behaviour the user wants.
1383     //
1384     // Currently, two modes are implemented:
1385     //
1386     // - If a single clip should be pasted, and it should be pasted inside another
1387     //   clip, no NEW clips are generated. The audio is simply inserted.
1388     //   This resembles the old (pre-multiclip support) behaviour. However, if
1389     //   the clip is pasted outside of any clip, a NEW clip is generated. This is
1390     //   the only behaviour which is different to what was done before, but it
1391     //   shouldn't confuse users too much.
1392     //
1393     // - If multiple clips should be pasted, or a single clip that does not fill
1394     // the duration of the pasted track, these are always pasted as single
1395     // clips, and the current clip is split, when necessary. This may seem
1396     // strange at first, but it probably is better than trying to auto-merge
1397     // anything. The user can still merge the clips by hand (which should be a
1398     // simple command reachable by a hotkey or single mouse click).
1399     //
1400 
1401     if (other->GetNumClips() == 0)
1402         return;
1403 
1404     //wxPrintf("paste: we have at least one clip\n");
1405 
1406     bool singleClipMode = (other->GetNumClips() == 1 &&
1407         other->GetStartTime() == 0.0);
1408 
1409     const double insertDuration = other->GetEndTime();
1410     if (insertDuration != 0 && insertDuration < 1.0 / mRate)
1411         // PRL:  I added this check to avoid violations of preconditions in other WaveClip and Sequence
1412         // methods, but allow the value 0 so I don't subvert the purpose of commit
1413         // 739422ba70ceb4be0bb1829b6feb0c5401de641e which causes append-recording always to make
1414         // a new clip.
1415         return;
1416 
1417     //wxPrintf("Check if we need to make room for the pasted data\n");
1418 
1419     auto pastingFromTempTrack = !other->GetOwner();
1420     bool editClipCanMove = GetEditClipsCanMove();
1421 
1422     // Make room for the pasted data
1423     if (editClipCanMove) {
1424         if (!singleClipMode) {
1425             // We need to insert multiple clips, so split the current clip and
1426             // move everything to the right, then try to paste again
1427             if (!IsEmpty(t0, GetEndTime())) {
1428                 auto tmp = Cut(t0, GetEndTime() + 1.0 / mRate);
1429                 Paste(t0 + insertDuration, tmp.get());
1430             }
1431         }
1432         else {
1433             // We only need to insert one single clip, so just move all clips
1434             // to the right of the paste point out of the way
1435             for (const auto& clip : mClips)
1436             {
1437                 if (clip->GetPlayStartTime() > t0 - (1.0 / mRate))
1438                     clip->Offset(insertDuration);
1439             }
1440         }
1441     }
1442 
1443     if (singleClipMode)
1444     {
1445         // Single clip mode
1446         // wxPrintf("paste: checking for single clip mode!\n");
1447 
1448         WaveClip* insideClip = nullptr;
1449 
1450         for (const auto& clip : mClips)
1451         {
1452             if (editClipCanMove)
1453             {
1454                 if (clip->WithinPlayRegion(t0))
1455                 {
1456                     //wxPrintf("t0=%.6f: inside clip is %.6f ... %.6f\n",
1457                     //       t0, clip->GetStartTime(), clip->GetEndTime());
1458                     insideClip = clip.get();
1459                     break;
1460                 }
1461             }
1462             else
1463             {
1464                 // If clips are immovable we also allow prepending to clips
1465                 if (clip->WithinPlayRegion(t0) ||
1466                     TimeToLongSamples(t0) == clip->GetPlayStartSample())
1467                 {
1468                     insideClip = clip.get();
1469                     break;
1470                 }
1471             }
1472         }
1473 
1474         if (insideClip)
1475         {
1476             // Exhibit traditional behaviour
1477             //wxPrintf("paste: traditional behaviour\n");
1478             if (!editClipCanMove)
1479             {
1480                 // We did not move other clips out of the way already, so
1481                 // check if we can paste without having to move other clips
1482                 for (const auto& clip : mClips)
1483                 {
1484                     if (clip->GetPlayStartTime() > insideClip->GetPlayStartTime() &&
1485                         insideClip->GetPlayEndTime() + insertDuration >
1486                         clip->GetPlayStartTime())
1487                         // Strong-guarantee in case of this path
1488                         // not that it matters.
1489                         throw SimpleMessageBoxException{
1490                            ExceptionType::BadUserAction,
1491                               XO("There is not enough room available to paste the selection"),
1492                               XO("Warning"),
1493                               "Error:_Insufficient_space_in_track"
1494                     };
1495                 }
1496             }
1497             insideClip->Paste(t0, other->GetClipByIndex(0));
1498             return;
1499         }
1500         // Just fall through and exhibit NEW behaviour
1501     }
1502 
1503     // Insert NEW clips
1504     //wxPrintf("paste: multi clip mode!\n");
1505 
1506     if (!editClipCanMove && !IsEmpty(t0, t0 + insertDuration - 1.0 / mRate))
1507         // Strong-guarantee in case of this path
1508         // not that it matters.
1509         throw SimpleMessageBoxException{
1510            ExceptionType::BadUserAction,
1511            XO("There is not enough room available to paste the selection"),
1512            XO("Warning"),
1513            "Error:_Insufficient_space_in_track"
1514     };
1515 
1516     for (const auto& clip : other->mClips)
1517     {
1518         // AWD Oct. 2009: Don't actually paste in placeholder clips
1519         if (!clip->GetIsPlaceholder())
1520         {
1521             auto newClip =
1522                 std::make_unique<WaveClip>(*clip, mpFactory, true);
1523             newClip->Resample(mRate);
1524             newClip->Offset(t0);
1525             newClip->MarkChanged();
1526             if (pastingFromTempTrack)
1527                 //Clips from the tracks which aren't bound to any TrackList are
1528                 //considered to be new entities, thus named using "new" name template
1529                 newClip->SetName(MakeNewClipName());
1530             else
1531                 newClip->SetName(MakeClipCopyName(clip->GetName()));
1532             mClips.push_back(std::move(newClip)); // transfer ownership
1533         }
1534     }
1535 }
1536 
1537 /*! @excsafety{Weak} */
Paste(double t0,const Track * src)1538 void WaveTrack::Paste(double t0, const Track *src)
1539 {
1540    if (auto other = dynamic_cast<const WaveTrack*>(src))
1541       PasteWaveTrack(t0, other);
1542    else
1543       // THROW_INCONSISTENCY_EXCEPTION; // ?
1544       (void)0;// Empty if intentional.
1545 }
1546 
Silence(double t0,double t1)1547 void WaveTrack::Silence(double t0, double t1)
1548 {
1549    if (t1 < t0)
1550       THROW_INCONSISTENCY_EXCEPTION;
1551 
1552    auto start = TimeToLongSamples(t0);
1553    auto end = TimeToLongSamples(t1);
1554 
1555    for (const auto &clip : mClips)
1556    {
1557       auto clipStart = clip->GetPlayStartSample();
1558       auto clipEnd = clip->GetPlayEndSample();
1559 
1560       if (clipEnd > start && clipStart < end)
1561       {
1562          auto offset = std::max(start - clipStart, sampleCount(0));
1563          // Clip sample region and Get/Put sample region overlap
1564          auto length = std::min(end, clipEnd) - (clipStart + offset);
1565 
1566          clip->SetSilence(offset, length);
1567       }
1568    }
1569 }
1570 
1571 /*! @excsafety{Strong} */
InsertSilence(double t,double len)1572 void WaveTrack::InsertSilence(double t, double len)
1573 {
1574    // Nothing to do, if length is zero.
1575    // Fixes Bug 1626
1576    if( len == 0 )
1577       return;
1578    if (len <= 0)
1579       THROW_INCONSISTENCY_EXCEPTION;
1580 
1581    if (mClips.empty())
1582    {
1583       // Special case if there is no clip yet
1584       auto clip = std::make_unique<WaveClip>(mpFactory, mFormat, mRate, this->GetWaveColorIndex());
1585       clip->InsertSilence(0, len);
1586       // use No-fail-guarantee
1587       mClips.push_back( std::move( clip ) );
1588       return;
1589    }
1590    else {
1591       // Assume at most one clip contains t
1592       const auto end = mClips.end();
1593       const auto it = std::find_if( mClips.begin(), end,
1594          [&](const WaveClipHolder &clip) { return clip->WithinPlayRegion(t); } );
1595 
1596       // use Strong-guarantee
1597       if (it != end)
1598          it->get()->InsertSilence(t, len);
1599 
1600       // use No-fail-guarantee
1601       for (const auto &clip : mClips)
1602       {
1603          if (clip->BeforePlayStartTime(t))
1604             clip->Offset(len);
1605       }
1606    }
1607 }
1608 
1609 //Performs the opposite of Join
1610 //Analyses selected region for possible Joined clips and disjoins them
1611 /*! @excsafety{Weak} */
Disjoin(double t0,double t1)1612 void WaveTrack::Disjoin(double t0, double t1)
1613 {
1614    auto minSamples = TimeToLongSamples( WAVETRACK_MERGE_POINT_TOLERANCE );
1615    const size_t maxAtOnce = 1048576;
1616    Floats buffer{ maxAtOnce };
1617    Regions regions;
1618 
1619    wxBusyCursor busy;
1620 
1621    for (const auto &clip : mClips)
1622    {
1623       double startTime = clip->GetPlayStartTime();
1624       double endTime = clip->GetPlayEndTime();
1625 
1626       if( endTime < t0 || startTime > t1 )
1627          continue;
1628 
1629       //simply look for a sequence of zeroes and if the sequence
1630       //is greater than minimum number, split-DELETE the region
1631 
1632       sampleCount seqStart = -1;
1633       auto start = clip->TimeToSamples(std::max(.0, t0 - startTime));
1634       auto end = clip->TimeToSamples(std::min(endTime, t1) - startTime);
1635 
1636       auto len = ( end - start );
1637       for( decltype(len) done = 0; done < len; done += maxAtOnce )
1638       {
1639          auto numSamples = limitSampleBufferSize( maxAtOnce, len - done );
1640 
1641          clip->GetSamples( ( samplePtr )buffer.get(), floatSample, start + done,
1642                numSamples );
1643          for( decltype(numSamples) i = 0; i < numSamples; i++ )
1644          {
1645             auto curSamplePos = start + done + i;
1646 
1647             //start a NEW sequence
1648             if( buffer[ i ] == 0.0 && seqStart == -1 )
1649                seqStart = curSamplePos;
1650             else if( buffer[ i ] != 0.0 || curSamplePos == end - 1 )
1651             {
1652                if( seqStart != -1 )
1653                {
1654                   decltype(end) seqEnd;
1655 
1656                   //consider the end case, where selection ends in zeroes
1657                   if( curSamplePos == end - 1 && buffer[ i ] == 0.0 )
1658                      seqEnd = end;
1659                   else
1660                      seqEnd = curSamplePos;
1661                   if( seqEnd - seqStart + 1 > minSamples )
1662                   {
1663                      regions.push_back(
1664                         Region(
1665                            startTime + clip->SamplesToTime(seqStart),
1666                            startTime + clip->SamplesToTime(seqEnd)
1667                         )
1668                      );
1669                   }
1670                   seqStart = -1;
1671                }
1672             }
1673          }
1674       }
1675    }
1676 
1677    for( unsigned int i = 0; i < regions.size(); i++ )
1678    {
1679       const Region &region = regions.at(i);
1680       SplitDelete(region.start, region.end );
1681    }
1682 }
1683 
1684 /*! @excsafety{Weak} */
Join(double t0,double t1)1685 void WaveTrack::Join(double t0, double t1)
1686 {
1687    // Merge all WaveClips overlapping selection into one
1688 
1689    WaveClipPointers clipsToDelete;
1690    WaveClip* newClip{};
1691 
1692    for (const auto &clip: mClips)
1693    {
1694       if (clip->GetPlayStartTime() < t1-(1.0/mRate) &&
1695           clip->GetPlayEndTime()-(1.0/mRate) > t0) {
1696 
1697          // Put in sorted order
1698          auto it = clipsToDelete.begin(), end = clipsToDelete.end();
1699          for (; it != end; ++it)
1700             if ((*it)->GetPlayStartTime() > clip->GetPlayStartTime())
1701                break;
1702          //wxPrintf("Insert clip %.6f at position %d\n", clip->GetStartTime(), i);
1703          clipsToDelete.insert(it, clip.get());
1704       }
1705    }
1706 
1707    //if there are no clips to DELETE, nothing to do
1708    if( clipsToDelete.size() == 0 )
1709       return;
1710 
1711    auto t = clipsToDelete[0]->GetPlayStartTime();
1712    //preserve left trim data if any
1713    newClip = CreateClip(clipsToDelete[0]->GetSequenceStartTime(),
1714       clipsToDelete[0]->GetName());
1715 
1716    for (const auto &clip : clipsToDelete)
1717    {
1718       //wxPrintf("t=%.6f adding clip (offset %.6f, %.6f ... %.6f)\n",
1719       //       t, clip->GetOffset(), clip->GetStartTime(), clip->GetEndTime());
1720 
1721       if (clip->GetPlayStartTime() - t > (1.0 / mRate)) {
1722          double addedSilence = (clip->GetPlayStartTime() - t);
1723          //wxPrintf("Adding %.6f seconds of silence\n");
1724          auto offset = clip->GetPlayStartTime();
1725          auto value = clip->GetEnvelope()->GetValue( offset );
1726          newClip->AppendSilence( addedSilence, value );
1727          t += addedSilence;
1728       }
1729 
1730       //wxPrintf("Pasting at %.6f\n", t);
1731       newClip->Paste(t, clip);
1732 
1733       t = newClip->GetPlayEndTime();
1734 
1735       auto it = FindClip(mClips, clip);
1736       mClips.erase(it); // deletes the clip
1737    }
1738 }
1739 
1740 /*! @excsafety{Partial}
1741 -- Some prefix (maybe none) of the buffer is appended,
1742 and no content already flushed to disk is lost. */
Append(constSamplePtr buffer,sampleFormat format,size_t len,unsigned int stride)1743 bool WaveTrack::Append(constSamplePtr buffer, sampleFormat format,
1744                        size_t len, unsigned int stride /* = 1 */)
1745 {
1746    return RightmostOrNewClip()->Append(buffer, format, len, stride);
1747 }
1748 
GetBlockStart(sampleCount s) const1749 sampleCount WaveTrack::GetBlockStart(sampleCount s) const
1750 {
1751    for (const auto &clip : mClips)
1752    {
1753       const auto startSample = clip->GetPlayStartSample();
1754       const auto endSample = clip->GetPlayEndSample();
1755       if (s >= startSample && s < endSample)
1756       {
1757           auto blockStartOffset = clip->GetSequence()->GetBlockStart(clip->ToSequenceSamples(s));
1758           return std::max(startSample, clip->GetSequenceStartSample() + blockStartOffset);
1759       }
1760    }
1761 
1762    return -1;
1763 }
1764 
GetBestBlockSize(sampleCount s) const1765 size_t WaveTrack::GetBestBlockSize(sampleCount s) const
1766 {
1767    auto bestBlockSize = GetMaxBlockSize();
1768 
1769    for (const auto &clip : mClips)
1770    {
1771       auto startSample = clip->GetPlayStartSample();
1772       auto endSample = clip->GetPlayEndSample();
1773       if (s >= startSample && s < endSample)
1774       {
1775          bestBlockSize = clip->GetSequence()->GetBestBlockSize(s - clip->GetSequenceStartSample());
1776          break;
1777       }
1778    }
1779 
1780    return bestBlockSize;
1781 }
1782 
GetMaxBlockSize() const1783 size_t WaveTrack::GetMaxBlockSize() const
1784 {
1785    decltype(GetMaxBlockSize()) maxblocksize = 0;
1786    for (const auto &clip : mClips)
1787    {
1788       maxblocksize = std::max(maxblocksize, clip->GetSequence()->GetMaxBlockSize());
1789    }
1790 
1791    if (maxblocksize == 0)
1792    {
1793       // We really need the maximum block size, so create a
1794       // temporary sequence to get it.
1795       maxblocksize = Sequence{ mpFactory, mFormat }.GetMaxBlockSize();
1796    }
1797 
1798    wxASSERT(maxblocksize > 0);
1799 
1800    return maxblocksize;
1801 }
1802 
GetIdealBlockSize()1803 size_t WaveTrack::GetIdealBlockSize()
1804 {
1805    return NewestOrNewClip()->GetSequence()->GetIdealBlockSize();
1806 }
1807 
1808 /*! @excsafety{Mixed} */
1809 /*! @excsafety{No-fail} -- The rightmost clip will be in a flushed state. */
1810 /*! @excsafety{Partial}
1811 -- Some initial portion (maybe none) of the append buffer of the rightmost
1812 clip gets appended; no previously saved contents are lost. */
Flush()1813 void WaveTrack::Flush()
1814 {
1815    // After appending, presumably.  Do this to the clip that gets appended.
1816    RightmostOrNewClip()->Flush();
1817 }
1818 
1819 namespace {
IsValidChannel(const int nValue)1820 bool IsValidChannel(const int nValue)
1821 {
1822    return (nValue >= Track::LeftChannel) && (nValue <= Track::MonoChannel);
1823 }
1824 }
1825 
HandleXMLTag(const std::string_view & tag,const AttributesList & attrs)1826 bool WaveTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
1827 {
1828    if (tag == "wavetrack") {
1829       double dblValue;
1830       long nValue;
1831 
1832       for (auto pair : attrs)
1833       {
1834          auto attr = pair.first;
1835          auto value = pair.second;
1836 
1837          if (attr == "rate")
1838          {
1839             // mRate is an int, but "rate" in the project file is a float.
1840             if (!value.TryGet(dblValue) ||
1841                   (dblValue < 1.0) || (dblValue > 1000000.0)) // allow a large range to be read
1842                return false;
1843 
1844             mRate = lrint(dblValue);
1845          }
1846          else if (attr == "offset" && value.TryGet(dblValue))
1847          {
1848             // Offset is only relevant for legacy project files. The value
1849             // is cached until the actual WaveClip containing the legacy
1850             // track is created.
1851             mLegacyProjectFileOffset = dblValue;
1852          }
1853          else if (this->PlayableTrack::HandleXMLAttribute(attr, value))
1854          {}
1855          else if (this->Track::HandleCommonXMLAttribute(attr, value))
1856             ;
1857          else if (attr == "gain" && value.TryGet(dblValue))
1858             mGain = dblValue;
1859          else if (attr == "pan" && value.TryGet(dblValue) &&
1860                   (dblValue >= -1.0) && (dblValue <= 1.0))
1861             mPan = dblValue;
1862          else if (attr == "channel")
1863          {
1864             if (!value.TryGet(nValue) ||
1865                   !IsValidChannel(nValue))
1866                return false;
1867             mChannel = static_cast<Track::ChannelType>( nValue );
1868          }
1869          else if (attr == "linked" && value.TryGet(nValue))
1870             SetLinkType(ToLinkType(nValue));
1871          else if (attr == "colorindex" && value.TryGet(nValue))
1872             // Don't use SetWaveColorIndex as it sets the clips too.
1873             mWaveColorIndex  = nValue;
1874          else if (attr == "sampleformat" && value.TryGet(nValue) &&
1875                   Sequence::IsValidSampleFormat(nValue))
1876             mFormat = static_cast<sampleFormat>(nValue);
1877       } // while
1878       return true;
1879    }
1880 
1881    return false;
1882 }
1883 
HandleXMLEndTag(const std::string_view & WXUNUSED (tag))1884 void WaveTrack::HandleXMLEndTag(const std::string_view&  WXUNUSED(tag))
1885 {
1886    // In case we opened a pre-multiclip project, we need to
1887    // simulate closing the waveclip tag.
1888    NewestOrNewClip()->HandleXMLEndTag("waveclip");
1889 }
1890 
HandleXMLChild(const std::string_view & tag)1891 XMLTagHandler *WaveTrack::HandleXMLChild(const std::string_view& tag)
1892 {
1893    //
1894    // This is legacy code (1.2 and previous) and is not called for NEW projects!
1895    //
1896    if (tag == "sequence" || tag == "envelope")
1897    {
1898       // This is a legacy project, so set the cached offset
1899       NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
1900 
1901       // Legacy project file tracks are imported as one single wave clip
1902       if (tag == "sequence")
1903          return NewestOrNewClip()->GetSequence();
1904       else if (tag == "envelope")
1905          return NewestOrNewClip()->GetEnvelope();
1906    }
1907 
1908    // JKC... for 1.1.0, one step better than what we had, but still badly broken.
1909    // If we see a waveblock at this level, we'd better generate a sequence.
1910    if (tag == "waveblock")
1911    {
1912       // This is a legacy project, so set the cached offset
1913       NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
1914       Sequence *pSeq = NewestOrNewClip()->GetSequence();
1915       return pSeq;
1916    }
1917 
1918    //
1919    // This is for the NEW file format (post-1.2)
1920    //
1921    if (tag == "waveclip")
1922       return CreateClip();
1923    else
1924       return NULL;
1925 }
1926 
WriteXML(XMLWriter & xmlFile) const1927 void WaveTrack::WriteXML(XMLWriter &xmlFile) const
1928 // may throw
1929 {
1930    xmlFile.StartTag(wxT("wavetrack"));
1931    this->Track::WriteCommonXMLAttributes( xmlFile );
1932    xmlFile.WriteAttr(wxT("channel"), mChannel);
1933    xmlFile.WriteAttr(wxT("linked"), static_cast<int>(GetLinkType()));
1934    this->PlayableTrack::WriteXMLAttributes(xmlFile);
1935    xmlFile.WriteAttr(wxT("rate"), mRate);
1936    xmlFile.WriteAttr(wxT("gain"), (double)mGain);
1937    xmlFile.WriteAttr(wxT("pan"), (double)mPan);
1938    xmlFile.WriteAttr(wxT("colorindex"), mWaveColorIndex );
1939    xmlFile.WriteAttr(wxT("sampleformat"), static_cast<long>(mFormat) );
1940 
1941    for (const auto &clip : mClips)
1942    {
1943       clip->WriteXML(xmlFile);
1944    }
1945 
1946    xmlFile.EndTag(wxT("wavetrack"));
1947 }
1948 
GetErrorOpening()1949 bool WaveTrack::GetErrorOpening()
1950 {
1951    for (const auto &clip : mClips)
1952       if (clip->GetSequence()->GetErrorOpening())
1953          return true;
1954 
1955    return false;
1956 }
1957 
CloseLock()1958 bool WaveTrack::CloseLock()
1959 {
1960    for (const auto &clip : mClips)
1961       clip->CloseLock();
1962 
1963    return true;
1964 }
1965 
TimeToLongSamples(double t0) const1966 AUDACITY_DLL_API sampleCount WaveTrack::TimeToLongSamples(double t0) const
1967 {
1968    return sampleCount( floor(t0 * mRate + 0.5) );
1969 }
1970 
LongSamplesToTime(sampleCount pos) const1971 double WaveTrack::LongSamplesToTime(sampleCount pos) const
1972 {
1973    return pos.as_double() / mRate;
1974 }
1975 
GetStartTime() const1976 double WaveTrack::GetStartTime() const
1977 {
1978    bool found = false;
1979    double best = 0.0;
1980 
1981    if (mClips.empty())
1982       return 0;
1983 
1984    for (const auto &clip : mClips)
1985       if (!found)
1986       {
1987          found = true;
1988          best = clip->GetPlayStartTime();
1989       }
1990       else if (clip->GetPlayStartTime() < best)
1991          best = clip->GetPlayStartTime();
1992 
1993    return best;
1994 }
1995 
GetEndTime() const1996 double WaveTrack::GetEndTime() const
1997 {
1998    bool found = false;
1999    double best = 0.0;
2000 
2001    if (mClips.empty())
2002       return 0;
2003 
2004    for (const auto &clip : mClips)
2005       if (!found)
2006       {
2007          found = true;
2008          best = clip->GetPlayEndTime();
2009       }
2010       else if (clip->GetPlayEndTime() > best)
2011          best = clip->GetPlayEndTime();
2012 
2013    return best;
2014 }
2015 
2016 //
2017 // Getting/setting samples.  The sample counts here are
2018 // expressed relative to t=0.0 at the track's sample rate.
2019 //
2020 
GetMinMax(double t0,double t1,bool mayThrow) const2021 std::pair<float, float> WaveTrack::GetMinMax(
2022    double t0, double t1, bool mayThrow) const
2023 {
2024    std::pair<float, float> results {
2025       // we need these at extremes to make sure we find true min and max
2026       FLT_MAX, -FLT_MAX
2027    };
2028    bool clipFound = false;
2029 
2030    if (t0 > t1) {
2031       if (mayThrow)
2032          THROW_INCONSISTENCY_EXCEPTION;
2033       return results;
2034    }
2035 
2036    if (t0 == t1)
2037       return results;
2038 
2039    for (const auto &clip: mClips)
2040    {
2041       if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
2042       {
2043          clipFound = true;
2044          auto clipResults = clip->GetMinMax(t0, t1, mayThrow);
2045          if (clipResults.first < results.first)
2046             results.first = clipResults.first;
2047          if (clipResults.second > results.second)
2048             results.second = clipResults.second;
2049       }
2050    }
2051 
2052    if(!clipFound)
2053    {
2054       results = { 0.f, 0.f }; // sensible defaults if no clips found
2055    }
2056 
2057    return results;
2058 }
2059 
GetRMS(double t0,double t1,bool mayThrow) const2060 float WaveTrack::GetRMS(double t0, double t1, bool mayThrow) const
2061 {
2062    if (t0 > t1) {
2063       if (mayThrow)
2064          THROW_INCONSISTENCY_EXCEPTION;
2065       return 0.f;
2066    }
2067 
2068    if (t0 == t1)
2069       return 0.f;
2070 
2071    double sumsq = 0.0;
2072    sampleCount length = 0;
2073 
2074    for (const auto &clip: mClips)
2075    {
2076       // If t1 == clip->GetStartTime() or t0 == clip->GetEndTime(), then the clip
2077       // is not inside the selection, so we don't want it.
2078       // if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
2079       if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
2080       {
2081          auto clipStart = clip->TimeToSequenceSamples(wxMax(t0, clip->GetPlayStartTime()));
2082          auto clipEnd = clip->TimeToSequenceSamples(wxMin(t1, clip->GetPlayEndTime()));
2083 
2084          float cliprms = clip->GetRMS(t0, t1, mayThrow);
2085 
2086          sumsq += cliprms * cliprms * (clipEnd - clipStart).as_float();
2087          length += (clipEnd - clipStart);
2088       }
2089    }
2090    return length > 0 ? sqrt(sumsq / length.as_double()) : 0.0;
2091 }
2092 
Get(samplePtr buffer,sampleFormat format,sampleCount start,size_t len,fillFormat fill,bool mayThrow,sampleCount * pNumWithinClips) const2093 bool WaveTrack::Get(samplePtr buffer, sampleFormat format,
2094                     sampleCount start, size_t len, fillFormat fill,
2095                     bool mayThrow, sampleCount * pNumWithinClips) const
2096 {
2097    // Simple optimization: When this buffer is completely contained within one clip,
2098    // don't clear anything (because we won't have to). Otherwise, just clear
2099    // everything to be on the safe side.
2100    bool doClear = true;
2101    bool result = true;
2102    sampleCount samplesCopied = 0;
2103    for (const auto &clip: mClips)
2104    {
2105       if (start >= clip->GetPlayStartSample() && start+len <= clip->GetPlayEndSample())
2106       {
2107          doClear = false;
2108          break;
2109       }
2110    }
2111    if (doClear)
2112    {
2113       // Usually we fill in empty space with zero
2114       if( fill == fillZero )
2115          ClearSamples(buffer, format, 0, len);
2116       // but we don't have to.
2117       else if( fill==fillTwo )
2118       {
2119          wxASSERT( format==floatSample );
2120          float * pBuffer = (float*)buffer;
2121          for(size_t i=0;i<len;i++)
2122             pBuffer[i]=2.0f;
2123       }
2124       else
2125       {
2126          wxFAIL_MSG(wxT("Invalid fill format"));
2127       }
2128    }
2129 
2130    // Iterate the clips.  They are not necessarily sorted by time.
2131    for (const auto &clip: mClips)
2132    {
2133       auto clipStart = clip->GetPlayStartSample();
2134       auto clipEnd = clip->GetPlayEndSample();
2135 
2136       if (clipEnd > start && clipStart < start+len)
2137       {
2138          // Clip sample region and Get/Put sample region overlap
2139          auto samplesToCopy =
2140             std::min( start+len - clipStart, clip->GetPlaySamplesCount() );
2141          auto startDelta = clipStart - start;
2142          decltype(startDelta) inclipDelta = 0;
2143          if (startDelta < 0)
2144          {
2145             inclipDelta = -startDelta; // make positive value
2146             samplesToCopy -= inclipDelta;
2147             // samplesToCopy is now either len or
2148             //    (clipEnd - clipStart) - (start - clipStart)
2149             //    == clipEnd - start > 0
2150             // samplesToCopy is not more than len
2151             //
2152             startDelta = 0;
2153             // startDelta is zero
2154          }
2155          else {
2156             // startDelta is nonnegative and less than len
2157             // samplesToCopy is positive and not more than len
2158          }
2159 
2160          if (!clip->GetSamples(
2161                (samplePtr)(((char*)buffer) +
2162                            startDelta.as_size_t() *
2163                            SAMPLE_SIZE(format)),
2164                format, inclipDelta, samplesToCopy.as_size_t(), mayThrow ))
2165             result = false;
2166          else
2167             samplesCopied += samplesToCopy;
2168       }
2169    }
2170    if( pNumWithinClips )
2171       *pNumWithinClips = samplesCopied;
2172    return result;
2173 }
2174 
2175 /*! @excsafety{Weak} */
Set(constSamplePtr buffer,sampleFormat format,sampleCount start,size_t len)2176 void WaveTrack::Set(constSamplePtr buffer, sampleFormat format,
2177                     sampleCount start, size_t len)
2178 {
2179    for (const auto &clip: mClips)
2180    {
2181       auto clipStart = clip->GetPlayStartSample();
2182       auto clipEnd = clip->GetPlayEndSample();
2183 
2184       if (clipEnd > start && clipStart < start+len)
2185       {
2186          // Clip sample region and Get/Put sample region overlap
2187          auto samplesToCopy =
2188             std::min( start+len - clipStart, clip->GetPlaySamplesCount() );
2189          auto startDelta = clipStart - start;
2190          decltype(startDelta) inclipDelta = 0;
2191          if (startDelta < 0)
2192          {
2193             inclipDelta = -startDelta; // make positive value
2194             samplesToCopy -= inclipDelta;
2195             // samplesToCopy is now either len or
2196             //    (clipEnd - clipStart) - (start - clipStart)
2197             //    == clipEnd - start > 0
2198             // samplesToCopy is not more than len
2199             //
2200             startDelta = 0;
2201             // startDelta is zero
2202          }
2203          else {
2204             // startDelta is nonnegative and less than len
2205             // samplesToCopy is positive and not more than len
2206          }
2207 
2208          clip->SetSamples(
2209                (constSamplePtr)(((const char*)buffer) +
2210                            startDelta.as_size_t() *
2211                            SAMPLE_SIZE(format)),
2212                           format, inclipDelta, samplesToCopy.as_size_t() );
2213          clip->MarkChanged();
2214       }
2215    }
2216 }
2217 
GetEnvelopeValues(double * buffer,size_t bufferLen,double t0) const2218 void WaveTrack::GetEnvelopeValues(double *buffer, size_t bufferLen,
2219                                   double t0) const
2220 {
2221    // The output buffer corresponds to an unbroken span of time which the callers expect
2222    // to be fully valid.  As clips are processed below, the output buffer is updated with
2223    // envelope values from any portion of a clip, start, end, middle, or none at all.
2224    // Since this does not guarantee that the entire buffer is filled with values we need
2225    // to initialize the entire buffer to a default value.
2226    //
2227    // This does mean that, in the cases where a usable clip is located, the buffer value will
2228    // be set twice.  Unfortunately, there is no easy way around this since the clips are not
2229    // stored in increasing time order.  If they were, we could just track the time as the
2230    // buffer is filled.
2231    for (decltype(bufferLen) i = 0; i < bufferLen; i++)
2232    {
2233       buffer[i] = 1.0;
2234    }
2235 
2236    double startTime = t0;
2237    auto tstep = 1.0 / mRate;
2238    double endTime = t0 + tstep * bufferLen;
2239    for (const auto &clip: mClips)
2240    {
2241       // IF clip intersects startTime..endTime THEN...
2242       auto dClipStartTime = clip->GetPlayStartTime();
2243       auto dClipEndTime = clip->GetPlayEndTime();
2244       if ((dClipStartTime < endTime) && (dClipEndTime > startTime))
2245       {
2246          auto rbuf = buffer;
2247          auto rlen = bufferLen;
2248          auto rt0 = t0;
2249 
2250          if (rt0 < dClipStartTime)
2251          {
2252             // This is not more than the number of samples in
2253             // (endTime - startTime) which is bufferLen:
2254             auto nDiff = (sampleCount)floor((dClipStartTime - rt0) * mRate + 0.5);
2255             auto snDiff = nDiff.as_size_t();
2256             rbuf += snDiff;
2257             wxASSERT(snDiff <= rlen);
2258             rlen -= snDiff;
2259             rt0 = dClipStartTime;
2260          }
2261 
2262          if (rt0 + rlen*tstep > dClipEndTime)
2263          {
2264             auto nClipLen = clip->GetPlayEndSample() - clip->GetPlayStartSample();
2265 
2266             if (nClipLen <= 0) // Testing for bug 641, this problem is consistently '== 0', but doesn't hurt to check <.
2267                return;
2268 
2269             // This check prevents problem cited in http://bugzilla.audacityteam.org/show_bug.cgi?id=528#c11,
2270             // Gale's cross_fade_out project, which was already corrupted by bug 528.
2271             // This conditional prevents the previous write past the buffer end, in clip->GetEnvelope() call.
2272             // Never increase rlen here.
2273             // PRL bug 827:  rewrote it again
2274             rlen = limitSampleBufferSize( rlen, nClipLen );
2275             rlen = std::min(rlen, size_t(floor(0.5 + (dClipEndTime - rt0) / tstep)));
2276          }
2277          // Samples are obtained for the purpose of rendering a wave track,
2278          // so quantize time
2279          clip->GetEnvelope()->GetValues(rbuf, rlen, rt0, tstep);
2280       }
2281    }
2282 }
2283 
GetClipAtSample(sampleCount sample)2284 WaveClip* WaveTrack::GetClipAtSample(sampleCount sample)
2285 {
2286    for (const auto &clip: mClips)
2287    {
2288       auto start = clip->GetPlayStartSample();
2289       auto len   = clip->GetPlaySamplesCount();
2290 
2291       if (sample >= start && sample < start + len)
2292          return clip.get();
2293    }
2294 
2295    return NULL;
2296 }
2297 
2298 // When the time is both the end of a clip and the start of the next clip, the
2299 // latter clip is returned.
GetClipAtTime(double time)2300 WaveClip* WaveTrack::GetClipAtTime(double time)
2301 {
2302 
2303    const auto clips = SortedClipArray();
2304    auto p = std::find_if(clips.rbegin(), clips.rend(), [&] (WaveClip* const& clip) {
2305       return time >= clip->GetPlayStartTime() && time <= clip->GetPlayEndTime(); });
2306 
2307    // When two clips are immediately next to each other, the GetPlayEndTime() of the first clip
2308    // and the GetPlayStartTime() of the second clip may not be exactly equal due to rounding errors.
2309    // If "time" is the end time of the first of two such clips, and the end time is slightly
2310    // less than the start time of the second clip, then the first rather than the
2311    // second clip is found by the above code. So correct this.
2312    if (p != clips.rend() && p != clips.rbegin() &&
2313       time == (*p)->GetPlayEndTime() &&
2314       (*p)->SharesBoundaryWithNextClip(*(p-1))) {
2315       p--;
2316    }
2317 
2318    return p != clips.rend() ? *p : nullptr;
2319 }
2320 
GetEnvelopeAtTime(double time)2321 Envelope* WaveTrack::GetEnvelopeAtTime(double time)
2322 {
2323    WaveClip* clip = GetClipAtTime(time);
2324    if (clip)
2325       return clip->GetEnvelope();
2326    else
2327       return NULL;
2328 }
2329 
GetSequenceAtTime(double time)2330 Sequence* WaveTrack::GetSequenceAtTime(double time)
2331 {
2332    WaveClip* clip = GetClipAtTime(time);
2333    if (clip)
2334       return clip->GetSequence();
2335    else
2336       return NULL;
2337 }
2338 
CreateClip(double offset,const wxString & name)2339 WaveClip* WaveTrack::CreateClip(double offset, const wxString& name)
2340 {
2341    auto clip = std::make_unique<WaveClip>(mpFactory, mFormat, mRate, GetWaveColorIndex());
2342    clip->SetName(name);
2343    clip->SetSequenceStartTime(offset);
2344    mClips.push_back(std::move(clip));
2345 
2346    return mClips.back().get();
2347 }
2348 
NewestOrNewClip()2349 WaveClip* WaveTrack::NewestOrNewClip()
2350 {
2351    if (mClips.empty()) {
2352       return CreateClip(mOffset, MakeNewClipName());
2353    }
2354    else
2355       return mClips.back().get();
2356 }
2357 
2358 /*! @excsafety{No-fail} */
RightmostOrNewClip()2359 WaveClip* WaveTrack::RightmostOrNewClip()
2360 {
2361    if (mClips.empty()) {
2362       return CreateClip(mOffset, MakeNewClipName());
2363    }
2364    else
2365    {
2366       auto it = mClips.begin();
2367       WaveClip *rightmost = (*it++).get();
2368       double maxOffset = rightmost->GetPlayStartTime();
2369       for (auto end = mClips.end(); it != end; ++it)
2370       {
2371          WaveClip *clip = it->get();
2372          double offset = clip->GetPlayStartTime();
2373          if (maxOffset < offset)
2374             maxOffset = offset, rightmost = clip;
2375       }
2376       return rightmost;
2377    }
2378 }
2379 
GetClipIndex(const WaveClip * clip) const2380 int WaveTrack::GetClipIndex(const WaveClip* clip) const
2381 {
2382    int result;
2383    FindClip(mClips, clip, &result);
2384    return result;
2385 }
2386 
GetClipByIndex(int index)2387 WaveClip* WaveTrack::GetClipByIndex(int index)
2388 {
2389    if(index < (int)mClips.size())
2390       return mClips[index].get();
2391    else
2392       return nullptr;
2393 }
2394 
GetClipByIndex(int index) const2395 const WaveClip* WaveTrack::GetClipByIndex(int index) const
2396 {
2397    return const_cast<WaveTrack&>(*this).GetClipByIndex(index);
2398 }
2399 
GetNumClips() const2400 int WaveTrack::GetNumClips() const
2401 {
2402    return mClips.size();
2403 }
2404 
CanOffsetClips(const std::vector<WaveClip * > & clips,double amount,double * allowedAmount)2405 bool WaveTrack::CanOffsetClips(
2406    const std::vector<WaveClip*> &clips,
2407    double amount,
2408    double *allowedAmount /* = NULL */)
2409 {
2410    if (allowedAmount)
2411       *allowedAmount = amount;
2412 
2413    const auto &moving = [&](WaveClip *clip){
2414       // linear search might be improved, but expecting few moving clips
2415       // compared with the fixed clips
2416       return clips.end() != std::find( clips.begin(), clips.end(), clip );
2417    };
2418 
2419    for (const auto &c: mClips) {
2420       if ( moving( c.get() ) )
2421          continue;
2422       for (const auto clip : clips) {
2423          if (c->GetPlayStartTime() < clip->GetPlayEndTime() + amount &&
2424             c->GetPlayEndTime() > clip->GetPlayStartTime() + amount)
2425          {
2426             if (!allowedAmount)
2427                return false; // clips overlap
2428 
2429             if (amount > 0)
2430             {
2431                if (c->GetPlayStartTime() - clip->GetPlayEndTime() < *allowedAmount)
2432                   *allowedAmount = c->GetPlayStartTime() - clip->GetPlayEndTime();
2433                if (*allowedAmount < 0)
2434                   *allowedAmount = 0;
2435             } else
2436             {
2437                if (c->GetPlayEndTime() - clip->GetPlayStartTime() > *allowedAmount)
2438                   *allowedAmount = c->GetPlayEndTime() - clip->GetPlayStartTime();
2439                if (*allowedAmount > 0)
2440                   *allowedAmount = 0;
2441             }
2442          }
2443       }
2444    }
2445 
2446    if (allowedAmount)
2447    {
2448       if (*allowedAmount == amount)
2449          return true;
2450 
2451       // Check if the NEW calculated amount would not violate
2452       // any other constraint
2453       if (!CanOffsetClips(clips, *allowedAmount, nullptr)) {
2454          *allowedAmount = 0; // play safe and don't allow anything
2455          return false;
2456       }
2457       else
2458          return true;
2459    } else
2460       return true;
2461 }
2462 
CanInsertClip(WaveClip * clip,double & slideBy,double & tolerance) const2463 bool WaveTrack::CanInsertClip(
2464    WaveClip* clip,  double &slideBy, double &tolerance) const
2465 {
2466    for (const auto &c : mClips)
2467    {
2468       double d1 = c->GetPlayStartTime() - (clip->GetPlayEndTime()+slideBy);
2469       double d2 = (clip->GetPlayStartTime()+slideBy) - c->GetPlayEndTime();
2470       if ( (d1<0) &&  (d2<0) )
2471       {
2472          // clips overlap.
2473          // Try to rescue it.
2474          // The rescue logic is not perfect, and will typically
2475          // move the clip at most once.
2476          // We divide by 1000 rather than set to 0, to allow for
2477          // a second 'micro move' that is really about rounding error.
2478          if( -d1 < tolerance ){
2479             // right edge of clip overlaps slightly.
2480             // slide clip left a small amount.
2481             slideBy +=d1;
2482             tolerance /=1000;
2483          } else if( -d2 < tolerance ){
2484             // left edge of clip overlaps slightly.
2485             // slide clip right a small amount.
2486             slideBy -= d2;
2487             tolerance /=1000;
2488          }
2489          else
2490             return false; // clips overlap  No tolerance left.
2491       }
2492    }
2493 
2494    return true;
2495 }
2496 
2497 /*! @excsafety{Weak} */
Split(double t0,double t1)2498 void WaveTrack::Split( double t0, double t1 )
2499 {
2500    SplitAt( t0 );
2501    if( t0 != t1 )
2502       SplitAt( t1 );
2503 }
2504 
2505 /*! @excsafety{Weak} */
SplitAt(double t)2506 void WaveTrack::SplitAt(double t)
2507 {
2508    for (const auto &c : mClips)
2509    {
2510       if (c->WithinPlayRegion(t))
2511       {
2512          t = LongSamplesToTime(TimeToLongSamples(t));
2513          auto newClip = std::make_unique<WaveClip>( *c, mpFactory, true );
2514          c->TrimRightTo(t);// put t on a sample
2515          newClip->TrimLeftTo(t);
2516 
2517          // This could invalidate the iterators for the loop!  But we return
2518          // at once so it's okay
2519          mClips.push_back(std::move(newClip)); // transfer ownership
2520          return;
2521       }
2522    }
2523 }
2524 
UpdateLocationsCache() const2525 void WaveTrack::UpdateLocationsCache() const
2526 {
2527    auto clips = SortedClipArray();
2528 
2529    mDisplayLocationsCache.clear();
2530 
2531    // Count number of display locations
2532    int num = 0;
2533    {
2534       const WaveClip *prev = nullptr;
2535       for (const auto clip : clips)
2536       {
2537          //enough for estimation
2538          num += clip->NumCutLines();
2539 
2540          if (prev && fabs(prev->GetPlayEndTime() -
2541                           clip->GetPlayStartTime()) < WAVETRACK_MERGE_POINT_TOLERANCE)
2542             ++num;
2543          prev = clip;
2544       }
2545    }
2546 
2547    if (num == 0)
2548       return;
2549 
2550    // Alloc necessary number of display locations
2551    mDisplayLocationsCache.reserve(num);
2552 
2553    // Add all display locations to cache
2554    int curpos = 0;
2555 
2556    const WaveClip *previousClip = nullptr;
2557    for (const auto clip: clips)
2558    {
2559       for (const auto &cc : clip->GetCutLines())
2560       {
2561          auto cutlinePosition = clip->GetSequenceStartTime() + cc->GetSequenceStartTime();
2562          if (clip->WithinPlayRegion(cutlinePosition))
2563          {
2564              // Add cut line expander point
2565              mDisplayLocationsCache.push_back(WaveTrackLocation{
2566                 cutlinePosition,
2567                 WaveTrackLocation::locationCutLine
2568              });
2569          }
2570          // If cutline is skipped, we still need to count it
2571          // so that curpos match num at the end
2572          curpos++;
2573       }
2574 
2575       if (previousClip)
2576       {
2577          if (fabs(previousClip->GetPlayEndTime() - clip->GetPlayStartTime())
2578                                           < WAVETRACK_MERGE_POINT_TOLERANCE)
2579          {
2580             // Add merge point
2581             mDisplayLocationsCache.push_back(WaveTrackLocation{
2582                previousClip->GetPlayEndTime(),
2583                WaveTrackLocation::locationMergePoint,
2584                GetClipIndex(previousClip),
2585                GetClipIndex(clip)
2586             });
2587             curpos++;
2588          }
2589       }
2590 
2591       previousClip = clip;
2592    }
2593 
2594    wxASSERT(curpos == num);
2595 }
2596 
2597 // Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
2598 /*! @excsafety{Strong} */
ExpandCutLine(double cutLinePosition,double * cutlineStart,double * cutlineEnd)2599 void WaveTrack::ExpandCutLine(double cutLinePosition, double* cutlineStart,
2600                               double* cutlineEnd)
2601 {
2602    bool editClipCanMove = GetEditClipsCanMove();
2603 
2604    // Find clip which contains this cut line
2605    double start = 0, end = 0;
2606    auto pEnd = mClips.end();
2607    auto pClip = std::find_if( mClips.begin(), pEnd,
2608       [&](const WaveClipHolder &clip) {
2609          return clip->FindCutLine(cutLinePosition, &start, &end); } );
2610    if (pClip != pEnd)
2611    {
2612       auto &clip = *pClip;
2613       if (!editClipCanMove)
2614       {
2615          // We are not allowed to move the other clips, so see if there
2616          // is enough room to expand the cut line
2617          for (const auto &clip2: mClips)
2618          {
2619             if (clip2->GetPlayStartTime() > clip->GetPlayStartTime() &&
2620                 clip->GetPlayEndTime() + end - start > clip2->GetPlayStartTime())
2621                // Strong-guarantee in case of this path
2622                throw SimpleMessageBoxException{
2623                   ExceptionType::BadUserAction,
2624                   XO("There is not enough room available to expand the cut line"),
2625                   XO("Warning"),
2626                   "Error:_Insufficient_space_in_track"
2627                };
2628           }
2629       }
2630 
2631       clip->ExpandCutLine(cutLinePosition);
2632 
2633       // Strong-guarantee provided that the following gives No-fail-guarantee
2634 
2635       if (cutlineStart)
2636          *cutlineStart = start;
2637       if (cutlineEnd)
2638          *cutlineEnd = end;
2639 
2640       // Move clips which are to the right of the cut line
2641       if (editClipCanMove)
2642       {
2643          for (const auto &clip2 : mClips)
2644          {
2645             if (clip2->GetPlayStartTime() > clip->GetPlayStartTime())
2646                clip2->Offset(end - start);
2647          }
2648       }
2649    }
2650 }
2651 
RemoveCutLine(double cutLinePosition)2652 bool WaveTrack::RemoveCutLine(double cutLinePosition)
2653 {
2654    for (const auto &clip : mClips)
2655       if (clip->RemoveCutLine(cutLinePosition))
2656          return true;
2657 
2658    return false;
2659 }
2660 
2661 /*! @excsafety{Strong} */
MergeClips(int clipidx1,int clipidx2)2662 void WaveTrack::MergeClips(int clipidx1, int clipidx2)
2663 {
2664    WaveClip* clip1 = GetClipByIndex(clipidx1);
2665    WaveClip* clip2 = GetClipByIndex(clipidx2);
2666 
2667    if (!clip1 || !clip2) // Could happen if one track of a linked pair had a split and the other didn't.
2668       return; // Don't throw, just do nothing.
2669 
2670    // Append data from second clip to first clip
2671    // use Strong-guarantee
2672    clip1->Paste(clip1->GetPlayEndTime(), clip2);
2673 
2674    // use No-fail-guarantee for the rest
2675    // Delete second clip
2676    auto it = FindClip(mClips, clip2);
2677    mClips.erase(it);
2678 }
2679 
2680 /*! @excsafety{Weak} -- Partial completion may leave clips at differing sample rates!
2681 */
Resample(int rate,ProgressDialog * progress)2682 void WaveTrack::Resample(int rate, ProgressDialog *progress)
2683 {
2684    for (const auto &clip : mClips)
2685       clip->Resample(rate, progress);
2686 
2687    mRate = rate;
2688 }
2689 
2690 namespace {
2691    template < typename Cont1, typename Cont2 >
2692    Cont1 FillSortedClipArray(const Cont2& mClips)
2693    {
2694       Cont1 clips;
2695       for (const auto &clip : mClips)
2696          clips.push_back(clip.get());
2697       std::sort(clips.begin(), clips.end(),
2698          [](const WaveClip *a, const WaveClip *b)
__anon8aaf33e21102(const WaveClip *a, const WaveClip *b) 2699       { return a->GetPlayStartTime() < b->GetPlayStartTime(); });
2700       return clips;
2701    }
2702 }
2703 
SortedClipArray()2704 WaveClipPointers WaveTrack::SortedClipArray()
2705 {
2706    return FillSortedClipArray<WaveClipPointers>(mClips);
2707 }
2708 
SortedClipArray() const2709 WaveClipConstPointers WaveTrack::SortedClipArray() const
2710 {
2711    return FillSortedClipArray<WaveClipConstPointers>(mClips);
2712 }
2713 
2714 ///Deletes all clips' wavecaches.  Careful, This may not be threadsafe.
ClearWaveCaches()2715 void WaveTrack::ClearWaveCaches()
2716 {
2717    for (const auto &clip : mClips)
2718       clip->ClearWaveCache();
2719 }
2720 
~WaveTrackCache()2721 WaveTrackCache::~WaveTrackCache()
2722 {
2723 }
2724 
SetTrack(const std::shared_ptr<const WaveTrack> & pTrack)2725 void WaveTrackCache::SetTrack(const std::shared_ptr<const WaveTrack> &pTrack)
2726 {
2727    if (mPTrack != pTrack) {
2728       if (pTrack) {
2729          mBufferSize = pTrack->GetMaxBlockSize();
2730          if (!mPTrack ||
2731              mPTrack->GetMaxBlockSize() != mBufferSize) {
2732             Free();
2733             mBuffers[0].data = Floats{ mBufferSize };
2734             mBuffers[1].data = Floats{ mBufferSize };
2735          }
2736       }
2737       else
2738          Free();
2739       mPTrack = pTrack;
2740       mNValidBuffers = 0;
2741    }
2742 }
2743 
GetFloats(sampleCount start,size_t len,bool mayThrow)2744 const float *WaveTrackCache::GetFloats(
2745    sampleCount start, size_t len, bool mayThrow)
2746 {
2747    constexpr auto format = floatSample;
2748    if (format == floatSample && len > 0) {
2749       const auto end = start + len;
2750 
2751       bool fillFirst = (mNValidBuffers < 1);
2752       bool fillSecond = (mNValidBuffers < 2);
2753 
2754       // Discard cached results that we no longer need
2755       if (mNValidBuffers > 0 &&
2756           (end <= mBuffers[0].start ||
2757            start >= mBuffers[mNValidBuffers - 1].end())) {
2758          // Complete miss
2759          fillFirst = true;
2760          fillSecond = true;
2761       }
2762       else if (mNValidBuffers == 2 &&
2763                start >= mBuffers[1].start &&
2764                end > mBuffers[1].end()) {
2765          // Request starts in the second buffer and extends past it.
2766          // Discard the first buffer.
2767          // (But don't deallocate the buffer space.)
2768          mBuffers[0] .swap ( mBuffers[1] );
2769          fillSecond = true;
2770          mNValidBuffers = 1;
2771       }
2772       else if (mNValidBuffers > 0 &&
2773          start < mBuffers[0].start &&
2774          0 <= mPTrack->GetBlockStart(start)) {
2775          // Request is not a total miss but starts before the cache,
2776          // and there is a clip to fetch from.
2777          // Not the access pattern for drawing spectrogram or playback,
2778          // but maybe scrubbing causes this.
2779          // Move the first buffer into second place, and later
2780          // refill the first.
2781          // (This case might be useful when marching backwards through
2782          // the track, as with scrubbing.)
2783          mBuffers[0] .swap ( mBuffers[1] );
2784          fillFirst = true;
2785          fillSecond = false;
2786          // Cache is not in a consistent state yet
2787          mNValidBuffers = 0;
2788       }
2789 
2790       // Refill buffers as needed
2791       if (fillFirst) {
2792          const auto start0 = mPTrack->GetBlockStart(start);
2793          if (start0 >= 0) {
2794             const auto len0 = mPTrack->GetBestBlockSize(start0);
2795             wxASSERT(len0 <= mBufferSize);
2796             if (!mPTrack->GetFloats(
2797                   mBuffers[0].data.get(), start0, len0,
2798                   fillZero, mayThrow))
2799                return nullptr;
2800             mBuffers[0].start = start0;
2801             mBuffers[0].len = len0;
2802             if (!fillSecond &&
2803                 mBuffers[0].end() != mBuffers[1].start)
2804                fillSecond = true;
2805             // Keep the partially updated state consistent:
2806             mNValidBuffers = fillSecond ? 1 : 2;
2807          }
2808          else {
2809             // Request may fall between the clips of a track.
2810             // Invalidate all.  WaveTrack::Get() will return zeroes.
2811             mNValidBuffers = 0;
2812             fillSecond = false;
2813          }
2814       }
2815       wxASSERT(!fillSecond || mNValidBuffers > 0);
2816       if (fillSecond) {
2817          mNValidBuffers = 1;
2818          const auto end0 = mBuffers[0].end();
2819          if (end > end0) {
2820             const auto start1 = mPTrack->GetBlockStart(end0);
2821             if (start1 == end0) {
2822                const auto len1 = mPTrack->GetBestBlockSize(start1);
2823                wxASSERT(len1 <= mBufferSize);
2824                if (!mPTrack->GetFloats(mBuffers[1].data.get(), start1, len1, fillZero, mayThrow))
2825                   return nullptr;
2826                mBuffers[1].start = start1;
2827                mBuffers[1].len = len1;
2828                mNValidBuffers = 2;
2829             }
2830          }
2831       }
2832       wxASSERT(mNValidBuffers < 2 || mBuffers[0].end() == mBuffers[1].start);
2833 
2834       samplePtr buffer = nullptr; // will point into mOverlapBuffer
2835       auto remaining = len;
2836 
2837       // Possibly get an initial portion that is uncached
2838 
2839       // This may be negative
2840       const auto initLen =
2841          mNValidBuffers < 1 ? sampleCount( len )
2842             : std::min(sampleCount( len ), mBuffers[0].start - start);
2843 
2844       if (initLen > 0) {
2845          // This might be fetching zeroes between clips
2846          mOverlapBuffer.Resize(len, format);
2847          // initLen is not more than len:
2848          auto sinitLen = initLen.as_size_t();
2849          if (!mPTrack->GetFloats(
2850             // See comment below about casting
2851             reinterpret_cast<float *>(mOverlapBuffer.ptr()),
2852             start, sinitLen, fillZero, mayThrow))
2853             return nullptr;
2854          wxASSERT( sinitLen <= remaining );
2855          remaining -= sinitLen;
2856          start += initLen;
2857          buffer = mOverlapBuffer.ptr() + sinitLen * SAMPLE_SIZE(format);
2858       }
2859 
2860       // Now satisfy the request from the buffers
2861       for (int ii = 0; ii < mNValidBuffers && remaining > 0; ++ii) {
2862          const auto starti = start - mBuffers[ii].start;
2863          // Treatment of initLen above establishes this loop invariant,
2864          // and statements below preserve it:
2865          wxASSERT(starti >= 0);
2866 
2867          // This may be negative
2868          const auto leni =
2869             std::min( sampleCount( remaining ), mBuffers[ii].len - starti );
2870          if (initLen <= 0 && leni == len) {
2871             // All is contiguous already.  We can completely avoid copying
2872             // leni is nonnegative, therefore start falls within mBuffers[ii],
2873             // so starti is bounded between 0 and buffer length
2874             return mBuffers[ii].data.get() + starti.as_size_t() ;
2875          }
2876          else if (leni > 0) {
2877             // leni is nonnegative, therefore start falls within mBuffers[ii]
2878             // But we can't satisfy all from one buffer, so copy
2879             if (!buffer) {
2880                mOverlapBuffer.Resize(len, format);
2881                buffer = mOverlapBuffer.ptr();
2882             }
2883             // leni is positive and not more than remaining
2884             const size_t size = sizeof(float) * leni.as_size_t();
2885             // starti is less than mBuffers[ii].len and nonnegative
2886             memcpy(buffer, mBuffers[ii].data.get() + starti.as_size_t(), size);
2887             wxASSERT( leni <= remaining );
2888             remaining -= leni.as_size_t();
2889             start += leni;
2890             buffer += size;
2891          }
2892       }
2893 
2894       if (remaining > 0) {
2895          // Very big request!
2896          // Fall back to direct fetch
2897          if (!buffer) {
2898             mOverlapBuffer.Resize(len, format);
2899             buffer = mOverlapBuffer.ptr();
2900          }
2901          // See comment below about casting
2902          if (!mPTrack->GetFloats( reinterpret_cast<float*>(buffer),
2903             start, remaining, fillZero, mayThrow))
2904             return 0;
2905       }
2906 
2907       // Overlap buffer was meant for the more general support of sample formats
2908       // besides float, which explains the cast
2909       return reinterpret_cast<const float*>(mOverlapBuffer.ptr());
2910    }
2911    else {
2912 #if 0
2913       // Cache works only for float format.
2914       mOverlapBuffer.Resize(len, format);
2915       if (mPTrack->Get(mOverlapBuffer.ptr(), format, start, len, fillZero, mayThrow))
2916          return mOverlapBuffer.ptr();
2917 #else
2918       // No longer handling other than float format.  Therefore len is 0.
2919 #endif
2920       return nullptr;
2921    }
2922 }
2923 
Free()2924 void WaveTrackCache::Free()
2925 {
2926    mBuffers[0].Free();
2927    mBuffers[1].Free();
2928    mOverlapBuffer.Free();
2929    mNValidBuffers = 0;
2930 }
2931 
operator ++()2932 auto WaveTrack::AllClipsIterator::operator ++ () -> AllClipsIterator &
2933 {
2934    // The unspecified sequence is a post-order, but there is no
2935    // promise whether sister nodes are ordered in time.
2936    if ( !mStack.empty() ) {
2937       auto &pair =  mStack.back();
2938       if ( ++pair.first == pair.second ) {
2939          mStack.pop_back();
2940       }
2941       else
2942          push( (*pair.first)->GetCutLines() );
2943    }
2944 
2945    return *this;
2946 }
2947 
push(WaveClipHolders & clips)2948 void WaveTrack::AllClipsIterator::push( WaveClipHolders &clips )
2949 {
2950    auto pClips = &clips;
2951    while (!pClips->empty()) {
2952       auto first = pClips->begin();
2953       mStack.push_back( Pair( first, pClips->end() ) );
2954       pClips = &(*first)->GetCutLines();
2955    }
2956 }
2957 
2958 #include "SampleBlock.h"
VisitBlocks(TrackList & tracks,BlockVisitor visitor,SampleBlockIDSet * pIDs)2959 void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
2960    SampleBlockIDSet *pIDs)
2961 {
2962    for (auto wt : tracks.Any< const WaveTrack >()) {
2963       // Scan all clips within current track
2964       for(const auto &clip : wt->GetAllClips()) {
2965          // Scan all sample blocks within current clip
2966          auto blocks = clip->GetSequenceBlockArray();
2967          for (const auto &block : *blocks) {
2968             auto &pBlock = block.sb;
2969             if ( pBlock ) {
2970                if ( pIDs && !pIDs->insert(pBlock->GetBlockID()).second )
2971                   continue;
2972                if ( visitor )
2973                   visitor( *pBlock );
2974             }
2975          }
2976       }
2977    }
2978 }
2979 
InspectBlocks(const TrackList & tracks,BlockInspector inspector,SampleBlockIDSet * pIDs)2980 void InspectBlocks(const TrackList &tracks, BlockInspector inspector,
2981    SampleBlockIDSet *pIDs)
2982 {
2983    VisitBlocks(
2984       const_cast<TrackList &>(tracks), std::move( inspector ), pIDs );
2985 }
2986 
2987 #include "Project.h"
2988 #include "SampleBlock.h"
__anon8aaf33e21202( AudacityProject &project ) 2989 static auto TrackFactoryFactory = []( AudacityProject &project ) {
2990    return std::make_shared< WaveTrackFactory >(
2991       ProjectRate::Get( project ),
2992       SampleBlockFactory::New( project ) );
2993 };
2994 
2995 static const AudacityProject::AttachedObjects::RegisteredFactory key2{
2996    TrackFactoryFactory
2997 };
2998 
Get(AudacityProject & project)2999 WaveTrackFactory &WaveTrackFactory::Get( AudacityProject &project )
3000 {
3001    return project.AttachedObjects::Get< WaveTrackFactory >( key2 );
3002 }
3003 
Get(const AudacityProject & project)3004 const WaveTrackFactory &WaveTrackFactory::Get( const AudacityProject &project )
3005 {
3006    return Get( const_cast< AudacityProject & >( project ) );
3007 }
3008 
Reset(AudacityProject & project)3009 WaveTrackFactory &WaveTrackFactory::Reset( AudacityProject &project )
3010 {
3011    auto result = TrackFactoryFactory( project );
3012    project.AttachedObjects::Assign( key2, result );
3013    return *result;
3014 }
3015 
Destroy(AudacityProject & project)3016 void WaveTrackFactory::Destroy( AudacityProject &project )
3017 {
3018    project.AttachedObjects::Assign( key2, nullptr );
3019 }
3020 
3021 ProjectFormatExtensionsRegistry::Extension smartClipsExtension(
3022    [](const AudacityProject& project) -> ProjectFormatVersion
__anon8aaf33e21302(const AudacityProject& project) 3023    {
3024       const TrackList& trackList = TrackList::Get(project);
3025 
3026       for (auto wt : trackList.Any<const WaveTrack>())
3027       {
3028          for (const auto& clip : wt->GetAllClips())
3029          {
3030             if (clip->GetTrimLeft() > 0.0 || clip->GetTrimRight() > 0.0)
3031                return { 3, 1, 0, 0 };
3032          }
3033       }
3034 
3035       return BaseProjectFormatVersion;
3036    }
3037 );
3038