diff --git a/src/ProjectAudioManager.cpp b/src/ProjectAudioManager.cpp index 3be8a7596..c32fde8f8 100644 --- a/src/ProjectAudioManager.cpp +++ b/src/ProjectAudioManager.cpp @@ -39,6 +39,7 @@ Paul Licameli split from ProjectManager.cpp #include "widgets/ErrorDialog.h" #include "widgets/MeterPanelBase.h" #include "widgets/Warning.h" +#include "widgets/AudacityMessageBox.h" static AudacityProject::AttachedObjects::RegisteredFactory @@ -383,7 +384,7 @@ void ProjectAudioManager::Pause() } WaveTrackArray ProjectAudioManager::ChooseExistingRecordingTracks( - AudacityProject &proj, bool selectedOnly) + AudacityProject &proj, bool selectedOnly, double targetRate) { auto p = &proj; size_t recordingChannels = @@ -391,6 +392,7 @@ WaveTrackArray ProjectAudioManager::ChooseExistingRecordingTracks( bool strictRules = (recordingChannels <= 2); // Iterate over all wave tracks, or over selected wave tracks only. + // If target rate was specified, ignore all tracks with other rates. // // In the usual cases of one or two recording channels, seek a first-fit // unbroken sub-sequence for which the total number of channels matches the @@ -414,6 +416,9 @@ WaveTrackArray ProjectAudioManager::ChooseExistingRecordingTracks( WaveTrackArray candidates; const auto range = trackList.Leaders(); for ( auto candidate : selectedOnly ? range + &Track::IsSelected : range ) { + if (targetRate != RATE_NOT_SELECTED && candidate->GetRate() != targetRate) + continue; + // count channels in this track const auto channels = TrackList::Channels( candidate ); unsigned nChannels = channels.size(); @@ -472,24 +477,53 @@ void ProjectAudioManager::OnRecord(bool altAppearance) if (t1 == t0) t1 = DBL_MAX; + auto options = DefaultPlayOptions(*p); WaveTrackArray existingTracks; + // Checking the selected tracks: counting them and + // making sure they all have the same rate + const auto selectedTracks{ GetPropertiesOfSelected(*p) }; + const int rateOfSelected{ selectedTracks.rateOfSelected }; + const int numberOfSelected{ selectedTracks.numberOfSelected }; + const bool allSameRate{ selectedTracks.allSameRate }; + + if (!allSameRate) { + AudacityMessageBox(XO("TRACK SELECTION PROBLEM:\nthe tracks selected " + "for recording must all have the same sampling rate"), + XO("Unfitting track selection"), + wxICON_ERROR | wxCENTRE); + + return; + } + if (appendRecord) { const auto trackRange = TrackList::Get( *p ).Any< const WaveTrack >(); // Try to find wave tracks to record into. (If any are selected, // try to choose only from them; else if wave tracks exist, may record into any.) - existingTracks = ChooseExistingRecordingTracks(*p, true); - if ( !existingTracks.empty() ) - t0 = std::max( t0, - ( trackRange + &Track::IsSelected ).max( &Track::GetEndTime ) ); + existingTracks = ChooseExistingRecordingTracks(*p, true, rateOfSelected); + if (!existingTracks.empty()) { + t0 = std::max(t0, + (trackRange + &Track::IsSelected).max(&Track::GetEndTime)); + } else { - existingTracks = ChooseExistingRecordingTracks(*p, false); + if (numberOfSelected > 0 && rateOfSelected != options.rate) { + AudacityMessageBox(XO("TRACK SELECTION PROBLEM:\n" + "Not enough tracks are selected for recording on non-project rate.\n" + "(keep in mind that Audacity doesn\'t allow " + "using only one channel of a stereo track)"), + XO("Insufficient track selection"), + wxICON_ERROR | wxCENTRE); + + return; + } + + existingTracks = ChooseExistingRecordingTracks(*p, false, options.rate); t0 = std::max( t0, trackRange.max( &Track::GetEndTime ) ); // If suitable tracks still not found, will record into NEW ones, // but the choice of t0 does not depend on that. } - + // Whether we decided on NEW tracks or not: if (t1 <= selectedRegion.t0() && selectedRegion.t1() > selectedRegion.t0()) { t1 = selectedRegion.t1(); // record within the selection @@ -515,7 +549,10 @@ void ProjectAudioManager::OnRecord(bool altAppearance) } transportTracks.captureTracks = existingTracks; - auto options = DefaultPlayOptions( *p ); + + if (rateOfSelected != RATE_NOT_SELECTED) + options.rate = rateOfSelected; + DoRecord(*p, transportTracks, t0, t1, altAppearance, options); } } @@ -1025,8 +1062,6 @@ void ProjectAudioManager::StopIfPaused() Stop(); } -#include "widgets/AudacityMessageBox.h" - bool ProjectAudioManager::DoPlayStopSelect( bool click, bool shift ) { auto &project = mProject; @@ -1108,3 +1143,31 @@ static RegisteredMenuItemEnabler stopIfPaused{{ ProjectAudioManager::Get( project ).StopIfPaused(); } }}; + +// GetSelectedProperties collects information about +// currently selected audio tracks +PropertiesOfSelected +GetPropertiesOfSelected(const AudacityProject &proj) +{ + double rateOfSelection{ RATE_NOT_SELECTED }; + + PropertiesOfSelected result; + result.allSameRate = true; + + const auto selectedTracks{ + TrackList::Get(proj).Selected< const WaveTrack >() }; + + for (const auto & track : selectedTracks) + { + if (rateOfSelection != RATE_NOT_SELECTED && + track->GetRate() != rateOfSelection) + result.allSameRate = false; + else if (rateOfSelection == RATE_NOT_SELECTED) + rateOfSelection = track->GetRate(); + } + + result.numberOfSelected = selectedTracks.size(); + result.rateOfSelected = rateOfSelection; + + return result; +} \ No newline at end of file diff --git a/src/ProjectAudioManager.h b/src/ProjectAudioManager.h index 08b9fe315..c8d4a4480 100644 --- a/src/ProjectAudioManager.h +++ b/src/ProjectAudioManager.h @@ -17,6 +17,8 @@ Paul Licameli split from ProjectManager.h #include "AudioIOListener.h" // to inherit #include "ClientData.h" // to inherit +constexpr int RATE_NOT_SELECTED{ -1 }; + class AudacityProject; struct AudioIOStartStreamOptions; class TrackList; @@ -47,7 +49,8 @@ public: // Find suitable tracks to record into, or return an empty array. static WaveTrackArray ChooseExistingRecordingTracks( - AudacityProject &proj, bool selectedOnly); + AudacityProject &proj, bool selectedOnly, + double targetRate = RATE_NOT_SELECTED); static bool UseDuplex(); @@ -161,6 +164,15 @@ private: AudioIOStartStreamOptions DefaultPlayOptions( AudacityProject &project ); AudioIOStartStreamOptions DefaultSpeedPlayOptions( AudacityProject &project ); +struct PropertiesOfSelected +{ + bool allSameRate{ false }; + int rateOfSelected{ RATE_NOT_SELECTED }; + int numberOfSelected{ 0 }; +}; + +PropertiesOfSelected GetPropertiesOfSelected(const AudacityProject &proj); + #include "commands/CommandFlag.h" extern const ReservedCommandFlag diff --git a/src/menus/TransportMenus.cpp b/src/menus/TransportMenus.cpp index 0d8cbd5f1..bffd454ad 100644 --- a/src/menus/TransportMenus.cpp +++ b/src/menus/TransportMenus.cpp @@ -282,6 +282,39 @@ void OnTimerRecord(const CommandContext &context) wxICON_INFORMATION | wxOK); return; } + + // We check the selected tracks to see if there is enough of them to accomodate + // all input channels and all of them have the same sampling rate. + // Those checks will be later performed by recording function anyway, + // but we want to warn the user about potential problems from the very start. + const auto selectedTracks{ GetPropertiesOfSelected(project) }; + const int rateOfSelected{ selectedTracks.rateOfSelected }; + const int numberOfSelected{ selectedTracks.numberOfSelected }; + const bool allSameRate{ selectedTracks.allSameRate }; + + if (!allSameRate) { + AudacityMessageBox(XO("TRACK SELECTION PROBLEM:\nthe tracks selected " + "for recording must all have the same sampling rate"), + XO("Unfitting track selection"), + wxICON_ERROR | wxCENTRE); + + return; + } + + const auto existingTracks{ ProjectAudioManager::ChooseExistingRecordingTracks(project, true, rateOfSelected) }; + if (existingTracks.empty()) { + if (numberOfSelected > 0 && rateOfSelected != settings.GetRate()) { + AudacityMessageBox(XO("TRACK SELECTION PROBLEM:\n" + "Not enough tracks are selected for recording on non-project rate.\n" + "(keep in mind that Audacity doesn\'t allow " + "using only one channel of a stereo track)"), + XO("Insufficient track selection"), + wxICON_ERROR | wxCENTRE); + + return; + } + } + // We use this variable to display "Current Project" in the Timer Recording // save project field bool bProjectSaved = ProjectFileIO::Get( project ).IsProjectSaved(); @@ -374,9 +407,23 @@ void OnPunchAndRoll(const CommandContext &context) viewInfo.selectedRegion.collapseToT0(); double t1 = std::max(0.0, viewInfo.selectedRegion.t1()); + // Checking the selected tracks: making sure they all have the same rate + const auto selectedTracks{ GetPropertiesOfSelected(project) }; + const int rateOfSelected{ selectedTracks.rateOfSelected }; + const bool allSameRate{ selectedTracks.allSameRate }; + + if (!allSameRate) { + AudacityMessageBox(XO("TRACK SELECTION PROBLEM:\nthe tracks selected " + "for recording must all have the same sampling rate"), + XO("Unfitting track selection"), + wxICON_ERROR | wxCENTRE); + + return; + } + // Decide which tracks to record in. auto tracks = - ProjectAudioManager::ChooseExistingRecordingTracks(project, true); + ProjectAudioManager::ChooseExistingRecordingTracks(project, true, rateOfSelected); if (tracks.empty()) { int recordingChannels = std::max(0L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2)); @@ -384,7 +431,7 @@ void OnPunchAndRoll(const CommandContext &context) (recordingChannels == 1) ? XO("Please select in a mono track.") : (recordingChannels == 2) - ? XO("Please select in a stereo track.") + ? XO("Please select in a stereo track or two mono tracks.") : XO("Please select at least %d channels.").Format( recordingChannels ); ShowErrorDialog(&window, XO("Error"), message, url); return; @@ -471,6 +518,7 @@ void OnPunchAndRoll(const CommandContext &context) // Try to start recording auto options = DefaultPlayOptions( project ); + options.rate = rateOfSelected; options.preRoll = std::max(0L, gPrefs->Read(AUDIO_PRE_ROLL_KEY, DEFAULT_PRE_ROLL_SECONDS)); options.pCrossfadeData = &crossfadeData;