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