mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-21 14:02:57 +02:00
Cut and paste functions into ProjectAudioManager.cpp
This commit is contained in:
@@ -8,15 +8,20 @@ Paul Licameli split from ProjectManager.cpp
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Audacity.h"
|
||||
#include "ProjectAudioManager.h"
|
||||
|
||||
#include "Experimental.h"
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/statusbr.h>
|
||||
|
||||
#include "AudioIO.h"
|
||||
#include "AutoRecovery.h"
|
||||
#include "CommonCommandFlags.h"
|
||||
#include "DirManager.h"
|
||||
#include "LabelTrack.h"
|
||||
#include "Menus.h"
|
||||
#include "Project.h"
|
||||
#include "ProjectAudioIO.h"
|
||||
#include "ProjectFileIO.h"
|
||||
@@ -24,10 +29,20 @@ Paul Licameli split from ProjectManager.cpp
|
||||
#include "ProjectSettings.h"
|
||||
#include "ProjectStatus.h"
|
||||
#include "TimeTrack.h"
|
||||
#include "TrackPanel.h"
|
||||
#include "UndoManager.h"
|
||||
#include "ViewInfo.h"
|
||||
#include "WaveTrack.h"
|
||||
#include "toolbars/ControlToolBar.h"
|
||||
#include "toolbars/ToolManager.h"
|
||||
#include "prefs/TracksPrefs.h"
|
||||
#include "tracks/ui/Scrubbing.h"
|
||||
#include "tracks/ui/TrackView.h"
|
||||
#include "widgets/ErrorDialog.h"
|
||||
#include "widgets/MeterPanelBase.h"
|
||||
#include "widgets/Warning.h"
|
||||
|
||||
|
||||
static AudacityProject::AttachedObjects::RegisteredFactory
|
||||
sProjectAudioManagerKey {
|
||||
[]( AudacityProject &project ) {
|
||||
@@ -81,6 +96,704 @@ auto ProjectAudioManager::StatusWidthFunction(
|
||||
return {};
|
||||
}
|
||||
|
||||
int ProjectAudioManager::PlayPlayRegion(const SelectedRegion &selectedRegion,
|
||||
const AudioIOStartStreamOptions &options,
|
||||
PlayMode mode,
|
||||
bool backwards, /* = false */
|
||||
bool playWhiteSpace /* = false */)
|
||||
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop )
|
||||
return -1;
|
||||
|
||||
bool useMidi = true;
|
||||
|
||||
// Remove these lines to experiment with scrubbing/seeking of note tracks
|
||||
if (options.pScrubbingOptions)
|
||||
useMidi = false;
|
||||
|
||||
// Uncomment this for laughs!
|
||||
// backwards = true;
|
||||
|
||||
double t0 = selectedRegion.t0();
|
||||
double t1 = selectedRegion.t1();
|
||||
// SelectedRegion guarantees t0 <= t1, so we need another boolean argument
|
||||
// to indicate backwards play.
|
||||
const bool looped = options.playLooped;
|
||||
|
||||
if (backwards)
|
||||
std::swap(t0, t1);
|
||||
|
||||
projectAudioManager.SetLooping( mode == PlayMode::loopedPlay );
|
||||
projectAudioManager.SetCutting( mode == PlayMode::cutPreviewPlay );
|
||||
|
||||
bool success = false;
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
if (gAudioIO->IsBusy())
|
||||
return -1;
|
||||
|
||||
const bool cutpreview = mode == PlayMode::cutPreviewPlay;
|
||||
if (cutpreview && t0==t1)
|
||||
return -1; /* msmeyer: makes no sense */
|
||||
|
||||
AudacityProject *p = &mProject;
|
||||
|
||||
auto &tracks = TrackList::Get( *p );
|
||||
|
||||
mLastPlayMode = mode;
|
||||
|
||||
bool hasaudio;
|
||||
if (useMidi)
|
||||
hasaudio = ! tracks.Any<PlayableTrack>().empty();
|
||||
else
|
||||
hasaudio = ! tracks.Any<WaveTrack>().empty();
|
||||
|
||||
double latestEnd = (playWhiteSpace)? t1 : tracks.GetEndTime();
|
||||
|
||||
if (!hasaudio)
|
||||
return -1; // No need to continue without audio tracks
|
||||
|
||||
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
||||
double init_seek = 0.0;
|
||||
#endif
|
||||
|
||||
if (t1 == t0) {
|
||||
if (looped) {
|
||||
const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
|
||||
// play selection if there is one, otherwise
|
||||
// set start of play region to project start,
|
||||
// and loop the project from current play position.
|
||||
|
||||
if ((t0 > selectedRegion.t0()) && (t0 < selectedRegion.t1())) {
|
||||
t0 = selectedRegion.t0();
|
||||
t1 = selectedRegion.t1();
|
||||
}
|
||||
else {
|
||||
// loop the entire project
|
||||
t0 = tracks.GetStartTime();
|
||||
t1 = tracks.GetEndTime();
|
||||
}
|
||||
} else {
|
||||
// move t0 to valid range
|
||||
if (t0 < 0) {
|
||||
t0 = tracks.GetStartTime();
|
||||
}
|
||||
else if (t0 > tracks.GetEndTime()) {
|
||||
t0 = tracks.GetEndTime();
|
||||
}
|
||||
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
||||
else {
|
||||
init_seek = t0; //AC: init_seek is where playback will 'start'
|
||||
t0 = tracks.GetStartTime();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
t1 = tracks.GetEndTime();
|
||||
}
|
||||
else {
|
||||
// maybe t1 < t0, with backwards scrubbing for instance
|
||||
if (backwards)
|
||||
std::swap(t0, t1);
|
||||
|
||||
t0 = std::max(0.0, std::min(t0, latestEnd));
|
||||
t1 = std::max(0.0, std::min(t1, latestEnd));
|
||||
|
||||
if (backwards)
|
||||
std::swap(t0, t1);
|
||||
}
|
||||
|
||||
int token = -1;
|
||||
|
||||
if (t1 != t0) {
|
||||
if (cutpreview) {
|
||||
const double tless = std::min(t0, t1);
|
||||
const double tgreater = std::max(t0, t1);
|
||||
double beforeLen, afterLen;
|
||||
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
|
||||
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
|
||||
double tcp0 = tless-beforeLen;
|
||||
double diff = tgreater - tless;
|
||||
double tcp1 = (tgreater+afterLen) - diff;
|
||||
SetupCutPreviewTracks(tcp0, tless, tgreater, tcp1);
|
||||
if (backwards)
|
||||
std::swap(tcp0, tcp1);
|
||||
if (mCutPreviewTracks)
|
||||
{
|
||||
AudioIOStartStreamOptions myOptions = options;
|
||||
myOptions.cutPreviewGapStart = t0;
|
||||
myOptions.cutPreviewGapLen = t1 - t0;
|
||||
token = gAudioIO->StartStream(
|
||||
GetAllPlaybackTracks(*mCutPreviewTracks, false, useMidi),
|
||||
tcp0, tcp1, myOptions);
|
||||
}
|
||||
else
|
||||
// Cannot create cut preview tracks, clean up and exit
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
token = gAudioIO->StartStream(
|
||||
GetAllPlaybackTracks( tracks, false, useMidi ),
|
||||
t0, t1, options);
|
||||
}
|
||||
if (token != 0) {
|
||||
success = true;
|
||||
ProjectAudioIO::Get( *p ).SetAudioIOToken(token);
|
||||
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
||||
//AC: If init_seek was set, now's the time to make it happen.
|
||||
gAudioIO->SeekStream(init_seek);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
// Bug1627 (part of it):
|
||||
// infinite error spew when trying to start scrub:
|
||||
// Problem was that the error dialog yields to events,
|
||||
// causing recursion to this function in the scrub timer
|
||||
// handler! Easy fix, just delay the user alert instead.
|
||||
auto &window = GetProjectFrame( mProject );
|
||||
window.CallAfter( [&]{
|
||||
// Show error message if stream could not be opened
|
||||
ShowErrorDialog(&window, _("Error"),
|
||||
_("Error opening sound device.\nTry changing the audio host, playback device and the project sample rate."),
|
||||
wxT("Error_opening_sound_device"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
return -1;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
void ProjectAudioManager::PlayCurrentRegion(bool looped /* = false */,
|
||||
bool cutpreview /* = false */)
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop )
|
||||
return;
|
||||
|
||||
AudacityProject *p = &mProject;
|
||||
|
||||
{
|
||||
|
||||
const auto &playRegion = ViewInfo::Get( *p ).playRegion;
|
||||
|
||||
auto options = DefaultPlayOptions( *p );
|
||||
options.playLooped = looped;
|
||||
if (cutpreview)
|
||||
options.envelope = nullptr;
|
||||
auto mode =
|
||||
cutpreview ? PlayMode::cutPreviewPlay
|
||||
: options.playLooped ? PlayMode::loopedPlay
|
||||
: PlayMode::normalPlay;
|
||||
PlayPlayRegion(SelectedRegion(playRegion.GetStart(), playRegion.GetEnd()),
|
||||
options,
|
||||
mode);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectAudioManager::Stop(bool stopStream /* = true*/)
|
||||
{
|
||||
AudacityProject *project = &mProject;
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop )
|
||||
return;
|
||||
|
||||
if(project) {
|
||||
// Let scrubbing code do some appearance change
|
||||
auto &scrubber = Scrubber::Get( *project );
|
||||
scrubber.StopScrubbing();
|
||||
}
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
|
||||
auto cleanup = finally( [&]{
|
||||
projectAudioManager.SetStopping( false );
|
||||
} );
|
||||
|
||||
if (stopStream && gAudioIO->IsBusy()) {
|
||||
// flag that we are stopping
|
||||
projectAudioManager.SetStopping( true );
|
||||
// Allow UI to update for that
|
||||
while( wxTheApp->ProcessIdle() )
|
||||
;
|
||||
}
|
||||
|
||||
if(stopStream)
|
||||
gAudioIO->StopStream();
|
||||
|
||||
projectAudioManager.SetLooping( false );
|
||||
projectAudioManager.SetCutting( false );
|
||||
|
||||
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
||||
gAudioIO->AILADisable();
|
||||
#endif
|
||||
|
||||
projectAudioManager.SetPaused( false );
|
||||
//Make sure you tell gAudioIO to unpause
|
||||
gAudioIO->SetPaused( false );
|
||||
|
||||
ClearCutPreviewTracks();
|
||||
|
||||
// So that we continue monitoring after playing or recording.
|
||||
// also clean the MeterQueues
|
||||
if( project ) {
|
||||
auto &projectAudioIO = ProjectAudioIO::Get( *project );
|
||||
auto meter = projectAudioIO.GetPlaybackMeter();
|
||||
if( meter ) {
|
||||
meter->Clear();
|
||||
}
|
||||
|
||||
meter = projectAudioIO.GetCaptureMeter();
|
||||
if( meter ) {
|
||||
meter->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
const auto toolbar = ToolManager::Get( *project ).GetToolBar(ScrubbingBarID);
|
||||
toolbar->EnableDisableButtons();
|
||||
}
|
||||
|
||||
void ProjectAudioManager::Pause()
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop ) {
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
gAudioIO->SetPaused(!gAudioIO->IsPaused());
|
||||
}
|
||||
else {
|
||||
OnPause();
|
||||
}
|
||||
}
|
||||
|
||||
WaveTrackArray ProjectAudioManager::ChooseExistingRecordingTracks(
|
||||
AudacityProject &proj, bool selectedOnly)
|
||||
{
|
||||
auto p = &proj;
|
||||
size_t recordingChannels =
|
||||
std::max(0L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
|
||||
bool strictRules = (recordingChannels <= 2);
|
||||
|
||||
// Iterate over all wave tracks, or over selected wave tracks only.
|
||||
//
|
||||
// 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
|
||||
// required number exactly. Never drop inputs or fill only some channels
|
||||
// of a track.
|
||||
//
|
||||
// In case of more than two recording channels, choose tracks only among the
|
||||
// selected. Simply take the earliest wave tracks, until the number of
|
||||
// channels is enough. If there are fewer channels than inputs, but at least
|
||||
// one channel, then some of the input channels will be dropped.
|
||||
//
|
||||
// Resulting tracks may be non-consecutive within the list of all tracks
|
||||
// (there may be non-wave tracks between, or non-selected tracks when
|
||||
// considering selected tracks only.)
|
||||
|
||||
if (!strictRules && !selectedOnly)
|
||||
return {};
|
||||
|
||||
auto &trackList = TrackList::Get( *p );
|
||||
std::vector<unsigned> channelCounts;
|
||||
WaveTrackArray candidates;
|
||||
const auto range = trackList.Leaders<WaveTrack>();
|
||||
for ( auto candidate : selectedOnly ? range + &Track::IsSelected : range ) {
|
||||
// count channels in this track
|
||||
const auto channels = TrackList::Channels( candidate );
|
||||
unsigned nChannels = channels.size();
|
||||
|
||||
if (strictRules && nChannels > recordingChannels) {
|
||||
// The recording would under-fill this track's channels
|
||||
// Can't use any partial accumulated results
|
||||
// either. Keep looking.
|
||||
candidates.clear();
|
||||
channelCounts.clear();
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// Might use this but may have to discard some of the accumulated
|
||||
while(strictRules &&
|
||||
nChannels + candidates.size() > recordingChannels) {
|
||||
auto nOldChannels = channelCounts[0];
|
||||
wxASSERT(nOldChannels > 0);
|
||||
channelCounts.erase(channelCounts.begin());
|
||||
candidates.erase(candidates.begin(),
|
||||
candidates.begin() + nOldChannels);
|
||||
}
|
||||
channelCounts.push_back(nChannels);
|
||||
for ( auto channel : channels ) {
|
||||
candidates.push_back(channel->SharedPointer<WaveTrack>());
|
||||
if(candidates.size() == recordingChannels)
|
||||
// Done!
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!strictRules && !candidates.empty())
|
||||
// good enough
|
||||
return candidates;
|
||||
|
||||
// If the loop didn't exit early, we could not find enough channels
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProjectAudioManager::OnRecord(bool altAppearance)
|
||||
// STRONG-GUARANTEE (for state of current project's tracks)
|
||||
{
|
||||
bool bPreferNewTrack;
|
||||
gPrefs->Read("/GUI/PreferNewTrackRecord", &bPreferNewTrack, false);
|
||||
const bool appendRecord = (altAppearance == bPreferNewTrack);
|
||||
|
||||
// Code from CommandHandler start...
|
||||
AudacityProject *p = &mProject;
|
||||
|
||||
if (p) {
|
||||
const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
|
||||
double t0 = selectedRegion.t0();
|
||||
double t1 = selectedRegion.t1();
|
||||
// When no time selection, recording duration is 'unlimited'.
|
||||
if (t1 == t0)
|
||||
t1 = DBL_MAX;
|
||||
|
||||
WaveTrackArray existingTracks;
|
||||
|
||||
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 ) );
|
||||
else {
|
||||
existingTracks = ChooseExistingRecordingTracks(*p, false);
|
||||
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
|
||||
}
|
||||
else {
|
||||
t1 = DBL_MAX; // record for a long, long time
|
||||
}
|
||||
}
|
||||
|
||||
TransportTracks transportTracks;
|
||||
if (UseDuplex()) {
|
||||
// Remove recording tracks from the list of tracks for duplex ("overdub")
|
||||
// playback.
|
||||
/* TODO: set up stereo tracks if that is how the user has set up
|
||||
* their preferences, and choose sample format based on prefs */
|
||||
transportTracks = GetAllPlaybackTracks(TrackList::Get( *p ), false, true);
|
||||
for (const auto &wt : existingTracks) {
|
||||
auto end = transportTracks.playbackTracks.end();
|
||||
auto it = std::find(transportTracks.playbackTracks.begin(), end, wt);
|
||||
if (it != end)
|
||||
transportTracks.playbackTracks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
transportTracks.captureTracks = existingTracks;
|
||||
auto options = DefaultPlayOptions( *p );
|
||||
DoRecord(*p, transportTracks, t0, t1, altAppearance, options);
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectAudioManager::UseDuplex()
|
||||
{
|
||||
bool duplex;
|
||||
gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex,
|
||||
#ifdef EXPERIMENTAL_DA
|
||||
false
|
||||
#else
|
||||
true
|
||||
#endif
|
||||
);
|
||||
return duplex;
|
||||
}
|
||||
|
||||
bool ProjectAudioManager::DoRecord(AudacityProject &project,
|
||||
const TransportTracks &tracks,
|
||||
double t0, double t1,
|
||||
bool altAppearance,
|
||||
const AudioIOStartStreamOptions &options)
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
|
||||
CommandFlag flags = AlwaysEnabledFlag; // 0 means recalc flags.
|
||||
|
||||
// NB: The call may have the side effect of changing flags.
|
||||
bool allowed = MenuManager::Get(project).TryToMakeActionAllowed(
|
||||
flags,
|
||||
AudioIONotBusyFlag | CanStopAudioStreamFlag);
|
||||
|
||||
if (!allowed)
|
||||
return false;
|
||||
// ...end of code from CommandHandler.
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
if (gAudioIO->IsBusy())
|
||||
return false;
|
||||
|
||||
projectAudioManager.SetAppending( !altAppearance );
|
||||
|
||||
bool success = false;
|
||||
|
||||
auto transportTracks = tracks;
|
||||
|
||||
// Will replace any given capture tracks with temporaries
|
||||
transportTracks.captureTracks.clear();
|
||||
|
||||
const auto p = &project;
|
||||
|
||||
bool appendRecord = !tracks.captureTracks.empty();
|
||||
|
||||
{
|
||||
if (appendRecord) {
|
||||
// Append recording:
|
||||
// Pad selected/all wave tracks to make them all the same length
|
||||
for (const auto &wt : tracks.captureTracks)
|
||||
{
|
||||
auto endTime = wt->GetEndTime();
|
||||
|
||||
// If the track was chosen for recording and playback both,
|
||||
// remember the original in preroll tracks, before making the
|
||||
// pending replacement.
|
||||
bool prerollTrack = make_iterator_range(transportTracks.playbackTracks).contains(wt);
|
||||
if (prerollTrack)
|
||||
transportTracks.prerollTracks.push_back(wt);
|
||||
|
||||
// A function that copies all the non-sample data between
|
||||
// wave tracks; in case the track recorded to changes scale
|
||||
// type (for instance), during the recording.
|
||||
auto updater = [](Track &d, const Track &s){
|
||||
auto &dst = static_cast<WaveTrack&>(d);
|
||||
auto &src = static_cast<const WaveTrack&>(s);
|
||||
dst.Reinit(src);
|
||||
};
|
||||
|
||||
// Get a copy of the track to be appended, to be pushed into
|
||||
// undo history only later.
|
||||
auto pending = std::static_pointer_cast<WaveTrack>(
|
||||
TrackList::Get( *p ).RegisterPendingChangedTrack(
|
||||
updater, wt.get() ) );
|
||||
|
||||
// End of current track is before or at recording start time.
|
||||
// Less than or equal, not just less than, to ensure a clip boundary.
|
||||
// when append recording.
|
||||
if (endTime <= t0) {
|
||||
|
||||
// Pad the recording track with silence, up to the
|
||||
// maximum time.
|
||||
auto newTrack = TrackFactory::Get( *p ).NewWaveTrack();
|
||||
newTrack->InsertSilence(0.0, t0 - endTime);
|
||||
newTrack->Flush();
|
||||
pending->Clear(endTime, t0);
|
||||
pending->Paste(endTime, newTrack.get());
|
||||
}
|
||||
transportTracks.captureTracks.push_back(pending);
|
||||
}
|
||||
TrackList::Get( *p ).UpdatePendingTracks();
|
||||
}
|
||||
|
||||
if( transportTracks.captureTracks.empty() )
|
||||
{ // recording to NEW track(s).
|
||||
bool recordingNameCustom, useTrackNumber, useDateStamp, useTimeStamp;
|
||||
wxString defaultTrackName, defaultRecordingTrackName;
|
||||
|
||||
// Count the tracks.
|
||||
auto &trackList = TrackList::Get( *p );
|
||||
auto numTracks = trackList.Leaders< const WaveTrack >().size();
|
||||
|
||||
auto recordingChannels = std::max(1L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
|
||||
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/RecordingNameCustom"), &recordingNameCustom, false);
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/TrackNumber"), &useTrackNumber, false);
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/DateStamp"), &useDateStamp, false);
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/TimeStamp"), &useTimeStamp, false);
|
||||
defaultTrackName = TracksPrefs::GetDefaultAudioTrackNamePreference();
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/RecodingTrackName"), &defaultRecordingTrackName, defaultTrackName);
|
||||
|
||||
wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
|
||||
|
||||
Track *first {};
|
||||
for (int c = 0; c < recordingChannels; c++) {
|
||||
auto newTrack = TrackFactory::Get( *p ).NewWaveTrack();
|
||||
if (!first)
|
||||
first = newTrack.get();
|
||||
|
||||
// Quantize bounds to the rate of the new track.
|
||||
if (c == 0) {
|
||||
if (t0 < DBL_MAX)
|
||||
t0 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t0));
|
||||
if (t1 < DBL_MAX)
|
||||
t1 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t1));
|
||||
}
|
||||
|
||||
newTrack->SetOffset(t0);
|
||||
wxString nameSuffix = wxString(wxT(""));
|
||||
|
||||
if (useTrackNumber) {
|
||||
nameSuffix += wxString::Format(wxT("%d"), 1 + numTracks + c);
|
||||
}
|
||||
|
||||
if (useDateStamp) {
|
||||
if (!nameSuffix.empty()) {
|
||||
nameSuffix += wxT("_");
|
||||
}
|
||||
nameSuffix += wxDateTime::Now().FormatISODate();
|
||||
}
|
||||
|
||||
if (useTimeStamp) {
|
||||
if (!nameSuffix.empty()) {
|
||||
nameSuffix += wxT("_");
|
||||
}
|
||||
nameSuffix += wxDateTime::Now().FormatISOTime();
|
||||
}
|
||||
|
||||
// ISO standard would be nice, but ":" is unsafe for file name.
|
||||
nameSuffix.Replace(wxT(":"), wxT("-"));
|
||||
|
||||
if (baseTrackName.empty()) {
|
||||
newTrack->SetName(nameSuffix);
|
||||
}
|
||||
else if (nameSuffix.empty()) {
|
||||
newTrack->SetName(baseTrackName);
|
||||
}
|
||||
else {
|
||||
newTrack->SetName(baseTrackName + wxT("_") + nameSuffix);
|
||||
}
|
||||
|
||||
if ((recordingChannels > 2) &&
|
||||
!(ProjectSettings::Get(*p).GetTracksFitVerticallyZoomed())) {
|
||||
TrackView::Get( *newTrack ).SetMinimized(true);
|
||||
}
|
||||
|
||||
TrackList::Get( *p ).RegisterPendingNewTrack( newTrack );
|
||||
transportTracks.captureTracks.push_back(newTrack);
|
||||
// Bug 1548. New track needs the focus.
|
||||
TrackPanel::Get( *p ).SetFocusedTrack( newTrack.get() );
|
||||
}
|
||||
TrackList::Get( *p ).GroupChannels(*first, recordingChannels);
|
||||
}
|
||||
|
||||
//Automated Input Level Adjustment Initialization
|
||||
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
||||
gAudioIO->AILAInitialize();
|
||||
#endif
|
||||
|
||||
int token = gAudioIO->StartStream(transportTracks, t0, t1, options);
|
||||
|
||||
success = (token != 0);
|
||||
|
||||
if (success) {
|
||||
ProjectAudioIO::Get( *p ).SetAudioIOToken(token);
|
||||
}
|
||||
else {
|
||||
CancelRecording();
|
||||
|
||||
// Show error message if stream could not be opened
|
||||
wxString msg = wxString::Format(_("Error opening recording device.\nError code: %s"), gAudioIO->LastPaErrorString());
|
||||
ShowErrorDialog(&GetProjectFrame( mProject ),
|
||||
_("Error"), msg, wxT("Error_opening_sound_device"));
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void ProjectAudioManager::OnPause()
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop ) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool paused = !projectAudioManager.Paused();
|
||||
projectAudioManager.SetPaused( paused );
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
|
||||
auto project = &mProject;
|
||||
auto &scrubber = Scrubber::Get( *project );
|
||||
|
||||
// Bug 1494 - Pausing a seek or scrub should just STOP as
|
||||
// it is confusing to be in a paused scrub state.
|
||||
bool bStopInstead = paused &&
|
||||
gAudioIO->IsScrubbing() &&
|
||||
!scrubber.IsSpeedPlaying();
|
||||
|
||||
if (bStopInstead) {
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (gAudioIO->IsScrubbing())
|
||||
scrubber.Pause(paused);
|
||||
else
|
||||
#endif
|
||||
{
|
||||
gAudioIO->SetPaused(paused);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectAudioManager::SetupCutPreviewTracks(double WXUNUSED(playStart), double cutStart,
|
||||
double cutEnd, double WXUNUSED(playEnd))
|
||||
|
||||
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
|
||||
{
|
||||
ClearCutPreviewTracks();
|
||||
AudacityProject *p = &mProject;
|
||||
{
|
||||
auto trackRange = TrackList::Get( *p ).Selected< const PlayableTrack >();
|
||||
if( !trackRange.empty() ) {
|
||||
auto cutPreviewTracks = TrackList::Create();
|
||||
for (const auto track1 : trackRange) {
|
||||
// Duplicate and change tracks
|
||||
// Clear has a very small chance of throwing
|
||||
|
||||
auto newTrack = track1->Duplicate();
|
||||
newTrack->Clear(cutStart, cutEnd);
|
||||
cutPreviewTracks->Add( newTrack );
|
||||
}
|
||||
// use NOTHROW-GUARANTEE:
|
||||
mCutPreviewTracks = cutPreviewTracks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectAudioManager::ClearCutPreviewTracks()
|
||||
{
|
||||
if (mCutPreviewTracks)
|
||||
mCutPreviewTracks->Clear();
|
||||
mCutPreviewTracks.reset();
|
||||
}
|
||||
|
||||
void ProjectAudioManager::CancelRecording()
|
||||
{
|
||||
const auto project = &mProject;
|
||||
TrackList::Get( *project ).ClearPendingTracks();
|
||||
}
|
||||
|
||||
void ProjectAudioManager::OnAudioIORate(int rate)
|
||||
{
|
||||
auto &project = mProject;
|
||||
@@ -267,10 +980,35 @@ DefaultSpeedPlayOptions( AudacityProject &project )
|
||||
return options;
|
||||
}
|
||||
|
||||
#include "Menus.h"
|
||||
#include "ViewInfo.h"
|
||||
#include "prefs/TracksPrefs.h"
|
||||
#include "tracks/ui/Scrubbing.h"
|
||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||
#include "NoteTrack.h"
|
||||
#endif
|
||||
|
||||
TransportTracks ProjectAudioManager::GetAllPlaybackTracks(
|
||||
TrackList &trackList, bool selectedOnly, bool useMidi)
|
||||
{
|
||||
TransportTracks result;
|
||||
{
|
||||
auto range = trackList.Any< WaveTrack >()
|
||||
+ (selectedOnly ? &Track::IsSelected : &Track::Any );
|
||||
for (auto pTrack: range)
|
||||
result.playbackTracks.push_back(
|
||||
pTrack->SharedPointer< WaveTrack >() );
|
||||
}
|
||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||
if (useMidi) {
|
||||
auto range = trackList.Any< const NoteTrack >() +
|
||||
(selectedOnly ? &Track::IsSelected : &Track::Any );
|
||||
for (auto pTrack: range)
|
||||
result.midiTracks.push_back(
|
||||
pTrack->SharedPointer< const NoteTrack >() );
|
||||
}
|
||||
#else
|
||||
WXUNUSED(useMidi);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
#include "widgets/AudacityMessageBox.h"
|
||||
|
||||
namespace TransportActions {
|
||||
|
@@ -59,9 +59,7 @@
|
||||
#include "../AColor.h"
|
||||
#include "../AllThemeResources.h"
|
||||
#include "../AudioIO.h"
|
||||
#include "../CommonCommandFlags.h"
|
||||
#include "../ImageManipulation.h"
|
||||
#include "../Menus.h"
|
||||
#include "../Prefs.h"
|
||||
#include "../Project.h"
|
||||
#include "../ProjectAudioIO.h"
|
||||
@@ -69,9 +67,8 @@
|
||||
#include "../ProjectSettings.h"
|
||||
#include "../ProjectStatus.h"
|
||||
#include "../ProjectWindow.h"
|
||||
#include "../ViewInfo.h"
|
||||
#include "../Track.h"
|
||||
#include "../widgets/AButton.h"
|
||||
#include "../widgets/MeterPanelBase.h"
|
||||
#include "../widgets/LinkingHtmlWindow.h"
|
||||
#include "../widgets/ErrorDialog.h"
|
||||
#include "../FileNames.h"
|
||||
@@ -79,9 +76,6 @@
|
||||
#include "../tracks/ui/Scrubbing.h"
|
||||
#include "../prefs/TracksPrefs.h"
|
||||
#include "../toolbars/ToolManager.h"
|
||||
#include "../TrackPanel.h"
|
||||
#include "../WaveTrack.h"
|
||||
#include "../tracks/ui/TrackView.h"
|
||||
|
||||
IMPLEMENT_CLASS(ControlToolBar, ToolBar);
|
||||
|
||||
@@ -525,208 +519,6 @@ void ControlToolBar::SetStop()
|
||||
EnableDisableButtons();
|
||||
}
|
||||
|
||||
int ProjectAudioManager::PlayPlayRegion(const SelectedRegion &selectedRegion,
|
||||
const AudioIOStartStreamOptions &options,
|
||||
PlayMode mode,
|
||||
bool backwards, /* = false */
|
||||
bool playWhiteSpace /* = false */)
|
||||
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop )
|
||||
return -1;
|
||||
|
||||
bool useMidi = true;
|
||||
|
||||
// Remove these lines to experiment with scrubbing/seeking of note tracks
|
||||
if (options.pScrubbingOptions)
|
||||
useMidi = false;
|
||||
|
||||
// Uncomment this for laughs!
|
||||
// backwards = true;
|
||||
|
||||
double t0 = selectedRegion.t0();
|
||||
double t1 = selectedRegion.t1();
|
||||
// SelectedRegion guarantees t0 <= t1, so we need another boolean argument
|
||||
// to indicate backwards play.
|
||||
const bool looped = options.playLooped;
|
||||
|
||||
if (backwards)
|
||||
std::swap(t0, t1);
|
||||
|
||||
projectAudioManager.SetLooping( mode == PlayMode::loopedPlay );
|
||||
projectAudioManager.SetCutting( mode == PlayMode::cutPreviewPlay );
|
||||
|
||||
bool success = false;
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
if (gAudioIO->IsBusy())
|
||||
return -1;
|
||||
|
||||
const bool cutpreview = mode == PlayMode::cutPreviewPlay;
|
||||
if (cutpreview && t0==t1)
|
||||
return -1; /* msmeyer: makes no sense */
|
||||
|
||||
AudacityProject *p = &mProject;
|
||||
|
||||
auto &tracks = TrackList::Get( *p );
|
||||
|
||||
mLastPlayMode = mode;
|
||||
|
||||
bool hasaudio;
|
||||
if (useMidi)
|
||||
hasaudio = ! tracks.Any<PlayableTrack>().empty();
|
||||
else
|
||||
hasaudio = ! tracks.Any<WaveTrack>().empty();
|
||||
|
||||
double latestEnd = (playWhiteSpace)? t1 : tracks.GetEndTime();
|
||||
|
||||
if (!hasaudio)
|
||||
return -1; // No need to continue without audio tracks
|
||||
|
||||
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
||||
double init_seek = 0.0;
|
||||
#endif
|
||||
|
||||
if (t1 == t0) {
|
||||
if (looped) {
|
||||
const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
|
||||
// play selection if there is one, otherwise
|
||||
// set start of play region to project start,
|
||||
// and loop the project from current play position.
|
||||
|
||||
if ((t0 > selectedRegion.t0()) && (t0 < selectedRegion.t1())) {
|
||||
t0 = selectedRegion.t0();
|
||||
t1 = selectedRegion.t1();
|
||||
}
|
||||
else {
|
||||
// loop the entire project
|
||||
t0 = tracks.GetStartTime();
|
||||
t1 = tracks.GetEndTime();
|
||||
}
|
||||
} else {
|
||||
// move t0 to valid range
|
||||
if (t0 < 0) {
|
||||
t0 = tracks.GetStartTime();
|
||||
}
|
||||
else if (t0 > tracks.GetEndTime()) {
|
||||
t0 = tracks.GetEndTime();
|
||||
}
|
||||
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
||||
else {
|
||||
init_seek = t0; //AC: init_seek is where playback will 'start'
|
||||
t0 = tracks.GetStartTime();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
t1 = tracks.GetEndTime();
|
||||
}
|
||||
else {
|
||||
// maybe t1 < t0, with backwards scrubbing for instance
|
||||
if (backwards)
|
||||
std::swap(t0, t1);
|
||||
|
||||
t0 = std::max(0.0, std::min(t0, latestEnd));
|
||||
t1 = std::max(0.0, std::min(t1, latestEnd));
|
||||
|
||||
if (backwards)
|
||||
std::swap(t0, t1);
|
||||
}
|
||||
|
||||
int token = -1;
|
||||
|
||||
if (t1 != t0) {
|
||||
if (cutpreview) {
|
||||
const double tless = std::min(t0, t1);
|
||||
const double tgreater = std::max(t0, t1);
|
||||
double beforeLen, afterLen;
|
||||
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
|
||||
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
|
||||
double tcp0 = tless-beforeLen;
|
||||
double diff = tgreater - tless;
|
||||
double tcp1 = (tgreater+afterLen) - diff;
|
||||
SetupCutPreviewTracks(tcp0, tless, tgreater, tcp1);
|
||||
if (backwards)
|
||||
std::swap(tcp0, tcp1);
|
||||
if (mCutPreviewTracks)
|
||||
{
|
||||
AudioIOStartStreamOptions myOptions = options;
|
||||
myOptions.cutPreviewGapStart = t0;
|
||||
myOptions.cutPreviewGapLen = t1 - t0;
|
||||
token = gAudioIO->StartStream(
|
||||
GetAllPlaybackTracks(*mCutPreviewTracks, false, useMidi),
|
||||
tcp0, tcp1, myOptions);
|
||||
}
|
||||
else
|
||||
// Cannot create cut preview tracks, clean up and exit
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
token = gAudioIO->StartStream(
|
||||
GetAllPlaybackTracks( tracks, false, useMidi ),
|
||||
t0, t1, options);
|
||||
}
|
||||
if (token != 0) {
|
||||
success = true;
|
||||
ProjectAudioIO::Get( *p ).SetAudioIOToken(token);
|
||||
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
||||
//AC: If init_seek was set, now's the time to make it happen.
|
||||
gAudioIO->SeekStream(init_seek);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
// Bug1627 (part of it):
|
||||
// infinite error spew when trying to start scrub:
|
||||
// Problem was that the error dialog yields to events,
|
||||
// causing recursion to this function in the scrub timer
|
||||
// handler! Easy fix, just delay the user alert instead.
|
||||
auto &window = GetProjectFrame( mProject );
|
||||
window.CallAfter( [&]{
|
||||
// Show error message if stream could not be opened
|
||||
ShowErrorDialog(&window, _("Error"),
|
||||
_("Error opening sound device.\nTry changing the audio host, playback device and the project sample rate."),
|
||||
wxT("Error_opening_sound_device"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
return -1;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
void ProjectAudioManager::PlayCurrentRegion(bool looped /* = false */,
|
||||
bool cutpreview /* = false */)
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop )
|
||||
return;
|
||||
|
||||
AudacityProject *p = &mProject;
|
||||
|
||||
{
|
||||
|
||||
const auto &playRegion = ViewInfo::Get( *p ).playRegion;
|
||||
|
||||
auto options = DefaultPlayOptions( *p );
|
||||
options.playLooped = looped;
|
||||
if (cutpreview)
|
||||
options.envelope = nullptr;
|
||||
auto mode =
|
||||
cutpreview ? PlayMode::cutPreviewPlay
|
||||
: options.playLooped ? PlayMode::loopedPlay
|
||||
: PlayMode::normalPlay;
|
||||
PlayPlayRegion(SelectedRegion(playRegion.GetStart(), playRegion.GetEnd()),
|
||||
options,
|
||||
mode);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlToolBar::OnKeyEvent(wxKeyEvent & event)
|
||||
{
|
||||
// PRL: is this handler really ever reached? Is the ControlToolBar ever
|
||||
@@ -794,156 +586,6 @@ void ControlToolBar::PlayDefault()
|
||||
ProjectAudioManager::Get( mProject ).PlayCurrentRegion(looped, cutPreview);
|
||||
}
|
||||
|
||||
void ProjectAudioManager::Stop(bool stopStream /* = true*/)
|
||||
{
|
||||
AudacityProject *project = &mProject;
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop )
|
||||
return;
|
||||
|
||||
if(project) {
|
||||
// Let scrubbing code do some appearance change
|
||||
auto &scrubber = Scrubber::Get( *project );
|
||||
scrubber.StopScrubbing();
|
||||
}
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
|
||||
auto cleanup = finally( [&]{
|
||||
projectAudioManager.SetStopping( false );
|
||||
} );
|
||||
|
||||
if (stopStream && gAudioIO->IsBusy()) {
|
||||
// flag that we are stopping
|
||||
projectAudioManager.SetStopping( true );
|
||||
// Allow UI to update for that
|
||||
while( wxTheApp->ProcessIdle() )
|
||||
;
|
||||
}
|
||||
|
||||
if(stopStream)
|
||||
gAudioIO->StopStream();
|
||||
|
||||
projectAudioManager.SetLooping( false );
|
||||
projectAudioManager.SetCutting( false );
|
||||
|
||||
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
||||
gAudioIO->AILADisable();
|
||||
#endif
|
||||
|
||||
projectAudioManager.SetPaused( false );
|
||||
//Make sure you tell gAudioIO to unpause
|
||||
gAudioIO->SetPaused( false );
|
||||
|
||||
ClearCutPreviewTracks();
|
||||
|
||||
// So that we continue monitoring after playing or recording.
|
||||
// also clean the MeterQueues
|
||||
if( project ) {
|
||||
auto &projectAudioIO = ProjectAudioIO::Get( *project );
|
||||
auto meter = projectAudioIO.GetPlaybackMeter();
|
||||
if( meter ) {
|
||||
meter->Clear();
|
||||
}
|
||||
|
||||
meter = projectAudioIO.GetCaptureMeter();
|
||||
if( meter ) {
|
||||
meter->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
const auto toolbar = ToolManager::Get( *project ).GetToolBar(ScrubbingBarID);
|
||||
toolbar->EnableDisableButtons();
|
||||
}
|
||||
|
||||
void ProjectAudioManager::Pause()
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop ) {
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
gAudioIO->SetPaused(!gAudioIO->IsPaused());
|
||||
}
|
||||
else {
|
||||
OnPause();
|
||||
}
|
||||
}
|
||||
|
||||
WaveTrackArray ProjectAudioManager::ChooseExistingRecordingTracks(
|
||||
AudacityProject &proj, bool selectedOnly)
|
||||
{
|
||||
auto p = &proj;
|
||||
size_t recordingChannels =
|
||||
std::max(0L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
|
||||
bool strictRules = (recordingChannels <= 2);
|
||||
|
||||
// Iterate over all wave tracks, or over selected wave tracks only.
|
||||
//
|
||||
// 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
|
||||
// required number exactly. Never drop inputs or fill only some channels
|
||||
// of a track.
|
||||
//
|
||||
// In case of more than two recording channels, choose tracks only among the
|
||||
// selected. Simply take the earliest wave tracks, until the number of
|
||||
// channels is enough. If there are fewer channels than inputs, but at least
|
||||
// one channel, then some of the input channels will be dropped.
|
||||
//
|
||||
// Resulting tracks may be non-consecutive within the list of all tracks
|
||||
// (there may be non-wave tracks between, or non-selected tracks when
|
||||
// considering selected tracks only.)
|
||||
|
||||
if (!strictRules && !selectedOnly)
|
||||
return {};
|
||||
|
||||
auto &trackList = TrackList::Get( *p );
|
||||
std::vector<unsigned> channelCounts;
|
||||
WaveTrackArray candidates;
|
||||
const auto range = trackList.Leaders<WaveTrack>();
|
||||
for ( auto candidate : selectedOnly ? range + &Track::IsSelected : range ) {
|
||||
// count channels in this track
|
||||
const auto channels = TrackList::Channels( candidate );
|
||||
unsigned nChannels = channels.size();
|
||||
|
||||
if (strictRules && nChannels > recordingChannels) {
|
||||
// The recording would under-fill this track's channels
|
||||
// Can't use any partial accumulated results
|
||||
// either. Keep looking.
|
||||
candidates.clear();
|
||||
channelCounts.clear();
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// Might use this but may have to discard some of the accumulated
|
||||
while(strictRules &&
|
||||
nChannels + candidates.size() > recordingChannels) {
|
||||
auto nOldChannels = channelCounts[0];
|
||||
wxASSERT(nOldChannels > 0);
|
||||
channelCounts.erase(channelCounts.begin());
|
||||
candidates.erase(candidates.begin(),
|
||||
candidates.begin() + nOldChannels);
|
||||
}
|
||||
channelCounts.push_back(nChannels);
|
||||
for ( auto channel : channels ) {
|
||||
candidates.push_back(channel->SharedPointer<WaveTrack>());
|
||||
if(candidates.size() == recordingChannels)
|
||||
// Done!
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!strictRules && !candidates.empty())
|
||||
// good enough
|
||||
return candidates;
|
||||
|
||||
// If the loop didn't exit early, we could not find enough channels
|
||||
return {};
|
||||
}
|
||||
|
||||
void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
// STRONG-GUARANTEE (for state of current project's tracks)
|
||||
{
|
||||
@@ -956,321 +598,11 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
||||
ProjectAudioManager::Get( mProject ).OnRecord( altAppearance );
|
||||
}
|
||||
|
||||
void ProjectAudioManager::OnRecord(bool altAppearance)
|
||||
// STRONG-GUARANTEE (for state of current project's tracks)
|
||||
{
|
||||
bool bPreferNewTrack;
|
||||
gPrefs->Read("/GUI/PreferNewTrackRecord", &bPreferNewTrack, false);
|
||||
const bool appendRecord = (altAppearance == bPreferNewTrack);
|
||||
|
||||
// Code from CommandHandler start...
|
||||
AudacityProject *p = &mProject;
|
||||
|
||||
if (p) {
|
||||
const auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
|
||||
double t0 = selectedRegion.t0();
|
||||
double t1 = selectedRegion.t1();
|
||||
// When no time selection, recording duration is 'unlimited'.
|
||||
if (t1 == t0)
|
||||
t1 = DBL_MAX;
|
||||
|
||||
WaveTrackArray existingTracks;
|
||||
|
||||
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 ) );
|
||||
else {
|
||||
existingTracks = ChooseExistingRecordingTracks(*p, false);
|
||||
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
|
||||
}
|
||||
else {
|
||||
t1 = DBL_MAX; // record for a long, long time
|
||||
}
|
||||
}
|
||||
|
||||
TransportTracks transportTracks;
|
||||
if (UseDuplex()) {
|
||||
// Remove recording tracks from the list of tracks for duplex ("overdub")
|
||||
// playback.
|
||||
/* TODO: set up stereo tracks if that is how the user has set up
|
||||
* their preferences, and choose sample format based on prefs */
|
||||
transportTracks = GetAllPlaybackTracks(TrackList::Get( *p ), false, true);
|
||||
for (const auto &wt : existingTracks) {
|
||||
auto end = transportTracks.playbackTracks.end();
|
||||
auto it = std::find(transportTracks.playbackTracks.begin(), end, wt);
|
||||
if (it != end)
|
||||
transportTracks.playbackTracks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
transportTracks.captureTracks = existingTracks;
|
||||
auto options = DefaultPlayOptions( *p );
|
||||
DoRecord(*p, transportTracks, t0, t1, altAppearance, options);
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectAudioManager::UseDuplex()
|
||||
{
|
||||
bool duplex;
|
||||
gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex,
|
||||
#ifdef EXPERIMENTAL_DA
|
||||
false
|
||||
#else
|
||||
true
|
||||
#endif
|
||||
);
|
||||
return duplex;
|
||||
}
|
||||
|
||||
bool ProjectAudioManager::DoRecord(AudacityProject &project,
|
||||
const TransportTracks &tracks,
|
||||
double t0, double t1,
|
||||
bool altAppearance,
|
||||
const AudioIOStartStreamOptions &options)
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
|
||||
CommandFlag flags = AlwaysEnabledFlag; // 0 means recalc flags.
|
||||
|
||||
// NB: The call may have the side effect of changing flags.
|
||||
bool allowed = MenuManager::Get(project).TryToMakeActionAllowed(
|
||||
flags,
|
||||
AudioIONotBusyFlag | CanStopAudioStreamFlag);
|
||||
|
||||
if (!allowed)
|
||||
return false;
|
||||
// ...end of code from CommandHandler.
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
if (gAudioIO->IsBusy())
|
||||
return false;
|
||||
|
||||
projectAudioManager.SetAppending( !altAppearance );
|
||||
|
||||
bool success = false;
|
||||
|
||||
auto transportTracks = tracks;
|
||||
|
||||
// Will replace any given capture tracks with temporaries
|
||||
transportTracks.captureTracks.clear();
|
||||
|
||||
const auto p = &project;
|
||||
|
||||
bool appendRecord = !tracks.captureTracks.empty();
|
||||
|
||||
{
|
||||
if (appendRecord) {
|
||||
// Append recording:
|
||||
// Pad selected/all wave tracks to make them all the same length
|
||||
for (const auto &wt : tracks.captureTracks)
|
||||
{
|
||||
auto endTime = wt->GetEndTime();
|
||||
|
||||
// If the track was chosen for recording and playback both,
|
||||
// remember the original in preroll tracks, before making the
|
||||
// pending replacement.
|
||||
bool prerollTrack = make_iterator_range(transportTracks.playbackTracks).contains(wt);
|
||||
if (prerollTrack)
|
||||
transportTracks.prerollTracks.push_back(wt);
|
||||
|
||||
// A function that copies all the non-sample data between
|
||||
// wave tracks; in case the track recorded to changes scale
|
||||
// type (for instance), during the recording.
|
||||
auto updater = [](Track &d, const Track &s){
|
||||
auto &dst = static_cast<WaveTrack&>(d);
|
||||
auto &src = static_cast<const WaveTrack&>(s);
|
||||
dst.Reinit(src);
|
||||
};
|
||||
|
||||
// Get a copy of the track to be appended, to be pushed into
|
||||
// undo history only later.
|
||||
auto pending = std::static_pointer_cast<WaveTrack>(
|
||||
TrackList::Get( *p ).RegisterPendingChangedTrack(
|
||||
updater, wt.get() ) );
|
||||
|
||||
// End of current track is before or at recording start time.
|
||||
// Less than or equal, not just less than, to ensure a clip boundary.
|
||||
// when append recording.
|
||||
if (endTime <= t0) {
|
||||
|
||||
// Pad the recording track with silence, up to the
|
||||
// maximum time.
|
||||
auto newTrack = TrackFactory::Get( *p ).NewWaveTrack();
|
||||
newTrack->InsertSilence(0.0, t0 - endTime);
|
||||
newTrack->Flush();
|
||||
pending->Clear(endTime, t0);
|
||||
pending->Paste(endTime, newTrack.get());
|
||||
}
|
||||
transportTracks.captureTracks.push_back(pending);
|
||||
}
|
||||
TrackList::Get( *p ).UpdatePendingTracks();
|
||||
}
|
||||
|
||||
if( transportTracks.captureTracks.empty() )
|
||||
{ // recording to NEW track(s).
|
||||
bool recordingNameCustom, useTrackNumber, useDateStamp, useTimeStamp;
|
||||
wxString defaultTrackName, defaultRecordingTrackName;
|
||||
|
||||
// Count the tracks.
|
||||
auto &trackList = TrackList::Get( *p );
|
||||
auto numTracks = trackList.Leaders< const WaveTrack >().size();
|
||||
|
||||
auto recordingChannels = std::max(1L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
|
||||
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/RecordingNameCustom"), &recordingNameCustom, false);
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/TrackNumber"), &useTrackNumber, false);
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/DateStamp"), &useDateStamp, false);
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/TimeStamp"), &useTimeStamp, false);
|
||||
defaultTrackName = TracksPrefs::GetDefaultAudioTrackNamePreference();
|
||||
gPrefs->Read(wxT("/GUI/TrackNames/RecodingTrackName"), &defaultRecordingTrackName, defaultTrackName);
|
||||
|
||||
wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
|
||||
|
||||
Track *first {};
|
||||
for (int c = 0; c < recordingChannels; c++) {
|
||||
auto newTrack = TrackFactory::Get( *p ).NewWaveTrack();
|
||||
if (!first)
|
||||
first = newTrack.get();
|
||||
|
||||
// Quantize bounds to the rate of the new track.
|
||||
if (c == 0) {
|
||||
if (t0 < DBL_MAX)
|
||||
t0 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t0));
|
||||
if (t1 < DBL_MAX)
|
||||
t1 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t1));
|
||||
}
|
||||
|
||||
newTrack->SetOffset(t0);
|
||||
wxString nameSuffix = wxString(wxT(""));
|
||||
|
||||
if (useTrackNumber) {
|
||||
nameSuffix += wxString::Format(wxT("%d"), 1 + numTracks + c);
|
||||
}
|
||||
|
||||
if (useDateStamp) {
|
||||
if (!nameSuffix.empty()) {
|
||||
nameSuffix += wxT("_");
|
||||
}
|
||||
nameSuffix += wxDateTime::Now().FormatISODate();
|
||||
}
|
||||
|
||||
if (useTimeStamp) {
|
||||
if (!nameSuffix.empty()) {
|
||||
nameSuffix += wxT("_");
|
||||
}
|
||||
nameSuffix += wxDateTime::Now().FormatISOTime();
|
||||
}
|
||||
|
||||
// ISO standard would be nice, but ":" is unsafe for file name.
|
||||
nameSuffix.Replace(wxT(":"), wxT("-"));
|
||||
|
||||
if (baseTrackName.empty()) {
|
||||
newTrack->SetName(nameSuffix);
|
||||
}
|
||||
else if (nameSuffix.empty()) {
|
||||
newTrack->SetName(baseTrackName);
|
||||
}
|
||||
else {
|
||||
newTrack->SetName(baseTrackName + wxT("_") + nameSuffix);
|
||||
}
|
||||
|
||||
if ((recordingChannels > 2) &&
|
||||
!(ProjectSettings::Get(*p).GetTracksFitVerticallyZoomed())) {
|
||||
TrackView::Get( *newTrack ).SetMinimized(true);
|
||||
}
|
||||
|
||||
TrackList::Get( *p ).RegisterPendingNewTrack( newTrack );
|
||||
transportTracks.captureTracks.push_back(newTrack);
|
||||
// Bug 1548. New track needs the focus.
|
||||
TrackPanel::Get( *p ).SetFocusedTrack( newTrack.get() );
|
||||
}
|
||||
TrackList::Get( *p ).GroupChannels(*first, recordingChannels);
|
||||
}
|
||||
|
||||
//Automated Input Level Adjustment Initialization
|
||||
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
||||
gAudioIO->AILAInitialize();
|
||||
#endif
|
||||
|
||||
int token = gAudioIO->StartStream(transportTracks, t0, t1, options);
|
||||
|
||||
success = (token != 0);
|
||||
|
||||
if (success) {
|
||||
ProjectAudioIO::Get( *p ).SetAudioIOToken(token);
|
||||
}
|
||||
else {
|
||||
CancelRecording();
|
||||
|
||||
// Show error message if stream could not be opened
|
||||
wxString msg = wxString::Format(_("Error opening recording device.\nError code: %s"), gAudioIO->LastPaErrorString());
|
||||
ShowErrorDialog(&GetProjectFrame( mProject ),
|
||||
_("Error"), msg, wxT("Error_opening_sound_device"));
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
void ControlToolBar::OnPause(wxCommandEvent & WXUNUSED(evt))
|
||||
{
|
||||
ProjectAudioManager::Get( mProject ).OnPause();
|
||||
}
|
||||
|
||||
|
||||
void ProjectAudioManager::OnPause()
|
||||
{
|
||||
auto &projectAudioManager = *this;
|
||||
bool canStop = projectAudioManager.CanStopAudioStream();
|
||||
|
||||
if ( !canStop ) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool paused = !projectAudioManager.Paused();
|
||||
projectAudioManager.SetPaused( paused );
|
||||
|
||||
auto gAudioIO = AudioIO::Get();
|
||||
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
|
||||
auto project = &mProject;
|
||||
auto &scrubber = Scrubber::Get( *project );
|
||||
|
||||
// Bug 1494 - Pausing a seek or scrub should just STOP as
|
||||
// it is confusing to be in a paused scrub state.
|
||||
bool bStopInstead = paused &&
|
||||
gAudioIO->IsScrubbing() &&
|
||||
!scrubber.IsSpeedPlaying();
|
||||
|
||||
if (bStopInstead) {
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (gAudioIO->IsScrubbing())
|
||||
scrubber.Pause(paused);
|
||||
else
|
||||
#endif
|
||||
{
|
||||
gAudioIO->SetPaused(paused);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlToolBar::OnIdle(wxIdleEvent & event)
|
||||
{
|
||||
event.Skip();
|
||||
@@ -1353,38 +685,6 @@ void ControlToolBar::OnFF(wxCommandEvent & WXUNUSED(evt))
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectAudioManager::SetupCutPreviewTracks(double WXUNUSED(playStart), double cutStart,
|
||||
double cutEnd, double WXUNUSED(playEnd))
|
||||
|
||||
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
|
||||
{
|
||||
ClearCutPreviewTracks();
|
||||
AudacityProject *p = &mProject;
|
||||
{
|
||||
auto trackRange = TrackList::Get( *p ).Selected< const PlayableTrack >();
|
||||
if( !trackRange.empty() ) {
|
||||
auto cutPreviewTracks = TrackList::Create();
|
||||
for (const auto track1 : trackRange) {
|
||||
// Duplicate and change tracks
|
||||
// Clear has a very small chance of throwing
|
||||
|
||||
auto newTrack = track1->Duplicate();
|
||||
newTrack->Clear(cutStart, cutEnd);
|
||||
cutPreviewTracks->Add( newTrack );
|
||||
}
|
||||
// use NOTHROW-GUARANTEE:
|
||||
mCutPreviewTracks = cutPreviewTracks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectAudioManager::ClearCutPreviewTracks()
|
||||
{
|
||||
if (mCutPreviewTracks)
|
||||
mCutPreviewTracks->Clear();
|
||||
mCutPreviewTracks.reset();
|
||||
}
|
||||
|
||||
// works out the width of the field in the status bar needed for the state (eg play, record pause)
|
||||
static ProjectStatus::RegisteredStatusWidthFunction
|
||||
registeredStatusWidthFunction{
|
||||
@@ -1521,41 +821,6 @@ void ControlToolBar::StopScrolling()
|
||||
(ProjectWindow::PlaybackScroller::Mode::Off);
|
||||
}
|
||||
|
||||
void ProjectAudioManager::CancelRecording()
|
||||
{
|
||||
const auto project = &mProject;
|
||||
TrackList::Get( *project ).ClearPendingTracks();
|
||||
}
|
||||
|
||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||
#include "../NoteTrack.h"
|
||||
#endif
|
||||
|
||||
TransportTracks ProjectAudioManager::GetAllPlaybackTracks(
|
||||
TrackList &trackList, bool selectedOnly, bool useMidi)
|
||||
{
|
||||
TransportTracks result;
|
||||
{
|
||||
auto range = trackList.Any< WaveTrack >()
|
||||
+ (selectedOnly ? &Track::IsSelected : &Track::Any );
|
||||
for (auto pTrack: range)
|
||||
result.playbackTracks.push_back(
|
||||
pTrack->SharedPointer< WaveTrack >() );
|
||||
}
|
||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||
if (useMidi) {
|
||||
auto range = trackList.Any< const NoteTrack >() +
|
||||
(selectedOnly ? &Track::IsSelected : &Track::Any );
|
||||
for (auto pTrack: range)
|
||||
result.midiTracks.push_back(
|
||||
pTrack->SharedPointer< const NoteTrack >() );
|
||||
}
|
||||
#else
|
||||
WXUNUSED(useMidi);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
static RegisteredToolbarFactory factory{ TransportBarID,
|
||||
[]( AudacityProject &project ){
|
||||
return ToolBar::Holder{ safenew ControlToolBar{ project } }; }
|
||||
|
Reference in New Issue
Block a user