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 "CommonCommandFlags.h"
24#include "Menus.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 "ProjectSettings.h"
33#include "ProjectStatus.h"
34#include "ProjectWindows.h"
35#include "ScrubState.h"
36#include "TrackPanelAx.h"
37#include "TransportUtilities.h"
38#include "UndoManager.h"
39#include "ViewInfo.h"
40#include "WaveClip.h"
41#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 // To do: eliminate this, use an event instead
559 const auto toolbar = ToolManager::Get( *project ).GetToolBar(wxT("Scrub"));
560 if (toolbar)
561 toolbar->EnableDisableButtons();
562}
563
564
566 AudacityProject &proj, bool selectedOnly, double targetRate)
567{
568 auto p = &proj;
569 size_t recordingChannels = std::max(0, AudioIORecordChannels.Read());
570 bool strictRules = (recordingChannels <= 2);
571
572 // Iterate over all wave tracks, or over selected wave tracks only.
573 // If target rate was specified, ignore all tracks with other rates.
574 //
575 // In the usual cases of one or two recording channels, seek a first-fit
576 // unbroken sub-sequence for which the total number of channels matches the
577 // required number exactly. Never drop inputs or fill only some channels
578 // of a track.
579 //
580 // In case of more than two recording channels, choose tracks only among the
581 // selected. Simply take the earliest wave tracks, until the number of
582 // channels is enough. If there are fewer channels than inputs, but at least
583 // one channel, then some of the input channels will be dropped.
584 //
585 // Resulting tracks may be non-consecutive within the list of all tracks
586 // (there may be non-wave tracks between, or non-selected tracks when
587 // considering selected tracks only.)
588
589 if (!strictRules && !selectedOnly)
590 return {};
591
592 auto &trackList = TrackList::Get(*p);
593 WritableSampleTrackArray candidates;
594 std::vector<unsigned> channelCounts;
595 size_t totalChannels = 0;
596 const auto range = trackList.Any<WaveTrack>();
597 for (auto candidate : selectedOnly ? range + &Track::IsSelected : range) {
598 if (targetRate != RATE_NOT_SELECTED && candidate->GetRate() != targetRate)
599 continue;
600
601 // count channels in this track
602 const auto nChannels = candidate->NChannels();
603 if (strictRules && nChannels > recordingChannels) {
604 // The recording would under-fill this track's channels
605 // Can't use any partial accumulated results
606 // either. Keep looking.
607 candidates.clear();
608 channelCounts.clear();
609 totalChannels = 0;
610 continue;
611 }
612 else {
613 // Might use this but may have to discard some of the accumulated
614 while(strictRules &&
615 nChannels + totalChannels > recordingChannels) {
616 candidates.erase(candidates.begin());
617 auto nOldChannels = channelCounts[0];
618 assert(nOldChannels > 0);
619 channelCounts.erase(channelCounts.begin());
620 totalChannels -= nOldChannels;
621 }
622 candidates.push_back(candidate->SharedPointer<WaveTrack>());
623 channelCounts.push_back(nChannels);
624 totalChannels += nChannels;
625 if (totalChannels >= recordingChannels)
626 // Done!
627 return candidates;
628 }
629 }
630
631 if (!strictRules && !candidates.empty())
632 // good enough
633 return candidates;
634
635 // If the loop didn't exit early, we could not find enough channels
636 return {};
637}
638
640void ProjectAudioManager::OnRecord(bool altAppearance)
641{
642 bool bPreferNewTrack;
643 gPrefs->Read("/GUI/PreferNewTrackRecord", &bPreferNewTrack, false);
644 const bool appendRecord = (altAppearance == bPreferNewTrack);
645
646 // Code from CommandHandler start...
647 AudacityProject *p = &mProject;
648
649 if (p) {
650 const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
651 double t0 = selectedRegion.t0();
652 double t1 = selectedRegion.t1();
653 // When no time selection, recording duration is 'unlimited'.
654 if (t1 == t0)
655 t1 = DBL_MAX;
656
657 auto options = ProjectAudioIO::GetDefaultOptions(*p);
658 WritableSampleTrackArray existingTracks;
659
660 // Checking the selected tracks: counting them and
661 // making sure they all have the same rate
662 const auto selectedTracks{ GetPropertiesOfSelected(*p) };
663 const int rateOfSelected{ selectedTracks.rateOfSelected };
664 const bool anySelected{ selectedTracks.anySelected };
665 const bool allSameRate{ selectedTracks.allSameRate };
666
667 if (!allSameRate) {
668 AudacityMessageBox(XO("The tracks selected "
669 "for recording must all have the same sampling rate"),
670 XO("Mismatched Sampling Rates"),
671 wxICON_ERROR | wxCENTRE);
672
673 return;
674 }
675
676 if (appendRecord) {
677 // Try to find wave tracks to record into. (If any are selected,
678 // try to choose only from them; else if wave tracks exist, may record into any.)
679 existingTracks = ChooseExistingRecordingTracks(*p, true, rateOfSelected);
680 if (!existingTracks.empty())
681 t0 = std::max(t0,
683 .max(&Track::GetEndTime));
684 else {
685 if (anySelected && rateOfSelected != options.rate) {
687 "Too few tracks are selected for recording at this sample rate.\n"
688 "(Audacity requires two channels at the same sample rate for\n"
689 "each stereo track)"),
690 XO("Too Few Compatible Tracks Selected"),
691 wxICON_ERROR | wxCENTRE);
692
693 return;
694 }
695
696 existingTracks = ChooseExistingRecordingTracks(*p, false, options.rate);
697 if (!existingTracks.empty())
698 {
699 const auto endTime = accumulate(
700 existingTracks.begin(), existingTracks.end(),
701 std::numeric_limits<double>::lowest(),
702 [](double acc, auto &pTrack) {
703 return std::max(acc, pTrack->GetEndTime());
704 }
705 );
706
707 //If there is a suitable track, then adjust t0 so
708 //that recording not starts before the end of that track
709 t0 = std::max(t0, endTime);
710 }
711 // If suitable tracks still not found, will record into NEW ones,
712 // starting with t0
713 }
714
715 // Whether we decided on NEW tracks or not:
716 if (t1 <= selectedRegion.t0() && selectedRegion.t1() > selectedRegion.t0()) {
717 t1 = selectedRegion.t1(); // record within the selection
718 }
719 else {
720 t1 = DBL_MAX; // record for a long, long time
721 }
722 }
723
724 TransportSequences transportTracks;
725 if (UseDuplex()) {
726 // Remove recording tracks from the list of tracks for duplex ("overdub")
727 // playback.
728 /* TODO: set up stereo tracks if that is how the user has set up
729 * their preferences, and choose sample format based on prefs */
730 transportTracks =
731 MakeTransportTracks(TrackList::Get( *p ), false, true);
732 for (const auto &wt : existingTracks) {
733 auto end = transportTracks.playbackSequences.end();
734 auto it = std::find_if(
735 transportTracks.playbackSequences.begin(), end,
736 [&wt](const auto& playbackSequence) {
737 return playbackSequence->FindChannelGroup() ==
738 wt->FindChannelGroup();
739 });
740 if (it != end)
741 transportTracks.playbackSequences.erase(it);
742 }
743 }
744
745 std::copy(existingTracks.begin(), existingTracks.end(),
746 back_inserter(transportTracks.captureSequences));
747
748 if (rateOfSelected != RATE_NOT_SELECTED)
749 options.rate = rateOfSelected;
750
751 DoRecord(*p, transportTracks, t0, t1, altAppearance, options);
752 }
753}
754
756{
757 bool duplex;
758 gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex,
759#ifdef EXPERIMENTAL_DA
760 false
761#else
762 true
763#endif
764 );
765 return duplex;
766}
767
769 const TransportSequences &sequences,
770 double t0, double t1,
771 bool altAppearance,
772 const AudioIOStartStreamOptions &options)
773{
774 auto &projectAudioManager = *this;
775
776 CommandFlag flags = AlwaysEnabledFlag; // 0 means recalc flags.
777
778 // NB: The call may have the side effect of changing flags.
780 flags,
782
783 if (!allowed)
784 return false;
785 // ...end of code from CommandHandler.
786
787 auto gAudioIO = AudioIO::Get();
788 if (gAudioIO->IsBusy())
789 return false;
790
791 projectAudioManager.SetAppending(!altAppearance);
792
793 bool success = false;
794
795 auto transportSequences = sequences;
796
797 // Will replace any given capture tracks with temporaries
798 transportSequences.captureSequences.clear();
799
800 const auto p = &project;
801 auto &trackList = TrackList::Get(project);
802
803 bool appendRecord = !sequences.captureSequences.empty();
804
805 auto makeNewClipName = [&](WaveTrack* track) {
806 for (auto i = 1; ; ++i) {
807 //i18n-hint a numerical suffix added to distinguish otherwise like-named clips when new record started
808 auto name = XC("%s #%d", "clip name template")
809 .Format(track->GetName(), i).Translation();
810 if (track->FindClipByName(name) == nullptr)
811 return name;
812 }
813 };
814
815 {
816 if (appendRecord) {
817 // Append recording:
818 // Pad selected/all wave tracks to make them all the same length
819 for (const auto &sequence : sequences.captureSequences) {
820 WaveTrack *wt{};
821 if (!(wt = dynamic_cast<WaveTrack *>(sequence.get()))) {
822 assert(false);
823 continue;
824 }
825 if (!wt->IsLeader())
826 continue;
827 auto endTime = wt->GetEndTime();
828
829 // If the track was chosen for recording and playback both,
830 // remember the original in preroll tracks, before making the
831 // pending replacement.
832 const auto shared = wt->SharedPointer<WaveTrack>();
833 // playbackSequences contains only leaders; prerollSequences should
834 // be a subset of it. Non-leader might not be found, but that is
835 // all right.
836 const auto &range = transportSequences.playbackSequences;
837 bool prerollTrack = any_of(range.begin(), range.end(),
838 [&](const auto &pSequence){
839 return shared.get() == pSequence->FindChannelGroup(); });
840 if (prerollTrack)
841 transportSequences.prerollSequences.push_back(shared);
842
843 // A function that copies all the non-sample data between
844 // wave tracks; in case the track recorded to changes scale
845 // type (for instance), during the recording.
846 auto updater = [](Track &d, const Track &s){
847 assert(d.IsLeader());
848 assert(s.IsLeader());
849 assert(d.NChannels() == s.NChannels());
850 auto &dst = static_cast<WaveTrack&>(d);
851 auto &src = static_cast<const WaveTrack&>(s);
852 dst.Reinit(src);
853 };
854
855 // Get a copy of the track to be appended, to be pushed into
856 // undo history only later.
857 const auto pending = static_cast<WaveTrack*>(
858 trackList.RegisterPendingChangedTrack(updater, wt)
859 );
860 // End of current track is before or at recording start time.
861 // Less than or equal, not just less than, to ensure a clip boundary.
862 // when append recording.
863 //const auto pending = static_cast<WaveTrack*>(newTrack);
864 const auto lastClip = pending->GetRightmostClip();
865 // RoundedT0 to have a new clip created when punch-and-roll
866 // recording with the cursor in the second half of the space
867 // between two samples
868 // (https://github.com/audacity/audacity/issues/5113#issuecomment-1705154108)
869 const auto recordingStart =
870 std::round(t0 * pending->GetRate()) / pending->GetRate();
871 const auto recordingStartsBeforeTrackEnd =
872 lastClip && recordingStart < lastClip->GetPlayEndTime();
873 // Recording doesn't start before the beginning of the last clip
874 // - or the check for creating a new clip or not should be more
875 // general than that ...
876 assert(
877 !recordingStartsBeforeTrackEnd ||
878 lastClip->WithinPlayRegion(recordingStart));
879 if (!recordingStartsBeforeTrackEnd ||
880 !lastClip->StretchRatioEquals(1))
881 pending->CreateWideClip(t0, makeNewClipName(pending));
882 transportSequences.captureSequences
883 .push_back(pending->SharedPointer<WaveTrack>());
884 }
885 trackList.UpdatePendingTracks();
886 }
887
888 if (transportSequences.captureSequences.empty()) {
889 // recording to NEW track(s).
890 bool recordingNameCustom, useTrackNumber, useDateStamp, useTimeStamp;
891 wxString defaultTrackName, defaultRecordingTrackName;
892
893 // Count the tracks.
894 auto numTracks = trackList.Any<const WaveTrack>().size();
895
896 auto recordingChannels = std::max(1, AudioIORecordChannels.Read());
897
898 gPrefs->Read(wxT("/GUI/TrackNames/RecordingNameCustom"), &recordingNameCustom, false);
899 gPrefs->Read(wxT("/GUI/TrackNames/TrackNumber"), &useTrackNumber, false);
900 gPrefs->Read(wxT("/GUI/TrackNames/DateStamp"), &useDateStamp, false);
901 gPrefs->Read(wxT("/GUI/TrackNames/TimeStamp"), &useTimeStamp, false);
902 defaultTrackName = trackList.MakeUniqueTrackName(WaveTrack::GetDefaultAudioTrackNamePreference());
903 gPrefs->Read(wxT("/GUI/TrackNames/RecodingTrackName"), &defaultRecordingTrackName, defaultTrackName);
904
905 wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
906
907 auto newTracks = WaveTrackFactory::Get(*p).Create(recordingChannels);
908 const auto first = *newTracks->begin();
909 int trackCounter = 0;
910 const auto minimizeChannelView = recordingChannels > 2 &&
912 for (auto newTrack : newTracks->Any<WaveTrack>()) {
913 // Quantize bounds to the rate of the new track.
914 if (newTrack == first) {
915 if (t0 < DBL_MAX)
916 t0 = newTrack->SnapToSample(t0);
917 if (t1 < DBL_MAX)
918 t1 = newTrack->SnapToSample(t1);
919 }
920
921 newTrack->MoveTo(t0);
922 wxString nameSuffix = wxString(wxT(""));
923
924 if (useTrackNumber) {
925 nameSuffix += wxString::Format(wxT("%d"), 1 + (int) numTracks + trackCounter++);
926 }
927
928 if (useDateStamp) {
929 if (!nameSuffix.empty()) {
930 nameSuffix += wxT("_");
931 }
932 nameSuffix += wxDateTime::Now().FormatISODate();
933 }
934
935 if (useTimeStamp) {
936 if (!nameSuffix.empty()) {
937 nameSuffix += wxT("_");
938 }
939 nameSuffix += wxDateTime::Now().FormatISOTime();
940 }
941
942 // ISO standard would be nice, but ":" is unsafe for file name.
943 nameSuffix.Replace(wxT(":"), wxT("-"));
944
945 if (baseTrackName.empty())
946 newTrack->SetName(nameSuffix);
947 else if (nameSuffix.empty())
948 newTrack->SetName(baseTrackName);
949 else
950 newTrack->SetName(baseTrackName + wxT("_") + nameSuffix);
951
952 //create a new clip with a proper name before recording is started
953 newTrack->CreateWideClip(t0, makeNewClipName(newTrack));
954
955 transportSequences.captureSequences.push_back(
956 std::static_pointer_cast<WaveTrack>(newTrack->shared_from_this())
957 );
958
959 for(auto channel : newTrack->Channels())
960 {
961 ChannelView::Get(*channel).SetMinimized(minimizeChannelView);
962 }
963 }
964 trackList.RegisterPendingNewTracks(std::move(*newTracks));
965 // Bug 1548. First of new tracks needs the focus.
966 TrackFocus::Get(project).Set(first);
967 if (!trackList.empty())
968 (*trackList.rbegin())->EnsureVisible();
969 }
970
971 //Automated Input Level Adjustment Initialization
972 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
973 gAudioIO->AILAInitialize();
974 #endif
975
976 int token =
977 gAudioIO->StartStream(transportSequences, t0, t1, t1, options);
978
979 success = (token != 0);
980
981 if (success) {
983 }
984 else {
985 CancelRecording();
986
987 // Show error message if stream could not be opened
988 auto msg = XO("Error opening recording device.\nError code: %s")
989 .Format( gAudioIO->LastPaErrorString() );
990 using namespace BasicUI;
992 XO("Error"), msg, wxT("Error_opening_sound_device"),
993 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
994 }
995 }
996
997 return success;
998}
999
1001{
1002 auto &projectAudioManager = *this;
1003 bool canStop = projectAudioManager.CanStopAudioStream();
1004
1005 if ( !canStop ) {
1006 return;
1007 }
1008
1009 bool paused = !projectAudioManager.Paused();
1010 TogglePaused();
1011
1012 auto gAudioIO = AudioIO::Get();
1013
1014#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
1015
1016 auto project = &mProject;
1017 auto &scrubber = Scrubber::Get( *project );
1018
1019 // Bug 1494 - Pausing a seek or scrub should just STOP as
1020 // it is confusing to be in a paused scrub state.
1021 bool bStopInstead = paused &&
1023 !scrubber.IsSpeedPlaying() &&
1024 !scrubber.IsKeyboardScrubbing();
1025
1026 if (bStopInstead) {
1027 Stop();
1028 return;
1029 }
1030
1032 scrubber.Pause(paused);
1033 else
1034#endif
1035 {
1036 gAudioIO->SetPaused(paused);
1037 }
1038}
1039
1040
1042{
1043 mPaused.fetch_xor(1, std::memory_order::memory_order_relaxed);
1044}
1045
1047{
1048 mPaused.store(0, std::memory_order::memory_order_relaxed);
1049}
1050
1052{
1053 return mPaused.load(std::memory_order_relaxed) == 1;
1054}
1055
1056
1058{
1059 const auto project = &mProject;
1060 TrackList::Get( *project ).ClearPendingTracks();
1061}
1062
1064{
1065 auto &project = mProject;
1066
1067 mDisplayedRate = rate;
1068
1069 auto display = FormatRate( rate );
1070
1072}
1073
1075{
1076 // Auto-save was done here before, but it is unnecessary, provided there
1077 // are sufficient autosaves when pushing or modifying undo states.
1078}
1079
1080// This is called after recording has stopped and all tracks have flushed.
1082{
1083 auto &project = mProject;
1084 auto &projectAudioIO = ProjectAudioIO::Get( project );
1085 auto &projectFileIO = ProjectFileIO::Get( project );
1086
1087 // Only push state if we were capturing and not monitoring
1088 if (projectAudioIO.GetAudioIOToken() > 0)
1089 {
1090 auto &history = ProjectHistory::Get( project );
1091
1092 if (IsTimerRecordCancelled()) {
1093 // discard recording
1094 history.RollbackState();
1095 // Reset timer record
1096 ResetTimerRecordCancelled();
1097 }
1098 else {
1099 // Add to history
1100 // We want this to have No-fail-guarantee if we get here from exception
1101 // handling of recording, and that means we rely on the last autosave
1102 // successfully committed to the database, not risking a failure
1103 auto flags = AudioIO::Get()->HasRecordingException()
1106 history.PushState(XO("Recorded Audio"), XO("Record"), flags);
1107
1108 // Now, we may add a label track to give information about
1109 // dropouts. We allow failure of this.
1110 auto gAudioIO = AudioIO::Get();
1111 auto &intervals = gAudioIO->LostCaptureIntervals();
1112 if (intervals.size())
1113 Publish( RecordingDropoutEvent{ intervals } );
1114 }
1115 }
1116}
1117
1119{
1120 auto &project = mProject;
1121 auto &projectFileIO = ProjectFileIO::Get( project );
1122
1123 wxTheApp->CallAfter( [&]{ projectFileIO.AutoSave(true); });
1124}
1125
1127{
1128 const auto project = &mProject;
1129 TrackList::Get( *project ).ApplyPendingTracks();
1130}
1131
1133{
1134 auto& project = mProject;
1135 auto gAudioIO = AudioIO::Get();
1136 if (gAudioIO && &project == gAudioIO->GetOwningProject().get())
1137 {
1138 bool canStop = CanStopAudioStream();
1139
1140 gAudioIO->SetPaused(!gAudioIO->IsPaused());
1141
1142 if (canStop)
1143 {
1144 // Instead of calling ::OnPause here, we can simply do the only thing it does (i.e. toggling the pause state),
1145 // because scrubbing can not happen while recording
1146 TogglePaused();
1147 }
1148 }
1149}
1150
1152{
1154 Stop();
1155}
1156
1158{
1159 auto gAudioIO = AudioIO::Get();
1160 return
1161 gAudioIO->IsBusy() &&
1162 CanStopAudioStream() &&
1163 // ... and not merely monitoring
1164 !gAudioIO->IsMonitoring() &&
1165 // ... and not punch-and-roll recording
1166 gAudioIO->GetNumCaptureChannels() == 0;
1167}
1168
1170{
1171 auto gAudioIO = AudioIO::Get();
1172 return
1173 gAudioIO->IsBusy() &&
1174 CanStopAudioStream() &&
1175 gAudioIO->GetNumCaptureChannels() > 0;
1176}
1177
1179{
1180 auto gAudioIO = AudioIO::Get();
1181 return (!gAudioIO->IsStreamActive() ||
1182 gAudioIO->IsMonitoring() ||
1183 gAudioIO->GetOwningProject().get() == &mProject );
1184}
1185
1188 [](const AudacityProject &project){
1189 auto &projectAudioManager = ProjectAudioManager::Get( project );
1190 bool canStop = projectAudioManager.CanStopAudioStream();
1191 return canStop;
1192 }
1193 }; return flag; }
1194
1197[](AudacityProject &project, bool newDefault) -> AudioIOStartStreamOptions {
1199 auto options = ProjectAudioIO::DefaultOptionsFactory(project, newDefault);
1200
1202 options.listener = ProjectAudioManager::Get(project).shared_from_this();
1203
1204 bool loopEnabled = ViewInfo::Get(project).playRegion.Active();
1205 options.loopEnabled = loopEnabled;
1206
1207 if (newDefault) {
1208 const double trackEndTime = TrackList::Get(project).GetEndTime();
1209 const double loopEndTime = ViewInfo::Get(project).playRegion.GetEnd();
1210 options.policyFactory = [&project, trackEndTime, loopEndTime](
1211 const AudioIOStartStreamOptions &options)
1212 -> std::unique_ptr<PlaybackPolicy>
1213 {
1214 return std::make_unique<DefaultPlaybackPolicy>( project,
1215 trackEndTime, loopEndTime,
1216 options.loopEnabled, options.variableSpeed);
1217 };
1218
1219 // Start play from left edge of selection
1220 options.pStartTime.emplace(ViewInfo::Get(project).selectedRegion.t0());
1221 }
1222
1223 return options;
1224} };
1225
1228{
1230 auto gAudioIO = AudioIO::Get();
1231 auto PlayAtSpeedRate = gAudioIO->GetBestRate(
1232 false, //not capturing
1233 true, //is playing
1234 ProjectRate::Get( project ).GetRate() //suggested rate
1235 );
1236 result.rate = PlayAtSpeedRate;
1237 return result;
1238}
1239
1240// Stop playing or recording, if paused.
1242{
1243 if( AudioIOBase::Get()->IsPaused() )
1244 Stop();
1245}
1246
1247bool ProjectAudioManager::DoPlayStopSelect( bool click, bool shift )
1248{
1249 auto &project = mProject;
1250 auto &scrubber = Scrubber::Get( project );
1252 auto &viewInfo = ViewInfo::Get( project );
1253 auto &selection = viewInfo.selectedRegion;
1254 auto gAudioIO = AudioIO::Get();
1255
1256 //If busy, stop playing, make sure everything is unpaused.
1257 if (scrubber.HasMark() ||
1258 gAudioIO->IsStreamActive(token)) {
1259 // change the selection
1260 auto time = gAudioIO->GetStreamTime();
1261 // Test WasSpeedPlaying(), not IsSpeedPlaying()
1262 // as we could be stopped now. Similarly WasKeyboardScrubbing().
1263 if (click && (scrubber.WasSpeedPlaying() || scrubber.WasKeyboardScrubbing()))
1264 {
1265 ;// don't change the selection.
1266 }
1267 else if (shift && click) {
1268 // Change the region selection, as if by shift-click at the play head
1269 auto t0 = selection.t0(), t1 = selection.t1();
1270 if (time < t0)
1271 // Grow selection
1272 t0 = time;
1273 else if (time > t1)
1274 // Grow selection
1275 t1 = time;
1276 else {
1277 // Shrink selection, changing the nearer boundary
1278 if (fabs(t0 - time) < fabs(t1 - time))
1279 t0 = time;
1280 else
1281 t1 = time;
1282 }
1283 selection.setTimes(t0, t1);
1284 }
1285 else if (click){
1286 // avoid a point at negative time.
1287 time = wxMax( time, 0 );
1288 // Set a point selection, as if by a click at the play head
1289 selection.setTimes(time, time);
1290 } else
1291 // How stop and set cursor always worked
1292 // -- change t0, collapsing to point only if t1 was greater
1293 selection.setT0(time, false);
1294
1295 ProjectHistory::Get( project ).ModifyState(false); // without bWantsAutoSave
1296 return true;
1297 }
1298 return false;
1299}
1300
1301// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and
1302// "OnStopSelect" merged.
1304{
1305 auto gAudioIO = AudioIO::Get();
1306 if (DoPlayStopSelect(false, false))
1307 Stop();
1308 else if (!gAudioIO->IsBusy()) {
1309 //Otherwise, start playing (assuming audio I/O isn't busy)
1310
1311 // Will automatically set mLastPlayMode
1312 PlayCurrentRegion(false);
1313 }
1314}
1315
1317 []{ return PausedFlag(); },
1318 []{ return AudioIONotBusyFlag(); },
1319 []( const AudacityProject &project ){
1324 }
1325}};
1326
1327// GetSelectedProperties collects information about
1328// currently selected audio tracks
1331{
1332 double rateOfSelection{ RATE_NOT_SELECTED };
1333
1334 PropertiesOfSelected result;
1335 result.allSameRate = true;
1336
1337 const auto selectedTracks{
1338 TrackList::Get(proj).Selected<const WaveTrack>() };
1339
1340 for (const auto & track : selectedTracks) {
1341 if (rateOfSelection != RATE_NOT_SELECTED &&
1342 track->GetRate() != rateOfSelection)
1343 result.allSameRate = false;
1344 else if (rateOfSelection == RATE_NOT_SELECTED)
1345 rateOfSelection = track->GetRate();
1346 }
1347
1348 result.anySelected = !selectedTracks.empty();
1349 result.rateOfSelected = rateOfSelection;
1350
1351 return result;
1352}
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:167
#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:93
static AudioIO * Get()
Definition: AudioIO.cpp:123
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:266
typename GlobalVariable< DefaultOptions, const std::function< std::remove_pointer_t< decltype(DefaultFunction)> >, Default, Options... >::Scope Scope
bool TryToMakeActionAllowed(CommandFlag &flags, CommandFlag flagsRqd)
Definition: Menus.cpp:647
static MenuManager & Get(AudacityProject &project)
Definition: Menus.cpp:69
bool mStopIfWasPaused
Definition: Menus.h:125
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)
Observer::Subscription mCheckpointFailureSubcription
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)
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
static ProjectSettings & Get(AudacityProject &project)
bool GetTracksFitVerticallyZoomed() const
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:187
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:205
virtual void EnableDisableButtons()=0
static ToolManager & Get(AudacityProject &project)
ToolBar * GetToolBar(const Identifier &type) const
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:123
bool IsSelected() const
Definition: Track.cpp:295
bool IsLeader() const override
Definition: Track.cpp:298
double GetEndTime() const
Return the greatest end time of the tracks, or 0 when no tracks.
Definition: Track.cpp:1008
bool ApplyPendingTracks()
Definition: Track.cpp:1109
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:354
void ClearPendingTracks(ListOfTracks *pAdded=nullptr)
Definition: Track.cpp:1067
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1108
Holds a msgid for the translation catalog; may also bind format arguments.
PlayRegion playRegion
Definition: ViewInfo.h:220
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:219
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
std::shared_ptr< WaveTrack > Create()
Creates an unnamed empty WaveTrack with default sample format and default rate.
Definition: WaveTrack.cpp:546
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:4079
A Track that contains audio waveform data.
Definition: WaveTrack.h:220
void Reinit(const WaveTrack &orig)
Definition: WaveTrack.cpp:649
static wxString GetDefaultAudioTrackNamePreference()
Definition: WaveTrack.cpp:528
const WaveClip * GetRightmostClip() const
Definition: WaveTrack.cpp:2670
size_t NChannels() const override
May report more than one only when this is a leader track.
Definition: WaveTrack.cpp:620
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:259
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:770
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:31
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:51
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