Audacity 3.2.0
LabelTrack.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 LabelTrack.cpp
6
7 Dominic Mazzoni
8 James Crook
9 Jun Wan
10
11*******************************************************************//****************************************************************//*******************************************************************/
30
31
32#include "LabelTrack.h"
33
34#include <algorithm>
35#include <limits.h>
36#include <float.h>
37
38#include <wx/log.h>
39#include <wx/tokenzr.h>
40
41#include "Prefs.h"
42#include "Project.h"
44
45#include "TimeWarper.h"
47
48wxDEFINE_EVENT(EVT_LABELTRACK_ADDITION, LabelTrackEvent);
49wxDEFINE_EVENT(EVT_LABELTRACK_DELETION, LabelTrackEvent);
50wxDEFINE_EVENT(EVT_LABELTRACK_PERMUTED, LabelTrackEvent);
51wxDEFINE_EVENT(EVT_LABELTRACK_SELECTION, LabelTrackEvent);
52
54 "labeltrack",
56};
57
59{
60 return _("Labels");
61}
62
64{
65 auto &tracks = TrackList::Get( project );
66 auto result = tracks.Add(std::make_shared<LabelTrack>());
67 result->AttachedTrackObjects::BuildAll();
68 return result;
69}
70
71LabelTrack* LabelTrack::Create(TrackList& trackList, const wxString& name)
72{
73 auto track = std::make_shared<LabelTrack>();
74 track->SetName(name);
75 trackList.Add(track);
76 return track.get();
77}
78
80{
81 return Create(trackList, trackList.MakeUniqueTrackName(GetDefaultName()));
82}
83
85 Track(),
86 mClipLen(0.0),
87 miLastLabel(-1)
88{
89}
90
92 : Track(orig, std::move(a))
93 , mClipLen(0.0)
94{
95 for (auto &original: orig.mLabels) {
96 LabelStruct l { original.selectedRegion, original.title };
97 mLabels.push_back(l);
98 }
99}
100
102{
103 static Track::TypeInfo info{
104 { "label", "label", XO("Label Track") }, true, &Track::ClassTypeInfo() };
105 return info;
106}
107
108auto LabelTrack::GetTypeInfo() const -> const TypeInfo &
109{
110 return typeInfo();
111}
112
114{
115 return typeInfo();
116}
117
119{
120 auto pNewTrack = std::make_shared<LabelTrack>();
121 pNewTrack->Init(*this);
122 pNewTrack->Paste(0.0, this);
123 return pNewTrack;
124}
125
126template<typename IntervalType>
127static IntervalType DoMakeInterval(const LabelStruct &label, size_t index)
128{
129 return {
130 label.getT0(), label.getT1(),
131 std::make_unique<LabelTrack::IntervalData>( index ) };
132}
133
134auto LabelTrack::MakeInterval( size_t index ) const -> ConstInterval
135{
136 return DoMakeInterval<ConstInterval>(mLabels[index], index);
137}
138
139auto LabelTrack::MakeInterval( size_t index ) -> Interval
140{
141 return DoMakeInterval<Interval>(mLabels[index], index);
142}
143
144template< typename Container, typename LabelTrack >
145static Container DoMakeIntervals(LabelTrack &track)
146{
147 Container result;
148 for (size_t ii = 0, nn = track.GetNumLabels(); ii < nn; ++ii)
149 result.emplace_back( track.MakeInterval( ii ) );
150 return result;
151}
152
154{
155 return DoMakeIntervals<ConstIntervals>(*this);
156}
157
159{
160 return DoMakeIntervals<Intervals>(*this);
161}
162
163void LabelTrack::SetLabel( size_t iLabel, const LabelStruct &newLabel )
164{
165 if( iLabel >= mLabels.size() ) {
166 wxASSERT( false );
167 mLabels.resize( iLabel + 1 );
168 }
169 mLabels[ iLabel ] = newLabel;
170}
171
173{
174}
175
176void LabelTrack::SetOffset(double dOffset)
177{
178 for (auto &labelStruct: mLabels)
179 labelStruct.selectedRegion.move(dOffset);
180}
181
182void LabelTrack::Clear(double b, double e)
183{
184 // May DELETE labels, so use subscripts to iterate
185 for (size_t i = 0; i < mLabels.size(); ++i) {
186 auto &labelStruct = mLabels[i];
188 labelStruct.RegionRelation(b, e, this);
189 if (relation == LabelStruct::BEFORE_LABEL)
190 labelStruct.selectedRegion.move(- (e-b));
191 else if (relation == LabelStruct::SURROUNDS_LABEL) {
192 DeleteLabel( i );
193 --i;
194 }
195 else if (relation == LabelStruct::ENDS_IN_LABEL)
196 labelStruct.selectedRegion.setTimes(
197 b,
198 labelStruct.getT1() - (e - b));
199 else if (relation == LabelStruct::BEGINS_IN_LABEL)
200 labelStruct.selectedRegion.setT1(b);
201 else if (relation == LabelStruct::WITHIN_LABEL)
202 labelStruct.selectedRegion.moveT1( - (e-b));
203 }
204}
205
206#if 0
207//used when we want to use clear only on the labels
208bool LabelTrack::SplitDelete(double b, double e)
209{
210 // May DELETE labels, so use subscripts to iterate
211 for (size_t i = 0, len = mLabels.size(); i < len; ++i) {
212 auto &labelStruct = mLabels[i];
214 labelStruct.RegionRelation(b, e, this);
215 if (relation == LabelStruct::SURROUNDS_LABEL) {
216 DeleteLabel(i);
217 --i;
218 }
219 else if (relation == LabelStruct::WITHIN_LABEL)
220 labelStruct.selectedRegion.moveT1( - (e-b));
221 else if (relation == LabelStruct::ENDS_IN_LABEL)
222 labelStruct.selectedRegion.setT0(e);
223 else if (relation == LabelStruct::BEGINS_IN_LABEL)
224 labelStruct.selectedRegion.setT1(b);
225 }
226
227 return true;
228}
229#endif
230
231void LabelTrack::ShiftLabelsOnInsert(double length, double pt)
232{
233 for (auto &labelStruct: mLabels) {
235 labelStruct.RegionRelation(pt, pt, this);
236
237 if (relation == LabelStruct::BEFORE_LABEL)
238 labelStruct.selectedRegion.move(length);
239 else if (relation == LabelStruct::WITHIN_LABEL)
240 labelStruct.selectedRegion.moveT1(length);
241 }
242}
243
244void LabelTrack::ChangeLabelsOnReverse(double b, double e)
245{
246 for (auto &labelStruct: mLabels) {
247 if (labelStruct.RegionRelation(b, e, this) ==
249 {
250 double aux = b + (e - labelStruct.getT1());
251 labelStruct.selectedRegion.setTimes(
252 aux,
253 e - (labelStruct.getT0() - b));
254 }
255 }
256 SortLabels();
257}
258
259void LabelTrack::ScaleLabels(double b, double e, double change)
260{
261 for (auto &labelStruct: mLabels) {
262 labelStruct.selectedRegion.setTimes(
263 AdjustTimeStampOnScale(labelStruct.getT0(), b, e, change),
264 AdjustTimeStampOnScale(labelStruct.getT1(), b, e, change));
265 }
266}
267
268double LabelTrack::AdjustTimeStampOnScale(double t, double b, double e, double change)
269{
270//t is the time stamp we'll be changing
271//b and e are the selection start and end
272
273 if (t < b){
274 return t;
275 }else if (t > e){
276 double shift = (e-b)*change - (e-b);
277 return t + shift;
278 }else{
279 double shift = (t-b)*change - (t-b);
280 return t + shift;
281 }
282}
283
284// Move the labels in the track according to the given TimeWarper.
285// (If necessary this could be optimised by ignoring labels that occur before a
286// specified time, as in most cases they don't need to move.)
288 for (auto &labelStruct: mLabels) {
289 labelStruct.selectedRegion.setTimes(
290 warper.Warp(labelStruct.getT0()),
291 warper.Warp(labelStruct.getT1()));
292 }
293
294 // This should not be needed, assuming the warper is nondecreasing, but
295 // let's not assume too much.
296 SortLabels();
297}
298
300 const wxString& aTitle)
301: selectedRegion(region)
302, title(aTitle)
303{
304 updated = false;
305 width = 0;
306 x = 0;
307 x1 = 0;
308 xText = 0;
309 y = 0;
310}
311
313 double t0, double t1,
314 const wxString& aTitle)
315: selectedRegion(region)
316, title(aTitle)
317{
318 // Overwrite the times
319 selectedRegion.setTimes(t0, t1);
320
321 updated = false;
322 width = 0;
323 x = 0;
324 x1 = 0;
325 xText = 0;
326 y = 0;
327}
328
330{
331 bool selected = GetSelected();
333 if ( selected != GetSelected() ) {
334 LabelTrackEvent evt{
335 EVT_LABELTRACK_SELECTION, SharedPointer<LabelTrack>(), {}, -1, -1
336 };
337 ProcessEvent( evt );
338 }
339}
340
342{
343 return mOffset;
344}
345
347{
348 if (mLabels.empty())
349 return 0.0;
350 else
351 return mLabels[0].getT0();
352}
353
355{
356 //we need to scan through all the labels, because the last
357 //label might not have the right-most end (if there is overlap).
358 if (mLabels.empty())
359 return 0.0;
360
361 double end = 0.0;
362 for (auto &labelStruct: mLabels) {
363 const double t1 = labelStruct.getT1();
364 if(t1 > end)
365 end = t1;
366 }
367 return end;
368}
369
371{
372 auto result = std::make_shared<LabelTrack>(*this, ProtectedCreationArg{});
373 result->Init(*this);
374 return result;
375}
376
377// Adjust label's left or right boundary, depending which is requested.
378// Return true iff the label flipped.
379bool LabelStruct::AdjustEdge( int iEdge, double fNewTime)
380{
381 updated = true;
382 if( iEdge < 0 )
383 return selectedRegion.setT0(fNewTime);
384 else
385 return selectedRegion.setT1(fNewTime);
386}
387
388// We're moving the label. Adjust both left and right edge.
389void LabelStruct::MoveLabel( int iEdge, double fNewTime)
390{
391 double fTimeSpan = getDuration();
392
393 if( iEdge < 0 )
394 {
395 selectedRegion.setTimes(fNewTime, fNewTime+fTimeSpan);
396 }
397 else
398 {
399 selectedRegion.setTimes(fNewTime-fTimeSpan, fNewTime);
400 }
401 updated = true;
402}
403
404LabelStruct LabelStruct::Import(wxTextFile &file, int &index)
405{
407 wxString title;
408 static const wxString continuation{ wxT("\\") };
409
410 wxString firstLine = file.GetLine(index++);
411
412 {
413 // Assume tab is an impossible character within the exported text
414 // of the label, so can be only a delimiter. But other white space may
415 // be part of the label text.
416 wxStringTokenizer toker { firstLine, wxT("\t") };
417
418 //get the timepoint of the left edge of the label.
419 auto token = toker.GetNextToken();
420
421 double t0;
422 if (!Internat::CompatibleToDouble(token, &t0))
423 throw BadFormatException{};
424
425 token = toker.GetNextToken();
426
427 double t1;
428 if (!Internat::CompatibleToDouble(token, &t1))
429 //s1 is not a number.
430 t1 = t0; //This is a one-sided label; t1 == t0.
431 else
432 token = toker.GetNextToken();
433
434 sr.setTimes( t0, t1 );
435
436 title = token;
437 }
438
439 // Newer selection fields are written on additional lines beginning with
440 // '\' which is an impossible numerical character that older versions of
441 // audacity will ignore. Test for the presence of such a line and then
442 // parse it if we can.
443
444 // There may also be additional continuation lines from future formats that
445 // we ignore.
446
447 // Advance index over all continuation lines first, before we might throw
448 // any exceptions.
449 int index2 = index;
450 while (index < (int)file.GetLineCount() &&
451 file.GetLine(index).StartsWith(continuation))
452 ++index;
453
454 if (index2 < index) {
455 wxStringTokenizer toker { file.GetLine(index2++), wxT("\t") };
456 auto token = toker.GetNextToken();
457 if (token != continuation)
458 throw BadFormatException{};
459
460 token = toker.GetNextToken();
461 double f0;
462 if (!Internat::CompatibleToDouble(token, &f0))
463 throw BadFormatException{};
464
465 token = toker.GetNextToken();
466 double f1;
467 if (!Internat::CompatibleToDouble(token, &f1))
468 throw BadFormatException{};
469
470 sr.setFrequencies(f0, f1);
471 }
472
473 return LabelStruct{ sr, title };
474}
475
476void LabelStruct::Export(wxTextFile &file) const
477{
478 file.AddLine(wxString::Format(wxT("%s\t%s\t%s"),
479 Internat::ToString(getT0(), FLT_DIG),
480 Internat::ToString(getT1(), FLT_DIG),
481 title
482 ));
483
484 // Do we need more lines?
485 auto f0 = selectedRegion.f0();
486 auto f1 = selectedRegion.f1();
490 return;
491
492 // Write a \ character at the start of a second line,
493 // so that earlier versions of Audacity ignore it.
494 file.AddLine(wxString::Format(wxT("\\\t%s\t%s"),
495 Internat::ToString(f0, FLT_DIG),
496 Internat::ToString(f1, FLT_DIG)
497 ));
498
499 // Additional lines in future formats should also start with '\'.
500}
501
503 double reg_t0, double reg_t1, const LabelTrack * WXUNUSED(parent)) const
505{
506 bool retainLabels = false;
507
508 wxASSERT(reg_t0 <= reg_t1);
509 gPrefs->Read(wxT("/GUI/RetainLabels"), &retainLabels);
510
511 if(retainLabels) {
512
513 // Desired behavior for edge cases: The length of the selection is smaller
514 // than the length of the label if the selection is within the label or
515 // matching exactly a (region) label.
516
517 if (reg_t0 < getT0() && reg_t1 > getT1())
518 return SURROUNDS_LABEL;
519 else if (reg_t1 < getT0())
520 return BEFORE_LABEL;
521 else if (reg_t0 > getT1())
522 return AFTER_LABEL;
523
524 else if (reg_t0 >= getT0() && reg_t0 <= getT1() &&
525 reg_t1 >= getT0() && reg_t1 <= getT1())
526 return WITHIN_LABEL;
527
528 else if (reg_t0 >= getT0() && reg_t0 <= getT1())
529 return BEGINS_IN_LABEL;
530 else
531 return ENDS_IN_LABEL;
532
533 } else {
534
535 // AWD: Desired behavior for edge cases: point labels bordered by the
536 // selection are included within it. Region labels are included in the
537 // selection to the extent that the selection covers them; specifically,
538 // they're not included at all if the selection borders them, and they're
539 // fully included if the selection covers them fully, even if it just
540 // borders their endpoints. This is just one of many possible schemes.
541
542 // The first test catches bordered point-labels and selected-through
543 // region-labels; move it to third and selection edges become inclusive
544 // WRT point-labels.
545 if (reg_t0 <= getT0() && reg_t1 >= getT1())
546 return SURROUNDS_LABEL;
547 else if (reg_t1 <= getT0())
548 return BEFORE_LABEL;
549 else if (reg_t0 >= getT1())
550 return AFTER_LABEL;
551
552 // At this point, all point labels should have returned.
553
554 else if (reg_t0 > getT0() && reg_t0 < getT1() &&
555 reg_t1 > getT0() && reg_t1 < getT1())
556 return WITHIN_LABEL;
557
558 // Knowing that none of the other relations match simplifies remaining
559 // tests
560 else if (reg_t0 > getT0() && reg_t0 < getT1())
561 return BEGINS_IN_LABEL;
562 else
563 return ENDS_IN_LABEL;
564
565 }
566}
567
569void LabelTrack::Export(wxTextFile & f) const
570{
571 // PRL: to do: export other selection fields
572 for (auto &labelStruct: mLabels)
573 labelStruct.Export(f);
574}
575
577void LabelTrack::Import(wxTextFile & in)
578{
579 int lines = in.GetLineCount();
580
581 mLabels.clear();
582 mLabels.reserve(lines);
583
584 //Currently, we expect a tag file to have two values and a label
585 //on each line. If the second token is not a number, we treat
586 //it as a single-value label.
587 bool error = false;
588 for (int index = 0; index < lines;) {
589 try {
590 // Let LabelStruct::Import advance index
591 LabelStruct l { LabelStruct::Import(in, index) };
592 mLabels.push_back(l);
593 }
594 catch(const LabelStruct::BadFormatException&) { error = true; }
595 }
596 if (error)
597 ::AudacityMessageBox( XO("One or more saved labels could not be read.") );
598 SortLabels();
599}
600
601bool LabelTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
602{
603 if (tag == "label") {
604
605 SelectedRegion selectedRegion;
606 wxString title;
607
608 // loop through attrs, which is a null-terminated list of
609 // attribute-value pairs
610 for (auto pair : attrs)
611 {
612 auto attr = pair.first;
613 auto value = pair.second;
614
615 if (selectedRegion.HandleXMLAttribute(attr, value, "t", "t1"))
616 ;
617 // Bug 1905 no longer applies, as valueView has no limits anyway
618 else if (attr == "title")
619 title = value.ToWString();
620
621 } // while
622
623 // Handle files created by Audacity 1.1. Labels in Audacity 1.1
624 // did not have separate start- and end-times.
625 // PRL: this superfluous now, given class SelectedRegion's internal
626 // consistency guarantees
627 //if (selectedRegion.t1() < 0)
628 // selectedRegion.collapseToT0();
629
630 LabelStruct l { selectedRegion, title };
631 mLabels.push_back(l);
632
633 return true;
634 }
635 else if (tag == "labeltrack") {
636 long nValue = -1;
637 for (auto pair : attrs)
638 {
639 auto attr = pair.first;
640 auto value = pair.second;
641
642 if (this->Track::HandleCommonXMLAttribute(attr, value))
643 ;
644 else if (attr == "numlabels" && value.TryGet(nValue))
645 {
646 if (nValue < 0)
647 {
648 wxLogWarning(wxT("Project shows negative number of labels: %d"), nValue);
649 return false;
650 }
651 mLabels.clear();
652 mLabels.reserve(nValue);
653 }
654 }
655
656 return true;
657 }
658
659 return false;
660}
661
662XMLTagHandler *LabelTrack::HandleXMLChild(const std::string_view& tag)
663{
664 if (tag == "label")
665 return this;
666 else
667 return NULL;
668}
669
670void LabelTrack::WriteXML(XMLWriter &xmlFile) const
671// may throw
672{
673 int len = mLabels.size();
674
675 xmlFile.StartTag(wxT("labeltrack"));
676 this->Track::WriteCommonXMLAttributes( xmlFile );
677 xmlFile.WriteAttr(wxT("numlabels"), len);
678
679 for (auto &labelStruct: mLabels) {
680 xmlFile.StartTag(wxT("label"));
681 labelStruct.getSelectedRegion()
682 .WriteXMLAttributes(xmlFile, "t", "t1");
683 // PRL: to do: write other selection fields
684 xmlFile.WriteAttr(wxT("title"), labelStruct.title);
685 xmlFile.EndTag(wxT("label"));
686 }
687
688 xmlFile.EndTag(wxT("labeltrack"));
689}
690
691Track::Holder LabelTrack::Cut(double t0, double t1)
692{
693 auto tmp = Copy(t0, t1);
694
695 Clear(t0, t1);
696
697 return tmp;
698}
699
700#if 0
701Track::Holder LabelTrack::SplitCut(double t0, double t1)
702{
703 // SplitCut() == Copy() + SplitDelete()
704
705 Track::Holder tmp = Copy(t0, t1);
706
707 if (!SplitDelete(t0, t1))
708 return {};
709
710 return tmp;
711}
712#endif
713
714Track::Holder LabelTrack::Copy(double t0, double t1, bool) const
715{
716 auto tmp = std::make_shared<LabelTrack>();
717 tmp->Init(*this);
718 const auto lt = static_cast<LabelTrack*>(tmp.get());
719
720 for (auto &labelStruct: mLabels) {
722 labelStruct.RegionRelation(t0, t1, this);
723 if (relation == LabelStruct::SURROUNDS_LABEL) {
724 LabelStruct l {
725 labelStruct.selectedRegion,
726 labelStruct.getT0() - t0,
727 labelStruct.getT1() - t0,
728 labelStruct.title
729 };
730 lt->mLabels.push_back(l);
731 }
732 else if (relation == LabelStruct::WITHIN_LABEL) {
733 LabelStruct l {
734 labelStruct.selectedRegion,
735 0,
736 t1-t0,
737 labelStruct.title
738 };
739 lt->mLabels.push_back(l);
740 }
741 else if (relation == LabelStruct::BEGINS_IN_LABEL) {
742 LabelStruct l {
743 labelStruct.selectedRegion,
744 0,
745 labelStruct.getT1() - t0,
746 labelStruct.title
747 };
748 lt->mLabels.push_back(l);
749 }
750 else if (relation == LabelStruct::ENDS_IN_LABEL) {
751 LabelStruct l {
752 labelStruct.selectedRegion,
753 labelStruct.getT0() - t0,
754 t1 - t0,
755 labelStruct.title
756 };
757 lt->mLabels.push_back(l);
758 }
759 }
760 lt->mClipLen = (t1 - t0);
761
762 return tmp;
763}
764
765
766bool LabelTrack::PasteOver(double t, const Track * src)
767{
768 auto result = src->TypeSwitch< bool >( [&](const LabelTrack *sl) {
769 int len = mLabels.size();
770 int pos = 0;
771
772 while (pos < len && mLabels[pos].getT0() < t)
773 pos++;
774
775 for (auto &labelStruct: sl->mLabels) {
776 LabelStruct l {
777 labelStruct.selectedRegion,
778 labelStruct.getT0() + t,
779 labelStruct.getT1() + t,
780 labelStruct.title
781 };
782 mLabels.insert(mLabels.begin() + pos++, l);
783 }
784
785 return true;
786 } );
787
788 if (! result )
789 // THROW_INCONSISTENCY_EXCEPTION; // ?
790 (void)0;// intentionally do nothing
791
792 return result;
793}
794
795void LabelTrack::Paste(double t, const Track *src)
796{
797 bool bOk = src->TypeSwitch< bool >( [&](const LabelTrack *lt) {
798 double shiftAmt = lt->mClipLen > 0.0 ? lt->mClipLen : lt->GetEndTime();
799
800 ShiftLabelsOnInsert(shiftAmt, t);
801 PasteOver(t, src);
802
803 return true;
804 } );
805
806 if ( !bOk )
807 // THROW_INCONSISTENCY_EXCEPTION; // ?
808 (void)0;// intentionally do nothing
809}
810
811// This repeats the labels in a time interval a specified number of times.
812bool LabelTrack::Repeat(double t0, double t1, int n)
813{
814 // Sanity-check the arguments
815 if (n < 0 || t1 < t0)
816 return false;
817
818 double tLen = t1 - t0;
819
820 // Insert space for the repetitions
821 ShiftLabelsOnInsert(tLen * n, t1);
822
823 // mLabels may resize as we iterate, so use subscripting
824 for (unsigned int i = 0; i < mLabels.size(); ++i)
825 {
827 mLabels[i].RegionRelation(t0, t1, this);
828 if (relation == LabelStruct::SURROUNDS_LABEL)
829 {
830 // Label is completely inside the selection; duplicate it in each
831 // repeat interval
832 unsigned int pos = i; // running label insertion position in mLabels
833
834 for (int j = 1; j <= n; j++)
835 {
836 const LabelStruct &label = mLabels[i];
837 LabelStruct l {
838 label.selectedRegion,
839 label.getT0() + j * tLen,
840 label.getT1() + j * tLen,
841 label.title
842 };
843
844 // Figure out where to insert
845 while (pos < mLabels.size() &&
846 mLabels[pos].getT0() < l.getT0())
847 pos++;
848 mLabels.insert(mLabels.begin() + pos, l);
849 }
850 }
851 else if (relation == LabelStruct::BEGINS_IN_LABEL)
852 {
853 // Label ends inside the selection; ShiftLabelsOnInsert() hasn't touched
854 // it, and we need to extend it through to the last repeat interval
855 mLabels[i].selectedRegion.moveT1(n * tLen);
856 }
857
858 // Other cases have already been handled by ShiftLabelsOnInsert()
859 }
860
861 return true;
862}
863
864void LabelTrack::SyncLockAdjust(double oldT1, double newT1)
865{
866 if (newT1 > oldT1) {
867 // Insert space within the track
868
869 if (oldT1 > GetEndTime())
870 return;
871
872 //Clear(oldT1, newT1);
873 ShiftLabelsOnInsert(newT1 - oldT1, oldT1);
874 }
875 else if (newT1 < oldT1) {
876 // Remove from the track
877 Clear(newT1, oldT1);
878 }
879}
880
881
882void LabelTrack::Silence(double t0, double t1)
883{
884 int len = mLabels.size();
885
886 // mLabels may resize as we iterate, so use subscripting
887 for (int i = 0; i < len; ++i) {
889 mLabels[i].RegionRelation(t0, t1, this);
890 if (relation == LabelStruct::WITHIN_LABEL)
891 {
892 // Split label around the selection
893 const LabelStruct &label = mLabels[i];
894 LabelStruct l {
895 label.selectedRegion,
896 t1,
897 label.getT1(),
898 label.title
899 };
900
901 mLabels[i].selectedRegion.setT1(t0);
902
903 // This might not be the right place to insert, but we sort at the end
904 ++i;
905 mLabels.insert(mLabels.begin() + i, l);
906 }
907 else if (relation == LabelStruct::ENDS_IN_LABEL)
908 {
909 // Beginning of label to selection end
910 mLabels[i].selectedRegion.setT0(t1);
911 }
912 else if (relation == LabelStruct::BEGINS_IN_LABEL)
913 {
914 // End of label to selection beginning
915 mLabels[i].selectedRegion.setT1(t0);
916 }
917 else if (relation == LabelStruct::SURROUNDS_LABEL)
918 {
919 DeleteLabel( i );
920 len--;
921 i--;
922 }
923 }
924
925 SortLabels();
926}
927
928void LabelTrack::InsertSilence(double t, double len)
929{
930 for (auto &labelStruct: mLabels) {
931 double t0 = labelStruct.getT0();
932 double t1 = labelStruct.getT1();
933 if (t0 >= t)
934 t0 += len;
935
936 if (t1 >= t)
937 t1 += len;
938 labelStruct.selectedRegion.setTimes(t0, t1);
939 }
940}
941
943{
944 return mLabels.size();
945}
946
947const LabelStruct *LabelTrack::GetLabel(int index) const
948{
949 return &mLabels[index];
950}
951
952int LabelTrack::AddLabel(const SelectedRegion &selectedRegion,
953 const wxString &title)
954{
955 LabelStruct l { selectedRegion, title };
956
957 int len = mLabels.size();
958 int pos = 0;
959
960 while (pos < len && mLabels[pos].getT0() < selectedRegion.t0())
961 pos++;
962
963 mLabels.insert(mLabels.begin() + pos, l);
964
965 LabelTrackEvent evt{
966 EVT_LABELTRACK_ADDITION, SharedPointer<LabelTrack>(), title, -1, pos
967 };
968 ProcessEvent( evt );
969
970 return pos;
971}
972
974{
975 wxASSERT((index < (int)mLabels.size()));
976 auto iter = mLabels.begin() + index;
977 const auto title = iter->title;
978 mLabels.erase(iter);
979
980 LabelTrackEvent evt{
981 EVT_LABELTRACK_DELETION, SharedPointer<LabelTrack>(), title, index, -1
982 };
983 ProcessEvent( evt );
984}
985
991{
992 const auto begin = mLabels.begin();
993 const auto nn = (int)mLabels.size();
994 int i = 1;
995 while (true)
996 {
997 // Find the next disorder
998 while (i < nn && mLabels[i - 1].getT0() <= mLabels[i].getT0())
999 ++i;
1000 if (i >= nn)
1001 break;
1002
1003 // Where must element i sink to? At most i - 1, maybe less
1004 int j = i - 2;
1005 while( (j >= 0) && (mLabels[j].getT0() > mLabels[i].getT0()) )
1006 --j;
1007 ++j;
1008
1009 // Now fix the disorder
1010 std::rotate(
1011 begin + j,
1012 begin + i,
1013 begin + i + 1
1014 );
1015
1016 // Let listeners update their stored indices
1017 LabelTrackEvent evt{
1018 EVT_LABELTRACK_PERMUTED, SharedPointer<LabelTrack>(),
1019 mLabels[j].title, i, j
1020 };
1021 ProcessEvent( evt );
1022 }
1023}
1024
1025wxString LabelTrack::GetTextOfLabels(double t0, double t1) const
1026{
1027 bool firstLabel = true;
1028 wxString retVal;
1029
1030 for (auto &labelStruct: mLabels) {
1031 if (labelStruct.getT0() >= t0 &&
1032 labelStruct.getT1() <= t1)
1033 {
1034 if (!firstLabel)
1035 retVal += '\t';
1036 firstLabel = false;
1037 retVal += labelStruct.title;
1038 }
1039 }
1040
1041 return retVal;
1042}
1043
1045{
1046 int i = -1;
1047
1048 if (!mLabels.empty()) {
1049 int len = (int) mLabels.size();
1050 if (miLastLabel >= 0 && miLastLabel + 1 < len
1051 && currentRegion.t0() == mLabels[miLastLabel].getT0()
1052 && currentRegion.t0() == mLabels[miLastLabel + 1].getT0() ) {
1053 i = miLastLabel + 1;
1054 }
1055 else {
1056 i = 0;
1057 if (currentRegion.t0() < mLabels[len - 1].getT0()) {
1058 while (i < len &&
1059 mLabels[i].getT0() <= currentRegion.t0()) {
1060 i++;
1061 }
1062 }
1063 }
1064 }
1065
1066 miLastLabel = i;
1067 return i;
1068}
1069
1071{
1072 int i = -1;
1073
1074 if (!mLabels.empty()) {
1075 int len = (int) mLabels.size();
1076 if (miLastLabel > 0 && miLastLabel < len
1077 && currentRegion.t0() == mLabels[miLastLabel].getT0()
1078 && currentRegion.t0() == mLabels[miLastLabel - 1].getT0() ) {
1079 i = miLastLabel - 1;
1080 }
1081 else {
1082 i = len - 1;
1083 if (currentRegion.t0() > mLabels[0].getT0()) {
1084 while (i >=0 &&
1085 mLabels[i].getT0() >= currentRegion.t0()) {
1086 i--;
1087 }
1088 }
1089 }
1090 }
1091
1092 miLastLabel = i;
1093 return i;
1094}
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
const TranslatableString name
Definition: Distortion.cpp:82
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
static ProjectFileIORegistry::ObjectReaderEntry readerEntry
Definition: LabelTrack.cpp:53
static Container DoMakeIntervals(LabelTrack &track)
Definition: LabelTrack.cpp:145
wxDEFINE_EVENT(EVT_LABELTRACK_ADDITION, LabelTrackEvent)
static IntervalType DoMakeInterval(const LabelStruct &label, size_t index)
Definition: LabelTrack.cpp:127
static const Track::TypeInfo & typeInfo()
Definition: LabelTrack.cpp:101
static const auto title
FileConfig * gPrefs
Definition: Prefs.cpp:71
TranslatableString label
Definition: TagsEditor.cpp:163
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
A start and an end time, and non-mutative access to optional extra information.
Definition: Track.h:178
static EnumSetting< bool > LabelStyleSetting
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:151
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:134
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:31
void MoveLabel(int iEdge, double fNewTime)
Definition: LabelTrack.cpp:389
double getT1() const
Definition: LabelTrack.h:42
int x1
Pixel position of left hand glyph.
Definition: LabelTrack.h:77
int x
width of the text in pixels.
Definition: LabelTrack.h:76
TimeRelations
Relationships between selection region and labels.
Definition: LabelTrack.h:54
@ BEGINS_IN_LABEL
Definition: LabelTrack.h:59
@ SURROUNDS_LABEL
Definition: LabelTrack.h:57
static LabelStruct Import(wxTextFile &file, int &index)
Definition: LabelTrack.cpp:404
void Export(wxTextFile &file) const
Definition: LabelTrack.cpp:476
LabelStruct()=default
double getT0() const
Definition: LabelTrack.h:41
wxString title
Definition: LabelTrack.h:72
TimeRelations RegionRelation(double reg_t0, double reg_t1, const LabelTrack *parent=NULL) const
Definition: LabelTrack.cpp:502
SelectedRegion selectedRegion
Definition: LabelTrack.h:71
bool AdjustEdge(int iEdge, double fNewTime)
Definition: LabelTrack.cpp:379
double getDuration() const
Definition: LabelTrack.h:40
bool updated
Pixel position of label.
Definition: LabelTrack.h:81
int width
Text of the label.
Definition: LabelTrack.h:73
int xText
Pixel position of right hand glyph.
Definition: LabelTrack.h:78
int y
Pixel position of left hand side of text box.
Definition: LabelTrack.h:79
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:89
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
Definition: LabelTrack.cpp:601
virtual ~LabelTrack()
Definition: LabelTrack.cpp:172
int GetNumLabels() const
Definition: LabelTrack.cpp:942
bool Repeat(double t0, double t1, int n)
Definition: LabelTrack.cpp:812
void ShiftLabelsOnInsert(double length, double pt)
Definition: LabelTrack.cpp:231
void ScaleLabels(double b, double e, double change)
Definition: LabelTrack.cpp:259
void InsertSilence(double t, double len) override
Definition: LabelTrack.cpp:928
void WriteXML(XMLWriter &xmlFile) const override
Definition: LabelTrack.cpp:670
double GetStartTime() const override
Definition: LabelTrack.cpp:346
void SyncLockAdjust(double oldT1, double newT1) override
Definition: LabelTrack.cpp:864
void Import(wxTextFile &f)
Import labels, handling files with or without end-times.
Definition: LabelTrack.cpp:577
const TypeInfo & GetTypeInfo() const override
Definition: LabelTrack.cpp:108
ConstIntervals GetIntervals() const override
Report times on the track where important intervals begin and end, for UI to snap to.
Definition: LabelTrack.cpp:153
int FindPrevLabel(const SelectedRegion &currentSelection)
double GetOffset() const override
Definition: LabelTrack.cpp:341
void Paste(double t, const Track *src) override
Definition: LabelTrack.cpp:795
const LabelStruct * GetLabel(int index) const
Definition: LabelTrack.cpp:947
void DeleteLabel(int index)
Definition: LabelTrack.cpp:973
int FindNextLabel(const SelectedRegion &currentSelection)
void SetSelected(bool s) override
Definition: LabelTrack.cpp:329
static LabelTrack * New(AudacityProject &project)
Definition: LabelTrack.cpp:63
void WarpLabels(const TimeWarper &warper)
Definition: LabelTrack.cpp:287
void ChangeLabelsOnReverse(double b, double e)
Definition: LabelTrack.cpp:244
Track::Holder Copy(double t0, double t1, bool forClipboard=true) const override
Definition: LabelTrack.cpp:714
ConstInterval MakeInterval(size_t index) const
Definition: LabelTrack.cpp:134
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: LabelTrack.cpp:662
void Silence(double t0, double t1) override
Definition: LabelTrack.cpp:882
Track::Holder Cut(double t0, double t1) override
Definition: LabelTrack.cpp:691
wxString GetTextOfLabels(double t0, double t1) const
void Export(wxTextFile &f) const
Export labels including label start and end-times.
Definition: LabelTrack.cpp:569
Track::Holder Clone() const override
Definition: LabelTrack.cpp:370
void SetLabel(size_t iLabel, const LabelStruct &newLabel)
Definition: LabelTrack.cpp:163
double GetEndTime() const override
Definition: LabelTrack.cpp:354
void SetOffset(double dOffset) override
Definition: LabelTrack.cpp:176
static LabelTrack * Create(TrackList &trackList, const wxString &name)
Create a new LabelTrack with specified name and append it to the trackList.
Definition: LabelTrack.cpp:71
void Clear(double t0, double t1) override
Definition: LabelTrack.cpp:182
double AdjustTimeStampOnScale(double t, double b, double e, double change)
Definition: LabelTrack.cpp:268
int AddLabel(const SelectedRegion &region, const wxString &title)
Definition: LabelTrack.cpp:952
bool PasteOver(double t, const Track *src)
Definition: LabelTrack.cpp:766
static wxString GetDefaultName()
Definition: LabelTrack.cpp:58
void SortLabels()
Definition: LabelTrack.cpp:990
Track::Holder PasteInto(AudacityProject &) const override
Find or create the destination track for a paste, maybe in a different project.
Definition: LabelTrack.cpp:118
int miLastLabel
Definition: LabelTrack.h:199
LabelArray mLabels
Definition: LabelTrack.h:194
static const TypeInfo & ClassTypeInfo()
Definition: LabelTrack.cpp:113
Defines a selected portion of a project.
bool setTimes(double t0, double t1)
double t0() const
bool HandleXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &value, const char *legacyT0Name=sDefaultT0Name, const char *legacyT1Name=sDefaultT1Name)
bool setT0(double t, bool maySwap=true)
bool setT1(double t, bool maySwap=true)
static const int UndefinedFrequency
Transforms one point in time to another point. For example, a time stretching effect might use one to...
Definition: TimeWarper.h:62
virtual double Warp(double originalTime) const =0
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
bool GetSelected() const
Definition: Track.h:469
virtual void SetSelected(bool s)
Definition: Track.cpp:87
static const TypeInfo & ClassTypeInfo()
Definition: Track.cpp:1251
double mOffset
Definition: Track.h:447
R TypeSwitch(const Functions &...functions)
Use this function rather than testing track type explicitly and making down-casts.
Definition: Track.h:832
std::shared_ptr< Track > Holder
Definition: Track.h:368
bool HandleCommonXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &valueView)
Definition: Track.cpp:1287
void WriteCommonXMLAttributes(XMLWriter &xmlFile, bool includeNameAndSelected=true) const
Definition: Track.cpp:1274
std::vector< Interval > Intervals
Definition: Track.h:335
std::vector< ConstInterval > ConstIntervals
Definition: Track.h:337
A start and an end time, and mutative access to optional extra information.
Definition: Track.h:205
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1338
wxString MakeUniqueTrackName(const wxString &baseTrackName) const
Returns string that contains baseTrackName, but is guaranteed to be unique among other tracks in that...
Definition: Track.cpp:543
TrackKind * Add(const std::shared_ptr< TrackKind > &t)
Definition: Track.h:1564
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:26
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
STL namespace.
Empty argument passed to some public constructors.
Definition: Track.h:232