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
19#include "AudioIO.h"
20#include "BasicUI.h"
21#include "CommonCommandFlags.h"
23#include "Menus.h"
24#include "Meter.h"
25#include "Mix.h"
26#include "Project.h"
27#include "ProjectAudioIO.h"
28#include "ProjectFileIO.h"
29#include "ProjectHistory.h"
30#include "ProjectRate.h"
31#include "ProjectSettings.h"
32#include "ProjectStatus.h"
33#include "ProjectWindows.h"
34#include "ScrubState.h"
35#include "TrackPanelAx.h"
36#include "UndoManager.h"
37#include "ViewInfo.h"
38#include "WaveTrack.h"
40#include "tracks/ui/Scrubbing.h"
41#include "tracks/ui/TrackView.h"
43#include "AudacityMessageBox.h"
44
45
48 []( AudacityProject &project ) {
49 return std::make_shared< ProjectAudioManager >( project );
50 }
51};
52
54{
55 return project.AttachedObjects::Get< ProjectAudioManager >(
57}
58
60 const AudacityProject &project )
61{
62 return Get( const_cast< AudacityProject & >( project ) );
63}
64
66 : mProject{ project }
67{
69 registerStatusWidthFunction{ StatusWidthFunction };
72}
73
75
77{
78 if (rate > 0) {
79 return XO("Actual Rate: %d").Format( rate );
80 }
81 else
82 // clear the status field
83 return {};
84}
85
87 const AudacityProject &project, StatusBarField field )
89{
90 if ( field == rateStatusBarField ) {
91 auto &audioManager = ProjectAudioManager::Get( project );
92 int rate = audioManager.mDisplayedRate;
93 return {
94 { { FormatRate( rate ) } },
95 50
96 };
97 }
98 return {};
99}
100
101namespace {
102// The implementation is general enough to allow backwards play too
104public:
106 double gapLeft,
107 double gapLength
108 );
110
111 void Initialize(PlaybackSchedule &schedule, double rate) override;
112
113 bool Done(PlaybackSchedule &schedule, unsigned long) override;
114
115 double OffsetTrackTime( PlaybackSchedule &schedule, double offset ) override;
116
117 PlaybackSlice GetPlaybackSlice(
118 PlaybackSchedule &schedule, size_t available) override;
119
120 std::pair<double, double> AdvancedTrackTime( PlaybackSchedule &schedule,
121 double trackTime, size_t nSamples) override;
122
123 bool RepositionPlayback(
124 PlaybackSchedule &schedule, const Mixers &playbackMixers,
125 size_t frames, size_t available) override;
126
127private:
128 double GapStart() const
129 { return mReversed ? mGapLeft + mGapLength : mGapLeft; }
130 double GapEnd() const
131 { return mReversed ? mGapLeft : mGapLeft + mGapLength; }
132 bool AtOrBefore(double trackTime1, double trackTime2) const
133 { return mReversed ? trackTime1 >= trackTime2 : trackTime1 <= trackTime2; }
134
136 const double mGapLeft, mGapLength;
137
139 double mStart = 0, mEnd = 0;
140
141 // Non-negative real time durations
142 double mDuration1 = 0, mDuration2 = 0;
143 double mInitDuration1 = 0, mInitDuration2 = 0;
144
145 bool mDiscontinuity{ false };
146 bool mReversed{ false };
147};
148
149CutPreviewPlaybackPolicy::CutPreviewPlaybackPolicy(
150 double gapLeft, double gapLength)
151: mGapLeft{ gapLeft }, mGapLength{ gapLength }
152{
153 wxASSERT(gapLength >= 0.0);
154}
155
157
159 PlaybackSchedule &schedule, double rate)
160{
161 PlaybackPolicy::Initialize(schedule, rate);
162
163 // Examine mT0 and mT1 in the schedule only now; ignore changes during play
164 double left = mStart = schedule.mT0;
165 double right = mEnd = schedule.mT1;
166 mReversed = left > right;
167 if (mReversed)
168 std::swap(left, right);
169
170 if (left < mGapLeft)
171 mDuration1 = schedule.ComputeWarpedLength(left, mGapLeft);
172 const auto gapEnd = mGapLeft + mGapLength;
173 if (gapEnd < right)
174 mDuration2 = schedule.ComputeWarpedLength(gapEnd, right);
175 if (mReversed)
177 if (sampleCount(mDuration2 * rate) == 0)
181}
182
184{
186 auto diff = schedule.GetTrackTime() - mEnd;
187 if (mReversed)
188 diff *= -1;
189 return sampleCount(diff * mRate) >= 0;
190}
191
193 PlaybackSchedule &schedule, double offset )
194{
195 // Compute new time by applying the offset, jumping over the gap
196 auto time = schedule.GetTrackTime();
197 if (offset >= 0) {
198 auto space = std::clamp(mGapLeft - time, 0.0, offset);
199 time += space;
200 offset -= space;
201 if (offset > 0)
202 time = std::max(time, mGapLeft + mGapLength) + offset;
203 }
204 else {
205 auto space = std::clamp(mGapLeft + mGapLength - time, offset, 0.0);
206 time += space;
207 offset -= space;
208 if (offset < 0)
209 time = std::min(time, mGapLeft) + offset;
210 }
211 time = std::clamp(time, std::min(mStart, mEnd), std::max(mStart, mEnd));
212
213 // Reset the durations
214 mDiscontinuity = false;
217 if (AtOrBefore(time, GapStart()))
218 mDuration1 = std::max(0.0,
219 mDuration1 - fabs(schedule.ComputeWarpedLength(mStart, time)));
220 else {
221 mDuration1 = 0;
222 mDuration2 = std::max(0.0,
223 mDuration2 - fabs(schedule.ComputeWarpedLength(GapEnd(), time)));
224 }
225
226 return time;
227}
228
230 PlaybackSchedule &, size_t available)
231{
232 size_t frames = available;
233 size_t toProduce = frames;
234 sampleCount samples1(mDuration1 * mRate + 0.5);
235 if (samples1 > 0 && samples1 < frames)
236 // Shorter slice than requested, up to the discontinuity
237 toProduce = frames = samples1.as_size_t();
238 else if (samples1 == 0) {
239 sampleCount samples2(mDuration2 * mRate + 0.5);
240 if (samples2 < frames) {
241 toProduce = samples2.as_size_t();
242 // Produce some extra silence so that the time queue consumer can
243 // satisfy its end condition
244 frames = std::min(available, toProduce + TimeQueueGrainSize + 1);
245 }
246 }
247 return { available, frames, toProduce };
248}
249
251 PlaybackSchedule &schedule, double trackTime, size_t nSamples)
252{
253 auto realDuration = nSamples / mRate;
254 if (mDuration1 > 0) {
255 mDuration1 = std::max(0.0, mDuration1 - realDuration);
256 if (sampleCount(mDuration1 * mRate) == 0) {
257 mDuration1 = 0;
258 mDiscontinuity = true;
259 return { GapStart(), GapEnd() };
260 }
261 }
262 else
263 mDuration2 = std::max(0.0, mDuration2 - realDuration);
264 if (mReversed)
265 realDuration *= -1;
266 const double time = schedule.SolveWarpedLength(trackTime, realDuration);
267
268 if ( mReversed ? time <= mEnd : time >= mEnd )
269 return {mEnd, std::numeric_limits<double>::infinity()};
270 else
271 return {time, time};
272}
273
275 const Mixers &playbackMixers, size_t, size_t )
276{
277 if (mDiscontinuity) {
278 mDiscontinuity = false;
279 auto newTime = GapEnd();
280 for (auto &pMixer : playbackMixers)
281 pMixer->Reposition(newTime, true);
282 // Tell TrackBufferExchange that we aren't done yet
283 return false;
284 }
285 return true;
286}
287}
288
290 const AudioIOStartStreamOptions &options,
291 PlayMode mode,
292 bool backwards /* = false */)
293{
294 auto &projectAudioManager = *this;
295 bool canStop = projectAudioManager.CanStopAudioStream();
296
297 if ( !canStop )
298 return -1;
299
300 auto &pStartTime = options.pStartTime;
301
302 bool nonWaveToo = options.playNonWaveTracks;
303
304 // Uncomment this for laughs!
305 // backwards = true;
306
307 double t0 = selectedRegion.t0();
308 double t1 = selectedRegion.t1();
309 // SelectedRegion guarantees t0 <= t1, so we need another boolean argument
310 // to indicate backwards play.
311 const bool newDefault = (mode == PlayMode::loopedPlay);
312
313 if (backwards)
314 std::swap(t0, t1);
315
316 projectAudioManager.SetLooping( mode == PlayMode::loopedPlay );
317 projectAudioManager.SetCutting( mode == PlayMode::cutPreviewPlay );
318
319 bool success = false;
320
321 auto gAudioIO = AudioIO::Get();
322 if (gAudioIO->IsBusy())
323 return -1;
324
325 const bool cutpreview = mode == PlayMode::cutPreviewPlay;
326 if (cutpreview && t0==t1)
327 return -1; /* msmeyer: makes no sense */
328
329 AudacityProject *p = &mProject;
330
331 auto &tracks = TrackList::Get( *p );
332
333 mLastPlayMode = mode;
334
335 bool hasaudio;
336 if (nonWaveToo)
337 hasaudio = ! tracks.Any<PlayableTrack>().empty();
338 else
339 hasaudio = ! tracks.Any<WaveTrack>().empty();
340
341 double latestEnd = tracks.GetEndTime();
342
343 if (!hasaudio)
344 return -1; // No need to continue without audio tracks
345
346#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
347 double initSeek = 0.0;
348#endif
349 double loopOffset = 0.0;
350
351 if (t1 == t0) {
352 if (newDefault) {
353 const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
354 // play selection if there is one, otherwise
355 // set start of play region to project start,
356 // and loop the project from current play position.
357
358 if ((t0 > selectedRegion.t0()) && (t0 < selectedRegion.t1())) {
359 t0 = selectedRegion.t0();
360 t1 = selectedRegion.t1();
361 }
362 else {
363 // loop the entire project
364 // Bug2347, loop playback from cursor position instead of project start
365 loopOffset = t0 - tracks.GetStartTime();
366 if (!pStartTime)
367 // TODO move this reassignment elsewhere so we don't need an
368 // ugly mutable member
369 pStartTime.emplace(loopOffset);
370 t0 = tracks.GetStartTime();
371 t1 = tracks.GetEndTime();
372 }
373 } else {
374 // move t0 to valid range
375 if (t0 < 0) {
376 t0 = tracks.GetStartTime();
377 }
378 else if (t0 > tracks.GetEndTime()) {
379 t0 = tracks.GetEndTime();
380 }
381#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
382 else {
383 initSeek = t0; //AC: initSeek is where playback will 'start'
384 if (!pStartTime)
385 pStartTime.emplace(initSeek);
386 t0 = tracks.GetStartTime();
387 }
388#endif
389 }
390 t1 = tracks.GetEndTime();
391 }
392 else {
393 // maybe t1 < t0, with backwards scrubbing for instance
394 if (backwards)
395 std::swap(t0, t1);
396
397 t0 = std::max(0.0, std::min(t0, latestEnd));
398 t1 = std::max(0.0, std::min(t1, latestEnd));
399
400 if (backwards)
401 std::swap(t0, t1);
402 }
403
404 int token = -1;
405
406 if (t1 != t0) {
407 if (cutpreview) {
408 const double tless = std::min(t0, t1);
409 const double tgreater = std::max(t0, t1);
410 double beforeLen, afterLen;
411 gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
412 gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
413 double tcp0 = tless-beforeLen;
414 const double diff = tgreater - tless;
415 double tcp1 = tgreater+afterLen;
416 if (backwards)
417 std::swap(tcp0, tcp1);
418 AudioIOStartStreamOptions myOptions = options;
419 myOptions.policyFactory =
420 [tless, diff](auto&) -> std::unique_ptr<PlaybackPolicy> {
421 return std::make_unique<CutPreviewPlaybackPolicy>(tless, diff);
422 };
423 token = gAudioIO->StartStream(
424 TransportTracks{ TrackList::Get(*p), false, nonWaveToo },
425 tcp0, tcp1, tcp1, myOptions);
426 }
427 else {
428 double mixerLimit = t1;
429 if (newDefault) {
430 mixerLimit = latestEnd;
431 if (pStartTime && *pStartTime >= t1)
432 t1 = latestEnd;
433 }
434 token = gAudioIO->StartStream(
435 TransportTracks{ tracks, false, nonWaveToo },
436 t0, t1, mixerLimit, options);
437 }
438 if (token != 0) {
439 success = true;
441 }
442 else {
443 // Bug1627 (part of it):
444 // infinite error spew when trying to start scrub:
445 // Problem was that the error dialog yields to events,
446 // causing recursion to this function in the scrub timer
447 // handler! Easy fix, just delay the user alert instead.
448 auto &window = GetProjectFrame( mProject );
449 window.CallAfter( [&]{
450 using namespace BasicUI;
451 // Show error message if stream could not be opened
453 XO("Error"),
454 XO("Error opening sound device.\nTry changing the audio host, playback device and the project sample rate."),
455 wxT("Error_opening_sound_device"),
456 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
457 });
458 }
459 }
460
461 if (!success)
462 return -1;
463
464 return token;
465}
466
467void ProjectAudioManager::PlayCurrentRegion(bool newDefault /* = false */,
468 bool cutpreview /* = false */)
469{
470 auto &projectAudioManager = *this;
471 bool canStop = projectAudioManager.CanStopAudioStream();
472
473 if ( !canStop )
474 return;
475
476 AudacityProject *p = &mProject;
477
478 {
479
480 const auto &playRegion = ViewInfo::Get( *p ).playRegion;
481
482 if (newDefault)
483 cutpreview = false;
484 auto options = ProjectAudioIO::GetDefaultOptions(*p, newDefault);
485 if (cutpreview)
486 options.envelope = nullptr;
487 auto mode =
488 cutpreview ? PlayMode::cutPreviewPlay
489 : newDefault ? PlayMode::loopedPlay
491 PlayPlayRegion(SelectedRegion(playRegion.GetStart(), playRegion.GetEnd()),
492 options,
493 mode);
494 }
495}
496
497void ProjectAudioManager::Stop(bool stopStream /* = true*/)
498{
499 AudacityProject *project = &mProject;
500 auto &projectAudioManager = *this;
501 bool canStop = projectAudioManager.CanStopAudioStream();
502
503 if ( !canStop )
504 return;
505
506 if(project) {
507 // Let scrubbing code do some appearance change
508 auto &scrubber = Scrubber::Get( *project );
509 scrubber.StopScrubbing();
510 }
511
512 auto gAudioIO = AudioIO::Get();
513
514 auto cleanup = finally( [&]{
515 projectAudioManager.SetStopping( false );
516 } );
517
518 if (stopStream && gAudioIO->IsBusy()) {
519 // flag that we are stopping
520 projectAudioManager.SetStopping( true );
521 // Allow UI to update for that
522 while( wxTheApp->ProcessIdle() )
523 ;
524 }
525
526 if(stopStream)
527 gAudioIO->StopStream();
528
529 projectAudioManager.SetLooping( false );
530 projectAudioManager.SetCutting( false );
531
532 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
533 gAudioIO->AILADisable();
534 #endif
535
536 projectAudioManager.SetPausedOff();
537 //Make sure you tell gAudioIO to unpause
538 gAudioIO->SetPaused( false );
539
540 // So that we continue monitoring after playing or recording.
541 // also clean the MeterQueues
542 if( project ) {
543 auto &projectAudioIO = ProjectAudioIO::Get( *project );
544 auto meter = projectAudioIO.GetPlaybackMeter();
545 if( meter ) {
546 meter->Clear();
547 }
548
549 meter = projectAudioIO.GetCaptureMeter();
550 if( meter ) {
551 meter->Clear();
552 }
553 }
554
555 // To do: eliminate this, use an event instead
556 const auto toolbar = ToolManager::Get( *project ).GetToolBar(wxT("Scrub"));
557 if (toolbar)
558 toolbar->EnableDisableButtons();
559}
560
561
563 AudacityProject &proj, bool selectedOnly, double targetRate)
564{
565 auto p = &proj;
566 size_t recordingChannels = std::max(0, AudioIORecordChannels.Read());
567 bool strictRules = (recordingChannels <= 2);
568
569 // Iterate over all wave tracks, or over selected wave tracks only.
570 // If target rate was specified, ignore all tracks with other rates.
571 //
572 // In the usual cases of one or two recording channels, seek a first-fit
573 // unbroken sub-sequence for which the total number of channels matches the
574 // required number exactly. Never drop inputs or fill only some channels
575 // of a track.
576 //
577 // In case of more than two recording channels, choose tracks only among the
578 // selected. Simply take the earliest wave tracks, until the number of
579 // channels is enough. If there are fewer channels than inputs, but at least
580 // one channel, then some of the input channels will be dropped.
581 //
582 // Resulting tracks may be non-consecutive within the list of all tracks
583 // (there may be non-wave tracks between, or non-selected tracks when
584 // considering selected tracks only.)
585
586 if (!strictRules && !selectedOnly)
587 return {};
588
589 auto &trackList = TrackList::Get( *p );
590 std::vector<unsigned> channelCounts;
591 WritableSampleTrackArray candidates;
592 const auto range = trackList.Leaders<WaveTrack>();
593 for ( auto candidate : selectedOnly ? range + &Track::IsSelected : range ) {
594 if (targetRate != RATE_NOT_SELECTED && candidate->GetRate() != targetRate)
595 continue;
596
597 // count channels in this track
598 const auto channels = TrackList::Channels( candidate );
599 unsigned nChannels = channels.size();
600
601 if (strictRules && nChannels > recordingChannels) {
602 // The recording would under-fill this track's channels
603 // Can't use any partial accumulated results
604 // either. Keep looking.
605 candidates.clear();
606 channelCounts.clear();
607 continue;
608 }
609 else {
610 // Might use this but may have to discard some of the accumulated
611 while(strictRules &&
612 nChannels + candidates.size() > recordingChannels) {
613 auto nOldChannels = channelCounts[0];
614 wxASSERT(nOldChannels > 0);
615 channelCounts.erase(channelCounts.begin());
616 candidates.erase(candidates.begin(),
617 candidates.begin() + nOldChannels);
618 }
619 channelCounts.push_back(nChannels);
620 for ( auto channel : channels ) {
621 candidates.push_back(channel->SharedPointer<WaveTrack>());
622 if(candidates.size() == recordingChannels)
623 // Done!
624 return candidates;
625 }
626 }
627 }
628
629 if (!strictRules && !candidates.empty())
630 // good enough
631 return candidates;
632
633 // If the loop didn't exit early, we could not find enough channels
634 return {};
635}
636
638void ProjectAudioManager::OnRecord(bool altAppearance)
639{
640 bool bPreferNewTrack;
641 gPrefs->Read("/GUI/PreferNewTrackRecord", &bPreferNewTrack, false);
642 const bool appendRecord = (altAppearance == bPreferNewTrack);
643
644 // Code from CommandHandler start...
645 AudacityProject *p = &mProject;
646
647 if (p) {
648 const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
649 double t0 = selectedRegion.t0();
650 double t1 = selectedRegion.t1();
651 // When no time selection, recording duration is 'unlimited'.
652 if (t1 == t0)
653 t1 = DBL_MAX;
654
655 auto options = ProjectAudioIO::GetDefaultOptions(*p);
656 WritableSampleTrackArray existingTracks;
657
658 // Checking the selected tracks: counting them and
659 // making sure they all have the same rate
660 const auto selectedTracks{ GetPropertiesOfSelected(*p) };
661 const int rateOfSelected{ selectedTracks.rateOfSelected };
662 const int numberOfSelected{ selectedTracks.numberOfSelected };
663 const bool allSameRate{ selectedTracks.allSameRate };
664
665 if (!allSameRate) {
666 AudacityMessageBox(XO("The tracks selected "
667 "for recording must all have the same sampling rate"),
668 XO("Mismatched Sampling Rates"),
669 wxICON_ERROR | wxCENTRE);
670
671 return;
672 }
673
674 if (appendRecord) {
675 const auto trackRange = TrackList::Get( *p ).Any< const WaveTrack >();
676
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,
682 (trackRange + &Track::IsSelected).max(&Track::GetEndTime));
683 }
684 else {
685 if (numberOfSelected > 0 && 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 auto endTime = std::max_element(
700 existingTracks.begin(),
701 existingTracks.end(),
702 [](const auto& a, const auto& b) {
703 return a->GetEndTime() < b->GetEndTime();
704 }
705 )->get()->GetEndTime();
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 TransportTracks 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 = TransportTracks{ TrackList::Get( *p ), false, true };
731 for (const auto &wt : existingTracks) {
732 auto end = transportTracks.playbackTracks.end();
733 auto it = std::find(transportTracks.playbackTracks.begin(), end, wt);
734 if (it != end)
735 transportTracks.playbackTracks.erase(it);
736 }
737 }
738
739 transportTracks.captureTracks = existingTracks;
740
741 if (rateOfSelected != RATE_NOT_SELECTED)
742 options.rate = rateOfSelected;
743
744 DoRecord(*p, transportTracks, t0, t1, altAppearance, options);
745 }
746}
747
749{
750 bool duplex;
751 gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex,
752#ifdef EXPERIMENTAL_DA
753 false
754#else
755 true
756#endif
757 );
758 return duplex;
759}
760
762 const TransportTracks &tracks,
763 double t0, double t1,
764 bool altAppearance,
765 const AudioIOStartStreamOptions &options)
766{
767 auto &projectAudioManager = *this;
768
769 CommandFlag flags = AlwaysEnabledFlag; // 0 means recalc flags.
770
771 // NB: The call may have the side effect of changing flags.
772 bool allowed = MenuManager::Get(project).TryToMakeActionAllowed(
773 flags,
775
776 if (!allowed)
777 return false;
778 // ...end of code from CommandHandler.
779
780 auto gAudioIO = AudioIO::Get();
781 if (gAudioIO->IsBusy())
782 return false;
783
784 projectAudioManager.SetAppending( !altAppearance );
785
786 bool success = false;
787
788 auto transportTracks = tracks;
789
790 // Will replace any given capture tracks with temporaries
791 transportTracks.captureTracks.clear();
792
793 const auto p = &project;
794
795 bool appendRecord = !tracks.captureTracks.empty();
796
797 auto makeNewClipName = [&](WaveTrack* track) {
798 for (auto i = 1;; ++i)
799 {
800 //i18n-hint a numerical suffix added to distinguish otherwise like-named clips when new record started
801 auto name = XC("%s #%d", "clip name template").Format(track->GetName(), i).Translation();
802 if (track->FindClipByName(name) == nullptr)
803 return name;
804 }
805 };
806
807 {
808 if (appendRecord) {
809 // Append recording:
810 // Pad selected/all wave tracks to make them all the same length
811 for (const auto &wt : tracks.captureTracks)
812 {
813 auto endTime = wt->GetEndTime();
814
815 // If the track was chosen for recording and playback both,
816 // remember the original in preroll tracks, before making the
817 // pending replacement.
818 bool prerollTrack = make_iterator_range(transportTracks.playbackTracks).contains(wt);
819 if (prerollTrack)
820 transportTracks.prerollTracks.push_back(wt);
821
822 // A function that copies all the non-sample data between
823 // wave tracks; in case the track recorded to changes scale
824 // type (for instance), during the recording.
825 auto updater = [](Track &d, const Track &s){
826 auto &dst = static_cast<WaveTrack&>(d);
827 auto &src = static_cast<const WaveTrack&>(s);
828 dst.Reinit(src);
829 };
830
831 // Get a copy of the track to be appended, to be pushed into
832 // undo history only later.
833 auto pending = std::static_pointer_cast<WaveTrack>(
835 updater, wt.get() ) );
836
837 // End of current track is before or at recording start time.
838 // Less than or equal, not just less than, to ensure a clip boundary.
839 // when append recording.
840 if (endTime <= t0) {
841 pending->CreateClip(t0, makeNewClipName(pending.get()));
842 }
843 transportTracks.captureTracks.push_back(pending);
844 }
846 }
847
848 if( transportTracks.captureTracks.empty() )
849 { // recording to NEW track(s).
850 bool recordingNameCustom, useTrackNumber, useDateStamp, useTimeStamp;
851 wxString defaultTrackName, defaultRecordingTrackName;
852
853 // Count the tracks.
854 auto &trackList = TrackList::Get( *p );
855 auto numTracks = trackList.Leaders< const WaveTrack >().size();
856
857 auto recordingChannels = std::max(1, AudioIORecordChannels.Read());
858
859 gPrefs->Read(wxT("/GUI/TrackNames/RecordingNameCustom"), &recordingNameCustom, false);
860 gPrefs->Read(wxT("/GUI/TrackNames/TrackNumber"), &useTrackNumber, false);
861 gPrefs->Read(wxT("/GUI/TrackNames/DateStamp"), &useDateStamp, false);
862 gPrefs->Read(wxT("/GUI/TrackNames/TimeStamp"), &useTimeStamp, false);
863 defaultTrackName = trackList.MakeUniqueTrackName(WaveTrack::GetDefaultAudioTrackNamePreference());
864 gPrefs->Read(wxT("/GUI/TrackNames/RecodingTrackName"), &defaultRecordingTrackName, defaultTrackName);
865
866 wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
867
868 Track *first {};
869 for (int c = 0; c < recordingChannels; c++) {
870 auto newTrack = WaveTrackFactory::Get( *p ).Create();
871 if (!first)
872 first = newTrack.get();
873
874 // Quantize bounds to the rate of the new track.
875 if (c == 0) {
876 if (t0 < DBL_MAX)
877 t0 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t0));
878 if (t1 < DBL_MAX)
879 t1 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t1));
880 }
881
882 newTrack->SetOffset(t0);
883 wxString nameSuffix = wxString(wxT(""));
884
885 if (useTrackNumber) {
886 nameSuffix += wxString::Format(wxT("%d"), 1 + (int) numTracks + c);
887 }
888
889 if (useDateStamp) {
890 if (!nameSuffix.empty()) {
891 nameSuffix += wxT("_");
892 }
893 nameSuffix += wxDateTime::Now().FormatISODate();
894 }
895
896 if (useTimeStamp) {
897 if (!nameSuffix.empty()) {
898 nameSuffix += wxT("_");
899 }
900 nameSuffix += wxDateTime::Now().FormatISOTime();
901 }
902
903 // ISO standard would be nice, but ":" is unsafe for file name.
904 nameSuffix.Replace(wxT(":"), wxT("-"));
905
906 if (baseTrackName.empty()) {
907 newTrack->SetName(nameSuffix);
908 }
909 else if (nameSuffix.empty()) {
910 newTrack->SetName(baseTrackName);
911 }
912 else {
913 newTrack->SetName(baseTrackName + wxT("_") + nameSuffix);
914 }
915 //create a new clip with a proper name before recording is started
916 newTrack->CreateClip(t0, makeNewClipName(newTrack.get()));
917
918 TrackList::Get( *p ).RegisterPendingNewTrack( newTrack );
919
920 if ((recordingChannels > 2) &&
922 TrackView::Get( *newTrack ).SetMinimized(true);
923 }
924
925 transportTracks.captureTracks.push_back(newTrack);
926 }
927 TrackList::Get( *p ).MakeMultiChannelTrack(*first, recordingChannels, true);
928 // Bug 1548. First of new tracks needs the focus.
929 TrackFocus::Get(*p).Set(first);
930 if (TrackList::Get(*p).back())
931 TrackList::Get(*p).back()->EnsureVisible();
932 }
933
934 //Automated Input Level Adjustment Initialization
935 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
936 gAudioIO->AILAInitialize();
937 #endif
938
939 int token = gAudioIO->StartStream(transportTracks, t0, t1, t1, options);
940
941 success = (token != 0);
942
943 if (success) {
945 }
946 else {
947 CancelRecording();
948
949 // Show error message if stream could not be opened
950 auto msg = XO("Error opening recording device.\nError code: %s")
951 .Format( gAudioIO->LastPaErrorString() );
952 using namespace BasicUI;
954 XO("Error"), msg, wxT("Error_opening_sound_device"),
955 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
956 }
957 }
958
959 return success;
960}
961
963{
964 auto &projectAudioManager = *this;
965 bool canStop = projectAudioManager.CanStopAudioStream();
966
967 if ( !canStop ) {
968 return;
969 }
970
971 bool paused = !projectAudioManager.Paused();
972 TogglePaused();
973
974 auto gAudioIO = AudioIO::Get();
975
976#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
977
978 auto project = &mProject;
979 auto &scrubber = Scrubber::Get( *project );
980
981 // Bug 1494 - Pausing a seek or scrub should just STOP as
982 // it is confusing to be in a paused scrub state.
983 bool bStopInstead = paused &&
985 !scrubber.IsSpeedPlaying() &&
986 !scrubber.IsKeyboardScrubbing();
987
988 if (bStopInstead) {
989 Stop();
990 return;
991 }
992
994 scrubber.Pause(paused);
995 else
996#endif
997 {
998 gAudioIO->SetPaused(paused);
999 }
1000}
1001
1002
1004{
1005 mPaused.fetch_xor(1, std::memory_order::memory_order_relaxed);
1006}
1007
1009{
1010 mPaused.store(0, std::memory_order::memory_order_relaxed);
1011}
1012
1014{
1015 return mPaused.load(std::memory_order_relaxed) == 1;
1016}
1017
1018
1020{
1021 const auto project = &mProject;
1022 TrackList::Get( *project ).ClearPendingTracks();
1023}
1024
1026{
1027 auto &project = mProject;
1028
1029 mDisplayedRate = rate;
1030
1031 auto display = FormatRate( rate );
1032
1033 ProjectStatus::Get( project ).Set( display, rateStatusBarField );
1034}
1035
1037{
1038 // Auto-save was done here before, but it is unnecessary, provided there
1039 // are sufficient autosaves when pushing or modifying undo states.
1040}
1041
1042// This is called after recording has stopped and all tracks have flushed.
1044{
1045 auto &project = mProject;
1046 auto &projectAudioIO = ProjectAudioIO::Get( project );
1047 auto &projectFileIO = ProjectFileIO::Get( project );
1048
1049 // Only push state if we were capturing and not monitoring
1050 if (projectAudioIO.GetAudioIOToken() > 0)
1051 {
1052 auto &history = ProjectHistory::Get( project );
1053
1054 if (IsTimerRecordCancelled()) {
1055 // discard recording
1056 history.RollbackState();
1057 // Reset timer record
1058 ResetTimerRecordCancelled();
1059 }
1060 else {
1061 // Add to history
1062 // We want this to have No-fail-guarantee if we get here from exception
1063 // handling of recording, and that means we rely on the last autosave
1064 // successfully committed to the database, not risking a failure
1065 auto flags = AudioIO::Get()->HasRecordingException()
1068 history.PushState(XO("Recorded Audio"), XO("Record"), flags);
1069
1070 // Now, we may add a label track to give information about
1071 // dropouts. We allow failure of this.
1072 auto gAudioIO = AudioIO::Get();
1073 auto &intervals = gAudioIO->LostCaptureIntervals();
1074 if (intervals.size())
1075 Publish( RecordingDropoutEvent{ intervals } );
1076 }
1077 }
1078}
1079
1081 const WritableSampleTrackArray *tracks)
1082{
1083 auto &project = mProject;
1084 auto &projectFileIO = ProjectFileIO::Get( project );
1085
1086 wxTheApp->CallAfter( [&]{ projectFileIO.AutoSave(true); });
1087}
1088
1090{
1091 const auto project = &mProject;
1092 TrackList::Get( *project ).ApplyPendingTracks();
1093}
1094
1096{
1097 auto& project = mProject;
1098 auto gAudioIO = AudioIO::Get();
1099 if (gAudioIO && &project == gAudioIO->GetOwningProject().get())
1100 {
1101 bool canStop = CanStopAudioStream();
1102
1103 gAudioIO->SetPaused(!gAudioIO->IsPaused());
1104
1105 if (canStop)
1106 {
1107 // Instead of calling ::OnPause here, we can simply do the only thing it does (i.e. toggling the pause state),
1108 // because scrubbing can not happen while recording
1109 TogglePaused();
1110 }
1111 }
1112}
1113
1115{
1117 Stop();
1118}
1119
1121{
1122 auto gAudioIO = AudioIO::Get();
1123 return
1124 gAudioIO->IsBusy() &&
1125 CanStopAudioStream() &&
1126 // ... and not merely monitoring
1127 !gAudioIO->IsMonitoring() &&
1128 // ... and not punch-and-roll recording
1129 gAudioIO->GetNumCaptureChannels() == 0;
1130}
1131
1133{
1134 auto gAudioIO = AudioIO::Get();
1135 return
1136 gAudioIO->IsBusy() &&
1137 CanStopAudioStream() &&
1138 gAudioIO->GetNumCaptureChannels() > 0;
1139}
1140
1142{
1143 auto gAudioIO = AudioIO::Get();
1144 return (!gAudioIO->IsStreamActive() ||
1145 gAudioIO->IsMonitoring() ||
1146 gAudioIO->GetOwningProject().get() == &mProject );
1147}
1148
1151 [](const AudacityProject &project){
1152 auto &projectAudioManager = ProjectAudioManager::Get( project );
1153 bool canStop = projectAudioManager.CanStopAudioStream();
1154 return canStop;
1155 }
1156 }; return flag; }
1157
1160[](AudacityProject &project, bool newDefault) -> AudioIOStartStreamOptions {
1162 auto options = ProjectAudioIO::DefaultOptionsFactory(project, newDefault);
1163
1165 options.listener = ProjectAudioManager::Get(project).shared_from_this();
1166
1167 bool loopEnabled = ViewInfo::Get(project).playRegion.Active();
1168 options.loopEnabled = loopEnabled;
1169
1170 if (newDefault) {
1171 const double trackEndTime = TrackList::Get(project).GetEndTime();
1172 const double loopEndTime = ViewInfo::Get(project).playRegion.GetEnd();
1173 options.policyFactory = [&project, trackEndTime, loopEndTime](
1174 const AudioIOStartStreamOptions &options)
1175 -> std::unique_ptr<PlaybackPolicy>
1176 {
1177 return std::make_unique<DefaultPlaybackPolicy>( project,
1178 trackEndTime, loopEndTime,
1179 options.loopEnabled, options.variableSpeed);
1180 };
1181
1182 // Start play from left edge of selection
1183 options.pStartTime.emplace(ViewInfo::Get(project).selectedRegion.t0());
1184 }
1185
1186 return options;
1187} };
1188
1191{
1192 auto result = ProjectAudioIO::GetDefaultOptions( project );
1193 auto gAudioIO = AudioIO::Get();
1194 auto PlayAtSpeedRate = gAudioIO->GetBestRate(
1195 false, //not capturing
1196 true, //is playing
1197 ProjectRate::Get( project ).GetRate() //suggested rate
1198 );
1199 result.rate = PlayAtSpeedRate;
1200 return result;
1201}
1202
1203// Stop playing or recording, if paused.
1205{
1206 if( AudioIOBase::Get()->IsPaused() )
1207 Stop();
1208}
1209
1210bool ProjectAudioManager::DoPlayStopSelect( bool click, bool shift )
1211{
1212 auto &project = mProject;
1213 auto &scrubber = Scrubber::Get( project );
1214 auto token = ProjectAudioIO::Get( project ).GetAudioIOToken();
1215 auto &viewInfo = ViewInfo::Get( project );
1216 auto &selection = viewInfo.selectedRegion;
1217 auto gAudioIO = AudioIO::Get();
1218
1219 //If busy, stop playing, make sure everything is unpaused.
1220 if (scrubber.HasMark() ||
1221 gAudioIO->IsStreamActive(token)) {
1222 // change the selection
1223 auto time = gAudioIO->GetStreamTime();
1224 // Test WasSpeedPlaying(), not IsSpeedPlaying()
1225 // as we could be stopped now. Similarly WasKeyboardScrubbing().
1226 if (click && (scrubber.WasSpeedPlaying() || scrubber.WasKeyboardScrubbing()))
1227 {
1228 ;// don't change the selection.
1229 }
1230 else if (shift && click) {
1231 // Change the region selection, as if by shift-click at the play head
1232 auto t0 = selection.t0(), t1 = selection.t1();
1233 if (time < t0)
1234 // Grow selection
1235 t0 = time;
1236 else if (time > t1)
1237 // Grow selection
1238 t1 = time;
1239 else {
1240 // Shrink selection, changing the nearer boundary
1241 if (fabs(t0 - time) < fabs(t1 - time))
1242 t0 = time;
1243 else
1244 t1 = time;
1245 }
1246 selection.setTimes(t0, t1);
1247 }
1248 else if (click){
1249 // avoid a point at negative time.
1250 time = wxMax( time, 0 );
1251 // Set a point selection, as if by a click at the play head
1252 selection.setTimes(time, time);
1253 } else
1254 // How stop and set cursor always worked
1255 // -- change t0, collapsing to point only if t1 was greater
1256 selection.setT0(time, false);
1257
1258 ProjectHistory::Get( project ).ModifyState(false); // without bWantsAutoSave
1259 return true;
1260 }
1261 return false;
1262}
1263
1264// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and
1265// "OnStopSelect" merged.
1267{
1268 auto gAudioIO = AudioIO::Get();
1269 if (DoPlayStopSelect(false, false))
1270 Stop();
1271 else if (!gAudioIO->IsBusy()) {
1272 //Otherwise, start playing (assuming audio I/O isn't busy)
1273
1274 // Will automatically set mLastPlayMode
1275 PlayCurrentRegion(false);
1276 }
1277}
1278
1280 []{ return PausedFlag(); },
1281 []{ return AudioIONotBusyFlag(); },
1282 []( const AudacityProject &project ){
1283 return MenuManager::Get( project ).mStopIfWasPaused; },
1284 []( AudacityProject &project, CommandFlag ){
1285 if ( MenuManager::Get( project ).mStopIfWasPaused )
1287 }
1288}};
1289
1290// GetSelectedProperties collects information about
1291// currently selected audio tracks
1294{
1295 double rateOfSelection{ RATE_NOT_SELECTED };
1296
1297 PropertiesOfSelected result;
1298 result.allSameRate = true;
1299
1300 const auto selectedTracks{
1301 TrackList::Get(proj).Selected< const WaveTrack >() };
1302
1303 for (const auto & track : selectedTracks)
1304 {
1305 if (rateOfSelection != RATE_NOT_SELECTED &&
1306 track->GetRate() != rateOfSelection)
1307 result.allSameRate = false;
1308 else if (rateOfSelection == RATE_NOT_SELECTED)
1309 rateOfSelection = track->GetRate();
1310 }
1311
1312 result.numberOfSelected = selectedTracks.size();
1313 result.rateOfSelected = rateOfSelection;
1314
1315 return result;
1316}
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
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
Definition: MemoryX.h:448
constexpr size_t TimeQueueGrainSize
FileConfig * gPrefs
Definition: Prefs.cpp:70
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:49
@ 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
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:91
static AudioIO * Get()
Definition: AudioIO.cpp:147
bool HasRecordingException() const
Definition: AudioIO.h:381
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:654
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: Track.h:917
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 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
void OnAudioIONewBlocks(const WritableSampleTrackArray *tracks) override
~ProjectAudioManager() override
ProjectAudioManager(AudacityProject &project)
int PlayPlayRegion(const SelectedRegion &selectedRegion, const AudioIOStartStreamOptions &options, PlayMode playMode, bool backwards=false)
void PlayCurrentRegion(bool newDefault=false, bool cutpreview=false)
void OnRecord(bool altAppearance)
bool DoRecord(AudacityProject &project, const TransportTracks &transportTracks, double t0, double t1, bool altAppearance, const AudioIOStartStreamOptions &options)
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:200
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:226
bool IsSelected() const
Definition: Track.cpp:403
virtual double GetEndTime() const =0
void RegisterPendingNewTrack(const std::shared_ptr< Track > &pTrack)
Definition: Track.cpp:1080
bool MakeMultiChannelTrack(Track &first, int nChannels, bool aligned)
Converts channels to a multichannel track.
Definition: Track.cpp:763
double GetEndTime() const
Definition: Track.cpp:1053
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1440
bool ApplyPendingTracks()
Definition: Track.cpp:1145
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
std::shared_ptr< Track > RegisterPendingChangedTrack(Updater updater, Track *src)
Definition: Track.cpp:1059
void ClearPendingTracks(ListOfTracks *pAdded=nullptr)
Definition: Track.cpp:1104
void UpdatePendingTracks()
Definition: Track.cpp:1086
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1457
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1544
void SetMinimized(bool minimized)
Definition: TrackView.cpp:93
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
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:116
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:2536
A Track that contains audio waveform data.
Definition: WaveTrack.h:51
void Reinit(const WaveTrack &orig)
Definition: WaveTrack.cpp:170
static wxString GetDefaultAudioTrackNamePreference()
Definition: WaveTrack.cpp:98
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 OffsetTrackTime(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.GetTrackTime() 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.
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:752
double GetRate(const Track &track)
Definition: TimeTrack.cpp:179
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 GetTrackTime() const
Get current track time value, unadjusted.
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 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
WritableSampleTrackArray captureTracks
Definition: AudioIO.h:88
SampleTrackConstArray playbackTracks
Definition: AudioIO.h:87