Audacity 3.2.0
ProjectAudioManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5ProjectAudioManager.cpp
6
7Paul Licameli split from ProjectManager.cpp
8
9**********************************************************************/
10
11
12#include "ProjectAudioManager.h"
13
14#include <wx/app.h>
15#include <wx/frame.h>
16#include <wx/statusbr.h>
17#include <algorithm>
18#include <numeric>
19
20#include "AudioIO.h"
21#include "BasicUI.h"
22#include "CommandManager.h"
23#include "CommonCommandFlags.h"
25#include "Meter.h"
26#include "Mix.h"
27#include "Project.h"
28#include "ProjectAudioIO.h"
29#include "ProjectFileIO.h"
30#include "ProjectHistory.h"
31#include "ProjectRate.h"
32#include "ProjectStatus.h"
33#include "ProjectWindows.h"
34#include "ScrubState.h"
35#include "TrackFocus.h"
36#include "prefs/TracksPrefs.h"
37#include "TransportUtilities.h"
38#include "UndoManager.h"
39#include "ViewInfo.h"
40#include "Viewport.h"
41#include "WaveClip.h"
42#include "WaveTrack.h"
43#include "tracks/ui/Scrubbing.h"
46#include "AudacityMessageBox.h"
47
48
52 return std::make_shared< ProjectAudioManager >( project );
53 }
54};
55
57{
58 return project.AttachedObjects::Get< ProjectAudioManager >(
60}
61
64{
65 return Get( const_cast< AudacityProject & >( project ) );
66}
67
69 : mProject{ project }
70{
72 registerStatusWidthFunction{ StatusWidthFunction };
75}
76
78
80{
81 if (rate > 0) {
82 return XO("Actual Rate: %d").Format( rate );
83 }
84 else
85 // clear the status field
86 return {};
87}
88
92{
93 if ( field == rateStatusBarField ) {
94 auto &audioManager = ProjectAudioManager::Get( project );
95 int rate = audioManager.mDisplayedRate;
96 return {
97 { { FormatRate( rate ) } },
98 50
99 };
100 }
101 return {};
102}
103
104namespace {
105// The implementation is general enough to allow backwards play too
107public:
109 double gapLeft,
110 double gapLength
111 );
113
114 void Initialize(PlaybackSchedule &schedule, double rate) override;
115
116 bool Done(PlaybackSchedule &schedule, unsigned long) override;
117
118 double OffsetSequenceTime( PlaybackSchedule &schedule, double offset ) override;
119
120 PlaybackSlice GetPlaybackSlice(
121 PlaybackSchedule &schedule, size_t available) override;
122
123 std::pair<double, double> AdvancedTrackTime( PlaybackSchedule &schedule,
124 double trackTime, size_t nSamples) override;
125
126 bool RepositionPlayback(
127 PlaybackSchedule &schedule, const Mixers &playbackMixers,
128 size_t frames, size_t available) override;
129
130private:
131 double GapStart() const
132 { return mReversed ? mGapLeft + mGapLength : mGapLeft; }
133 double GapEnd() const
134 { return mReversed ? mGapLeft : mGapLeft + mGapLength; }
135 bool AtOrBefore(double trackTime1, double trackTime2) const
136 { return mReversed ? trackTime1 >= trackTime2 : trackTime1 <= trackTime2; }
137
139 const double mGapLeft, mGapLength;
140
142 double mStart = 0, mEnd = 0;
143
144 // Non-negative real time durations
145 double mDuration1 = 0, mDuration2 = 0;
146 double mInitDuration1 = 0, mInitDuration2 = 0;
147
148 bool mDiscontinuity{ false };
149 bool mReversed{ false };
150};
151
152CutPreviewPlaybackPolicy::CutPreviewPlaybackPolicy(
153 double gapLeft, double gapLength)
154: mGapLeft{ gapLeft }, mGapLength{ gapLength }
155{
156 wxASSERT(gapLength >= 0.0);
157}
158
160
162 PlaybackSchedule &schedule, double rate)
163{
164 PlaybackPolicy::Initialize(schedule, rate);
165
166 // Examine mT0 and mT1 in the schedule only now; ignore changes during play
167 double left = mStart = schedule.mT0;
168 double right = mEnd = schedule.mT1;
169 mReversed = left > right;
170 if (mReversed)
171 std::swap(left, right);
172
173 if (left < mGapLeft)
174 mDuration1 = schedule.ComputeWarpedLength(left, mGapLeft);
175 const auto gapEnd = mGapLeft + mGapLength;
176 if (gapEnd < right)
177 mDuration2 = schedule.ComputeWarpedLength(gapEnd, right);
178 if (mReversed)
180 if (sampleCount(mDuration2 * rate) == 0)
184}
185
187{
189 auto diff = schedule.GetSequenceTime() - mEnd;
190 if (mReversed)
191 diff *= -1;
192 return sampleCount(diff * mRate) >= 0;
193}
194
196 PlaybackSchedule &schedule, double offset )
197{
198 // Compute new time by applying the offset, jumping over the gap
199 auto time = schedule.GetSequenceTime();
200 if (offset >= 0) {
201 auto space = std::clamp(mGapLeft - time, 0.0, offset);
202 time += space;
203 offset -= space;
204 if (offset > 0)
205 time = std::max(time, mGapLeft + mGapLength) + offset;
206 }
207 else {
208 auto space = std::clamp(mGapLeft + mGapLength - time, offset, 0.0);
209 time += space;
210 offset -= space;
211 if (offset < 0)
212 time = std::min(time, mGapLeft) + offset;
213 }
214 time = std::clamp(time, std::min(mStart, mEnd), std::max(mStart, mEnd));
215
216 // Reset the durations
217 mDiscontinuity = false;
220 if (AtOrBefore(time, GapStart()))
221 mDuration1 = std::max(0.0,
222 mDuration1 - fabs(schedule.ComputeWarpedLength(mStart, time)));
223 else {
224 mDuration1 = 0;
225 mDuration2 = std::max(0.0,
226 mDuration2 - fabs(schedule.ComputeWarpedLength(GapEnd(), time)));
227 }
228
229 return time;
230}
231
233 PlaybackSchedule &, size_t available)
234{
235 size_t frames = available;
236 size_t toProduce = frames;
237 sampleCount samples1(mDuration1 * mRate + 0.5);
238 if (samples1 > 0 && samples1 < frames)
239 // Shorter slice than requested, up to the discontinuity
240 toProduce = frames = samples1.as_size_t();
241 else if (samples1 == 0) {
242 sampleCount samples2(mDuration2 * mRate + 0.5);
243 if (samples2 < frames) {
244 toProduce = samples2.as_size_t();
245 // Produce some extra silence so that the time queue consumer can
246 // satisfy its end condition
247 frames = std::min(available, toProduce + TimeQueueGrainSize + 1);
248 }
249 }
250 return { available, frames, toProduce };
251}
252
254 PlaybackSchedule &schedule, double trackTime, size_t nSamples)
255{
256 auto realDuration = nSamples / mRate;
257 if (mDuration1 > 0) {
258 mDuration1 = std::max(0.0, mDuration1 - realDuration);
259 if (sampleCount(mDuration1 * mRate) == 0) {
260 mDuration1 = 0;
261 mDiscontinuity = true;
262 return { GapStart(), GapEnd() };
263 }
264 }
265 else
266 mDuration2 = std::max(0.0, mDuration2 - realDuration);
267 if (mReversed)
268 realDuration *= -1;
269 const double time = schedule.SolveWarpedLength(trackTime, realDuration);
270
271 if ( mReversed ? time <= mEnd : time >= mEnd )
272 return {mEnd, std::numeric_limits<double>::infinity()};
273 else
274 return {time, time};
275}
276
278 const Mixers &playbackMixers, size_t, size_t )
279{
280 if (mDiscontinuity) {
281 mDiscontinuity = false;
282 auto newTime = GapEnd();
283 for (auto &pMixer : playbackMixers)
284 pMixer->Reposition(newTime, true);
285 // Tell SequenceBufferExchange that we aren't done yet
286 return false;
287 }
288 return true;
289}
290}
291
293 const AudioIOStartStreamOptions &options,
294 PlayMode mode,
295 bool backwards /* = false */)
296{
297 auto &projectAudioManager = *this;
298 bool canStop = projectAudioManager.CanStopAudioStream();
299
300 if ( !canStop )
301 return -1;
302
303 auto &pStartTime = options.pStartTime;
304
305 bool nonWaveToo = options.playNonWaveTracks;
306
307 // Uncomment this for laughs!
308 // backwards = true;
309
310 double t0 = selectedRegion.t0();
311 double t1 = selectedRegion.t1();
312 // SelectedRegion guarantees t0 <= t1, so we need another boolean argument
313 // to indicate backwards play.
314 const bool newDefault = (mode == PlayMode::loopedPlay);
315
316 if (backwards)
317 std::swap(t0, t1);
318
319 projectAudioManager.SetLooping( mode == PlayMode::loopedPlay );
320 projectAudioManager.SetCutting( mode == PlayMode::cutPreviewPlay );
321
322 bool success = false;
323
324 auto gAudioIO = AudioIO::Get();
325 if (gAudioIO->IsBusy())
326 return -1;
327
328 const bool cutpreview = mode == PlayMode::cutPreviewPlay;
329 if (cutpreview && t0==t1)
330 return -1; /* msmeyer: makes no sense */
331
332 AudacityProject *p = &mProject;
333
334 auto &tracks = TrackList::Get(*p);
335
336 mLastPlayMode = mode;
337
338 bool hasaudio;
339 if (nonWaveToo)
340 hasaudio = ! tracks.Any<PlayableTrack>().empty();
341 else
342 hasaudio = ! tracks.Any<WaveTrack>().empty();
343
344 double latestEnd = tracks.GetEndTime();
345
346 if (!hasaudio)
347 return -1; // No need to continue without audio tracks
348
349#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
350 double initSeek = 0.0;
351#endif
352 double loopOffset = 0.0;
353
354 if (t1 == t0) {
355 if (newDefault) {
356 const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
357 // play selection if there is one, otherwise
358 // set start of play region to project start,
359 // and loop the project from current play position.
360
361 if ((t0 > selectedRegion.t0()) && (t0 < selectedRegion.t1())) {
362 t0 = selectedRegion.t0();
363 t1 = selectedRegion.t1();
364 }
365 else {
366 // loop the entire project
367 // Bug2347, loop playback from cursor position instead of project start
368 loopOffset = t0 - tracks.GetStartTime();
369 if (!pStartTime)
370 // TODO move this reassignment elsewhere so we don't need an
371 // ugly mutable member
372 pStartTime.emplace(loopOffset);
373 t0 = tracks.GetStartTime();
374 t1 = tracks.GetEndTime();
375 }
376 } else {
377 // move t0 to valid range
378 if (t0 < 0) {
379 t0 = tracks.GetStartTime();
380 }
381 else if (t0 > tracks.GetEndTime()) {
382 t0 = tracks.GetEndTime();
383 }
384#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
385 else {
386 initSeek = t0; //AC: initSeek is where playback will 'start'
387 if (!pStartTime)
388 pStartTime.emplace(initSeek);
389 t0 = tracks.GetStartTime();
390 }
391#endif
392 }
393 t1 = tracks.GetEndTime();
394 }
395 else {
396 // maybe t1 < t0, with backwards scrubbing for instance
397 if (backwards)
398 std::swap(t0, t1);
399
400 t0 = std::max(0.0, std::min(t0, latestEnd));
401 t1 = std::max(0.0, std::min(t1, latestEnd));
402
403 if (backwards)
404 std::swap(t0, t1);
405 }
406
407 int token = -1;
408
409 if (t1 != t0) {
410 if (cutpreview) {
411 const double tless = std::min(t0, t1);
412 const double tgreater = std::max(t0, t1);
413 double beforeLen, afterLen;
414 gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
415 gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
416 double tcp0 = tless-beforeLen;
417 const double diff = tgreater - tless;
418 double tcp1 = tgreater+afterLen;
419 if (backwards)
420 std::swap(tcp0, tcp1);
421 AudioIOStartStreamOptions myOptions = options;
422 myOptions.policyFactory =
423 [tless, diff](auto&) -> std::unique_ptr<PlaybackPolicy> {
424 return std::make_unique<CutPreviewPlaybackPolicy>(tless, diff);
425 };
426 token = gAudioIO->StartStream(
427 MakeTransportTracks(TrackList::Get(*p), false, nonWaveToo),
428 tcp0, tcp1, tcp1, myOptions);
429 }
430 else {
431 double mixerLimit = t1;
432 if (newDefault) {
433 mixerLimit = latestEnd;
434 if (pStartTime && *pStartTime >= t1)
435 t1 = latestEnd;
436 }
437 token = gAudioIO->StartStream(
438 MakeTransportTracks(tracks, false, nonWaveToo),
439 t0, t1, mixerLimit, options);
440 }
441 if (token != 0) {
442 success = true;
444 }
445 else {
446 // Bug1627 (part of it):
447 // infinite error spew when trying to start scrub:
448 // Problem was that the error dialog yields to events,
449 // causing recursion to this function in the scrub timer
450 // handler! Easy fix, just delay the user alert instead.
451 auto &window = GetProjectFrame( mProject );
452 window.CallAfter( [&]{
453 using namespace BasicUI;
454 // Show error message if stream could not be opened
456 XO("Error"),
457 XO("Error opening sound device.\nTry changing the audio host, playback device and the project sample rate."),
458 wxT("Error_opening_sound_device"),
459 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
460 });
461 }
462 }
463
464 if (!success)
465 return -1;
466
467 return token;
468}
469
470void ProjectAudioManager::PlayCurrentRegion(bool newDefault /* = false */,
471 bool cutpreview /* = false */)
472{
473 auto &projectAudioManager = *this;
474 bool canStop = projectAudioManager.CanStopAudioStream();
475
476 if ( !canStop )
477 return;
478
479 AudacityProject *p = &mProject;
480
481 {
482
483 const auto &playRegion = ViewInfo::Get( *p ).playRegion;
484
485 if (newDefault)
486 cutpreview = false;
487 auto options = ProjectAudioIO::GetDefaultOptions(*p, newDefault);
488 if (cutpreview)
489 options.envelope = nullptr;
490 auto mode =
491 cutpreview ? PlayMode::cutPreviewPlay
492 : newDefault ? PlayMode::loopedPlay
494 PlayPlayRegion(SelectedRegion(playRegion.GetStart(), playRegion.GetEnd()),
495 options,
496 mode);
497 }
498}
499
500void ProjectAudioManager::Stop(bool stopStream /* = true*/)
501{
502 AudacityProject *project = &mProject;
503 auto &projectAudioManager = *this;
504 bool canStop = projectAudioManager.CanStopAudioStream();
505
506 if ( !canStop )
507 return;
508
509 if(project) {
510 // Let scrubbing code do some appearance change
511 auto &scrubber = Scrubber::Get( *project );
512 scrubber.StopScrubbing();
513 }
514
515 auto gAudioIO = AudioIO::Get();
516
517 auto cleanup = finally( [&]{
518 projectAudioManager.SetStopping( false );
519 } );
520
521 if (stopStream && gAudioIO->IsBusy()) {
522 // flag that we are stopping
523 projectAudioManager.SetStopping( true );
524 // Allow UI to update for that
525 while( wxTheApp->ProcessIdle() )
526 ;
527 }
528
529 if(stopStream)
530 gAudioIO->StopStream();
531
532 projectAudioManager.SetLooping( false );
533 projectAudioManager.SetCutting( false );
534
535 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
536 gAudioIO->AILADisable();
537 #endif
538
539 projectAudioManager.SetPausedOff();
540 //Make sure you tell gAudioIO to unpause
541 gAudioIO->SetPaused( false );
542
543 // So that we continue monitoring after playing or recording.
544 // also clean the MeterQueues
545 if( project ) {
546 auto &projectAudioIO = ProjectAudioIO::Get( *project );
547 auto meter = projectAudioIO.GetPlaybackMeter();
548 if( meter ) {
549 meter->Clear();
550 }
551
552 meter = projectAudioIO.GetCaptureMeter();
553 if( meter ) {
554 meter->Clear();
555 }
556 }
557}
558
559
561 AudacityProject &proj, bool selectedOnly, double targetRate)
562{
563 auto p = &proj;
564 size_t recordingChannels = std::max(0, AudioIORecordChannels.Read());
565 bool strictRules = (recordingChannels <= 2);
566
567 // Iterate over all wave tracks, or over selected wave tracks only.
568 // If target rate was specified, ignore all tracks with other rates.
569 //
570 // In the usual cases of one or two recording channels, seek a first-fit
571 // unbroken sub-sequence for which the total number of channels matches the
572 // required number exactly. Never drop inputs or fill only some channels
573 // of a track.
574 //
575 // In case of more than two recording channels, choose tracks only among the
576 // selected. Simply take the earliest wave tracks, until the number of
577 // channels is enough. If there are fewer channels than inputs, but at least
578 // one channel, then some of the input channels will be dropped.
579 //
580 // Resulting tracks may be non-consecutive within the list of all tracks
581 // (there may be non-wave tracks between, or non-selected tracks when
582 // considering selected tracks only.)
583
584 if (!strictRules && !selectedOnly)
585 return {};
586
587 auto &trackList = TrackList::Get(*p);
588 WritableSampleTrackArray candidates;
589 std::vector<unsigned> channelCounts;
590 size_t totalChannels = 0;
591 const auto range = trackList.Any<WaveTrack>();
592 for (auto candidate : selectedOnly ? range + &Track::IsSelected : range) {
593 if (targetRate != RATE_NOT_SELECTED && candidate->GetRate() != targetRate)
594 continue;
595
596 // count channels in this track
597 const auto nChannels = candidate->NChannels();
598 if (strictRules && nChannels > recordingChannels) {
599 // The recording would under-fill this track's channels
600 // Can't use any partial accumulated results
601 // either. Keep looking.
602 candidates.clear();
603 channelCounts.clear();
604 totalChannels = 0;
605 continue;
606 }
607 else {
608 // Might use this but may have to discard some of the accumulated
609 while(strictRules &&
610 nChannels + totalChannels > recordingChannels) {
611 candidates.erase(candidates.begin());
612 auto nOldChannels = channelCounts[0];
613 assert(nOldChannels > 0);
614 channelCounts.erase(channelCounts.begin());
615 totalChannels -= nOldChannels;
616 }
617 candidates.push_back(candidate->SharedPointer<WaveTrack>());
618 channelCounts.push_back(nChannels);
619 totalChannels += nChannels;
620 if (totalChannels >= recordingChannels)
621 // Done!
622 return candidates;
623 }
624 }
625
626 if (!strictRules && !candidates.empty())
627 // good enough
628 return candidates;
629
630 // If the loop didn't exit early, we could not find enough channels
631 return {};
632}
633
635void ProjectAudioManager::OnRecord(bool altAppearance)
636{
637 bool bPreferNewTrack;
638 gPrefs->Read("/GUI/PreferNewTrackRecord", &bPreferNewTrack, false);
639 const bool appendRecord = (altAppearance == bPreferNewTrack);
640
641 // Code from CommandHandler start...
642 AudacityProject *p = &mProject;
643
644 if (p) {
645 const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
646 double t0 = selectedRegion.t0();
647 double t1 = selectedRegion.t1();
648 // When no time selection, recording duration is 'unlimited'.
649 if (t1 == t0)
650 t1 = DBL_MAX;
651
652 auto options = ProjectAudioIO::GetDefaultOptions(*p);
653 WritableSampleTrackArray existingTracks;
654
655 // Checking the selected tracks: counting them and
656 // making sure they all have the same rate
657 const auto selectedTracks{ GetPropertiesOfSelected(*p) };
658 const int rateOfSelected{ selectedTracks.rateOfSelected };
659 const bool anySelected{ selectedTracks.anySelected };
660 const bool allSameRate{ selectedTracks.allSameRate };
661
662 if (!allSameRate) {
663 AudacityMessageBox(XO("The tracks selected "
664 "for recording must all have the same sampling rate"),
665 XO("Mismatched Sampling Rates"),
666 wxICON_ERROR | wxCENTRE);
667
668 return;
669 }
670
671 if (appendRecord) {
672 // Try to find wave tracks to record into. (If any are selected,
673 // try to choose only from them; else if wave tracks exist, may record into any.)
674 existingTracks = ChooseExistingRecordingTracks(*p, true, rateOfSelected);
675 if (!existingTracks.empty())
676 t0 = std::max(t0,
678 .max(&Track::GetEndTime));
679 else {
680 if (anySelected && rateOfSelected != options.rate) {
682 "Too few tracks are selected for recording at this sample rate.\n"
683 "(Audacity requires two channels at the same sample rate for\n"
684 "each stereo track)"),
685 XO("Too Few Compatible Tracks Selected"),
686 wxICON_ERROR | wxCENTRE);
687
688 return;
689 }
690
691 existingTracks = ChooseExistingRecordingTracks(*p, false, options.rate);
692 if (!existingTracks.empty())
693 {
694 const auto endTime = accumulate(
695 existingTracks.begin(), existingTracks.end(),
696 std::numeric_limits<double>::lowest(),
697 [](double acc, auto &pTrack) {
698 return std::max(acc, pTrack->GetEndTime());
699 }
700 );
701
702 //If there is a suitable track, then adjust t0 so
703 //that recording not starts before the end of that track
704 t0 = std::max(t0, endTime);
705 }
706 // If suitable tracks still not found, will record into NEW ones,
707 // starting with t0
708 }
709
710 // Whether we decided on NEW tracks or not:
711 if (t1 <= selectedRegion.t0() && selectedRegion.t1() > selectedRegion.t0()) {
712 t1 = selectedRegion.t1(); // record within the selection
713 }
714 else {
715 t1 = DBL_MAX; // record for a long, long time
716 }
717 }
718
719 TransportSequences transportTracks;
720 if (UseDuplex()) {
721 // Remove recording tracks from the list of tracks for duplex ("overdub")
722 // playback.
723 /* TODO: set up stereo tracks if that is how the user has set up
724 * their preferences, and choose sample format based on prefs */
725 transportTracks =
726 MakeTransportTracks(TrackList::Get( *p ), false, true);
727 for (const auto &wt : existingTracks) {
728 auto end = transportTracks.playbackSequences.end();
729 auto it = std::find_if(
730 transportTracks.playbackSequences.begin(), end,
731 [&wt](const auto& playbackSequence) {
732 return playbackSequence->FindChannelGroup() ==
733 wt->FindChannelGroup();
734 });
735 if (it != end)
736 transportTracks.playbackSequences.erase(it);
737 }
738 }
739
740 std::copy(existingTracks.begin(), existingTracks.end(),
741 back_inserter(transportTracks.captureSequences));
742
743 if (rateOfSelected != RATE_NOT_SELECTED)
744 options.rate = rateOfSelected;
745
746 DoRecord(*p, transportTracks, t0, t1, altAppearance, options);
747 }
748}
749
751{
752 bool duplex;
753 gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex, true);
754 return duplex;
755}
756
758 const TransportSequences &sequences,
759 double t0, double t1,
760 bool altAppearance,
761 const AudioIOStartStreamOptions &options)
762{
763 auto &projectAudioManager = *this;
764
765 CommandFlag flags = AlwaysEnabledFlag; // 0 means recalc flags.
766
767 // NB: The call may have the side effect of changing flags.
769 flags,
771
772 if (!allowed)
773 return false;
774 // ...end of code from CommandHandler.
775
776 auto gAudioIO = AudioIO::Get();
777 if (gAudioIO->IsBusy())
778 return false;
779
780 projectAudioManager.SetAppending(!altAppearance);
781
782 bool success = false;
783
784 auto transportSequences = sequences;
785
786 // Will replace any given capture tracks with temporaries
787 transportSequences.captureSequences.clear();
788
789 const auto p = &project;
790 auto &trackList = TrackList::Get(project);
791
792 bool appendRecord = !sequences.captureSequences.empty();
793
794 auto makeNewClipName = [&](WaveTrack* track) {
795 for (auto i = 1; ; ++i) {
796 //i18n-hint a numerical suffix added to distinguish otherwise like-named clips when new record started
797 auto name = XC("%s #%d", "clip name template")
798 .Format(track->GetName(), i).Translation();
799 if (track->FindClipByName(name) == nullptr)
800 return name;
801 }
802 };
803
804 {
805 if (appendRecord) {
806 // Append recording:
807 // Pad selected/all wave tracks to make them all the same length
808 for (const auto &sequence : sequences.captureSequences) {
809 WaveTrack *wt{};
810 if (!(wt = dynamic_cast<WaveTrack *>(sequence.get()))) {
811 assert(false);
812 continue;
813 }
814 if (!wt->IsLeader())
815 continue;
816 auto endTime = wt->GetEndTime();
817
818 // If the track was chosen for recording and playback both,
819 // remember the original in preroll tracks, before making the
820 // pending replacement.
821 const auto shared = wt->SharedPointer<WaveTrack>();
822 // playbackSequences contains only leaders; prerollSequences should
823 // be a subset of it. Non-leader might not be found, but that is
824 // all right.
825 const auto &range = transportSequences.playbackSequences;
826 bool prerollTrack = any_of(range.begin(), range.end(),
827 [&](const auto &pSequence){
828 return shared.get() == pSequence->FindChannelGroup(); });
829 if (prerollTrack)
830 transportSequences.prerollSequences.push_back(shared);
831
832 // A function that copies all the non-sample data between
833 // wave tracks; in case the track recorded to changes scale
834 // type (for instance), during the recording.
835 auto updater = [](Track &d, const Track &s){
836 assert(d.IsLeader());
837 assert(s.IsLeader());
838 assert(d.NChannels() == s.NChannels());
839 auto &dst = static_cast<WaveTrack&>(d);
840 auto &src = static_cast<const WaveTrack&>(s);
841 dst.Reinit(src);
842 };
843
844 // Get a copy of the track to be appended, to be pushed into
845 // undo history only later.
846 const auto pending = static_cast<WaveTrack*>(
847 trackList.RegisterPendingChangedTrack(updater, wt)
848 );
849 // End of current track is before or at recording start time.
850 // Less than or equal, not just less than, to ensure a clip boundary.
851 // when append recording.
852 //const auto pending = static_cast<WaveTrack*>(newTrack);
853 const auto lastClip = pending->GetRightmostClip();
854 // RoundedT0 to have a new clip created when punch-and-roll
855 // recording with the cursor in the second half of the space
856 // between two samples
857 // (https://github.com/audacity/audacity/issues/5113#issuecomment-1705154108)
858 const auto recordingStart =
859 std::round(t0 * pending->GetRate()) / pending->GetRate();
860 const auto recordingStartsBeforeTrackEnd =
861 lastClip && recordingStart < lastClip->GetPlayEndTime();
862 // Recording doesn't start before the beginning of the last clip
863 // - or the check for creating a new clip or not should be more
864 // general than that ...
865 assert(
866 !recordingStartsBeforeTrackEnd ||
867 lastClip->WithinPlayRegion(recordingStart));
868 if (!recordingStartsBeforeTrackEnd ||
869 lastClip->HasPitchOrSpeed())
870 pending->CreateWideClip(t0, makeNewClipName(pending));
871 transportSequences.captureSequences
872 .push_back(pending->SharedPointer<WaveTrack>());
873 }
874 trackList.UpdatePendingTracks();
875 }
876
877 if (transportSequences.captureSequences.empty()) {
878 // recording to NEW track(s).
879 bool recordingNameCustom, useTrackNumber, useDateStamp, useTimeStamp;
880 wxString defaultTrackName, defaultRecordingTrackName;
881
882 // Count the tracks.
883 auto numTracks = trackList.Any<const WaveTrack>().size();
884
885 auto recordingChannels = std::max(1, AudioIORecordChannels.Read());
886
887 gPrefs->Read(wxT("/GUI/TrackNames/RecordingNameCustom"), &recordingNameCustom, false);
888 gPrefs->Read(wxT("/GUI/TrackNames/TrackNumber"), &useTrackNumber, false);
889 gPrefs->Read(wxT("/GUI/TrackNames/DateStamp"), &useDateStamp, false);
890 gPrefs->Read(wxT("/GUI/TrackNames/TimeStamp"), &useTimeStamp, false);
891 defaultTrackName = trackList.MakeUniqueTrackName(WaveTrack::GetDefaultAudioTrackNamePreference());
892 gPrefs->Read(wxT("/GUI/TrackNames/RecodingTrackName"), &defaultRecordingTrackName, defaultTrackName);
893
894 wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
895
896 auto newTracks = WaveTrackFactory::Get(*p).Create(recordingChannels);
897 const auto first = *newTracks->begin();
898 int trackCounter = 0;
899 const auto minimizeChannelView = recordingChannels > 2
901 for (auto newTrack : newTracks->Any<WaveTrack>()) {
902 // Quantize bounds to the rate of the new track.
903 if (newTrack == first) {
904 if (t0 < DBL_MAX)
905 t0 = newTrack->SnapToSample(t0);
906 if (t1 < DBL_MAX)
907 t1 = newTrack->SnapToSample(t1);
908 }
909
910 newTrack->MoveTo(t0);
911 wxString nameSuffix = wxString(wxT(""));
912
913 if (useTrackNumber) {
914 nameSuffix += wxString::Format(wxT("%d"), 1 + (int) numTracks + trackCounter++);
915 }
916
917 if (useDateStamp) {
918 if (!nameSuffix.empty()) {
919 nameSuffix += wxT("_");
920 }
921 nameSuffix += wxDateTime::Now().FormatISODate();
922 }
923
924 if (useTimeStamp) {
925 if (!nameSuffix.empty()) {
926 nameSuffix += wxT("_");
927 }
928 nameSuffix += wxDateTime::Now().FormatISOTime();
929 }
930
931 // ISO standard would be nice, but ":" is unsafe for file name.
932 nameSuffix.Replace(wxT(":"), wxT("-"));
933
934 if (baseTrackName.empty())
935 newTrack->SetName(nameSuffix);
936 else if (nameSuffix.empty())
937 newTrack->SetName(baseTrackName);
938 else
939 newTrack->SetName(baseTrackName + wxT("_") + nameSuffix);
940
941 //create a new clip with a proper name before recording is started
942 newTrack->CreateWideClip(t0, makeNewClipName(newTrack));
943
944 transportSequences.captureSequences.push_back(
945 std::static_pointer_cast<WaveTrack>(newTrack->shared_from_this())
946 );
947
948 for(auto channel : newTrack->Channels())
949 {
950 ChannelView::Get(*channel).SetMinimized(minimizeChannelView);
951 }
952 }
953 trackList.RegisterPendingNewTracks(std::move(*newTracks));
954 // Bug 1548. First of new tracks needs the focus.
955 TrackFocus::Get(project).Set(first);
956 if (!trackList.empty())
957 Viewport::Get(project).ShowTrack(**trackList.rbegin());
958 }
959
960 //Automated Input Level Adjustment Initialization
961 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
962 gAudioIO->AILAInitialize();
963 #endif
964
965 int token =
966 gAudioIO->StartStream(transportSequences, t0, t1, t1, options);
967
968 success = (token != 0);
969
970 if (success) {
972 }
973 else {
974 CancelRecording();
975
976 // Show error message if stream could not be opened
977 auto msg = XO("Error opening recording device.\nError code: %s")
978 .Format( gAudioIO->LastPaErrorString() );
979 using namespace BasicUI;
981 XO("Error"), msg, wxT("Error_opening_sound_device"),
982 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
983 }
984 }
985
986 return success;
987}
988
990{
991 auto &projectAudioManager = *this;
992 bool canStop = projectAudioManager.CanStopAudioStream();
993
994 if ( !canStop ) {
995 return;
996 }
997
998 bool paused = !projectAudioManager.Paused();
999 TogglePaused();
1000
1001 auto gAudioIO = AudioIO::Get();
1002
1003#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
1004
1005 auto project = &mProject;
1006 auto &scrubber = Scrubber::Get( *project );
1007
1008 // Bug 1494 - Pausing a seek or scrub should just STOP as
1009 // it is confusing to be in a paused scrub state.
1010 bool bStopInstead = paused &&
1012 !scrubber.IsSpeedPlaying() &&
1013 !scrubber.IsKeyboardScrubbing();
1014
1015 if (bStopInstead) {
1016 Stop();
1017 return;
1018 }
1019
1021 scrubber.Pause(paused);
1022 else
1023#endif
1024 {
1025 gAudioIO->SetPaused(paused);
1026 }
1027}
1028
1029
1031{
1032 mPaused.fetch_xor(1, std::memory_order::memory_order_relaxed);
1033}
1034
1036{
1037 mPaused.store(0, std::memory_order::memory_order_relaxed);
1038}
1039
1041{
1042 return mPaused.load(std::memory_order_relaxed) == 1;
1043}
1044
1045
1047{
1048 const auto project = &mProject;
1049 TrackList::Get( *project ).ClearPendingTracks();
1050}
1051
1053{
1054 auto &project = mProject;
1055
1056 mDisplayedRate = rate;
1057
1058 auto display = FormatRate( rate );
1059
1061}
1062
1064{
1065 // Auto-save was done here before, but it is unnecessary, provided there
1066 // are sufficient autosaves when pushing or modifying undo states.
1067}
1068
1069// This is called after recording has stopped and all tracks have flushed.
1071{
1072 auto &project = mProject;
1073 auto &projectAudioIO = ProjectAudioIO::Get( project );
1074 auto &projectFileIO = ProjectFileIO::Get( project );
1075
1076 // Only push state if we were capturing and not monitoring
1077 if (projectAudioIO.GetAudioIOToken() > 0)
1078 {
1079 auto &history = ProjectHistory::Get( project );
1080
1081 if (IsTimerRecordCancelled()) {
1082 // discard recording
1083 history.RollbackState();
1084 // Reset timer record
1085 ResetTimerRecordCancelled();
1086 }
1087 else {
1088 // Add to history
1089 // We want this to have No-fail-guarantee if we get here from exception
1090 // handling of recording, and that means we rely on the last autosave
1091 // successfully committed to the database, not risking a failure
1092 auto flags = AudioIO::Get()->HasRecordingException()
1095 history.PushState(XO("Recorded Audio"), XO("Record"), flags);
1096
1097 // Now, we may add a label track to give information about
1098 // dropouts. We allow failure of this.
1099 auto gAudioIO = AudioIO::Get();
1100 auto &intervals = gAudioIO->LostCaptureIntervals();
1101 if (intervals.size())
1102 Publish( RecordingDropoutEvent{ intervals } );
1103 }
1104 }
1105}
1106
1108{
1109 auto &project = mProject;
1110 auto &projectFileIO = ProjectFileIO::Get( project );
1111
1112 wxTheApp->CallAfter( [&]{ projectFileIO.AutoSave(true); });
1113}
1114
1116{
1117 const auto project = &mProject;
1118 TrackList::Get( *project ).ApplyPendingTracks();
1119}
1120
1122{
1123 auto& project = mProject;
1124 auto gAudioIO = AudioIO::Get();
1125 if (gAudioIO && &project == gAudioIO->GetOwningProject().get())
1126 {
1127 bool canStop = CanStopAudioStream();
1128
1129 gAudioIO->SetPaused(!gAudioIO->IsPaused());
1130
1131 if (canStop)
1132 {
1133 // Instead of calling ::OnPause here, we can simply do the only thing it does (i.e. toggling the pause state),
1134 // because scrubbing can not happen while recording
1135 TogglePaused();
1136 }
1137 }
1138}
1139
1141{
1143 Stop();
1144}
1145
1147{
1148 auto gAudioIO = AudioIO::Get();
1149 return
1150 gAudioIO->IsBusy() &&
1151 CanStopAudioStream() &&
1152 // ... and not merely monitoring
1153 !gAudioIO->IsMonitoring() &&
1154 // ... and not punch-and-roll recording
1155 gAudioIO->GetNumCaptureChannels() == 0;
1156}
1157
1159{
1160 auto gAudioIO = AudioIO::Get();
1161 return
1162 gAudioIO->IsBusy() &&
1163 CanStopAudioStream() &&
1164 gAudioIO->GetNumCaptureChannels() > 0;
1165}
1166
1168{
1169 auto gAudioIO = AudioIO::Get();
1170 return (!gAudioIO->IsStreamActive() ||
1171 gAudioIO->IsMonitoring() ||
1172 gAudioIO->GetOwningProject().get() == &mProject );
1173}
1174
1177 [](const AudacityProject &project){
1178 auto &projectAudioManager = ProjectAudioManager::Get( project );
1179 bool canStop = projectAudioManager.CanStopAudioStream();
1180 return canStop;
1181 }
1182 }; return flag; }
1183
1186[](AudacityProject &project, bool newDefault) -> AudioIOStartStreamOptions {
1188 auto options = ProjectAudioIO::DefaultOptionsFactory(project, newDefault);
1189
1191 options.listener = ProjectAudioManager::Get(project).shared_from_this();
1192
1193 bool loopEnabled = ViewInfo::Get(project).playRegion.Active();
1194 options.loopEnabled = loopEnabled;
1195
1196 if (newDefault) {
1197 const double trackEndTime = TrackList::Get(project).GetEndTime();
1198 const double loopEndTime = ViewInfo::Get(project).playRegion.GetEnd();
1199 options.policyFactory = [&project, trackEndTime, loopEndTime](
1200 const AudioIOStartStreamOptions &options)
1201 -> std::unique_ptr<PlaybackPolicy>
1202 {
1203 return std::make_unique<DefaultPlaybackPolicy>( project,
1204 trackEndTime, loopEndTime, options.pStartTime,
1205 options.loopEnabled, options.variableSpeed);
1206 };
1207
1208 // Start play from left edge of selection
1209 options.pStartTime.emplace(ViewInfo::Get(project).selectedRegion.t0());
1210 }
1211
1212 return options;
1213} };
1214
1217{
1219 auto gAudioIO = AudioIO::Get();
1220 auto PlayAtSpeedRate = gAudioIO->GetBestRate(
1221 false, //not capturing
1222 true, //is playing
1223 ProjectRate::Get( project ).GetRate() //suggested rate
1224 );
1225 result.rate = PlayAtSpeedRate;
1226 return result;
1227}
1228
1229// Stop playing or recording, if paused.
1231{
1232 if( AudioIOBase::Get()->IsPaused() )
1233 Stop();
1234}
1235
1236bool ProjectAudioManager::DoPlayStopSelect( bool click, bool shift )
1237{
1238 auto &project = mProject;
1239 auto &scrubber = Scrubber::Get( project );
1241 auto &viewInfo = ViewInfo::Get( project );
1242 auto &selection = viewInfo.selectedRegion;
1243 auto gAudioIO = AudioIO::Get();
1244
1245 //If busy, stop playing, make sure everything is unpaused.
1246 if (scrubber.HasMark() ||
1247 gAudioIO->IsStreamActive(token)) {
1248 // change the selection
1249 auto time = gAudioIO->GetStreamTime();
1250 // Test WasSpeedPlaying(), not IsSpeedPlaying()
1251 // as we could be stopped now. Similarly WasKeyboardScrubbing().
1252 if (click && (scrubber.WasSpeedPlaying() || scrubber.WasKeyboardScrubbing()))
1253 {
1254 ;// don't change the selection.
1255 }
1256 else if (shift && click) {
1257 // Change the region selection, as if by shift-click at the play head
1258 auto t0 = selection.t0(), t1 = selection.t1();
1259 if (time < t0)
1260 // Grow selection
1261 t0 = time;
1262 else if (time > t1)
1263 // Grow selection
1264 t1 = time;
1265 else {
1266 // Shrink selection, changing the nearer boundary
1267 if (fabs(t0 - time) < fabs(t1 - time))
1268 t0 = time;
1269 else
1270 t1 = time;
1271 }
1272 selection.setTimes(t0, t1);
1273 }
1274 else if (click){
1275 // avoid a point at negative time.
1276 time = wxMax( time, 0 );
1277 // Set a point selection, as if by a click at the play head
1278 selection.setTimes(time, time);
1279 } else
1280 // How stop and set cursor always worked
1281 // -- change t0, collapsing to point only if t1 was greater
1282 selection.setT0(time, false);
1283
1284 ProjectHistory::Get( project ).ModifyState(false); // without bWantsAutoSave
1285 return true;
1286 }
1287 return false;
1288}
1289
1290// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and
1291// "OnStopSelect" merged.
1293{
1294 auto gAudioIO = AudioIO::Get();
1295 if (DoPlayStopSelect(false, false))
1296 Stop();
1297 else if (!gAudioIO->IsBusy()) {
1298 //Otherwise, start playing (assuming audio I/O isn't busy)
1299
1300 // Will automatically set mLastPlayMode
1301 PlayCurrentRegion(false);
1302 }
1303}
1304
1306 []{ return PausedFlag(); },
1307 []{ return AudioIONotBusyFlag(); },
1308 []( const AudacityProject &project ){
1313 }
1314}};
1315
1316// GetSelectedProperties collects information about
1317// currently selected audio tracks
1320{
1321 double rateOfSelection{ RATE_NOT_SELECTED };
1322
1323 PropertiesOfSelected result;
1324 result.allSameRate = true;
1325
1326 const auto selectedTracks{
1327 TrackList::Get(proj).Selected<const WaveTrack>() };
1328
1329 for (const auto & track : selectedTracks) {
1330 if (rateOfSelection != RATE_NOT_SELECTED &&
1331 track->GetRate() != rateOfSelection)
1332 result.allSameRate = false;
1333 else if (rateOfSelection == RATE_NOT_SELECTED)
1334 rateOfSelection = track->GetRate();
1335 }
1336
1337 result.anySelected = !selectedTracks.empty();
1338 result.rateOfSelected = rateOfSelection;
1339
1340 return result;
1341}
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
IntSetting AudioIORecordChannels
Toolkit-neutral facade for basic user interface services.
constexpr CommandFlag AlwaysEnabledFlag
Definition: CommandFlag.h:34
std::bitset< NCommandFlags > CommandFlag
Definition: CommandFlag.h:30
const ReservedCommandFlag & AudioIONotBusyFlag()
const ReservedCommandFlag & PausedFlag()
int min(int a, int b)
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
#define field(n, t)
Definition: ImportAUP.cpp:165
#define XC(s, c)
Definition: Internat.h:37
constexpr size_t TimeQueueGrainSize
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
std::unique_ptr< const BasicUI::WindowPlacement > ProjectFramePlacement(AudacityProject *project)
Make a WindowPlacement object suitable for project (which may be null)
Definition: Project.cpp:129
static TranslatableString FormatRate(int rate)
static ProjectAudioIO::DefaultOptions::Scope sScope
Install an implementation in a library hook.
AudioIOStartStreamOptions DefaultSpeedPlayOptions(AudacityProject &project)
static RegisteredMenuItemEnabler stopIfPaused
static AudacityProject::AttachedObjects::RegisteredFactory sProjectAudioManagerKey
const ReservedCommandFlag & CanStopAudioStreamFlag()
PropertiesOfSelected GetPropertiesOfSelected(const AudacityProject &proj)
@ cutPreviewPlay
std::vector< std::shared_ptr< WritableSampleTrack > > WritableSampleTrackArray
constexpr int RATE_NOT_SELECTED
ProjectFileIOMessage
Subscribe to ProjectFileIO to receive messages; always in idle time.
Definition: ProjectFileIO.h:50
@ CheckpointFailure
Failure happened in a worker thread.
an object holding per-project preferred sample rate
StatusBarField
Definition: ProjectStatus.h:24
@ rateStatusBarField
Definition: ProjectStatus.h:27
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
accessors for certain important windows associated with each project
const auto tracks
const auto project
TransportSequences MakeTransportTracks(TrackList &trackList, bool selectedOnly, bool nonWaveToo)
static CustomUpdaterValue updater
static std::once_flag flag
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static AudioIOBase * Get()
Definition: AudioIOBase.cpp:94
static AudioIO * Get()
Definition: AudioIO.cpp:126
bool HasRecordingException() const
Definition: AudioIO.h:374
double GetEndTime() const
Get the maximum of End() values of intervals, or 0 when none.
Definition: Channel.cpp:135
virtual size_t NChannels() const =0
Report the number of channels.
static ChannelView & Get(Channel &channel)
void SetMinimized(bool minimized)
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:274
bool TryToMakeActionAllowed(CommandFlag &flags, CommandFlag flagsRqd)
static CommandManager & Get(AudacityProject &project)
typename GlobalVariable< DefaultOptions, const std::function< std::remove_pointer_t< decltype(DefaultFunction)> >, Default, Options... >::Scope Scope
double t0() const
Definition: ViewInfo.h:35
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
double GetEnd() const
Definition: ViewInfo.h:135
bool Active() const
Definition: ViewInfo.h:124
AudioTrack subclass that can also be audibly replayed by the program.
Definition: PlayableTrack.h:40
Directs which parts of tracks to fetch for playback.
std::vector< std::unique_ptr< Mixer > > Mixers
virtual void Initialize(PlaybackSchedule &schedule, double rate)
Called before starting an audio stream.
static AudioIOStartStreamOptions DefaultOptionsFactory(AudacityProject &project, bool newDefaults)
Default factory function ignores the second argument.
int GetAudioIOToken() const
void SetAudioIOToken(int token)
static AudioIOStartStreamOptions GetDefaultOptions(AudacityProject &project, bool newDefaults=false)
Invoke the global hook, supplying a default argument.
static ProjectAudioIO & Get(AudacityProject &project)
void Stop(bool stopStream=true)
static std::pair< TranslatableStrings, unsigned > StatusWidthFunction(const AudacityProject &project, StatusBarField field)
void OnAudioIONewBlocks() override
void OnAudioIOStartRecording() override
void OnCommitRecording() override
static WritableSampleTrackArray ChooseExistingRecordingTracks(AudacityProject &proj, bool selectedOnly, double targetRate=RATE_NOT_SELECTED)
void OnAudioIORate(int rate) override
static ProjectAudioManager & Get(AudacityProject &project)
void OnCheckpointFailure(ProjectFileIOMessage)
void OnAudioIOStopRecording() override
~ProjectAudioManager() override
ProjectAudioManager(AudacityProject &project)
int PlayPlayRegion(const SelectedRegion &selectedRegion, const AudioIOStartStreamOptions &options, PlayMode playMode, bool backwards=false)
bool DoRecord(AudacityProject &project, const TransportSequences &transportSequences, double t0, double t1, bool altAppearance, const AudioIOStartStreamOptions &options)
Observer::Subscription mCheckpointFailureSubscription
void PlayCurrentRegion(bool newDefault=false, bool cutpreview=false)
void OnRecord(bool altAppearance)
void OnSoundActivationThreshold() override
static ProjectFileIO & Get(AudacityProject &project)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
std::pair< std::vector< TranslatableString >, unsigned > StatusWidthResult
Definition: ProjectStatus.h:49
static ProjectStatus & Get(AudacityProject &project)
void Set(const TranslatableString &msg, StatusBarField field=mainStatusBarField)
static Scrubber & Get(AudacityProject &project)
Definition: Scrubbing.cpp:188
Defines a selected portion of a project.
double t1() const
double t0() const
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
Track * Get()
Definition: TrackFocus.cpp:156
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:122
bool IsSelected() const
Definition: Track.cpp:288
bool IsLeader() const override
Definition: Track.cpp:291
double GetEndTime() const
Return the greatest end time of the tracks, or 0 when no tracks.
Definition: Track.cpp:991
bool ApplyPendingTracks()
Definition: Track.cpp:1092
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
void ClearPendingTracks(ListOfTracks *pAdded=nullptr)
Definition: Track.cpp:1050
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1114
static BoolSetting TracksFitVerticallyZoomed
Definition: TracksPrefs.h:30
Holds a msgid for the translation catalog; may also bind format arguments.
PlayRegion playRegion
Definition: ViewInfo.h:216
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:215
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
void ShowTrack(const Track &track)
Definition: Viewport.cpp:454
static Viewport & Get(AudacityProject &project)
Definition: Viewport.cpp:32
std::shared_ptr< WaveTrack > Create()
Creates an unnamed empty WaveTrack with default sample format and default rate.
Definition: WaveTrack.cpp:761
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:4516
A Track that contains audio waveform data.
Definition: WaveTrack.h:227
void Reinit(const WaveTrack &orig)
Definition: WaveTrack.cpp:865
static wxString GetDefaultAudioTrackNamePreference()
Definition: WaveTrack.cpp:743
const WaveClip * GetRightmostClip() const
Definition: WaveTrack.cpp:3045
size_t NChannels() const override
May report more than one only when this is a leader track.
Definition: WaveTrack.cpp:836
std::pair< double, double > AdvancedTrackTime(PlaybackSchedule &schedule, double trackTime, size_t nSamples) override
Compute a new point in a track's timeline from an old point and a real duration.
PlaybackSlice GetPlaybackSlice(PlaybackSchedule &schedule, size_t available) override
Choose length of one fetch of samples from tracks in a call to AudioIO::FillPlayBuffers.
double OffsetSequenceTime(PlaybackSchedule &schedule, double offset) override
Called when the play head needs to jump a certain distance.
void Initialize(PlaybackSchedule &schedule, double rate) override
Called before starting an audio stream.
double mStart
Starting and ending track times set in Initialize()
bool Done(PlaybackSchedule &schedule, unsigned long) override
Returns true if schedule.GetSequenceTime() has reached the end of playback.
bool RepositionPlayback(PlaybackSchedule &schedule, const Mixers &playbackMixers, size_t frames, size_t available) override
AudioIO::FillPlayBuffers calls this to update its cursors into tracks for changes of position or spee...
const double mGapLeft
Fixed at construction time; these are a track time and duration.
virtual bool Read(const wxString &key, bool *value) const =0
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
size_t as_size_t() const
Definition: SampleCount.cpp:16
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:262
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:645
double GetRate(const Track &track)
Definition: TimeTrack.cpp:196
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
struct holding stream options, including a pointer to the time warp info and AudioIOListener and whet...
Definition: AudioIOBase.h:44
PolicyFactory policyFactory
Definition: AudioIOBase.h:73
std::optional< double > pStartTime
Definition: AudioIOBase.h:58
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:52
double mT0
Playback starts at offset of mT0, which is measured in seconds.
double mT1
Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 duri...
double SolveWarpedLength(double t0, double length) const
Compute how much unwarped time must have elapsed if length seconds of warped time has elapsed,...
double GetSequenceTime() const
Get current track time value, unadjusted.
double ComputeWarpedLength(double t0, double t1) const
Compute signed duration (in seconds at playback) of the specified region of the track.
Describes an amount of contiguous (but maybe time-warped) data to be extracted from tracks to play.
Notification, after recording has stopped, when dropouts have been detected.
static bool IsScrubbing()
Definition: ScrubState.cpp:482
RecordableSequences captureSequences
Definition: AudioIO.h:71
ConstPlayableSequences playbackSequences
Definition: AudioIO.h:70