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