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