2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
ControlToolBar.cpp
|
|
|
|
|
|
|
|
Dominic Mazzoni
|
|
|
|
Shane T. Mueller
|
|
|
|
James Crook
|
|
|
|
Leland Lucius
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
*******************************************************************//**
|
|
|
|
|
|
|
|
\class ControlToolBar
|
2010-11-16 01:18:39 +00:00
|
|
|
\brief A ToolBar that has the main Transport buttons.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
In the GUI, this is referred to as "Transport Toolbar", as
|
|
|
|
it corresponds to commands in the Transport menu.
|
2010-11-16 01:18:39 +00:00
|
|
|
"Control Toolbar" is historic.
|
2010-01-23 19:44:49 +00:00
|
|
|
This class, which is a child of Toolbar, creates the
|
2010-11-16 01:18:39 +00:00
|
|
|
window containing the Transport (rewind/play/stop/record/ff)
|
|
|
|
buttons. The window can be embedded within a
|
2010-01-23 19:44:49 +00:00
|
|
|
normal project window, or within a ToolbarFrame that is
|
|
|
|
managed by a global ToolBarStub called
|
|
|
|
gControlToolBarStub.
|
|
|
|
|
|
|
|
All of the controls in this window were custom-written for
|
|
|
|
Audacity - they are not native controls on any platform -
|
|
|
|
however, it is intended that the images could be easily
|
|
|
|
replaced to allow "skinning" or just customization to
|
2014-06-03 20:30:19 +00:00
|
|
|
match the look and feel of each platform.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
#include <algorithm>
|
2016-09-19 14:38:42 +00:00
|
|
|
#include <cfloat>
|
2014-11-29 22:09:57 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "../Audacity.h"
|
|
|
|
#include "../Experimental.h"
|
2015-07-03 04:20:21 +00:00
|
|
|
#include "ControlToolBar.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// For compilers that support precompilation, includes "wx/wx.h".
|
|
|
|
#include <wx/wxprec.h>
|
|
|
|
|
|
|
|
#ifndef WX_PRECOMP
|
|
|
|
#include <wx/app.h>
|
|
|
|
#include <wx/dc.h>
|
|
|
|
#include <wx/event.h>
|
|
|
|
#include <wx/image.h>
|
|
|
|
#include <wx/intl.h>
|
2015-05-29 18:57:49 +00:00
|
|
|
#include <wx/statusbr.h>
|
2013-07-30 01:11:36 +00:00
|
|
|
#include <wx/timer.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|
|
|
|
#include <wx/tooltip.h>
|
2016-01-16 14:44:45 +00:00
|
|
|
#include <wx/datetime.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 17:22:05 +00:00
|
|
|
#include "TranscriptionToolBar.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "MeterToolBar.h"
|
|
|
|
|
|
|
|
#include "../AColor.h"
|
2018-10-23 20:36:02 +00:00
|
|
|
#include "../AdornedRulerPanel.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "../AllThemeResources.h"
|
|
|
|
#include "../AudioIO.h"
|
|
|
|
#include "../ImageManipulation.h"
|
2018-10-16 20:45:26 +00:00
|
|
|
#include "../Menus.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "../Prefs.h"
|
|
|
|
#include "../Project.h"
|
|
|
|
#include "../Theme.h"
|
2015-07-03 04:20:21 +00:00
|
|
|
#include "../WaveTrack.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "../widgets/AButton.h"
|
2014-12-17 19:16:08 +00:00
|
|
|
#include "../widgets/Meter.h"
|
2017-05-20 16:11:02 +00:00
|
|
|
#include "../widgets/LinkingHtmlWindow.h"
|
|
|
|
#include "../widgets/ErrorDialog.h"
|
|
|
|
#include "../FileNames.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-19 01:49:27 +00:00
|
|
|
#include "../tracks/ui/Scrubbing.h"
|
2016-08-10 17:38:37 +00:00
|
|
|
#include "../prefs/TracksPrefs.h"
|
2016-06-16 00:00:47 +00:00
|
|
|
#include "../toolbars/ToolManager.h"
|
2018-03-24 16:05:10 +00:00
|
|
|
#include "../TrackPanel.h"
|
2016-04-19 01:49:27 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
IMPLEMENT_CLASS(ControlToolBar, ToolBar);
|
|
|
|
|
|
|
|
//static
|
|
|
|
AudacityProject *ControlToolBar::mBusyProject = NULL;
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
/// Methods for ControlToolBar
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
BEGIN_EVENT_TABLE(ControlToolBar, ToolBar)
|
|
|
|
EVT_CHAR(ControlToolBar::OnKeyEvent)
|
|
|
|
EVT_BUTTON(ID_PLAY_BUTTON, ControlToolBar::OnPlay)
|
|
|
|
EVT_BUTTON(ID_STOP_BUTTON, ControlToolBar::OnStop)
|
|
|
|
EVT_BUTTON(ID_RECORD_BUTTON, ControlToolBar::OnRecord)
|
|
|
|
EVT_BUTTON(ID_REW_BUTTON, ControlToolBar::OnRewind)
|
|
|
|
EVT_BUTTON(ID_FF_BUTTON, ControlToolBar::OnFF)
|
|
|
|
EVT_BUTTON(ID_PAUSE_BUTTON, ControlToolBar::OnPause)
|
|
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
|
|
//Standard constructor
|
2011-04-04 14:14:54 +00:00
|
|
|
// This was called "Control" toolbar in the GUI before - now it is "Transport".
|
|
|
|
// Note that we use the legacy "Control" string as the section because this
|
|
|
|
// gets written to prefs and cannot be changed in prefs to maintain backwards
|
|
|
|
// compatibility
|
2010-01-23 19:44:49 +00:00
|
|
|
ControlToolBar::ControlToolBar()
|
2011-04-04 14:14:54 +00:00
|
|
|
: ToolBar(TransportBarID, _("Transport"), wxT("Control"))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-07-30 01:11:36 +00:00
|
|
|
mPaused = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
gPrefs->Read(wxT("/GUI/ErgonomicTransportButtons"), &mErgonomicTransportButtons, true);
|
2013-07-30 00:36:57 +00:00
|
|
|
mStrLocale = gPrefs->Read(wxT("/Locale/Language"), wxT(""));
|
2013-07-30 01:11:36 +00:00
|
|
|
|
|
|
|
mSizer = NULL;
|
2015-05-29 12:45:15 +00:00
|
|
|
|
2015-06-13 15:48:57 +00:00
|
|
|
/* i18n-hint: These are strings for the status bar, and indicate whether Audacity
|
|
|
|
is playing or recording or stopped, and whether it is paused. */
|
2015-06-02 08:29:31 +00:00
|
|
|
mStatePlay = XO("Playing");
|
|
|
|
mStateStop = XO("Stopped");
|
|
|
|
mStateRecord = XO("Recording");
|
|
|
|
mStatePause = XO("Paused");
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ControlToolBar::~ControlToolBar()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ControlToolBar::Create(wxWindow * parent)
|
|
|
|
{
|
|
|
|
ToolBar::Create(parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a convenience function that allows for button creation in
|
|
|
|
// MakeButtons() with fewer arguments
|
2017-10-24 13:18:27 +00:00
|
|
|
AButton *ControlToolBar::MakeButton(ControlToolBar *pBar,
|
|
|
|
teBmps eEnabledUp, teBmps eEnabledDown, teBmps eDisabled,
|
2010-01-23 19:44:49 +00:00
|
|
|
int id,
|
|
|
|
bool processdownevents,
|
|
|
|
const wxChar *label)
|
|
|
|
{
|
2017-10-24 13:18:27 +00:00
|
|
|
AButton *r = ToolBar::MakeButton(pBar,
|
2017-12-14 14:39:56 +00:00
|
|
|
bmpRecoloredUpLarge, bmpRecoloredDownLarge, bmpRecoloredUpHiliteLarge, bmpRecoloredHiliteLarge,
|
2010-10-27 04:36:26 +00:00
|
|
|
eEnabledUp, eEnabledDown, eDisabled,
|
2010-01-23 19:44:49 +00:00
|
|
|
wxWindowID(id),
|
|
|
|
wxDefaultPosition, processdownevents,
|
|
|
|
theTheme.ImageSize( bmpRecoloredUpLarge ));
|
|
|
|
r->SetLabel(label);
|
2016-06-25 03:48:03 +00:00
|
|
|
enum { deflation =
|
|
|
|
#ifdef __WXMAC__
|
2017-06-16 22:07:43 +00:00
|
|
|
6
|
2016-06-25 03:48:03 +00:00
|
|
|
#else
|
|
|
|
12
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
r->SetFocusRect( r->GetClientRect().Deflate( deflation, deflation ) );
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2014-11-29 17:22:05 +00:00
|
|
|
// static
|
|
|
|
void ControlToolBar::MakeAlternateImages(AButton &button, int idx,
|
|
|
|
teBmps eEnabledUp,
|
|
|
|
teBmps eEnabledDown,
|
|
|
|
teBmps eDisabled)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-11-29 17:22:05 +00:00
|
|
|
ToolBar::MakeAlternateImages(button, idx,
|
2017-12-14 14:39:56 +00:00
|
|
|
bmpRecoloredUpLarge, bmpRecoloredDownLarge, bmpRecoloredUpHiliteLarge, bmpRecoloredHiliteLarge,
|
2014-11-29 17:22:05 +00:00
|
|
|
eEnabledUp, eEnabledDown, eDisabled,
|
|
|
|
theTheme.ImageSize( bmpRecoloredUpLarge ));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::Populate()
|
|
|
|
{
|
2017-04-04 13:43:47 +00:00
|
|
|
SetBackgroundColour( theTheme.Colour( clrMedium ) );
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeButtonBackgroundsLarge();
|
|
|
|
|
2017-10-24 13:18:27 +00:00
|
|
|
mPause = MakeButton(this, bmpPause, bmpPause, bmpPauseDisabled,
|
2010-01-23 19:44:49 +00:00
|
|
|
ID_PAUSE_BUTTON, true, _("Pause"));
|
|
|
|
|
2017-10-24 13:18:27 +00:00
|
|
|
mPlay = MakeButton(this, bmpPlay, bmpPlay, bmpPlayDisabled,
|
2010-01-23 19:44:49 +00:00
|
|
|
ID_PLAY_BUTTON, true, _("Play"));
|
2014-11-29 17:22:05 +00:00
|
|
|
MakeAlternateImages(*mPlay, 1, bmpLoop, bmpLoop, bmpLoopDisabled);
|
|
|
|
MakeAlternateImages(*mPlay, 2,
|
|
|
|
bmpCutPreview, bmpCutPreview, bmpCutPreviewDisabled);
|
2016-04-19 21:34:38 +00:00
|
|
|
MakeAlternateImages(*mPlay, 3,
|
|
|
|
bmpScrub, bmpScrub, bmpScrubDisabled);
|
2016-06-02 19:26:20 +00:00
|
|
|
MakeAlternateImages(*mPlay, 4,
|
|
|
|
bmpSeek, bmpSeek, bmpSeekDisabled);
|
2014-11-29 17:22:05 +00:00
|
|
|
mPlay->FollowModifierKeys();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-10-24 13:18:27 +00:00
|
|
|
mStop = MakeButton(this, bmpStop, bmpStop, bmpStopDisabled ,
|
2010-01-23 19:44:49 +00:00
|
|
|
ID_STOP_BUTTON, false, _("Stop"));
|
|
|
|
|
2017-10-24 13:18:27 +00:00
|
|
|
mRewind = MakeButton(this, bmpRewind, bmpRewind, bmpRewindDisabled,
|
2013-07-23 22:07:56 +00:00
|
|
|
ID_REW_BUTTON, false, _("Skip to Start"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-10-24 13:18:27 +00:00
|
|
|
mFF = MakeButton(this, bmpFFwd, bmpFFwd, bmpFFwdDisabled,
|
2013-07-23 22:07:56 +00:00
|
|
|
ID_FF_BUTTON, false, _("Skip to End"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-10-24 13:18:27 +00:00
|
|
|
mRecord = MakeButton(this, bmpRecord, bmpRecord, bmpRecordDisabled,
|
2018-01-06 16:15:04 +00:00
|
|
|
ID_RECORD_BUTTON, false, _("Record"));
|
2017-05-01 10:46:12 +00:00
|
|
|
|
2017-07-09 13:07:56 +00:00
|
|
|
bool bPreferNewTrack;
|
|
|
|
gPrefs->Read("/GUI/PreferNewTrackRecord",&bPreferNewTrack, false);
|
|
|
|
if( !bPreferNewTrack )
|
2017-05-01 10:46:12 +00:00
|
|
|
MakeAlternateImages(*mRecord, 1, bmpRecordBelow, bmpRecordBelow,
|
|
|
|
bmpRecordBelowDisabled);
|
|
|
|
else
|
|
|
|
MakeAlternateImages(*mRecord, 1, bmpRecordBeside, bmpRecordBeside,
|
|
|
|
bmpRecordBesideDisabled);
|
|
|
|
|
2014-11-29 17:22:05 +00:00
|
|
|
mRecord->FollowModifierKeys();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#if wxUSE_TOOLTIPS
|
2016-06-12 03:28:01 +00:00
|
|
|
RegenerateTooltips();
|
2014-06-03 20:30:19 +00:00
|
|
|
wxToolTip::Enable(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
wxToolTip::SetDelay(1000);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Set default order and mode
|
|
|
|
ArrangeButtons();
|
|
|
|
}
|
|
|
|
|
2016-06-12 03:28:01 +00:00
|
|
|
void ControlToolBar::RegenerateTooltips()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
#if wxUSE_TOOLTIPS
|
2013-08-05 02:12:46 +00:00
|
|
|
for (long iWinID = ID_PLAY_BUTTON; iWinID < BUTTON_COUNT; iWinID++)
|
2013-07-23 22:07:56 +00:00
|
|
|
{
|
2016-06-05 17:11:01 +00:00
|
|
|
auto pCtrl = static_cast<AButton*>(this->FindWindow(iWinID));
|
2018-01-08 18:57:51 +00:00
|
|
|
const wxChar *name = nullptr;
|
2016-06-05 17:11:01 +00:00
|
|
|
switch (iWinID)
|
2013-07-23 22:07:56 +00:00
|
|
|
{
|
2016-06-05 17:11:01 +00:00
|
|
|
case ID_PLAY_BUTTON:
|
2017-04-02 22:07:13 +00:00
|
|
|
// Without shift
|
2018-01-08 18:57:51 +00:00
|
|
|
name = wxT("PlayStop");
|
2016-06-05 17:11:01 +00:00
|
|
|
break;
|
|
|
|
case ID_RECORD_BUTTON:
|
2017-04-02 22:07:13 +00:00
|
|
|
// Without shift
|
2018-01-08 18:57:51 +00:00
|
|
|
//name = wxT("Record");
|
|
|
|
name = wxT("Record1stChoice");
|
|
|
|
break;
|
|
|
|
case ID_PAUSE_BUTTON:
|
|
|
|
name = wxT("Pause");
|
|
|
|
break;
|
|
|
|
case ID_STOP_BUTTON:
|
|
|
|
name = wxT("Stop");
|
|
|
|
break;
|
|
|
|
case ID_FF_BUTTON:
|
|
|
|
name = wxT("CursProjectEnd");
|
|
|
|
break;
|
|
|
|
case ID_REW_BUTTON:
|
|
|
|
name = wxT("CursProjectStart");
|
|
|
|
break;
|
|
|
|
}
|
2018-03-15 23:26:20 +00:00
|
|
|
std::vector<TranslatedInternalString> commands(
|
|
|
|
1u, { name, pCtrl->GetLabel() } );
|
2018-01-08 18:57:51 +00:00
|
|
|
|
|
|
|
// Some have a second
|
|
|
|
switch (iWinID)
|
|
|
|
{
|
|
|
|
case ID_PLAY_BUTTON:
|
|
|
|
// With shift
|
2018-03-15 23:26:20 +00:00
|
|
|
commands.push_back( { wxT("PlayLooped"), _("Loop Play") } );
|
2018-01-08 18:57:51 +00:00
|
|
|
break;
|
|
|
|
case ID_RECORD_BUTTON:
|
|
|
|
// With shift
|
2017-07-09 13:07:56 +00:00
|
|
|
{ bool bPreferNewTrack;
|
|
|
|
gPrefs->Read("/GUI/PreferNewTrackRecord",&bPreferNewTrack, false);
|
2017-05-01 19:00:04 +00:00
|
|
|
// For the shortcut tooltip.
|
2018-03-15 23:26:20 +00:00
|
|
|
commands.push_back( {
|
|
|
|
wxT("Record2ndChoice"),
|
2018-01-08 18:57:51 +00:00
|
|
|
!bPreferNewTrack
|
|
|
|
? _("Record New Track")
|
2018-03-15 23:26:20 +00:00
|
|
|
: _("Append Record")
|
|
|
|
} );
|
2017-05-01 10:46:12 +00:00
|
|
|
}
|
2016-06-05 17:11:01 +00:00
|
|
|
break;
|
|
|
|
case ID_PAUSE_BUTTON:
|
|
|
|
break;
|
|
|
|
case ID_STOP_BUTTON:
|
|
|
|
break;
|
|
|
|
case ID_FF_BUTTON:
|
2017-05-14 17:44:53 +00:00
|
|
|
// With shift
|
2018-03-15 23:26:20 +00:00
|
|
|
commands.push_back( {
|
|
|
|
wxT("SelEnd"), _("Select to End") } );
|
2016-06-05 17:11:01 +00:00
|
|
|
break;
|
|
|
|
case ID_REW_BUTTON:
|
2017-05-14 17:44:53 +00:00
|
|
|
// With shift
|
2018-03-15 23:26:20 +00:00
|
|
|
commands.push_back( {
|
|
|
|
wxT("SelStart"), _("Select to Start") } );
|
2016-06-05 17:11:01 +00:00
|
|
|
break;
|
2013-07-23 22:07:56 +00:00
|
|
|
}
|
2018-03-15 23:26:20 +00:00
|
|
|
ToolBar::SetButtonToolTip(*pCtrl, commands.data(), commands.size());
|
2013-07-23 22:07:56 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::UpdatePrefs()
|
|
|
|
{
|
|
|
|
bool updated = false;
|
|
|
|
bool active;
|
|
|
|
|
|
|
|
gPrefs->Read( wxT("/GUI/ErgonomicTransportButtons"), &active, true );
|
|
|
|
if( mErgonomicTransportButtons != active )
|
|
|
|
{
|
|
|
|
mErgonomicTransportButtons = active;
|
|
|
|
updated = true;
|
|
|
|
}
|
2013-07-30 00:36:57 +00:00
|
|
|
wxString strLocale = gPrefs->Read(wxT("/Locale/Language"), wxT(""));
|
|
|
|
if (mStrLocale != strLocale)
|
|
|
|
{
|
|
|
|
mStrLocale = strLocale;
|
|
|
|
updated = true;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if( updated )
|
|
|
|
{
|
2016-06-12 03:28:01 +00:00
|
|
|
ReCreateButtons(); // side effect: calls RegenerateTooltips()
|
2010-01-23 19:44:49 +00:00
|
|
|
Updated();
|
|
|
|
}
|
2013-07-30 00:36:57 +00:00
|
|
|
else
|
2014-06-03 20:30:19 +00:00
|
|
|
// The other reason to regenerate tooltips is if keyboard shortcuts for
|
|
|
|
// transport buttons changed, but that's too much work to check for, so just
|
|
|
|
// always do it. (Much cheaper than calling ReCreateButtons() in all cases.
|
2016-06-12 03:28:01 +00:00
|
|
|
RegenerateTooltips();
|
2013-07-30 00:36:57 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Set label to pull in language change
|
2010-11-16 01:18:39 +00:00
|
|
|
SetLabel(_("Transport"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Give base class a chance
|
|
|
|
ToolBar::UpdatePrefs();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::ArrangeButtons()
|
|
|
|
{
|
|
|
|
int flags = wxALIGN_CENTER | wxRIGHT;
|
|
|
|
|
|
|
|
// (Re)allocate the button sizer
|
|
|
|
if( mSizer )
|
|
|
|
{
|
|
|
|
Detach( mSizer );
|
2016-04-06 22:32:38 +00:00
|
|
|
std::unique_ptr < wxSizer > {mSizer}; // DELETE it
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2016-02-18 19:53:43 +00:00
|
|
|
|
|
|
|
Add((mSizer = safenew wxBoxSizer(wxHORIZONTAL)), 1, wxEXPAND);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Start with a little extra space
|
|
|
|
mSizer->Add( 5, 55 );
|
|
|
|
|
|
|
|
// Add the buttons in order based on ergonomic setting
|
|
|
|
if( mErgonomicTransportButtons )
|
|
|
|
{
|
|
|
|
mPause->MoveBeforeInTabOrder( mRecord );
|
|
|
|
mPlay->MoveBeforeInTabOrder( mRecord );
|
|
|
|
mStop->MoveBeforeInTabOrder( mRecord );
|
|
|
|
mRewind->MoveBeforeInTabOrder( mRecord );
|
|
|
|
mFF->MoveBeforeInTabOrder( mRecord );
|
|
|
|
|
|
|
|
mSizer->Add( mPause, 0, flags, 2 );
|
|
|
|
mSizer->Add( mPlay, 0, flags, 2 );
|
|
|
|
mSizer->Add( mStop, 0, flags, 2 );
|
|
|
|
mSizer->Add( mRewind, 0, flags, 2 );
|
|
|
|
mSizer->Add( mFF, 0, flags, 10 );
|
|
|
|
mSizer->Add( mRecord, 0, flags, 5 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mRewind->MoveBeforeInTabOrder( mFF );
|
|
|
|
mPlay->MoveBeforeInTabOrder( mFF );
|
|
|
|
mRecord->MoveBeforeInTabOrder( mFF );
|
|
|
|
mPause->MoveBeforeInTabOrder( mFF );
|
|
|
|
mStop->MoveBeforeInTabOrder( mFF );
|
|
|
|
|
|
|
|
mSizer->Add( mRewind, 0, flags, 2 );
|
|
|
|
mSizer->Add( mPlay, 0, flags, 2 );
|
|
|
|
mSizer->Add( mRecord, 0, flags, 2 );
|
|
|
|
mSizer->Add( mPause, 0, flags, 2 );
|
|
|
|
mSizer->Add( mStop, 0, flags, 2 );
|
|
|
|
mSizer->Add( mFF, 0, flags, 5 );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Layout the sizer
|
|
|
|
mSizer->Layout();
|
|
|
|
|
|
|
|
// Layout the toolbar
|
|
|
|
Layout();
|
|
|
|
|
|
|
|
// (Re)Establish the minimum size
|
|
|
|
SetMinSize( GetSizer()->GetMinSize() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::ReCreateButtons()
|
|
|
|
{
|
2015-01-01 11:46:52 +00:00
|
|
|
bool playDown = false;
|
|
|
|
bool playShift = false;
|
|
|
|
bool pauseDown = false;
|
|
|
|
bool recordDown = false;
|
|
|
|
bool recordShift = false;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// ToolBar::ReCreateButtons() will get rid of its sizer and
|
|
|
|
// since we've attached our sizer to it, ours will get deleted too
|
|
|
|
// so clean ours up first.
|
|
|
|
if( mSizer )
|
|
|
|
{
|
2015-01-01 11:46:52 +00:00
|
|
|
playDown = mPlay->IsDown();
|
|
|
|
playShift = mPlay->WasShiftDown();
|
|
|
|
pauseDown = mPause->IsDown();
|
|
|
|
recordDown = mRecord->IsDown();
|
|
|
|
recordShift = mRecord->WasShiftDown();
|
2010-01-23 19:44:49 +00:00
|
|
|
Detach( mSizer );
|
2015-01-01 11:46:52 +00:00
|
|
|
|
2016-04-06 22:32:38 +00:00
|
|
|
std::unique_ptr < wxSizer > {mSizer}; // DELETE it
|
2010-01-23 19:44:49 +00:00
|
|
|
mSizer = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ToolBar::ReCreateButtons();
|
|
|
|
|
2015-01-01 11:46:52 +00:00
|
|
|
if (playDown)
|
|
|
|
{
|
2016-04-19 21:34:38 +00:00
|
|
|
ControlToolBar::PlayAppearance appearance =
|
|
|
|
playShift ? ControlToolBar::PlayAppearance::Looped
|
|
|
|
: ControlToolBar::PlayAppearance::Straight;
|
|
|
|
SetPlay(playDown, appearance);
|
2015-01-01 11:46:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pauseDown)
|
|
|
|
{
|
|
|
|
mPause->PushDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recordDown)
|
|
|
|
{
|
|
|
|
SetRecord(recordDown, recordShift);
|
|
|
|
}
|
|
|
|
|
|
|
|
EnableDisableButtons();
|
|
|
|
|
2016-06-12 03:28:01 +00:00
|
|
|
RegenerateTooltips();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::Repaint( wxDC *dc )
|
|
|
|
{
|
|
|
|
#ifndef USE_AQUA_THEME
|
|
|
|
wxSize s = mSizer->GetSize();
|
|
|
|
wxPoint p = mSizer->GetPosition();
|
|
|
|
|
|
|
|
wxRect bevelRect( p.x, p.y, s.GetWidth() - 1, s.GetHeight() - 1 );
|
|
|
|
AColor::Bevel( *dc, true, bevelRect );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::EnableDisableButtons()
|
|
|
|
{
|
|
|
|
AudacityProject *p = GetActiveProject();
|
2016-03-28 20:04:49 +00:00
|
|
|
|
2017-05-14 19:21:16 +00:00
|
|
|
bool paused = mPause->IsDown();
|
2010-01-23 19:44:49 +00:00
|
|
|
bool playing = mPlay->IsDown();
|
|
|
|
bool recording = mRecord->IsDown();
|
2016-05-17 05:47:52 +00:00
|
|
|
bool busy = gAudioIO->IsBusy();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Only interested in audio type tracks
|
2018-09-18 16:02:37 +00:00
|
|
|
bool tracks = p && p->GetTracks()->Any<AudioTrack>(); // PRL: PlayableTrack ?
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-03-28 20:04:49 +00:00
|
|
|
if (p) {
|
|
|
|
TranscriptionToolBar *const playAtSpeedTB = p->GetTranscriptionToolBar();
|
|
|
|
if (playAtSpeedTB)
|
|
|
|
playAtSpeedTB->SetEnabled(CanStopAudioStream() && tracks && !recording);
|
2015-01-01 11:46:52 +00:00
|
|
|
}
|
2014-11-29 17:22:05 +00:00
|
|
|
|
2016-03-28 20:04:49 +00:00
|
|
|
mPlay->SetEnabled(CanStopAudioStream() && tracks && !recording);
|
2016-05-17 05:47:52 +00:00
|
|
|
mRecord->SetEnabled(
|
|
|
|
CanStopAudioStream() &&
|
2017-05-14 19:21:16 +00:00
|
|
|
!(busy && !recording && !paused) &&
|
|
|
|
!(playing && !paused)
|
2016-05-17 05:47:52 +00:00
|
|
|
);
|
2016-03-28 20:04:49 +00:00
|
|
|
mStop->SetEnabled(CanStopAudioStream() && (playing || recording));
|
2016-11-08 15:46:11 +00:00
|
|
|
mRewind->SetEnabled(IsPauseDown() || (!playing && !recording));
|
|
|
|
mFF->SetEnabled(tracks && (IsPauseDown() || (!playing && !recording)));
|
2016-04-19 01:49:27 +00:00
|
|
|
|
2016-09-11 19:30:06 +00:00
|
|
|
//auto pProject = GetActiveProject();
|
2016-05-11 23:59:11 +00:00
|
|
|
mPause->SetEnabled(CanStopAudioStream());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 21:34:38 +00:00
|
|
|
void ControlToolBar::SetPlay(bool down, PlayAppearance appearance)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-08-17 10:23:31 +00:00
|
|
|
if (down) {
|
2016-04-19 21:34:38 +00:00
|
|
|
mPlay->SetShift(appearance == PlayAppearance::Looped);
|
|
|
|
mPlay->SetControl(appearance == PlayAppearance::CutPreview);
|
|
|
|
mPlay->SetAlternateIdx(static_cast<int>(appearance));
|
2010-01-23 19:44:49 +00:00
|
|
|
mPlay->PushDown();
|
2014-11-29 17:22:05 +00:00
|
|
|
}
|
|
|
|
else {
|
2010-01-23 19:44:49 +00:00
|
|
|
mPlay->PopUp();
|
2014-11-29 17:22:05 +00:00
|
|
|
mPlay->SetAlternateIdx(0);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
EnableDisableButtons();
|
2016-04-18 23:28:56 +00:00
|
|
|
UpdateStatusBar(GetActiveProject());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::SetStop(bool down)
|
|
|
|
{
|
|
|
|
if (down)
|
|
|
|
mStop->PushDown();
|
|
|
|
else {
|
|
|
|
if(FindFocus() == mStop)
|
|
|
|
mPlay->SetFocus();
|
|
|
|
mStop->PopUp();
|
|
|
|
}
|
|
|
|
EnableDisableButtons();
|
|
|
|
}
|
|
|
|
|
2018-06-09 19:09:51 +00:00
|
|
|
void ControlToolBar::SetRecord(bool down, bool altAppearance)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (down)
|
2015-01-01 11:46:52 +00:00
|
|
|
{
|
2018-06-09 19:09:51 +00:00
|
|
|
mRecord->SetAlternateIdx(altAppearance ? 1 : 0);
|
2010-01-23 19:44:49 +00:00
|
|
|
mRecord->PushDown();
|
2015-01-01 11:46:52 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mRecord->SetAlternateIdx(0);
|
2010-01-23 19:44:49 +00:00
|
|
|
mRecord->PopUp();
|
|
|
|
}
|
|
|
|
EnableDisableButtons();
|
|
|
|
}
|
|
|
|
|
2016-05-11 23:59:11 +00:00
|
|
|
bool ControlToolBar::IsPauseDown() const
|
|
|
|
{
|
|
|
|
return mPause->IsDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ControlToolBar::IsRecordDown() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
return mRecord->IsDown();
|
|
|
|
}
|
2015-04-14 18:52:22 +00:00
|
|
|
|
|
|
|
int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
|
|
|
|
const AudioIOStartStreamOptions &options,
|
2016-04-18 21:50:17 +00:00
|
|
|
PlayMode mode,
|
2016-04-19 21:34:38 +00:00
|
|
|
PlayAppearance appearance, /* = PlayOption::Straight */
|
2015-04-17 16:52:13 +00:00
|
|
|
bool backwards, /* = false */
|
|
|
|
bool playWhiteSpace /* = false */)
|
2016-12-03 14:29:58 +00:00
|
|
|
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-03-28 20:04:49 +00:00
|
|
|
if (!CanStopAudioStream())
|
|
|
|
return -1;
|
|
|
|
|
2017-07-17 01:09:28 +00:00
|
|
|
bool useMidi = true;
|
|
|
|
|
|
|
|
// Remove these lines to experiment with scrubbing/seeking of note tracks
|
|
|
|
if (options.pScrubbingOptions)
|
|
|
|
useMidi = false;
|
|
|
|
|
2015-04-16 21:35:58 +00:00
|
|
|
// Uncomment this for laughs!
|
|
|
|
// backwards = true;
|
|
|
|
|
2015-04-14 18:52:22 +00:00
|
|
|
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;
|
|
|
|
|
2015-04-16 21:35:58 +00:00
|
|
|
if (backwards)
|
|
|
|
std::swap(t0, t1);
|
2015-04-14 18:52:22 +00:00
|
|
|
|
2016-04-19 21:34:38 +00:00
|
|
|
SetPlay(true, appearance);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-12-03 14:29:58 +00:00
|
|
|
bool success = false;
|
|
|
|
auto cleanup = finally( [&] {
|
|
|
|
if (!success) {
|
|
|
|
SetPlay(false);
|
|
|
|
SetStop(false);
|
|
|
|
SetRecord(false);
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
if (gAudioIO->IsBusy())
|
2015-04-14 18:52:22 +00:00
|
|
|
return -1;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-19 21:34:38 +00:00
|
|
|
const bool cutpreview = appearance == PlayAppearance::CutPreview;
|
2016-12-03 14:29:58 +00:00
|
|
|
if (cutpreview && t0==t1)
|
2015-04-14 18:52:22 +00:00
|
|
|
return -1; /* msmeyer: makes no sense */
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
AudacityProject *p = GetActiveProject();
|
2016-12-03 14:29:58 +00:00
|
|
|
if (!p)
|
2015-04-14 18:52:22 +00:00
|
|
|
return -1; // Should never happen, but...
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
TrackList *t = p->GetTracks();
|
2016-12-03 14:29:58 +00:00
|
|
|
if (!t)
|
2015-04-14 18:52:22 +00:00
|
|
|
return -1; // Should never happen, but...
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-18 21:50:17 +00:00
|
|
|
p->mLastPlayMode = mode;
|
|
|
|
|
2018-09-18 16:02:37 +00:00
|
|
|
bool hasaudio;
|
|
|
|
if (useMidi)
|
|
|
|
hasaudio = ! p->GetTracks()->Any<PlayableTrack>().empty();
|
|
|
|
else
|
|
|
|
hasaudio = ! p->GetTracks()->Any<WaveTrack>().empty();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 16:52:13 +00:00
|
|
|
double latestEnd = (playWhiteSpace)? t1 : t->GetEndTime();
|
|
|
|
|
2016-12-03 14:29:58 +00:00
|
|
|
if (!hasaudio)
|
2015-04-14 18:52:22 +00:00
|
|
|
return -1; // No need to continue without audio tracks
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
|
|
|
double init_seek = 0.0;
|
2014-06-03 20:30:19 +00:00
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t1 == t0) {
|
|
|
|
if (looped) {
|
2015-04-17 16:52:13 +00:00
|
|
|
// 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 > p->GetSel0()) && (t0 < p->GetSel1())) {
|
|
|
|
t0 = p->GetSel0();
|
|
|
|
t1 = p->GetSel1();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// loop the entire project
|
|
|
|
t0 = t->GetStartTime();
|
|
|
|
t1 = t->GetEndTime();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
} else {
|
|
|
|
// move t0 to valid range
|
|
|
|
if (t0 < 0) {
|
|
|
|
t0 = t->GetStartTime();
|
|
|
|
}
|
|
|
|
else if (t0 > t->GetEndTime()) {
|
|
|
|
t0 = t->GetEndTime();
|
|
|
|
}
|
|
|
|
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
|
|
|
|
else {
|
|
|
|
init_seek = t0; //AC: init_seek is where playback will 'start'
|
|
|
|
t0 = t->GetStartTime();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
t1 = t->GetEndTime();
|
|
|
|
}
|
|
|
|
else {
|
2015-04-16 21:35:58 +00:00
|
|
|
// maybe t1 < t0, with backwards scrubbing for instance
|
|
|
|
if (backwards)
|
|
|
|
std::swap(t0, t1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-05-20 02:52:16 +00:00
|
|
|
t0 = std::max(0.0, std::min(t0, latestEnd));
|
|
|
|
t1 = std::max(0.0, std::min(t1, latestEnd));
|
2015-04-16 21:35:58 +00:00
|
|
|
|
|
|
|
if (backwards)
|
|
|
|
std::swap(t0, t1);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-14 18:52:22 +00:00
|
|
|
int token = -1;
|
2016-12-03 14:29:58 +00:00
|
|
|
|
2015-04-16 21:35:58 +00:00
|
|
|
if (t1 != t0) {
|
2010-01-23 19:44:49 +00:00
|
|
|
if (cutpreview) {
|
2015-04-16 21:35:58 +00:00
|
|
|
const double tless = std::min(t0, t1);
|
|
|
|
const double tgreater = std::max(t0, t1);
|
2010-01-23 19:44:49 +00:00
|
|
|
double beforeLen, afterLen;
|
2012-01-25 02:21:08 +00:00
|
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
|
2010-01-23 19:44:49 +00:00
|
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
|
2015-04-16 21:35:58 +00:00
|
|
|
double tcp0 = tless-beforeLen;
|
|
|
|
double diff = tgreater - tless;
|
|
|
|
double tcp1 = (tgreater+afterLen) - diff;
|
|
|
|
SetupCutPreviewTracks(tcp0, tless, tgreater, tcp1);
|
|
|
|
if (backwards)
|
|
|
|
std::swap(tcp0, tcp1);
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mCutPreviewTracks)
|
|
|
|
{
|
2015-04-14 18:52:22 +00:00
|
|
|
AudioIOStartStreamOptions myOptions = options;
|
|
|
|
myOptions.cutPreviewGapStart = t0;
|
|
|
|
myOptions.cutPreviewGapLen = t1 - t0;
|
2010-01-23 19:44:49 +00:00
|
|
|
token = gAudioIO->StartStream(
|
2018-05-29 00:44:37 +00:00
|
|
|
GetAllPlaybackTracks(*mCutPreviewTracks, false, useMidi),
|
2016-05-19 20:26:20 +00:00
|
|
|
tcp0, tcp1, myOptions);
|
2016-05-29 19:47:11 +00:00
|
|
|
}
|
2016-12-03 14:29:58 +00:00
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
// Cannot create cut preview tracks, clean up and exit
|
2015-04-14 18:52:22 +00:00
|
|
|
return -1;
|
2016-05-29 19:47:11 +00:00
|
|
|
}
|
|
|
|
else {
|
2015-04-14 18:52:22 +00:00
|
|
|
// Lifted the following into AudacityProject::GetDefaultPlayOptions()
|
|
|
|
/*
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!timetrack) {
|
|
|
|
timetrack = t->GetTimeTrack();
|
|
|
|
}
|
2015-04-14 18:52:22 +00:00
|
|
|
*/
|
2018-05-29 00:44:37 +00:00
|
|
|
token = gAudioIO->StartStream(
|
|
|
|
GetAllPlaybackTracks(*t, false, useMidi),
|
|
|
|
t0, t1, options);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
if (token != 0) {
|
|
|
|
success = true;
|
|
|
|
p->SetAudioIOToken(token);
|
|
|
|
mBusyProject = p;
|
|
|
|
#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 {
|
2017-07-21 05:39:13 +00:00
|
|
|
// 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.
|
|
|
|
CallAfter( [=]{
|
2017-05-20 16:11:02 +00:00
|
|
|
// Show error message if stream could not be opened
|
|
|
|
ShowErrorDialog(this, _("Error"),
|
|
|
|
_("Error opening sound device.\nTry changing the audio host, playback device and the project sample rate."),
|
2017-09-06 12:19:42 +00:00
|
|
|
wxT("Error_opening_sound_device"));
|
2017-07-21 05:39:13 +00:00
|
|
|
});
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-03 14:29:58 +00:00
|
|
|
if (!success)
|
2015-04-14 18:52:22 +00:00
|
|
|
return -1;
|
|
|
|
|
2016-05-29 19:47:11 +00:00
|
|
|
StartScrollingIfPreferred();
|
|
|
|
|
2016-04-24 18:52:38 +00:00
|
|
|
// Let other UI update appearance
|
|
|
|
if (p)
|
2018-08-04 15:49:08 +00:00
|
|
|
p->GetRulerPanel()->DrawBothOverlays();
|
2016-04-24 18:52:38 +00:00
|
|
|
|
2015-04-14 18:52:22 +00:00
|
|
|
return token;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::PlayCurrentRegion(bool looped /* = false */,
|
|
|
|
bool cutpreview /* = false */)
|
|
|
|
{
|
2016-03-28 20:04:49 +00:00
|
|
|
if (!CanStopAudioStream())
|
|
|
|
return;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
AudacityProject *p = GetActiveProject();
|
|
|
|
|
|
|
|
if (p)
|
|
|
|
{
|
|
|
|
|
|
|
|
double playRegionStart, playRegionEnd;
|
|
|
|
p->GetPlayRegion(&playRegionStart, &playRegionEnd);
|
|
|
|
|
2015-04-14 18:52:22 +00:00
|
|
|
AudioIOStartStreamOptions options(p->GetDefaultPlayOptions());
|
|
|
|
options.playLooped = looped;
|
|
|
|
if (cutpreview)
|
|
|
|
options.timeTrack = NULL;
|
2016-04-19 21:34:38 +00:00
|
|
|
ControlToolBar::PlayAppearance appearance =
|
|
|
|
cutpreview ? ControlToolBar::PlayAppearance::CutPreview
|
|
|
|
: looped ? ControlToolBar::PlayAppearance::Looped
|
|
|
|
: ControlToolBar::PlayAppearance::Straight;
|
2015-04-14 18:52:22 +00:00
|
|
|
PlayPlayRegion(SelectedRegion(playRegionStart, playRegionEnd),
|
2016-04-18 21:50:17 +00:00
|
|
|
options,
|
|
|
|
(looped ? PlayMode::loopedPlay : PlayMode::normalPlay),
|
2016-04-19 21:34:38 +00:00
|
|
|
appearance);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::OnKeyEvent(wxKeyEvent & event)
|
|
|
|
{
|
|
|
|
if (event.ControlDown() || event.AltDown()) {
|
|
|
|
event.Skip();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-28 20:04:49 +00:00
|
|
|
// Does not appear to be needed on Linux. Perhaps on some other platform?
|
|
|
|
// If so, "!CanStopAudioStream()" should probably apply.
|
2010-01-23 19:44:49 +00:00
|
|
|
if (event.GetKeyCode() == WXK_SPACE) {
|
|
|
|
if (gAudioIO->IsStreamActive(GetActiveProject()->GetAudioIOToken())) {
|
|
|
|
SetPlay(false);
|
|
|
|
SetStop(true);
|
|
|
|
StopPlaying();
|
|
|
|
}
|
|
|
|
else if (!gAudioIO->IsBusy()) {
|
2014-08-17 13:06:54 +00:00
|
|
|
//SetPlay(true);// Not needed as done in PlayPlayRegion
|
2010-01-23 19:44:49 +00:00
|
|
|
SetStop(false);
|
|
|
|
PlayCurrentRegion();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
event.Skip();
|
|
|
|
}
|
|
|
|
|
2013-07-23 22:07:56 +00:00
|
|
|
void ControlToolBar::OnPlay(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-05-04 02:39:31 +00:00
|
|
|
auto p = GetActiveProject();
|
2016-03-28 20:04:49 +00:00
|
|
|
|
2016-05-29 19:58:47 +00:00
|
|
|
if (!CanStopAudioStream())
|
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-05-29 19:58:47 +00:00
|
|
|
StopPlaying();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-05-29 19:58:47 +00:00
|
|
|
if (p) p->TP_DisplaySelection();
|
2016-05-04 02:39:31 +00:00
|
|
|
|
2016-12-03 14:29:58 +00:00
|
|
|
auto cleanup = finally( [&]{ UpdateStatusBar(p); } );
|
2016-05-29 19:58:47 +00:00
|
|
|
PlayDefault();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-07-23 22:07:56 +00:00
|
|
|
void ControlToolBar::OnStop(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-03-28 20:04:49 +00:00
|
|
|
if (CanStopAudioStream()) {
|
|
|
|
StopPlaying();
|
2016-04-18 23:28:56 +00:00
|
|
|
UpdateStatusBar(GetActiveProject());
|
2016-03-28 20:04:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ControlToolBar::CanStopAudioStream()
|
|
|
|
{
|
|
|
|
return (!gAudioIO->IsStreamActive() ||
|
|
|
|
gAudioIO->IsMonitoring() ||
|
|
|
|
gAudioIO->GetOwningProject() == GetActiveProject());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::PlayDefault()
|
|
|
|
{
|
2014-11-29 17:22:05 +00:00
|
|
|
// Let control have precedence over shift
|
|
|
|
const bool cutPreview = mPlay->WasControlDown();
|
|
|
|
const bool looped = !cutPreview &&
|
|
|
|
mPlay->WasShiftDown();
|
|
|
|
PlayCurrentRegion(looped, cutPreview);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::StopPlaying(bool stopStream /* = true*/)
|
|
|
|
{
|
2016-05-29 18:28:06 +00:00
|
|
|
StopScrolling();
|
|
|
|
|
2016-04-24 15:59:01 +00:00
|
|
|
AudacityProject *project = GetActiveProject();
|
|
|
|
|
2016-05-04 02:39:31 +00:00
|
|
|
if(project) {
|
2016-04-24 15:59:01 +00:00
|
|
|
// Let scrubbing code do some appearance change
|
|
|
|
project->GetScrubber().StopScrubbing();
|
2016-05-04 02:39:31 +00:00
|
|
|
}
|
2016-04-24 15:59:01 +00:00
|
|
|
|
2016-03-28 20:04:49 +00:00
|
|
|
if (!CanStopAudioStream())
|
|
|
|
return;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mStop->PushDown();
|
|
|
|
|
|
|
|
SetStop(false);
|
|
|
|
if(stopStream)
|
|
|
|
gAudioIO->StopStream();
|
|
|
|
SetPlay(false);
|
|
|
|
SetRecord(false);
|
|
|
|
|
2015-08-31 19:32:33 +00:00
|
|
|
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
2010-01-23 19:44:49 +00:00
|
|
|
gAudioIO->AILADisable();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mPause->PopUp();
|
|
|
|
mPaused=false;
|
|
|
|
//Make sure you tell gAudioIO to unpause
|
2014-12-17 19:16:08 +00:00
|
|
|
gAudioIO->SetPaused(mPaused);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
ClearCutPreviewTracks();
|
|
|
|
|
|
|
|
mBusyProject = NULL;
|
|
|
|
// So that we continue monitoring after playing or recording.
|
|
|
|
// also clean the MeterQueues
|
|
|
|
if( project ) {
|
|
|
|
project->MayStartMonitoring();
|
2014-12-17 19:16:08 +00:00
|
|
|
|
2018-02-21 19:25:22 +00:00
|
|
|
MeterPanel *meter = project->GetPlaybackMeter();
|
2014-12-17 19:16:08 +00:00
|
|
|
if( meter ) {
|
|
|
|
meter->Clear();
|
|
|
|
}
|
2018-08-12 15:12:22 +00:00
|
|
|
|
2014-12-18 05:51:38 +00:00
|
|
|
meter = project->GetCaptureMeter();
|
2014-12-17 19:16:08 +00:00
|
|
|
if( meter ) {
|
|
|
|
meter->Clear();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2016-06-16 00:00:47 +00:00
|
|
|
|
|
|
|
const auto toolbar = project->GetToolManager()->GetToolBar(ScrubbingBarID);
|
|
|
|
toolbar->EnableDisableButtons();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-08-26 05:13:14 +00:00
|
|
|
void ControlToolBar::Pause()
|
|
|
|
{
|
2016-03-28 20:04:49 +00:00
|
|
|
if (!CanStopAudioStream())
|
|
|
|
gAudioIO->SetPaused(!gAudioIO->IsPaused());
|
|
|
|
else {
|
|
|
|
wxCommandEvent dummy;
|
|
|
|
OnPause(dummy);
|
|
|
|
}
|
2015-08-26 05:13:14 +00:00
|
|
|
}
|
|
|
|
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
WaveTrackArray ControlToolBar::ChooseExistingRecordingTracks(
|
|
|
|
AudacityProject &proj, bool selectedOnly)
|
2018-05-28 18:38:37 +00:00
|
|
|
{
|
|
|
|
auto p = &proj;
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
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)
|
2018-05-28 18:38:37 +00:00
|
|
|
return {};
|
|
|
|
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
auto trackList = p->GetTracks();
|
|
|
|
std::vector<unsigned> channelCounts;
|
2018-05-28 18:38:37 +00:00
|
|
|
WaveTrackArray candidates;
|
2018-09-18 16:02:37 +00:00
|
|
|
const auto range = trackList->Leaders<WaveTrack>();
|
|
|
|
for ( auto candidate : selectedOnly ? range + &Track::IsSelected : range ) {
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
// count channels in this track
|
2018-09-18 16:02:37 +00:00
|
|
|
const auto channels = TrackList::Channels( candidate );
|
|
|
|
unsigned nChannels = channels.size();
|
2018-05-28 18:38:37 +00:00
|
|
|
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
if (strictRules && nChannels > recordingChannels) {
|
|
|
|
// The recording would under-fill this track's channels
|
|
|
|
// Can't use any partial accumulated results
|
|
|
|
// either. Keep looking.
|
2018-05-28 18:38:37 +00:00
|
|
|
candidates.clear();
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
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);
|
2018-09-18 16:02:37 +00:00
|
|
|
for ( auto channel : channels ) {
|
2018-05-28 18:38:37 +00:00
|
|
|
candidates.push_back(Track::Pointer<WaveTrack>(channel));
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
if(candidates.size() == recordingChannels)
|
|
|
|
// Done!
|
|
|
|
return candidates;
|
|
|
|
}
|
2018-05-28 18:38:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Rewrite logic for choosing append-record tracks...
... If recording one or two channels, use first unbroken subsequence within the
selected wave tracks for which the total of channels matches exactly; failing
that, use such a sequence among all wave tracks; failing that, record to
new tracks.
If recording more than two channels, and there is at least one selected wave
track, then use the topmost selected wave channels, up to the number of
recording channels, and if fewer channels are selected, just drop the extra
input channels.
If recording more than two, and no wave tracks are selected, ignore existing
wave tracks and record to new tracks.
In any case, the tracks chosen for append-record might not be consecutive
among all the tracks, because non-wave or non-selected tracks may come between.
2018-06-09 17:05:34 +00:00
|
|
|
if (!strictRules && !candidates.empty())
|
|
|
|
// good enough
|
|
|
|
return candidates;
|
|
|
|
|
|
|
|
// If the loop didn't exit early, we could not find enough channels
|
|
|
|
return {};
|
2018-05-28 18:38:37 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void ControlToolBar::OnRecord(wxCommandEvent &evt)
|
2016-12-03 14:29:58 +00:00
|
|
|
// STRONG-GUARANTEE (for state of current project's tracks)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-05-14 19:21:16 +00:00
|
|
|
// TODO: It would be neater if Menu items and Toolbar buttons used the same code for
|
|
|
|
// enabling/disabling, and all fell into the same action routines.
|
|
|
|
// Here instead we reduplicate some logic (from CommandHandler) because it isn't
|
|
|
|
// normally used for buttons.
|
|
|
|
|
|
|
|
// Code from CommandHandler start...
|
2018-05-18 11:13:09 +00:00
|
|
|
AudacityProject * p = GetActiveProject();
|
2018-05-28 18:38:37 +00:00
|
|
|
wxASSERT(p);
|
|
|
|
if (!p)
|
2017-05-14 19:21:16 +00:00
|
|
|
return;
|
|
|
|
|
2018-06-09 19:40:09 +00:00
|
|
|
bool altAppearance = mRecord->WasShiftDown();
|
2018-05-28 18:38:37 +00:00
|
|
|
if (evt.GetInt() == 1) // used when called by keyboard shortcut. Default (0) ignored.
|
2018-06-09 19:40:09 +00:00
|
|
|
altAppearance = true;
|
2018-05-28 18:38:37 +00:00
|
|
|
if (evt.GetInt() == 2)
|
2018-06-09 19:40:09 +00:00
|
|
|
altAppearance = false;
|
2016-09-10 20:34:14 +00:00
|
|
|
|
2017-07-09 13:07:56 +00:00
|
|
|
bool bPreferNewTrack;
|
2018-05-28 18:38:37 +00:00
|
|
|
gPrefs->Read("/GUI/PreferNewTrackRecord", &bPreferNewTrack, false);
|
2018-06-09 19:40:09 +00:00
|
|
|
const bool appendRecord = (altAppearance == bPreferNewTrack);
|
2016-12-03 14:29:58 +00:00
|
|
|
|
|
|
|
if (p) {
|
2010-01-23 19:44:49 +00:00
|
|
|
double t0 = p->GetSel0();
|
|
|
|
double t1 = p->GetSel1();
|
2018-05-18 12:01:21 +00:00
|
|
|
// When no time selection, recording duration is 'unlimited'.
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t1 == t0)
|
2018-05-18 12:01:21 +00:00
|
|
|
t1 = DBL_MAX;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-05-28 18:38:37 +00:00
|
|
|
WaveTrackArray existingTracks;
|
2018-04-08 21:38:02 +00:00
|
|
|
|
2018-02-28 21:19:33 +00:00
|
|
|
if (appendRecord) {
|
2018-09-18 16:02:37 +00:00
|
|
|
const auto trackRange = p->GetTracks()->Any< const WaveTrack >();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-05-28 18:38:37 +00:00
|
|
|
// 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);
|
2018-09-18 16:02:37 +00:00
|
|
|
if ( !existingTracks.empty() )
|
|
|
|
t0 = std::max( t0,
|
|
|
|
( trackRange + &Track::IsSelected ).max( &Track::GetEndTime ) );
|
2018-06-11 19:02:20 +00:00
|
|
|
else {
|
2018-05-28 18:38:37 +00:00
|
|
|
existingTracks = ChooseExistingRecordingTracks(*p, false);
|
2018-09-18 16:02:37 +00:00
|
|
|
t0 = std::max( t0, trackRange.max( &Track::GetEndTime ) );
|
2018-06-11 19:02:20 +00:00
|
|
|
// 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 <= p->GetSel0() && p->GetSel1() > p->GetSel0()) {
|
|
|
|
t1 = p->GetSel1(); // record within the selection
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
t1 = DBL_MAX; // record for a long, long time
|
2018-05-28 18:38:37 +00:00
|
|
|
}
|
2018-04-05 10:53:58 +00:00
|
|
|
}
|
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
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(*p->GetTracks(), 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;
|
|
|
|
AudioIOStartStreamOptions options(p->GetDefaultPlayOptions());
|
2018-06-09 19:40:09 +00:00
|
|
|
DoRecord(*p, transportTracks, t0, t1, altAppearance, options);
|
2018-05-28 18:38:37 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-10 02:33:03 +00:00
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
bool ControlToolBar::UseDuplex()
|
2018-05-28 18:38:37 +00:00
|
|
|
{
|
|
|
|
bool duplex;
|
|
|
|
gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex,
|
|
|
|
#ifdef EXPERIMENTAL_DA
|
|
|
|
false
|
|
|
|
#else
|
|
|
|
true
|
|
|
|
#endif
|
|
|
|
);
|
2018-05-29 00:44:37 +00:00
|
|
|
return duplex;
|
|
|
|
}
|
2018-05-28 18:38:37 +00:00
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
bool ControlToolBar::DoRecord(AudacityProject &project,
|
|
|
|
const TransportTracks &tracks,
|
|
|
|
double t0, double t1,
|
2018-06-09 19:40:09 +00:00
|
|
|
bool altAppearance,
|
2018-05-29 00:44:37 +00:00
|
|
|
const AudioIOStartStreamOptions &options)
|
|
|
|
{
|
2018-06-09 19:40:09 +00:00
|
|
|
CommandFlag flags = AlwaysEnabledFlag; // 0 means recalc flags.
|
|
|
|
|
|
|
|
// NB: The call may have the side effect of changing flags.
|
2018-10-15 18:07:32 +00:00
|
|
|
bool allowed = GetMenuManager(project).TryToMakeActionAllowed(
|
2017-08-20 04:16:22 +00:00
|
|
|
project,
|
2018-06-09 19:40:09 +00:00
|
|
|
flags,
|
|
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag,
|
|
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag);
|
|
|
|
|
|
|
|
if (!allowed)
|
|
|
|
return false;
|
|
|
|
// ...end of code from CommandHandler.
|
|
|
|
|
|
|
|
if (gAudioIO->IsBusy()) {
|
|
|
|
if (!CanStopAudioStream() || 0 == gAudioIO->GetNumCaptureChannels())
|
|
|
|
mRecord->PopUp();
|
|
|
|
else
|
|
|
|
mRecord->PushDown();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
SetRecord(true, altAppearance);
|
|
|
|
|
|
|
|
bool success = false;
|
|
|
|
auto cleanup = finally([&] {
|
|
|
|
if (!success) {
|
|
|
|
SetPlay(false);
|
|
|
|
SetStop(false);
|
|
|
|
SetRecord(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Success or not:
|
|
|
|
UpdateStatusBar(GetActiveProject());
|
|
|
|
});
|
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
auto transportTracks = tracks;
|
2018-05-28 18:38:37 +00:00
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
// Will replace any given capture tracks with temporaries
|
|
|
|
transportTracks.captureTracks.clear();
|
2018-05-28 18:38:37 +00:00
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
const auto p = &project;
|
|
|
|
|
|
|
|
bool appendRecord = !tracks.captureTracks.empty();
|
2018-05-28 18:38:37 +00:00
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
{
|
2018-05-28 18:38:37 +00:00
|
|
|
if (appendRecord) {
|
2018-02-28 21:19:33 +00:00
|
|
|
// Append recording:
|
2010-01-23 19:44:49 +00:00
|
|
|
// Pad selected/all wave tracks to make them all the same length
|
2018-05-29 00:44:37 +00:00
|
|
|
for (const auto &wt : tracks.captureTracks)
|
2018-04-08 02:36:43 +00:00
|
|
|
{
|
2018-06-11 19:02:20 +00:00
|
|
|
auto endTime = wt->GetEndTime();
|
2018-04-08 02:36:43 +00:00
|
|
|
|
2018-06-01 06:25:51 +00:00
|
|
|
// 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);
|
|
|
|
|
2018-04-08 02:36:43 +00:00
|
|
|
// 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>(
|
|
|
|
p->GetTracks()->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.
|
2018-06-11 19:02:20 +00:00
|
|
|
if (endTime <= t0) {
|
2018-04-08 02:36:43 +00:00
|
|
|
|
|
|
|
// Pad the recording track with silence, up to the
|
|
|
|
// maximum time.
|
|
|
|
auto newTrack = p->GetTrackFactory()->NewWaveTrack();
|
|
|
|
newTrack->SetWaveColorIndex( wt->GetWaveColorIndex() );
|
2018-06-11 19:02:20 +00:00
|
|
|
newTrack->InsertSilence(0.0, t0 - endTime);
|
2018-04-08 02:36:43 +00:00
|
|
|
newTrack->Flush();
|
2018-06-11 19:02:20 +00:00
|
|
|
pending->Clear(endTime, t0);
|
|
|
|
pending->Paste(endTime, newTrack.get());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2018-05-29 00:44:37 +00:00
|
|
|
transportTracks.captureTracks.push_back(pending);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2018-08-12 23:28:24 +00:00
|
|
|
p->GetTracks()->UpdatePendingTracks();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-07-06 15:17:43 +00:00
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
if( transportTracks.captureTracks.empty() )
|
2018-05-28 18:38:37 +00:00
|
|
|
{ // recording to NEW track(s).
|
2016-01-16 14:44:45 +00:00
|
|
|
bool recordingNameCustom, useTrackNumber, useDateStamp, useTimeStamp;
|
|
|
|
wxString defaultTrackName, defaultRecordingTrackName;
|
2016-09-10 20:34:14 +00:00
|
|
|
|
2018-09-18 16:02:37 +00:00
|
|
|
// Count the tracks.
|
|
|
|
const auto trackList = p->GetTracks();
|
|
|
|
auto numTracks = trackList->Leaders<const WaveTrack>().size();
|
2016-09-10 20:34:14 +00:00
|
|
|
|
2018-05-28 18:38:37 +00:00
|
|
|
auto recordingChannels = std::max(1L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
|
2016-01-16 14:44:45 +00:00
|
|
|
|
|
|
|
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);
|
2017-07-21 21:22:48 +00:00
|
|
|
defaultTrackName = TracksPrefs::GetDefaultAudioTrackNamePreference();
|
2016-01-16 14:44:45 +00:00
|
|
|
gPrefs->Read(wxT("/GUI/TrackNames/RecodingTrackName"), &defaultRecordingTrackName, defaultTrackName);
|
|
|
|
|
|
|
|
wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
|
|
|
|
|
2018-09-20 14:44:51 +00:00
|
|
|
Track *first {};
|
2010-01-23 19:44:49 +00:00
|
|
|
for (int c = 0; c < recordingChannels; c++) {
|
2018-01-10 18:23:01 +00:00
|
|
|
std::shared_ptr<WaveTrack> newTrack{
|
|
|
|
p->GetTrackFactory()->NewWaveTrack().release()
|
|
|
|
};
|
2018-09-20 14:44:51 +00:00
|
|
|
if (!first)
|
|
|
|
first = newTrack.get();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-05-27 03:39:12 +00:00
|
|
|
// Quantize bounds to the rate of the new track.
|
|
|
|
if (c == 0) {
|
2018-05-28 17:26:47 +00:00
|
|
|
if (t0 < DBL_MAX)
|
|
|
|
t0 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t0));
|
|
|
|
if (t1 < DBL_MAX)
|
|
|
|
t1 = newTrack->LongSamplesToTime(newTrack->TimeToLongSamples(t1));
|
2018-05-27 03:39:12 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
newTrack->SetOffset(t0);
|
2016-01-16 14:44:45 +00:00
|
|
|
wxString nameSuffix = wxString(wxT(""));
|
|
|
|
|
|
|
|
if (useTrackNumber) {
|
2018-09-18 16:02:37 +00:00
|
|
|
nameSuffix += wxString::Format(wxT("%d"), 1 + numTracks + c);
|
2016-01-16 14:44:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (useDateStamp) {
|
2019-02-12 00:10:48 +00:00
|
|
|
if (!nameSuffix.empty()) {
|
2016-01-16 14:44:45 +00:00
|
|
|
nameSuffix += wxT("_");
|
|
|
|
}
|
|
|
|
nameSuffix += wxDateTime::Now().FormatISODate();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (useTimeStamp) {
|
2019-02-12 00:10:48 +00:00
|
|
|
if (!nameSuffix.empty()) {
|
2016-01-16 14:44:45 +00:00
|
|
|
nameSuffix += wxT("_");
|
|
|
|
}
|
|
|
|
nameSuffix += wxDateTime::Now().FormatISOTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
// ISO standard would be nice, but ":" is unsafe for file name.
|
|
|
|
nameSuffix.Replace(wxT(":"), wxT("-"));
|
|
|
|
|
2019-02-12 00:10:48 +00:00
|
|
|
if (baseTrackName.empty()) {
|
2016-01-16 14:44:45 +00:00
|
|
|
newTrack->SetName(nameSuffix);
|
|
|
|
}
|
2019-02-12 00:10:48 +00:00
|
|
|
else if (nameSuffix.empty()) {
|
2016-01-16 14:44:45 +00:00
|
|
|
newTrack->SetName(baseTrackName);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
newTrack->SetName(baseTrackName + wxT("_") + nameSuffix);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-08-26 20:10:57 +00:00
|
|
|
if ((recordingChannels > 2) && !(p->GetTracksFitVerticallyZoomed())) {
|
|
|
|
newTrack->SetMinimized(true);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-01-10 18:23:01 +00:00
|
|
|
p->GetTracks()->RegisterPendingNewTrack( newTrack );
|
2018-05-29 00:44:37 +00:00
|
|
|
transportTracks.captureTracks.push_back(newTrack);
|
2018-03-24 16:05:10 +00:00
|
|
|
// Bug 1548. New track needs the focus.
|
|
|
|
p->GetTrackPanel()->SetFocusedTrack( newTrack.get() );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2018-09-20 14:44:51 +00:00
|
|
|
p->GetTracks()->GroupChannels(*first, recordingChannels);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2018-09-20 14:44:51 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Automated Input Level Adjustment Initialization
|
2015-08-31 19:32:33 +00:00
|
|
|
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
2010-01-23 19:44:49 +00:00
|
|
|
gAudioIO->AILAInitialize();
|
|
|
|
#endif
|
2016-03-28 20:04:49 +00:00
|
|
|
|
2018-05-29 00:44:37 +00:00
|
|
|
int token = gAudioIO->StartStream(transportTracks, t0, t1, options);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-12-03 14:29:58 +00:00
|
|
|
success = (token != 0);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (success) {
|
|
|
|
p->SetAudioIOToken(token);
|
|
|
|
mBusyProject = p;
|
2016-05-29 19:47:11 +00:00
|
|
|
|
|
|
|
StartScrollingIfPreferred();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else {
|
2018-01-10 18:23:01 +00:00
|
|
|
CancelRecording();
|
|
|
|
|
2017-05-20 16:11:02 +00:00
|
|
|
// Show error message if stream could not be opened
|
2018-08-12 15:12:22 +00:00
|
|
|
wxString msg = wxString::Format(_("Error opening recording device.\nError code: %s"), gAudioIO->LastPaErrorString());
|
|
|
|
ShowErrorDialog(this, _("Error"), msg, wxT("Error_opening_sound_device"));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2018-05-28 18:38:37 +00:00
|
|
|
|
|
|
|
return success;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-23 22:07:56 +00:00
|
|
|
void ControlToolBar::OnPause(wxCommandEvent & WXUNUSED(evt))
|
2014-06-03 20:30:19 +00:00
|
|
|
{
|
2016-03-28 20:04:49 +00:00
|
|
|
if (!CanStopAudioStream()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-26 16:58:39 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if(mPaused)
|
|
|
|
{
|
|
|
|
mPause->PopUp();
|
|
|
|
mPaused=false;
|
|
|
|
}
|
|
|
|
else
|
2014-06-03 20:30:19 +00:00
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
mPause->PushDown();
|
|
|
|
mPaused=true;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-05-11 23:59:11 +00:00
|
|
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
2018-08-08 17:40:16 +00:00
|
|
|
|
|
|
|
// Bug 1494 - Pausing a seek or scrub should just STOP as
|
|
|
|
// it is confusing to be in a paused scrub state.
|
|
|
|
bool bStopInstead = mPaused &&
|
|
|
|
gAudioIO->IsScrubbing() &&
|
|
|
|
!GetActiveProject()->GetScrubber().IsSpeedPlaying();
|
|
|
|
|
|
|
|
if (bStopInstead) {
|
|
|
|
wxCommandEvent dummy;
|
|
|
|
OnStop(dummy);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-11 23:59:11 +00:00
|
|
|
if (gAudioIO->IsScrubbing())
|
|
|
|
GetActiveProject()->GetScrubber().Pause(mPaused);
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
gAudioIO->SetPaused(mPaused);
|
|
|
|
}
|
|
|
|
|
2016-04-18 23:28:56 +00:00
|
|
|
UpdateStatusBar(GetActiveProject());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-07-23 22:07:56 +00:00
|
|
|
void ControlToolBar::OnRewind(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mRewind->PushDown();
|
|
|
|
mRewind->PopUp();
|
|
|
|
|
|
|
|
AudacityProject *p = GetActiveProject();
|
|
|
|
if (p) {
|
2016-11-08 15:46:11 +00:00
|
|
|
p->StopIfPaused();
|
2010-01-23 19:44:49 +00:00
|
|
|
p->Rewind(mRewind->WasShiftDown());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-23 22:07:56 +00:00
|
|
|
void ControlToolBar::OnFF(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mFF->PushDown();
|
|
|
|
mFF->PopUp();
|
|
|
|
|
|
|
|
AudacityProject *p = GetActiveProject();
|
|
|
|
|
|
|
|
if (p) {
|
2016-11-08 15:46:11 +00:00
|
|
|
p->StopIfPaused();
|
2010-01-23 19:44:49 +00:00
|
|
|
p->SkipEnd(mFF->WasShiftDown());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-23 22:07:56 +00:00
|
|
|
void ControlToolBar::SetupCutPreviewTracks(double WXUNUSED(playStart), double cutStart,
|
|
|
|
double cutEnd, double WXUNUSED(playEnd))
|
2016-12-03 14:29:58 +00:00
|
|
|
|
|
|
|
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
ClearCutPreviewTracks();
|
|
|
|
AudacityProject *p = GetActiveProject();
|
|
|
|
if (p) {
|
2018-09-18 16:02:37 +00:00
|
|
|
auto trackRange = p->GetTracks()->Selected<const PlayableTrack>();
|
|
|
|
if( !trackRange.empty() ) {
|
|
|
|
auto cutPreviewTracks = TrackList::Create();
|
|
|
|
for (const auto track1 : trackRange) {
|
2018-09-10 15:57:36 +00:00
|
|
|
// Duplicate and change tracks
|
|
|
|
// Clear has a very small chance of throwing
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2018-09-18 16:02:37 +00:00
|
|
|
auto newTrack = track1->Duplicate();
|
|
|
|
newTrack->Clear(cutStart, cutEnd);
|
|
|
|
cutPreviewTracks->Add(std::move(newTrack));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2018-09-18 16:02:37 +00:00
|
|
|
// use NOTHROW-GUARANTEE:
|
2018-09-10 21:50:10 +00:00
|
|
|
mCutPreviewTracks = cutPreviewTracks;
|
2018-09-18 16:02:37 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::ClearCutPreviewTracks()
|
|
|
|
{
|
|
|
|
if (mCutPreviewTracks)
|
2016-04-06 22:32:38 +00:00
|
|
|
mCutPreviewTracks->Clear();
|
|
|
|
mCutPreviewTracks.reset();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-05-29 12:45:15 +00:00
|
|
|
// works out the width of the field in the status bar needed for the state (eg play, record pause)
|
2015-06-02 14:18:12 +00:00
|
|
|
int ControlToolBar::WidthForStatusBar(wxStatusBar* const sb)
|
2015-05-29 12:45:15 +00:00
|
|
|
{
|
|
|
|
int xMax = 0;
|
2016-04-24 19:13:03 +00:00
|
|
|
const auto pauseString = wxT(" ") + wxGetTranslation(mStatePause);
|
2015-05-29 12:45:15 +00:00
|
|
|
|
2016-05-11 23:59:11 +00:00
|
|
|
auto update = [&] (const wxString &state) {
|
2016-04-24 19:13:03 +00:00
|
|
|
int x, y;
|
|
|
|
sb->GetTextExtent(
|
2016-05-11 23:59:11 +00:00
|
|
|
wxGetTranslation(state) + pauseString + wxT("."),
|
2016-04-24 19:13:03 +00:00
|
|
|
&x, &y
|
|
|
|
);
|
|
|
|
xMax = std::max(x, xMax);
|
|
|
|
};
|
2015-05-29 12:45:15 +00:00
|
|
|
|
2016-05-11 23:59:11 +00:00
|
|
|
update(mStatePlay);
|
|
|
|
update(mStateStop);
|
|
|
|
update(mStateRecord);
|
2015-05-29 12:45:15 +00:00
|
|
|
|
2016-04-18 23:28:56 +00:00
|
|
|
// Note that Scrubbing + Paused is not allowed.
|
2016-04-24 19:13:03 +00:00
|
|
|
for(const auto &state : Scrubber::GetAllUntranslatedStatusStrings())
|
2016-05-11 23:59:11 +00:00
|
|
|
update(state);
|
2016-04-18 23:28:56 +00:00
|
|
|
|
2015-05-29 12:45:15 +00:00
|
|
|
return xMax + 30; // added constant needed because xMax isn't large enough for some reason, plus some space.
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:18:12 +00:00
|
|
|
wxString ControlToolBar::StateForStatusBar()
|
2015-05-29 12:45:15 +00:00
|
|
|
{
|
|
|
|
wxString state;
|
|
|
|
|
2016-04-19 01:49:27 +00:00
|
|
|
auto pProject = GetActiveProject();
|
2016-04-24 19:13:03 +00:00
|
|
|
auto scrubState =
|
|
|
|
pProject ? pProject->GetScrubber().GetUntranslatedStateString() : wxString();
|
2019-02-12 00:10:48 +00:00
|
|
|
if (!scrubState.empty())
|
2016-04-24 19:13:03 +00:00
|
|
|
state = wxGetTranslation(scrubState);
|
2016-04-18 23:28:56 +00:00
|
|
|
else if (mPlay->IsDown())
|
2015-06-02 08:29:31 +00:00
|
|
|
state = wxGetTranslation(mStatePlay);
|
2015-05-29 12:45:15 +00:00
|
|
|
else if (mRecord->IsDown())
|
2015-06-02 08:29:31 +00:00
|
|
|
state = wxGetTranslation(mStateRecord);
|
2015-05-29 12:45:15 +00:00
|
|
|
else
|
2015-06-02 08:29:31 +00:00
|
|
|
state = wxGetTranslation(mStateStop);
|
2015-05-29 12:45:15 +00:00
|
|
|
|
|
|
|
if (mPause->IsDown())
|
|
|
|
{
|
|
|
|
state.Append(wxT(" "));
|
2015-06-02 08:29:31 +00:00
|
|
|
state.Append(wxGetTranslation(mStatePause));
|
2015-05-29 12:45:15 +00:00
|
|
|
}
|
|
|
|
|
2015-06-01 09:08:33 +00:00
|
|
|
state.Append(wxT("."));
|
|
|
|
|
2015-06-02 14:18:12 +00:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2016-04-18 23:28:56 +00:00
|
|
|
void ControlToolBar::UpdateStatusBar(AudacityProject *pProject)
|
2015-06-02 14:18:12 +00:00
|
|
|
{
|
2016-04-18 23:28:56 +00:00
|
|
|
pProject->GetStatusBar()->SetStatusText(StateForStatusBar(), stateStatusBarField);
|
2015-05-29 12:45:15 +00:00
|
|
|
}
|
2016-05-29 18:28:06 +00:00
|
|
|
|
2018-08-30 15:51:28 +00:00
|
|
|
bool ControlToolBar::IsTransportingPinned()
|
|
|
|
{
|
|
|
|
if (!TracksPrefs::GetPinnedHeadPreference())
|
|
|
|
return false;
|
|
|
|
const auto &scrubber = ::GetActiveProject()->GetScrubber();
|
|
|
|
return
|
|
|
|
!(scrubber.HasMark() &&
|
|
|
|
!scrubber.WasSpeedPlaying() &&
|
|
|
|
!Scrubber::ShouldScrubPinned());
|
|
|
|
}
|
|
|
|
|
2016-05-29 19:47:11 +00:00
|
|
|
void ControlToolBar::StartScrollingIfPreferred()
|
|
|
|
{
|
2018-08-30 15:51:28 +00:00
|
|
|
if (IsTransportingPinned())
|
2016-05-29 19:47:11 +00:00
|
|
|
StartScrolling();
|
2016-05-30 14:24:50 +00:00
|
|
|
#ifdef __WXMAC__
|
2018-07-29 18:11:17 +00:00
|
|
|
else if (::GetActiveProject()->GetScrubber().HasMark()) {
|
2016-05-30 14:24:50 +00:00
|
|
|
// PRL: cause many "unnecessary" refreshes. For reasons I don't understand,
|
|
|
|
// doing this causes wheel rotation events (mapped from the double finger vertical
|
|
|
|
// swipe) to be delivered more uniformly to the application, so that speed control
|
|
|
|
// works better.
|
|
|
|
::GetActiveProject()->GetPlaybackScroller().Activate
|
|
|
|
(AudacityProject::PlaybackScroller::Mode::Refresh);
|
|
|
|
}
|
|
|
|
#endif
|
2016-05-29 19:47:11 +00:00
|
|
|
else
|
|
|
|
StopScrolling();
|
|
|
|
}
|
|
|
|
|
2016-05-29 18:28:06 +00:00
|
|
|
void ControlToolBar::StartScrolling()
|
|
|
|
{
|
|
|
|
using Mode = AudacityProject::PlaybackScroller::Mode;
|
|
|
|
const auto project = GetActiveProject();
|
|
|
|
if (project) {
|
2018-08-01 01:32:48 +00:00
|
|
|
auto mode = Mode::Pinned;
|
2016-05-29 18:28:06 +00:00
|
|
|
|
2016-06-08 22:08:51 +00:00
|
|
|
#if 0
|
|
|
|
// Enable these lines to pin the playhead right instead of center,
|
|
|
|
// when recording but not overdubbing.
|
2016-05-29 18:28:06 +00:00
|
|
|
if (gAudioIO->GetNumCaptureChannels() > 0) {
|
|
|
|
// recording
|
|
|
|
|
|
|
|
// Display a fixed recording head while scrolling the waves continuously.
|
|
|
|
// If you overdub, you may want to anticipate some context in existing tracks,
|
|
|
|
// so center the head. If not, put it rightmost to display as much wave as we can.
|
|
|
|
bool duplex;
|
2017-06-26 18:44:39 +00:00
|
|
|
#ifdef EXPERIMENTAL_DA
|
2017-06-26 14:31:11 +00:00
|
|
|
gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex, false);
|
2017-06-26 18:44:39 +00:00
|
|
|
#else
|
|
|
|
gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex, true);
|
|
|
|
#endif
|
2016-05-29 18:28:06 +00:00
|
|
|
if (duplex) {
|
|
|
|
// See if there is really anything being overdubbed
|
|
|
|
if (gAudioIO->GetNumPlaybackChannels() == 0)
|
|
|
|
// No.
|
|
|
|
duplex = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!duplex)
|
|
|
|
mode = Mode::Right;
|
|
|
|
}
|
2016-06-08 22:08:51 +00:00
|
|
|
#endif
|
2016-05-29 18:28:06 +00:00
|
|
|
|
|
|
|
project->GetPlaybackScroller().Activate(mode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::StopScrolling()
|
|
|
|
{
|
|
|
|
const auto project = GetActiveProject();
|
|
|
|
if(project)
|
|
|
|
project->GetPlaybackScroller().Activate
|
|
|
|
(AudacityProject::PlaybackScroller::Mode::Off);
|
|
|
|
}
|
2018-01-10 18:23:01 +00:00
|
|
|
|
|
|
|
void ControlToolBar::CommitRecording()
|
|
|
|
{
|
|
|
|
const auto project = GetActiveProject();
|
|
|
|
project->GetTracks()->ApplyPendingTracks();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ControlToolBar::CancelRecording()
|
|
|
|
{
|
|
|
|
const auto project = GetActiveProject();
|
|
|
|
project->GetTracks()->ClearPendingTracks();
|
|
|
|
}
|