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),
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 return std::make_shared<LabelTrack>( *this );
373}
374
375// Adjust label's left or right boundary, depending which is requested.
376// Return true iff the label flipped.
377bool LabelStruct::AdjustEdge( int iEdge, double fNewTime)
378{
379 updated = true;
380 if( iEdge < 0 )
381 return selectedRegion.setT0(fNewTime);
382 else
383 return selectedRegion.setT1(fNewTime);
384}
385
386// We're moving the label. Adjust both left and right edge.
387void LabelStruct::MoveLabel( int iEdge, double fNewTime)
388{
389 double fTimeSpan = getDuration();
390
391 if( iEdge < 0 )
392 {
393 selectedRegion.setTimes(fNewTime, fNewTime+fTimeSpan);
394 }
395 else
396 {
397 selectedRegion.setTimes(fNewTime-fTimeSpan, fNewTime);
398 }
399 updated = true;
400}
401
402LabelStruct LabelStruct::Import(wxTextFile &file, int &index)
403{
405 wxString title;
406 static const wxString continuation{ wxT("\\") };
407
408 wxString firstLine = file.GetLine(index++);
409
410 {
411 // Assume tab is an impossible character within the exported text
412 // of the label, so can be only a delimiter. But other white space may
413 // be part of the label text.
414 wxStringTokenizer toker { firstLine, wxT("\t") };
415
416 //get the timepoint of the left edge of the label.
417 auto token = toker.GetNextToken();
418
419 double t0;
420 if (!Internat::CompatibleToDouble(token, &t0))
421 throw BadFormatException{};
422
423 token = toker.GetNextToken();
424
425 double t1;
426 if (!Internat::CompatibleToDouble(token, &t1))
427 //s1 is not a number.
428 t1 = t0; //This is a one-sided label; t1 == t0.
429 else
430 token = toker.GetNextToken();
431
432 sr.setTimes( t0, t1 );
433
434 title = token;
435 }
436
437 // Newer selection fields are written on additional lines beginning with
438 // '\' which is an impossible numerical character that older versions of
439 // audacity will ignore. Test for the presence of such a line and then
440 // parse it if we can.
441
442 // There may also be additional continuation lines from future formats that
443 // we ignore.
444
445 // Advance index over all continuation lines first, before we might throw
446 // any exceptions.
447 int index2 = index;
448 while (index < (int)file.GetLineCount() &&
449 file.GetLine(index).StartsWith(continuation))
450 ++index;
451
452 if (index2 < index) {
453 wxStringTokenizer toker { file.GetLine(index2++), wxT("\t") };
454 auto token = toker.GetNextToken();
455 if (token != continuation)
456 throw BadFormatException{};
457
458 token = toker.GetNextToken();
459 double f0;
460 if (!Internat::CompatibleToDouble(token, &f0))
461 throw BadFormatException{};
462
463 token = toker.GetNextToken();
464 double f1;
465 if (!Internat::CompatibleToDouble(token, &f1))
466 throw BadFormatException{};
467
468 sr.setFrequencies(f0, f1);
469 }
470
471 return LabelStruct{ sr, title };
472}
473
474void LabelStruct::Export(wxTextFile &file) const
475{
476 file.AddLine(wxString::Format(wxT("%s\t%s\t%s"),
477 Internat::ToString(getT0(), FLT_DIG),
478 Internat::ToString(getT1(), FLT_DIG),
479 title
480 ));
481
482 // Do we need more lines?
483 auto f0 = selectedRegion.f0();
484 auto f1 = selectedRegion.f1();
488 return;
489
490 // Write a \ character at the start of a second line,
491 // so that earlier versions of Audacity ignore it.
492 file.AddLine(wxString::Format(wxT("\\\t%s\t%s"),
493 Internat::ToString(f0, FLT_DIG),
494 Internat::ToString(f1, FLT_DIG)
495 ));
496
497 // Additional lines in future formats should also start with '\'.
498}
499
501 double reg_t0, double reg_t1, const LabelTrack * WXUNUSED(parent)) const
503{
504 bool retainLabels = false;
505
506 wxASSERT(reg_t0 <= reg_t1);
507 gPrefs->Read(wxT("/GUI/RetainLabels"), &retainLabels);
508
509 if(retainLabels) {
510
511 // Desired behavior for edge cases: The length of the selection is smaller
512 // than the length of the label if the selection is within the label or
513 // matching exactly a (region) label.
514
515 if (reg_t0 < getT0() && reg_t1 > getT1())
516 return SURROUNDS_LABEL;
517 else if (reg_t1 < getT0())
518 return BEFORE_LABEL;
519 else if (reg_t0 > getT1())
520 return AFTER_LABEL;
521
522 else if (reg_t0 >= getT0() && reg_t0 <= getT1() &&
523 reg_t1 >= getT0() && reg_t1 <= getT1())
524 return WITHIN_LABEL;
525
526 else if (reg_t0 >= getT0() && reg_t0 <= getT1())
527 return BEGINS_IN_LABEL;
528 else
529 return ENDS_IN_LABEL;
530
531 } else {
532
533 // AWD: Desired behavior for edge cases: point labels bordered by the
534 // selection are included within it. Region labels are included in the
535 // selection to the extent that the selection covers them; specifically,
536 // they're not included at all if the selection borders them, and they're
537 // fully included if the selection covers them fully, even if it just
538 // borders their endpoints. This is just one of many possible schemes.
539
540 // The first test catches bordered point-labels and selected-through
541 // region-labels; move it to third and selection edges become inclusive
542 // WRT point-labels.
543 if (reg_t0 <= getT0() && reg_t1 >= getT1())
544 return SURROUNDS_LABEL;
545 else if (reg_t1 <= getT0())
546 return BEFORE_LABEL;
547 else if (reg_t0 >= getT1())
548 return AFTER_LABEL;
549
550 // At this point, all point labels should have returned.
551
552 else if (reg_t0 > getT0() && reg_t0 < getT1() &&
553 reg_t1 > getT0() && reg_t1 < getT1())
554 return WITHIN_LABEL;
555
556 // Knowing that none of the other relations match simplifies remaining
557 // tests
558 else if (reg_t0 > getT0() && reg_t0 < getT1())
559 return BEGINS_IN_LABEL;
560 else
561 return ENDS_IN_LABEL;
562
563 }
564}
565
567void LabelTrack::Export(wxTextFile & f) const
568{
569 // PRL: to do: export other selection fields
570 for (auto &labelStruct: mLabels)
571 labelStruct.Export(f);
572}
573
575void LabelTrack::Import(wxTextFile & in)
576{
577 int lines = in.GetLineCount();
578
579 mLabels.clear();
580 mLabels.reserve(lines);
581
582 //Currently, we expect a tag file to have two values and a label
583 //on each line. If the second token is not a number, we treat
584 //it as a single-value label.
585 bool error = false;
586 for (int index = 0; index < lines;) {
587 try {
588 // Let LabelStruct::Import advance index
589 LabelStruct l { LabelStruct::Import(in, index) };
590 mLabels.push_back(l);
591 }
592 catch(const LabelStruct::BadFormatException&) { error = true; }
593 }
594 if (error)
595 ::AudacityMessageBox( XO("One or more saved labels could not be read.") );
596 SortLabels();
597}
598
599bool LabelTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
600{
601 if (tag == "label") {
602
603 SelectedRegion selectedRegion;
604 wxString title;
605
606 // loop through attrs, which is a null-terminated list of
607 // attribute-value pairs
608 for (auto pair : attrs)
609 {
610 auto attr = pair.first;
611 auto value = pair.second;
612
613 if (selectedRegion.HandleXMLAttribute(attr, value, "t", "t1"))
614 ;
615 // Bug 1905 no longer applies, as valueView has no limits anyway
616 else if (attr == "title")
617 title = value.ToWString();
618
619 } // while
620
621 // Handle files created by Audacity 1.1. Labels in Audacity 1.1
622 // did not have separate start- and end-times.
623 // PRL: this superfluous now, given class SelectedRegion's internal
624 // consistency guarantees
625 //if (selectedRegion.t1() < 0)
626 // selectedRegion.collapseToT0();
627
628 LabelStruct l { selectedRegion, title };
629 mLabels.push_back(l);
630
631 return true;
632 }
633 else if (tag == "labeltrack") {
634 long nValue = -1;
635 for (auto pair : attrs)
636 {
637 auto attr = pair.first;
638 auto value = pair.second;
639
640 if (this->Track::HandleCommonXMLAttribute(attr, value))
641 ;
642 else if (attr == "numlabels" && value.TryGet(nValue))
643 {
644 if (nValue < 0)
645 {
646 wxLogWarning(wxT("Project shows negative number of labels: %d"), nValue);
647 return false;
648 }
649 mLabels.clear();
650 mLabels.reserve(nValue);
651 }
652 }
653
654 return true;
655 }
656
657 return false;
658}
659
660XMLTagHandler *LabelTrack::HandleXMLChild(const std::string_view& tag)
661{
662 if (tag == "label")
663 return this;
664 else
665 return NULL;
666}
667
668void LabelTrack::WriteXML(XMLWriter &xmlFile) const
669// may throw
670{
671 int len = mLabels.size();
672
673 xmlFile.StartTag(wxT("labeltrack"));
674 this->Track::WriteCommonXMLAttributes( xmlFile );
675 xmlFile.WriteAttr(wxT("numlabels"), len);
676
677 for (auto &labelStruct: mLabels) {
678 xmlFile.StartTag(wxT("label"));
679 labelStruct.getSelectedRegion()
680 .WriteXMLAttributes(xmlFile, "t", "t1");
681 // PRL: to do: write other selection fields
682 xmlFile.WriteAttr(wxT("title"), labelStruct.title);
683 xmlFile.EndTag(wxT("label"));
684 }
685
686 xmlFile.EndTag(wxT("labeltrack"));
687}
688
689Track::Holder LabelTrack::Cut(double t0, double t1)
690{
691 auto tmp = Copy(t0, t1);
692
693 Clear(t0, t1);
694
695 return tmp;
696}
697
698#if 0
699Track::Holder LabelTrack::SplitCut(double t0, double t1)
700{
701 // SplitCut() == Copy() + SplitDelete()
702
703 Track::Holder tmp = Copy(t0, t1);
704
705 if (!SplitDelete(t0, t1))
706 return {};
707
708 return tmp;
709}
710#endif
711
712Track::Holder LabelTrack::Copy(double t0, double t1, bool) const
713{
714 auto tmp = std::make_shared<LabelTrack>();
715 tmp->Init(*this);
716 const auto lt = static_cast<LabelTrack*>(tmp.get());
717
718 for (auto &labelStruct: mLabels) {
720 labelStruct.RegionRelation(t0, t1, this);
721 if (relation == LabelStruct::SURROUNDS_LABEL) {
722 LabelStruct l {
723 labelStruct.selectedRegion,
724 labelStruct.getT0() - t0,
725 labelStruct.getT1() - t0,
726 labelStruct.title
727 };
728 lt->mLabels.push_back(l);
729 }
730 else if (relation == LabelStruct::WITHIN_LABEL) {
731 LabelStruct l {
732 labelStruct.selectedRegion,
733 0,
734 t1-t0,
735 labelStruct.title
736 };
737 lt->mLabels.push_back(l);
738 }
739 else if (relation == LabelStruct::BEGINS_IN_LABEL) {
740 LabelStruct l {
741 labelStruct.selectedRegion,
742 0,
743 labelStruct.getT1() - t0,
744 labelStruct.title
745 };
746 lt->mLabels.push_back(l);
747 }
748 else if (relation == LabelStruct::ENDS_IN_LABEL) {
749 LabelStruct l {
750 labelStruct.selectedRegion,
751 labelStruct.getT0() - t0,
752 t1 - t0,
753 labelStruct.title
754 };
755 lt->mLabels.push_back(l);
756 }
757 }
758 lt->mClipLen = (t1 - t0);
759
760 return tmp;
761}
762
763
764bool LabelTrack::PasteOver(double t, const Track * src)
765{
766 auto result = src->TypeSwitch< bool >( [&](const LabelTrack *sl) {
767 int len = mLabels.size();
768 int pos = 0;
769
770 while (pos < len && mLabels[pos].getT0() < t)
771 pos++;
772
773 for (auto &labelStruct: sl->mLabels) {
774 LabelStruct l {
775 labelStruct.selectedRegion,
776 labelStruct.getT0() + t,
777 labelStruct.getT1() + t,
778 labelStruct.title
779 };
780 mLabels.insert(mLabels.begin() + pos++, l);
781 }
782
783 return true;
784 } );
785
786 if (! result )
787 // THROW_INCONSISTENCY_EXCEPTION; // ?
788 (void)0;// intentionally do nothing
789
790 return result;
791}
792
793void LabelTrack::Paste(double t, const Track *src)
794{
795 bool bOk = src->TypeSwitch< bool >( [&](const LabelTrack *lt) {
796 double shiftAmt = lt->mClipLen > 0.0 ? lt->mClipLen : lt->GetEndTime();
797
798 ShiftLabelsOnInsert(shiftAmt, t);
799 PasteOver(t, src);
800
801 return true;
802 } );
803
804 if ( !bOk )
805 // THROW_INCONSISTENCY_EXCEPTION; // ?
806 (void)0;// intentionally do nothing
807}
808
809// This repeats the labels in a time interval a specified number of times.
810bool LabelTrack::Repeat(double t0, double t1, int n)
811{
812 // Sanity-check the arguments
813 if (n < 0 || t1 < t0)
814 return false;
815
816 double tLen = t1 - t0;
817
818 // Insert space for the repetitions
819 ShiftLabelsOnInsert(tLen * n, t1);
820
821 // mLabels may resize as we iterate, so use subscripting
822 for (unsigned int i = 0; i < mLabels.size(); ++i)
823 {
825 mLabels[i].RegionRelation(t0, t1, this);
826 if (relation == LabelStruct::SURROUNDS_LABEL)
827 {
828 // Label is completely inside the selection; duplicate it in each
829 // repeat interval
830 unsigned int pos = i; // running label insertion position in mLabels
831
832 for (int j = 1; j <= n; j++)
833 {
834 const LabelStruct &label = mLabels[i];
835 LabelStruct l {
836 label.selectedRegion,
837 label.getT0() + j * tLen,
838 label.getT1() + j * tLen,
839 label.title
840 };
841
842 // Figure out where to insert
843 while (pos < mLabels.size() &&
844 mLabels[pos].getT0() < l.getT0())
845 pos++;
846 mLabels.insert(mLabels.begin() + pos, l);
847 }
848 }
849 else if (relation == LabelStruct::BEGINS_IN_LABEL)
850 {
851 // Label ends inside the selection; ShiftLabelsOnInsert() hasn't touched
852 // it, and we need to extend it through to the last repeat interval
853 mLabels[i].selectedRegion.moveT1(n * tLen);
854 }
855
856 // Other cases have already been handled by ShiftLabelsOnInsert()
857 }
858
859 return true;
860}
861
862void LabelTrack::SyncLockAdjust(double oldT1, double newT1)
863{
864 if (newT1 > oldT1) {
865 // Insert space within the track
866
867 if (oldT1 > GetEndTime())
868 return;
869
870 //Clear(oldT1, newT1);
871 ShiftLabelsOnInsert(newT1 - oldT1, oldT1);
872 }
873 else if (newT1 < oldT1) {
874 // Remove from the track
875 Clear(newT1, oldT1);
876 }
877}
878
879
880void LabelTrack::Silence(double t0, double t1)
881{
882 int len = mLabels.size();
883
884 // mLabels may resize as we iterate, so use subscripting
885 for (int i = 0; i < len; ++i) {
887 mLabels[i].RegionRelation(t0, t1, this);
888 if (relation == LabelStruct::WITHIN_LABEL)
889 {
890 // Split label around the selection
891 const LabelStruct &label = mLabels[i];
892 LabelStruct l {
893 label.selectedRegion,
894 t1,
895 label.getT1(),
896 label.title
897 };
898
899 mLabels[i].selectedRegion.setT1(t0);
900
901 // This might not be the right place to insert, but we sort at the end
902 ++i;
903 mLabels.insert(mLabels.begin() + i, l);
904 }
905 else if (relation == LabelStruct::ENDS_IN_LABEL)
906 {
907 // Beginning of label to selection end
908 mLabels[i].selectedRegion.setT0(t1);
909 }
910 else if (relation == LabelStruct::BEGINS_IN_LABEL)
911 {
912 // End of label to selection beginning
913 mLabels[i].selectedRegion.setT1(t0);
914 }
915 else if (relation == LabelStruct::SURROUNDS_LABEL)
916 {
917 DeleteLabel( i );
918 len--;
919 i--;
920 }
921 }
922
923 SortLabels();
924}
925
926void LabelTrack::InsertSilence(double t, double len)
927{
928 for (auto &labelStruct: mLabels) {
929 double t0 = labelStruct.getT0();
930 double t1 = labelStruct.getT1();
931 if (t0 >= t)
932 t0 += len;
933
934 if (t1 >= t)
935 t1 += len;
936 labelStruct.selectedRegion.setTimes(t0, t1);
937 }
938}
939
941{
942 return mLabels.size();
943}
944
945const LabelStruct *LabelTrack::GetLabel(int index) const
946{
947 return &mLabels[index];
948}
949
950int LabelTrack::AddLabel(const SelectedRegion &selectedRegion,
951 const wxString &title)
952{
953 LabelStruct l { selectedRegion, title };
954
955 int len = mLabels.size();
956 int pos = 0;
957
958 while (pos < len && mLabels[pos].getT0() < selectedRegion.t0())
959 pos++;
960
961 mLabels.insert(mLabels.begin() + pos, l);
962
963 LabelTrackEvent evt{
964 EVT_LABELTRACK_ADDITION, SharedPointer<LabelTrack>(), title, -1, pos
965 };
966 ProcessEvent( evt );
967
968 return pos;
969}
970
972{
973 wxASSERT((index < (int)mLabels.size()));
974 auto iter = mLabels.begin() + index;
975 const auto title = iter->title;
976 mLabels.erase(iter);
977
978 LabelTrackEvent evt{
979 EVT_LABELTRACK_DELETION, SharedPointer<LabelTrack>(), title, index, -1
980 };
981 ProcessEvent( evt );
982}
983
989{
990 const auto begin = mLabels.begin();
991 const auto nn = (int)mLabels.size();
992 int i = 1;
993 while (true)
994 {
995 // Find the next disorder
996 while (i < nn && mLabels[i - 1].getT0() <= mLabels[i].getT0())
997 ++i;
998 if (i >= nn)
999 break;
1000
1001 // Where must element i sink to? At most i - 1, maybe less
1002 int j = i - 2;
1003 while( (j >= 0) && (mLabels[j].getT0() > mLabels[i].getT0()) )
1004 --j;
1005 ++j;
1006
1007 // Now fix the disorder
1008 std::rotate(
1009 begin + j,
1010 begin + i,
1011 begin + i + 1
1012 );
1013
1014 // Let listeners update their stored indices
1015 LabelTrackEvent evt{
1016 EVT_LABELTRACK_PERMUTED, SharedPointer<LabelTrack>(),
1017 mLabels[j].title, i, j
1018 };
1019 ProcessEvent( evt );
1020 }
1021}
1022
1023wxString LabelTrack::GetTextOfLabels(double t0, double t1) const
1024{
1025 bool firstLabel = true;
1026 wxString retVal;
1027
1028 for (auto &labelStruct: mLabels) {
1029 if (labelStruct.getT0() >= t0 &&
1030 labelStruct.getT1() <= t1)
1031 {
1032 if (!firstLabel)
1033 retVal += '\t';
1034 firstLabel = false;
1035 retVal += labelStruct.title;
1036 }
1037 }
1038
1039 return retVal;
1040}
1041
1043{
1044 int i = -1;
1045
1046 if (!mLabels.empty()) {
1047 int len = (int) mLabels.size();
1048 if (miLastLabel >= 0 && miLastLabel + 1 < len
1049 && currentRegion.t0() == mLabels[miLastLabel].getT0()
1050 && currentRegion.t0() == mLabels[miLastLabel + 1].getT0() ) {
1051 i = miLastLabel + 1;
1052 }
1053 else {
1054 i = 0;
1055 if (currentRegion.t0() < mLabels[len - 1].getT0()) {
1056 while (i < len &&
1057 mLabels[i].getT0() <= currentRegion.t0()) {
1058 i++;
1059 }
1060 }
1061 }
1062 }
1063
1064 miLastLabel = i;
1065 return i;
1066}
1067
1069{
1070 int i = -1;
1071
1072 if (!mLabels.empty()) {
1073 int len = (int) mLabels.size();
1074 if (miLastLabel > 0 && miLastLabel < len
1075 && currentRegion.t0() == mLabels[miLastLabel].getT0()
1076 && currentRegion.t0() == mLabels[miLastLabel - 1].getT0() ) {
1077 i = miLastLabel - 1;
1078 }
1079 else {
1080 i = len - 1;
1081 if (currentRegion.t0() > mLabels[0].getT0()) {
1082 while (i >=0 &&
1083 mLabels[i].getT0() >= currentRegion.t0()) {
1084 i--;
1085 }
1086 }
1087 }
1088 }
1089
1090 miLastLabel = i;
1091 return i;
1092}
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:387
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:402
void Export(wxTextFile &file) const
Definition: LabelTrack.cpp:474
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:500
SelectedRegion selectedRegion
Definition: LabelTrack.h:71
bool AdjustEdge(int iEdge, double fNewTime)
Definition: LabelTrack.cpp:377
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:599
virtual ~LabelTrack()
Definition: LabelTrack.cpp:172
int GetNumLabels() const
Definition: LabelTrack.cpp:940
bool Repeat(double t0, double t1, int n)
Definition: LabelTrack.cpp:810
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:926
void WriteXML(XMLWriter &xmlFile) const override
Definition: LabelTrack.cpp:668
double GetStartTime() const override
Definition: LabelTrack.cpp:346
void SyncLockAdjust(double oldT1, double newT1) override
Definition: LabelTrack.cpp:862
void Import(wxTextFile &f)
Import labels, handling files with or without end-times.
Definition: LabelTrack.cpp:575
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:793
const LabelStruct * GetLabel(int index) const
Definition: LabelTrack.cpp:945
void DeleteLabel(int index)
Definition: LabelTrack.cpp:971
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:712
ConstInterval MakeInterval(size_t index) const
Definition: LabelTrack.cpp:134
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: LabelTrack.cpp:660
void Silence(double t0, double t1) override
Definition: LabelTrack.cpp:880
Track::Holder Cut(double t0, double t1) override
Definition: LabelTrack.cpp:689
wxString GetTextOfLabels(double t0, double t1) const
void Export(wxTextFile &f) const
Export labels including label start and end-times.
Definition: LabelTrack.cpp:567
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:950
bool PasteOver(double t, const Track *src)
Definition: LabelTrack.cpp:764
static wxString GetDefaultName()
Definition: LabelTrack.cpp:58
void SortLabels()
Definition: LabelTrack.cpp:988
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:461
virtual void SetSelected(bool s)
Definition: Track.cpp:88
static const TypeInfo & ClassTypeInfo()
Definition: Track.cpp:1228
double mOffset
Definition: Track.h:440
R TypeSwitch(const Functions &...functions)
Use this function rather than testing track type explicitly and making down-casts.
Definition: Track.h:824
std::shared_ptr< Track > Holder
Definition: Track.h:361
bool HandleCommonXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &valueView)
Definition: Track.cpp:1264
void WriteCommonXMLAttributes(XMLWriter &xmlFile, bool includeNameAndSelected=true) const
Definition: Track.cpp:1251
std::vector< Interval > Intervals
Definition: Track.h:328
std::vector< ConstInterval > ConstIntervals
Definition: Track.h:330
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:1330
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:524
TrackKind * Add(const std::shared_ptr< TrackKind > &t)
Definition: Track.h:1556
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:467
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, if Traits<Type>::iterated_type is defined.
Definition: PackedArray.h:126
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for, if Traits<Type>::iterated_type is defined.
Definition: PackedArray.h:112