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