1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-17 17:17:40 +02:00

Merge branch 'master' into scrollplay

This commit is contained in:
Paul Licameli 2016-06-08 18:05:25 -04:00
commit d793152442
23 changed files with 5222 additions and 4730 deletions

View File

@ -67,8 +67,10 @@ from there. Audacity will look for a file called "Pause.png".
DEFINE_IMAGE( bmpCutPreviewDisabled, wxImage( 16, 16 ), wxT("CutPreviewDisabled")); DEFINE_IMAGE( bmpCutPreviewDisabled, wxImage( 16, 16 ), wxT("CutPreviewDisabled"));
DEFINE_IMAGE( bmpAppendRecord, wxImage( 16, 16 ), wxT("AppendRecord")); DEFINE_IMAGE( bmpAppendRecord, wxImage( 16, 16 ), wxT("AppendRecord"));
DEFINE_IMAGE( bmpAppendRecordDisabled, wxImage( 16, 16 ), wxT("AppendRecordDisabled")); DEFINE_IMAGE( bmpAppendRecordDisabled, wxImage( 16, 16 ), wxT("AppendRecordDisabled"));
DEFINE_IMAGE( bmpScrubDisabled, wxImage( 16, 16 ), wxT("ScrubDisabled")); DEFINE_IMAGE( bmpScrubDisabled, wxImage( 18, 16 ), wxT("ScrubDisabled"));
DEFINE_IMAGE( bmpScrub, wxImage( 16, 16 ), wxT("Scrub")); DEFINE_IMAGE( bmpScrub, wxImage( 18, 16 ), wxT("Scrub"));
DEFINE_IMAGE( bmpSeekDisabled, wxImage( 26, 16 ), wxT("SeekDisabled"));
DEFINE_IMAGE( bmpSeek, wxImage( 26, 16 ), wxT("Seek"));
SET_THEME_FLAGS( resFlagNewLine ); SET_THEME_FLAGS( resFlagNewLine );
DEFINE_IMAGE( bmpUpButtonLarge, wxImage( 48, 48 ), wxT("UpButtonLarge")); DEFINE_IMAGE( bmpUpButtonLarge, wxImage( 48, 48 ), wxT("UpButtonLarge"));

View File

@ -1529,10 +1529,14 @@ void AudacityApp::OnKeyDown(wxKeyEvent &event)
// Stop play, including scrub, but not record // Stop play, including scrub, but not record
auto project = ::GetActiveProject(); auto project = ::GetActiveProject();
auto token = project->GetAudioIOToken(); auto token = project->GetAudioIOToken();
auto &scrubber = project->GetScrubber();
auto scrubbing = scrubber.HasStartedScrubbing();
if (scrubbing)
scrubber.Cancel();
if((token > 0 && if((token > 0 &&
gAudioIO->IsAudioTokenActive(token) && gAudioIO->IsAudioTokenActive(token) &&
gAudioIO->GetNumCaptureChannels() == 0) || gAudioIO->GetNumCaptureChannels() == 0) ||
project->GetScrubber().HasStartedScrubbing()) scrubbing)
// ESC out of other play (but not record) // ESC out of other play (but not record)
project->OnStop(); project->OnStop();
else else

View File

@ -1798,6 +1798,10 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
if (mListener && captureChannels > 0) if (mListener && captureChannels > 0)
mListener->OnAudioIOStopRecording(); mListener->OnAudioIOStopRecording();
mStreamToken = 0; mStreamToken = 0;
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
return 0; return 0;
} }
@ -2100,6 +2104,10 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
mScrubQueue = 0; mScrubQueue = 0;
} }
#endif #endif
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
} }
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
@ -2530,6 +2538,9 @@ void AudioIO::StopStream()
// Tell UI to hide sample rate // Tell UI to hide sample rate
mListener->OnAudioIORate(0); mListener->OnAudioIORate(0);
} }
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
} }
void AudioIO::SetPaused(bool state) void AudioIO::SetPaused(bool state)

View File

@ -726,6 +726,7 @@ void AudacityProject::CreateMenusAndCommands()
c->AddCheck(wxT("ShowTranscriptionTB"), _("Transcri&ption Toolbar"), FN(OnShowTranscriptionToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); c->AddCheck(wxT("ShowTranscriptionTB"), _("Transcri&ption Toolbar"), FN(OnShowTranscriptionToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
/* i18n-hint: Clicking this menu item shows the toolbar with the big buttons on it (play record etc)*/ /* i18n-hint: Clicking this menu item shows the toolbar with the big buttons on it (play record etc)*/
c->AddCheck(wxT("ShowTransportTB"), _("&Transport Toolbar"), FN(OnShowTransportToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); c->AddCheck(wxT("ShowTransportTB"), _("&Transport Toolbar"), FN(OnShowTransportToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
c->AddCheck(wxT("ShowScrubbingTB"), _("Scrubbing Toolbar"), FN(OnShowScrubbingToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
c->AddSeparator(); c->AddSeparator();
@ -1807,6 +1808,8 @@ void AudacityProject::ModifyToolbarMenus()
return; return;
} }
mCommandManager.Check(wxT("ShowScrubbingTB"),
mToolManager->IsVisible(ScrubbingBarID));
mCommandManager.Check(wxT("ShowDeviceTB"), mCommandManager.Check(wxT("ShowDeviceTB"),
mToolManager->IsVisible(DeviceBarID)); mToolManager->IsVisible(DeviceBarID));
mCommandManager.Check(wxT("ShowEditTB"), mCommandManager.Check(wxT("ShowEditTB"),
@ -2292,19 +2295,30 @@ void AudacityProject::OnRecordAppend()
GetControlToolBar()->OnRecord(evt); GetControlToolBar()->OnRecord(evt);
} }
// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and "OnStopSelect" merged.
void AudacityProject::OnPlayStopSelect() void AudacityProject::OnPlayStopSelect()
{ {
DoPlayStopSelect(false, false); ControlToolBar *toolbar = GetControlToolBar();
wxCommandEvent evt;
if (DoPlayStopSelect(false, false))
toolbar->OnStop(evt);
else if (!gAudioIO->IsBusy()) {
//Otherwise, start playing (assuming audio I/O isn't busy)
//toolbar->SetPlay(true); // Not needed as set in PlayPlayRegion()
toolbar->SetStop(false);
// Will automatically set mLastPlayMode
toolbar->PlayCurrentRegion(false);
}
} }
// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and "OnStopSelect" merged. bool AudacityProject::DoPlayStopSelect(bool click, bool shift)
void AudacityProject::DoPlayStopSelect(bool click, bool shift)
{ {
wxCommandEvent evt;
ControlToolBar *toolbar = GetControlToolBar(); ControlToolBar *toolbar = GetControlToolBar();
//If busy, stop playing, make sure everything is unpaused. //If busy, stop playing, make sure everything is unpaused.
if (gAudioIO->IsStreamActive(GetAudioIOToken())) { if (GetScrubber().HasStartedScrubbing() ||
gAudioIO->IsStreamActive(GetAudioIOToken())) {
toolbar->SetPlay(false); //Pops toolbar->SetPlay(false); //Pops
toolbar->SetStop(true); //Pushes stop down toolbar->SetStop(true); //Pushes stop down
@ -2338,16 +2352,9 @@ void AudacityProject::DoPlayStopSelect(bool click, bool shift)
selection.setT0(time, false); selection.setT0(time, false);
ModifyState(false); // without bWantsAutoSave ModifyState(false); // without bWantsAutoSave
toolbar->OnStop(evt); return true;
}
else if (!gAudioIO->IsBusy()) {
//Otherwise, start playing (assuming audio I/O isn't busy)
//toolbar->SetPlay(true); // Not needed as set in PlayPlayRegion()
toolbar->SetStop(false);
// Will automatically set mLastPlayMode
toolbar->PlayCurrentRegion(false);
} }
return false;
} }
void AudacityProject::OnStopSelect() void AudacityProject::OnStopSelect()
@ -2384,7 +2391,6 @@ void AudacityProject::OnTogglePinnedHead()
auto ruler = GetRulerPanel(); auto ruler = GetRulerPanel();
if (ruler) if (ruler)
// Update button image // Update button image
ruler->UpdateButtonStates(); ruler->UpdateButtonStates();
auto &scrubber = GetScrubber(); auto &scrubber = GetScrubber();
@ -5439,6 +5445,12 @@ void AudacityProject::OnShowMixerToolBar()
ModifyToolbarMenus(); ModifyToolbarMenus();
} }
void AudacityProject::OnShowScrubbingToolBar()
{
mToolManager->ShowHide( ScrubbingBarID );
ModifyToolbarMenus();
}
void AudacityProject::OnShowSelectionToolBar() void AudacityProject::OnShowSelectionToolBar()
{ {
mToolManager->ShowHide( SelectionBarID ); mToolManager->ShowHide( SelectionBarID );

View File

@ -80,7 +80,7 @@ void OnSeekRightLong();
bool MakeReadyToPlay(bool loop = false, bool cutpreview = false); // Helper function that sets button states etc. bool MakeReadyToPlay(bool loop = false, bool cutpreview = false); // Helper function that sets button states etc.
void OnPlayStop(); void OnPlayStop();
void DoPlayStopSelect(bool click, bool shift); bool DoPlayStopSelect(bool click, bool shift);
void OnPlayStopSelect(); void OnPlayStopSelect();
void OnPlayOneSecond(); void OnPlayOneSecond();
void OnPlayToSelection(); void OnPlayToSelection();
@ -305,6 +305,7 @@ void OnShowSelectionToolBar();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING #ifdef EXPERIMENTAL_SPECTRAL_EDITING
void OnShowSpectralSelectionToolBar(); void OnShowSpectralSelectionToolBar();
#endif #endif
void OnShowScrubbingToolBar();
void OnShowToolsToolBar(); void OnShowToolsToolBar();
void OnShowTranscriptionToolBar(); void OnShowTranscriptionToolBar();
void OnResetToolBars(); void OnResetToolBars();

View File

@ -890,7 +890,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
mRuler = safenew AdornedRulerPanel( this, mRuler = safenew AdornedRulerPanel( this,
wxID_ANY, wxID_ANY,
wxDefaultPosition, wxDefaultPosition,
wxSize( -1, AdornedRulerPanel::GetRulerHeight() ), wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
&mViewInfo ); &mViewInfo );
// //
@ -4370,6 +4370,14 @@ MixerToolBar *AudacityProject::GetMixerToolBar()
NULL); NULL);
} }
ScrubbingToolBar *AudacityProject::GetScrubbingToolBar()
{
return dynamic_cast<ScrubbingToolBar*>
(mToolManager ?
mToolManager->GetToolBar(ScrubbingBarID) :
nullptr);
}
SelectionBar *AudacityProject::GetSelectionBar() SelectionBar *AudacityProject::GetSelectionBar()
{ {
return (SelectionBar *) return (SelectionBar *)

View File

@ -75,6 +75,7 @@ class EditToolBar;
class MeterToolBar; class MeterToolBar;
class MixerToolBar; class MixerToolBar;
class Scrubber; class Scrubber;
class ScrubbingToolBar;
class SelectionBar; class SelectionBar;
class SpectralSelectionBar; class SpectralSelectionBar;
class Toolbar; class Toolbar;
@ -442,6 +443,7 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame,
DeviceToolBar *GetDeviceToolBar(); DeviceToolBar *GetDeviceToolBar();
EditToolBar *GetEditToolBar(); EditToolBar *GetEditToolBar();
MixerToolBar *GetMixerToolBar(); MixerToolBar *GetMixerToolBar();
ScrubbingToolBar *GetScrubbingToolBar();
SelectionBar *GetSelectionBar(); SelectionBar *GetSelectionBar();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING #ifdef EXPERIMENTAL_SPECTRAL_EDITING
SpectralSelectionBar *GetSpectralSelectionBar(); SpectralSelectionBar *GetSpectralSelectionBar();

File diff suppressed because it is too large Load Diff

View File

@ -452,6 +452,10 @@ bool ScreenshotCommand::Apply(CommandExecutionContext context)
{ {
CaptureToolbar(context.GetProject()->GetToolManager(), TranscriptionBarID, fileName); CaptureToolbar(context.GetProject()->GetToolManager(), TranscriptionBarID, fileName);
} }
else if (captureMode.IsSameAs(wxT("scrubbing")))
{
CaptureToolbar(context.GetProject()->GetToolManager(), ScrubbingBarID, fileName);
}
else if (captureMode.IsSameAs(wxT("trackpanel"))) else if (captureMode.IsSameAs(wxT("trackpanel")))
{ {
TrackPanel *panel = context.GetProject()->GetTrackPanel(); TrackPanel *panel = context.GetProject()->GetTrackPanel();

View File

@ -1,7 +1,7 @@
/********************************************************************** /**********************************************************************
Audacity: A Digital Audio Editor Audacity: A Digital Audio Editor
Audacity(R) is copyright (c) 1999-2013 Audacity Team. Audacity(R) is copyright (c) 1999-2016 Audacity Team.
License: GPL v2. See License.txt. License: GPL v2. See License.txt.
BassTreble.cpp BassTreble.cpp
@ -12,16 +12,13 @@
\class EffectBassTreble \class EffectBassTreble
\brief A high shelf and low shelf filter. \brief A high shelf and low shelf filter.
The first pass applies the equalization and calculates the
peak value. The second pass, if enabled, normalizes to the
level set by the level control.
*//*******************************************************************/ *//*******************************************************************/
#include "../Audacity.h" #include "../Audacity.h"
#include "BassTreble.h" #include "BassTreble.h"
#include <math.h> #include <math.h>
#include <algorithm>
#include <wx/button.h> #include <wx/button.h>
#include <wx/intl.h> #include <wx/intl.h>
@ -37,8 +34,8 @@ enum
{ {
ID_Bass = 10000, ID_Bass = 10000,
ID_Treble, ID_Treble,
ID_Level, ID_Gain,
ID_Normalize, ID_Link
}; };
// Define keys, defaults, minimums, and maximums for the effect parameters // Define keys, defaults, minimums, and maximums for the effect parameters
@ -46,12 +43,11 @@ enum
// Name Type Key Def Min Max Scale // Name Type Key Def Min Max Scale
Param( Bass, double, XO("Bass"), 0.0, -30.0, 30.0, 1 ); Param( Bass, double, XO("Bass"), 0.0, -30.0, 30.0, 1 );
Param( Treble, double, XO("Treble"), 0.0, -30.0, 30.0, 1 ); Param( Treble, double, XO("Treble"), 0.0, -30.0, 30.0, 1 );
Param( Level, double, XO("Level"), -1.0, -30.0, 0.0, 1 ); Param( Gain, double, XO("Gain"), 0.0, -30.0, 30.0, 1 );
Param( Normalize, bool, XO("Normalize"), true, false, true, 1 ); Param( Link, bool, XO("Link Sliders"), false, false, true, 1 );
// Sliders are integer, so range is x 10 #include <wx/arrimpl.cpp>
// to allow 1 decimal place resolution WX_DEFINE_OBJARRAY(EffectBassTrebleStateArray);
static const int kSliderScale = 10;
// Used to communicate the type of the filter. // Used to communicate the type of the filter.
enum kShelfType enum kShelfType
@ -63,21 +59,21 @@ enum kShelfType
BEGIN_EVENT_TABLE(EffectBassTreble, wxEvtHandler) BEGIN_EVENT_TABLE(EffectBassTreble, wxEvtHandler)
EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider) EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider)
EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider) EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider)
EVT_SLIDER(ID_Level, EffectBassTreble::OnLevelSlider) EVT_SLIDER(ID_Gain, EffectBassTreble::OnGainSlider)
EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText) EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText)
EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText) EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText)
EVT_TEXT(ID_Level, EffectBassTreble::OnLevelText) EVT_TEXT(ID_Gain, EffectBassTreble::OnGainText)
EVT_CHECKBOX(ID_Normalize, EffectBassTreble::OnNormalize) EVT_CHECKBOX(ID_Link, EffectBassTreble::OnLinkCheckbox)
END_EVENT_TABLE() END_EVENT_TABLE()
EffectBassTreble::EffectBassTreble() EffectBassTreble::EffectBassTreble()
{ {
dB_bass = DEF_Bass; mBass = DEF_Bass;
dB_treble = DEF_Treble; mTreble = DEF_Treble;
dB_level = DEF_Level; mGain = DEF_Gain;
mbNormalize = DEF_Normalize; mLink = DEF_Link;
SetLinearEffectFlag(false); SetLinearEffectFlag(true);
} }
EffectBassTreble::~EffectBassTreble() EffectBassTreble::~EffectBassTreble()
@ -93,7 +89,7 @@ wxString EffectBassTreble::GetSymbol()
wxString EffectBassTreble::GetDescription() wxString EffectBassTreble::GetDescription()
{ {
return XO("Increases or decreases the lower frequencies and higher frequencies of your audio independently"); return XO("Simple tone control effect");
} }
// EffectIdentInterface implementation // EffectIdentInterface implementation
@ -103,6 +99,16 @@ EffectType EffectBassTreble::GetType()
return EffectTypeProcess; return EffectTypeProcess;
} }
bool EffectBassTreble::SupportsRealtime()
{
#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS)
return true;
#else
return false;
#endif
}
// EffectClientInterface implementation // EffectClientInterface implementation
int EffectBassTreble::GetAudioInCount() int EffectBassTreble::GetAudioInCount()
@ -117,61 +123,57 @@ int EffectBassTreble::GetAudioOutCount()
bool EffectBassTreble::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) bool EffectBassTreble::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{ {
if (GetPass() == 1) InstanceInit(mMaster, mSampleRate);
{
const float slope = 0.4f; // same slope for both filters
const double hzBass = 250.0f;
const double hzTreble = 4000.0f;
//(re)initialise filter parameters
xn1Bass=xn2Bass=yn1Bass=yn2Bass=0;
xn1Treble=xn2Treble=yn1Treble=yn2Treble=0;
// Compute coefficents of the low shelf biquand IIR filter
Coefficents(hzBass, slope, dB_bass, kBass,
a0Bass, a1Bass, a2Bass,
b0Bass, b1Bass, b2Bass);
// Compute coefficents of the high shelf biquand IIR filter
Coefficents(hzTreble, slope, dB_treble, kTreble,
a0Treble, a1Treble, a2Treble,
b0Treble, b1Treble, b2Treble);
}
return true; return true;
} }
sampleCount EffectBassTreble::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) sampleCount EffectBassTreble::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen)
{ {
float *ibuf = inBlock[0]; return InstanceProcess(mMaster, inBlock, outBlock, blockLen);
float *obuf = outBlock[0];
if (GetPass() == 1)
{
for (sampleCount i = 0; i < blockLen; i++)
{
obuf[i] = DoFilter(ibuf[i]) / mPreGain;
}
}
else
{
float gain = DB_TO_LINEAR(dB_level) / mMax;
for (sampleCount i = 0; i < blockLen; i++)
{
// Normalize to specified level
obuf[i] = ibuf[i] * (mPreGain * gain);
}
} }
return blockLen; bool EffectBassTreble::RealtimeInitialize()
{
SetBlockSize(512);
mSlaves.Clear();
return true;
}
bool EffectBassTreble::RealtimeAddProcessor(int WXUNUSED(numChannels), float sampleRate)
{
EffectBassTrebleState slave;
InstanceInit(slave, sampleRate);
mSlaves.Add(slave);
return true;
}
bool EffectBassTreble::RealtimeFinalize()
{
mSlaves.Clear();
return true;
}
sampleCount EffectBassTreble::RealtimeProcess(int group,
float **inbuf,
float **outbuf,
sampleCount numSamples)
{
return InstanceProcess(mSlaves[group], inbuf, outbuf, numSamples);
} }
bool EffectBassTreble::GetAutomationParameters(EffectAutomationParameters & parms) bool EffectBassTreble::GetAutomationParameters(EffectAutomationParameters & parms)
{ {
parms.Write(KEY_Bass, dB_bass); parms.Write(KEY_Bass, mBass);
parms.Write(KEY_Treble, dB_treble); parms.Write(KEY_Treble, mTreble);
parms.Write(KEY_Level, dB_level); parms.Write(KEY_Gain, mGain);
parms.Write(KEY_Normalize, mbNormalize); parms.Write(KEY_Link, mLink);
return true; return true;
} }
@ -180,144 +182,88 @@ bool EffectBassTreble::SetAutomationParameters(EffectAutomationParameters & parm
{ {
ReadAndVerifyDouble(Bass); ReadAndVerifyDouble(Bass);
ReadAndVerifyDouble(Treble); ReadAndVerifyDouble(Treble);
ReadAndVerifyDouble(Level); ReadAndVerifyDouble(Gain);
ReadAndVerifyBool(Normalize); ReadAndVerifyBool(Link);
dB_bass = Bass; mBass = Bass;
dB_treble = Treble; mTreble = Treble;
dB_level = Level; mGain = Gain;
mbNormalize = Normalize; mLink = Link;
return true; return true;
} }
bool EffectBassTreble::CheckWhetherSkipEffect()
{
return (mBass == 0.0 && mTreble == 0.0 && mGain == 0.0);
}
// Effect implementation // Effect implementation
bool EffectBassTreble::Startup()
{
wxString base = wxT("/Effects/BassTreble/");
// Migrate settings from 2.1.0 or before
// Already migrated, so bail
if (gPrefs->Exists(base + wxT("Migrated")))
{
return true;
}
// Load the old "current" settings
if (gPrefs->Exists(base))
{
int readBool;
gPrefs->Read(base + wxT("Bass"), &dB_bass, 0.0);
gPrefs->Read(base + wxT("Treble"), &dB_treble, 0.0);
gPrefs->Read(base + wxT("Level"), &dB_level, -1.0);
gPrefs->Read(base + wxT("Normalize"), &readBool, 1 );
// Validate data
dB_level = (dB_level > 0) ? 0 : dB_level;
mbNormalize = (readBool != 0);
SaveUserPreset(GetCurrentSettingsGroup());
// Do not migrate again
gPrefs->Write(base + wxT("Migrated"), true);
gPrefs->Flush();
}
return true;
}
bool EffectBassTreble::InitPass1()
{
mMax = 0.0;
// Integer format tracks require headroom to avoid clipping
// when saved between passes (bug 619)
if (mbNormalize) // don't need to calculate this if only doing one pass.
{
// Up to (gain + 6dB) headroom required for treble boost (experimental).
mPreGain = (dB_treble > 0) ? (dB_treble + 6.0) : 0.0;
if (dB_bass >= 0)
{
mPreGain = (mPreGain > dB_bass) ? mPreGain : dB_bass;
}
else
{
// Up to 6 dB headroom reaquired for bass cut (experimental)
mPreGain = (mPreGain > 6.0) ? mPreGain : 6.0;
}
mPreGain = (exp(log(10.0) * mPreGain / 20)); // to linear
}
else
{
mPreGain = 1.0; // Unity gain
}
return true;
}
bool EffectBassTreble::InitPass2()
{
return mbNormalize && mMax != 0;
}
void EffectBassTreble::PopulateOrExchange(ShuttleGui & S) void EffectBassTreble::PopulateOrExchange(ShuttleGui & S)
{ {
S.StartVerticalLay(0); S.SetBorder(5);
S.AddSpace(0, 5);
S.StartStatic(_("Tone controls"));
{ {
S.StartMultiColumn(3, wxEXPAND); S.StartMultiColumn(3, wxEXPAND);
S.SetStretchyCol(2);
{ {
S.SetStretchyCol(2);
// Bass control // Bass control
FloatingPointValidator<double> vldBass(1, &dB_bass); FloatingPointValidator<double> vldBass(1, &mBass);
vldBass.SetRange(MIN_Bass, MAX_Bass); vldBass.SetRange(MIN_Bass, MAX_Bass);
mBassT = S.Id(ID_Bass).AddTextBox(_("&Bass (dB):"), wxT(""), 10); mBassT = S.Id(ID_Bass).AddTextBox(_("&Bass (dB):"), wxT(""), 10);
mBassT->SetName(_("Bass (dB):")); mBassT->SetName(_("Bass (dB):"));
mBassT->SetValidator(vldBass); mBassT->SetValidator(vldBass);
S.SetStyle(wxSL_HORIZONTAL); S.SetStyle(wxSL_HORIZONTAL);
mBassS = S.Id(ID_Bass).AddSlider(wxT(""), 0, MAX_Bass * kSliderScale, MIN_Bass * kSliderScale); mBassS = S.Id(ID_Bass).AddSlider(wxT(""), 0, MAX_Bass * SCL_Bass, MIN_Bass * SCL_Bass);
mBassS->SetName(_("Bass")); mBassS->SetName(_("Bass"));
mBassS->SetPageSize(30);
// Treble control // Treble control
FloatingPointValidator<double> vldTreble(1, &dB_treble); FloatingPointValidator<double> vldTreble(1, &mTreble);
vldTreble.SetRange(MIN_Treble, MAX_Treble); vldTreble.SetRange(MIN_Treble, MAX_Treble);
mTrebleT = S.Id(ID_Treble).AddTextBox(_("&Treble (dB):"), wxT(""), 10); mTrebleT = S.Id(ID_Treble).AddTextBox(_("&Treble (dB):"), wxT(""), 10);
mTrebleT->SetValidator(vldTreble); mTrebleT->SetValidator(vldTreble);
S.SetStyle(wxSL_HORIZONTAL); S.SetStyle(wxSL_HORIZONTAL);
mTrebleS = S.Id(ID_Treble).AddSlider(wxT(""), 0, MAX_Treble * kSliderScale, MIN_Treble * kSliderScale); mTrebleS = S.Id(ID_Treble).AddSlider(wxT(""), 0, MAX_Treble * SCL_Treble, MIN_Treble * SCL_Treble);
mTrebleS->SetName(_("Treble")); mTrebleS->SetName(_("Treble"));
mTrebleS->SetPageSize(30); }
S.EndMultiColumn();
}
S.EndStatic();
// Level control S.StartStatic("Output");
FloatingPointValidator<double> vldLevel(1, &dB_level); {
vldLevel.SetRange(MIN_Level, MAX_Level); S.StartMultiColumn(3, wxEXPAND);
mLevelT = S.Id(ID_Level).AddTextBox(_("&Level (dB):"), wxT(""), 10); {
mLevelT->SetValidator(vldLevel); S.SetStretchyCol(2);
// Gain control
FloatingPointValidator<double> vldGain(1, &mGain);
vldGain.SetRange(MIN_Gain, MAX_Gain);
mGainT = S.Id(ID_Gain).AddTextBox(_("&Volume (dB):"), wxT(""), 10);
mGainT->SetValidator(vldGain);
S.SetStyle(wxSL_HORIZONTAL); S.SetStyle(wxSL_HORIZONTAL);
mLevelS = S.Id(ID_Level).AddSlider(wxT(""), 0, MAX_Level * kSliderScale, MIN_Level * kSliderScale); mGainS = S.Id(ID_Gain).AddSlider(wxT(""), 0, MAX_Gain * SCL_Gain, MIN_Gain * SCL_Gain);
mLevelS->SetName(_("Level")); mGainS->SetName(_("Level"));
mLevelS->SetPageSize(30);
} }
S.EndMultiColumn(); S.EndMultiColumn();
// Normalize checkbox S.StartMultiColumn(2, wxCENTER);
S.StartHorizontalLay(wxLEFT, true);
{ {
mNormalizeCheckBox = S.Id(ID_Normalize).AddCheckBox(_("&Enable level control"), // Link checkbox
DEF_Normalize ? wxT("true") : wxT("false")); mLinkCheckBox = S.Id(ID_Link).AddCheckBox(_("Link Volume control to Tone controls"),
mWarning = S.AddVariableText(wxT(""), false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); DEF_Link ? wxT("true") : wxT("false"));
} }
S.EndHorizontalLay(); S.EndMultiColumn();
} }
S.EndVerticalLay(); S.EndStatic();
return;
} }
bool EffectBassTreble::TransferDataToWindow() bool EffectBassTreble::TransferDataToWindow()
@ -327,12 +273,10 @@ bool EffectBassTreble::TransferDataToWindow()
return false; return false;
} }
mBassS->SetValue((int) dB_bass * kSliderScale + 0.5); mBassS->SetValue((int) (mBass * SCL_Bass));
mTrebleS->SetValue((int) dB_treble * kSliderScale + 0.5); mTrebleS->SetValue((int) mTreble *SCL_Treble);
mLevelS->SetValue((int) dB_level * kSliderScale + 0.5); mGainS->SetValue((int) mGain * SCL_Gain);
mNormalizeCheckBox->SetValue(mbNormalize); mLinkCheckBox->SetValue(mLink);
UpdateUI();
return true; return true;
} }
@ -344,18 +288,96 @@ bool EffectBassTreble::TransferDataFromWindow()
return false; return false;
} }
mbNormalize = mNormalizeCheckBox->GetValue();
return true; return true;
} }
// EffectBassTreble implementation // EffectBassTreble implementation
void EffectBassTreble::Coefficents(double hz, float slope, double gain, int type, void EffectBassTreble::InstanceInit(EffectBassTrebleState & data, float sampleRate)
float& a0, float& a1, float& a2,
float& b0, float& b1, float& b2)
{ {
double w = 2 * M_PI * hz / mSampleRate; data.samplerate = sampleRate;
data.slope = 0.4f; // same slope for both filters
data.hzBass = 250.0f; // could be tunable in a more advanced version
data.hzTreble = 4000.0f; // could be tunable in a more advanced version
data.a0Bass = 1;
data.a1Bass = 0;
data.a2Bass = 0;
data.b0Bass = 0;
data.b1Bass = 0;
data.b2Bass = 0;
data.a0Treble = 1;
data.a1Treble = 0;
data.a2Treble = 0;
data.b0Treble = 0;
data.b1Treble = 0;
data.b2Treble = 0;
data.xn1Bass = 0;
data.xn2Bass = 0;
data.yn1Bass = 0;
data.yn2Bass = 0;
data.xn1Treble = 0;
data.xn2Treble = 0;
data.yn1Treble = 0;
data.yn2Treble = 0;
data.bass = -1;
data.treble = -1;
data.gain = DB_TO_LINEAR(mGain);
}
// EffectClientInterface implementation
sampleCount EffectBassTreble::InstanceProcess(EffectBassTrebleState & data,
float **inBlock,
float **outBlock,
sampleCount blockLen)
{
float *ibuf = inBlock[0];
float *obuf = outBlock[0];
// Set value to ensure correct rounding
double oldBass = DB_TO_LINEAR(mBass);
double oldTreble = DB_TO_LINEAR(mTreble);
data.gain = DB_TO_LINEAR(mGain);
// Compute coefficents of the low shelf biquand IIR filter
if (data.bass != oldBass)
Coefficents(data.hzBass, data.slope, mBass, data.samplerate, kBass,
data.a0Bass, data.a1Bass, data.a2Bass,
data.b0Bass, data.b1Bass, data.b2Bass);
// Compute coefficents of the high shelf biquand IIR filter
if (data.treble != oldTreble)
Coefficents(data.hzTreble, data.slope, mTreble, data.samplerate, kTreble,
data.a0Treble, data.a1Treble, data.a2Treble,
data.b0Treble, data.b1Treble, data.b2Treble);
for (sampleCount i = 0; i < blockLen; i++) {
obuf[i] = DoFilter(data, ibuf[i]) * data.gain;
}
return blockLen;
}
// Effect implementation
void EffectBassTreble::Coefficents(double hz, double slope, double gain, double samplerate, int type,
double& a0, double& a1, double& a2,
double& b0, double& b1, double& b2)
{
double w = 2 * M_PI * hz / samplerate;
double a = exp(log(10.0) * gain / 40); double a = exp(log(10.0) * gain / 40);
double b = sqrt((a * a + 1) / slope - (pow((a - 1), 2))); double b = sqrt((a * a + 1) / slope - (pow((a - 1), 2)));
@ -379,109 +401,112 @@ void EffectBassTreble::Coefficents(double hz, float slope, double gain, int type
} }
} }
float EffectBassTreble::DoFilter(float in) float EffectBassTreble::DoFilter(EffectBassTrebleState & data, float in)
{ {
// Bass filter // Bass filter
float out = (b0Bass * in + b1Bass * xn1Bass + b2Bass * xn2Bass - float out = (data.b0Bass * in + data.b1Bass * data.xn1Bass + data.b2Bass * data.xn2Bass -
a1Bass * yn1Bass - a2Bass * yn2Bass) / a0Bass; data.a1Bass * data.yn1Bass - data.a2Bass * data.yn2Bass) / data.a0Bass;
xn2Bass = xn1Bass; data.xn2Bass = data.xn1Bass;
xn1Bass = in; data.xn1Bass = in;
yn2Bass = yn1Bass; data.yn2Bass = data.yn1Bass;
yn1Bass = out; data.yn1Bass = out;
// Treble filter // Treble filter
in = out; in = out;
out = (b0Treble * in + b1Treble * xn1Treble + b2Treble * xn2Treble - out = (data.b0Treble * in + data.b1Treble * data.xn1Treble + data.b2Treble * data.xn2Treble -
a1Treble * yn1Treble - a2Treble * yn2Treble) / a0Treble; data.a1Treble * data.yn1Treble - data.a2Treble * data.yn2Treble) / data.a0Treble;
xn2Treble = xn1Treble; data.xn2Treble = data.xn1Treble;
xn1Treble = in; data.xn1Treble = in;
yn2Treble = yn1Treble; data.yn2Treble = data.yn1Treble;
yn1Treble = out; data.yn1Treble = out;
// Retain the maximum value for use in the normalization pass
if(mMax < fabs(out))
mMax = fabs(out);
return out; return out;
} }
void EffectBassTreble::UpdateUI()
{
double bass, treble, level;
mBassT->GetValue().ToDouble(&bass);
mTrebleT->GetValue().ToDouble(&treble);
mLevelT->GetValue().ToDouble(&level);
bool enable = mNormalizeCheckBox->GetValue();
// Disallow level control if disabled
mLevelT->Enable(enable);
mLevelS->Enable(enable);
if (bass == 0 && treble == 0 && !enable)
{
// Disallow Apply if nothing to do
EnableApply(false);
mWarning->SetLabel(_(" No change to apply."));
}
else
{
if (level > 0 && enable)
{
// Disallow Apply if level enabled and > 0
EnableApply(false);
mWarning->SetLabel(_(": Maximum 0 dB."));
}
else
{
// Apply enabled
EnableApply(true);
mWarning->SetLabel(wxT(""));
}
}
}
void EffectBassTreble::OnBassText(wxCommandEvent & WXUNUSED(evt)) void EffectBassTreble::OnBassText(wxCommandEvent & WXUNUSED(evt))
{ {
mBassT->GetValidator()->TransferFromWindow(); double oldBass = mBass;
mBassS->SetValue((int) floor(dB_bass * kSliderScale + 0.5));
UpdateUI(); if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
if (mLink) UpdateGain(oldBass, kBass);
mBassS->SetValue((int) (mBass * SCL_Bass));
} }
void EffectBassTreble::OnTrebleText(wxCommandEvent & WXUNUSED(evt)) void EffectBassTreble::OnTrebleText(wxCommandEvent & WXUNUSED(evt))
{ {
mTrebleT->GetValidator()->TransferFromWindow(); double oldTreble = mTreble;
mTrebleS->SetValue((int) floor(dB_treble * kSliderScale + 0.5));
UpdateUI(); if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
} }
void EffectBassTreble::OnLevelText(wxCommandEvent & WXUNUSED(evt)) if (mLink) UpdateGain(oldTreble, kTreble);
mTrebleS->SetValue((int) (mTreble * SCL_Treble));
}
void EffectBassTreble::OnGainText(wxCommandEvent & WXUNUSED(evt))
{ {
mLevelT->GetValidator()->TransferFromWindow(); if (!EnableApply(mUIParent->TransferDataFromWindow()))
mLevelS->SetValue((int) floor(dB_level * kSliderScale + 0.5)); {
UpdateUI(); return;
}
mGainS->SetValue((int) (mGain * SCL_Gain));
} }
void EffectBassTreble::OnBassSlider(wxCommandEvent & evt) void EffectBassTreble::OnBassSlider(wxCommandEvent & evt)
{ {
dB_bass = (double) evt.GetInt() / kSliderScale; double oldBass = mBass;
mBass = (double) evt.GetInt() / SCL_Bass;
mBassT->GetValidator()->TransferToWindow(); mBassT->GetValidator()->TransferToWindow();
UpdateUI();
if (mLink) UpdateGain(oldBass, kBass);
EnableApply(mUIParent->Validate());
} }
void EffectBassTreble::OnTrebleSlider(wxCommandEvent & evt) void EffectBassTreble::OnTrebleSlider(wxCommandEvent & evt)
{ {
dB_treble = (double) evt.GetInt() / kSliderScale; double oldTreble = mTreble;
mTreble = (double) evt.GetInt() / SCL_Treble;
mTrebleT->GetValidator()->TransferToWindow(); mTrebleT->GetValidator()->TransferToWindow();
UpdateUI();
if (mLink) UpdateGain(oldTreble, kTreble);
EnableApply(mUIParent->Validate());
} }
void EffectBassTreble::OnLevelSlider(wxCommandEvent & evt) void EffectBassTreble::OnGainSlider(wxCommandEvent & evt)
{ {
dB_level = (double) evt.GetInt() / kSliderScale; mGain = (double) evt.GetInt() / SCL_Gain;
mLevelT->GetValidator()->TransferToWindow(); mGainT->GetValidator()->TransferToWindow();
UpdateUI();
EnableApply(mUIParent->Validate());
} }
void EffectBassTreble::OnNormalize(wxCommandEvent& WXUNUSED(evt)) void EffectBassTreble::OnLinkCheckbox(wxCommandEvent& evt)
{ {
UpdateUI(); mLink = mLinkCheckBox->GetValue();
}
void EffectBassTreble::UpdateGain(double oldVal, int control)
{
double newVal;
oldVal = (oldVal > 0)? oldVal / 2.0 : oldVal / 4.0;
if (control == kBass)
newVal = (mBass > 0)? mBass / 2.0 : mBass / 4.0;
else
newVal = (mTreble > 0)? mTreble / 2.0 : mTreble / 4.0;
mGain -= newVal - oldVal;
mGain = std::min(MAX_Gain, std::max(MIN_Gain, mGain));
mGainS->SetValue(mGain);
mGainT->GetValidator()->TransferToWindow();
} }

View File

@ -1,7 +1,7 @@
/********************************************************************** /**********************************************************************
Audacity: A Digital Audio Editor Audacity: A Digital Audio Editor
Audacity(R) is copyright (c) 1999-2013 Audacity Team. Audacity(R) is copyright (c) 1999-2016 Audacity Team.
License: GPL v2. See License.txt. License: GPL v2. See License.txt.
BassTreble.h (two shelf filters) BassTreble.h (two shelf filters)
@ -12,12 +12,12 @@
#ifndef __AUDACITY_EFFECT_BASS_TREBLE__ #ifndef __AUDACITY_EFFECT_BASS_TREBLE__
#define __AUDACITY_EFFECT_BASS_TREBLE__ #define __AUDACITY_EFFECT_BASS_TREBLE__
#include <wx/checkbox.h>
#include <wx/event.h> #include <wx/event.h>
#include <wx/slider.h> #include <wx/slider.h>
#include <wx/stattext.h> #include <wx/stattext.h>
#include <wx/string.h> #include <wx/string.h>
#include <wx/textctrl.h> #include <wx/textctrl.h>
#include <wx/checkbox.h>
#include "Effect.h" #include "Effect.h"
@ -25,7 +25,23 @@ class ShuttleGui;
#define BASSTREBLE_PLUGIN_SYMBOL XO("Bass and Treble") #define BASSTREBLE_PLUGIN_SYMBOL XO("Bass and Treble")
class EffectBassTreble final : public Effect class EffectBassTrebleState
{
public:
float samplerate;
double treble;
double bass;
double gain;
double slope, hzBass, hzTreble;
double a0Bass, a1Bass, a2Bass, b0Bass, b1Bass, b2Bass;
double a0Treble, a1Treble, a2Treble, b0Treble, b1Treble, b2Treble;
double xn1Bass, xn2Bass, yn1Bass, yn2Bass;
double xn1Treble, xn2Treble, yn1Treble, yn2Treble;
};
WX_DECLARE_OBJARRAY(EffectBassTrebleState, EffectBassTrebleStateArray);
class EffectBassTreble : public Effect
{ {
public: public:
EffectBassTreble(); EffectBassTreble();
@ -39,6 +55,7 @@ public:
// EffectIdentInterface implementation // EffectIdentInterface implementation
EffectType GetType() override; EffectType GetType() override;
bool SupportsRealtime() override;
// EffectClientInterface implementation // EffectClientInterface implementation
@ -46,57 +63,64 @@ public:
int GetAudioOutCount() override; int GetAudioOutCount() override;
bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL) override; bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL) override;
sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) override; sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) override;
bool RealtimeInitialize() override;
bool RealtimeAddProcessor(int numChannels, float sampleRate) override;
bool RealtimeFinalize() override;
sampleCount RealtimeProcess(int group,
float **inbuf,
float **outbuf,
sampleCount numSamples) override;
bool GetAutomationParameters(EffectAutomationParameters & parms) override; bool GetAutomationParameters(EffectAutomationParameters & parms) override;
bool SetAutomationParameters(EffectAutomationParameters & parms) override; bool SetAutomationParameters(EffectAutomationParameters & parms) override;
// Effect Implementation
bool Startup() override; // Effect Implementation
bool InitPass1() override;
bool InitPass2() override;
void PopulateOrExchange(ShuttleGui & S) override; void PopulateOrExchange(ShuttleGui & S) override;
bool TransferDataToWindow() override; bool TransferDataToWindow() override;
bool TransferDataFromWindow() override; bool TransferDataFromWindow() override;
bool CheckWhetherSkipEffect() override;
private: private:
// EffectBassTreble implementation // EffectBassTreble implementation
void Coefficents(double hz, float slope, double gain, int type, void InstanceInit(EffectBassTrebleState & data, float sampleRate);
float& a0, float& a1, float& a2, float& b0, float& b1, float& b2); sampleCount InstanceProcess(EffectBassTrebleState & data, float **inBlock, float **outBlock, sampleCount blockLen);
float DoFilter(float in);
void UpdateUI(); void Coefficents(double hz, double slope, double gain, double samplerate, int type,
double& a0, double& a1, double& a2, double& b0, double& b1, double& b2);
float DoFilter(EffectBassTrebleState & data, float in);
void OnBassText(wxCommandEvent & evt); void OnBassText(wxCommandEvent & evt);
void OnTrebleText(wxCommandEvent & evt); void OnTrebleText(wxCommandEvent & evt);
void OnLevelText(wxCommandEvent & evt); void OnGainText(wxCommandEvent & evt);
void OnBassSlider(wxCommandEvent & evt); void OnBassSlider(wxCommandEvent & evt);
void OnTrebleSlider(wxCommandEvent & evt); void OnTrebleSlider(wxCommandEvent & evt);
void OnLevelSlider(wxCommandEvent & evt); void OnGainSlider(wxCommandEvent & evt);
void OnNormalize(wxCommandEvent & evt); void OnLinkCheckbox(wxCommandEvent & evt);
// Auto-adjust gain to reduce variation in peak level
void UpdateGain(double oldVal, int control );
private: private:
float xn1Bass, xn2Bass, yn1Bass, yn2Bass, EffectBassTrebleState mMaster;
wBass, swBass, cwBass, aBass, bBass, EffectBassTrebleStateArray mSlaves;
a0Bass, a1Bass, a2Bass, b0Bass, b1Bass, b2Bass;
// High shelf
float xn1Treble, xn2Treble, yn1Treble, yn2Treble,
wTreble, swTreble, cwTreble, aTreble, bTreble,
b0Treble, b1Treble, b2Treble, a0Treble, a1Treble, a2Treble;
double dB_bass, dB_treble, dB_level; double mBass;
double mMax; double mTreble;
bool mbNormalize; double mGain;
double mPreGain; bool mLink;
wxSlider *mBassS; wxSlider *mBassS;
wxSlider *mTrebleS; wxSlider *mTrebleS;
wxSlider *mLevelS; wxSlider *mGainS;
wxTextCtrl *mBassT; wxTextCtrl *mBassT;
wxTextCtrl *mTrebleT; wxTextCtrl *mTrebleT;
wxTextCtrl *mLevelT; wxTextCtrl *mGainT;
wxCheckBox *mNormalizeCheckBox;
wxStaticText *mWarning; wxCheckBox *mLinkCheckBox;
DECLARE_EVENT_TABLE(); DECLARE_EVENT_TABLE();
}; };

View File

@ -168,6 +168,8 @@ void ControlToolBar::Populate()
bmpCutPreview, bmpCutPreview, bmpCutPreviewDisabled); bmpCutPreview, bmpCutPreview, bmpCutPreviewDisabled);
MakeAlternateImages(*mPlay, 3, MakeAlternateImages(*mPlay, 3,
bmpScrub, bmpScrub, bmpScrubDisabled); bmpScrub, bmpScrub, bmpScrubDisabled);
MakeAlternateImages(*mPlay, 4,
bmpSeek, bmpSeek, bmpSeekDisabled);
mPlay->FollowModifierKeys(); mPlay->FollowModifierKeys();
mStop = MakeButton( bmpStop, bmpStop, bmpStopDisabled , mStop = MakeButton( bmpStop, bmpStop, bmpStopDisabled ,

View File

@ -62,7 +62,7 @@ class ControlToolBar final : public ToolBar {
// Choice among the appearances of the play button: // Choice among the appearances of the play button:
enum class PlayAppearance { enum class PlayAppearance {
Straight, Looped, CutPreview, Scrub Straight, Looped, CutPreview, Scrub, Seek
}; };
//These allow buttons to be controlled externally: //These allow buttons to be controlled externally:

View File

@ -330,3 +330,203 @@ void EditToolBar::EnableDisableButtons()
mButtons[ETBSyncLockID]->PopUp(); mButtons[ETBSyncLockID]->PopUp();
#endif #endif
} }
// PRL: to do: move the below to its own file
// Much of this is imitative of EditToolBar. Should there be a common base
// class?
#include "../Audacity.h"
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/event.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/sizer.h>
#include <wx/tooltip.h>
#endif
#include "../AllThemeResources.h"
#include "../AudioIO.h"
#include "../ImageManipulation.h"
#include "../Internat.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../Theme.h"
#include "../Track.h"
#include "../UndoManager.h"
#include "../widgets/AButton.h"
#include "../tracks/ui/Scrubbing.h"
#include "../Experimental.h"
IMPLEMENT_CLASS(ScrubbingToolBar, ToolBar);
//const int BUTTON_WIDTH = 27;
//const int SEPARATOR_WIDTH = 14;
////////////////////////////////////////////////////////////
/// Methods for ScrubbingToolBar
////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE( ScrubbingToolBar, ToolBar )
EVT_COMMAND_RANGE( STBStartID,
STBStartID + STBNumButtons - 1,
wxEVT_COMMAND_BUTTON_CLICKED,
ScrubbingToolBar::OnButton )
END_EVENT_TABLE()
//Standard contructor
ScrubbingToolBar::ScrubbingToolBar()
: ToolBar(ScrubbingBarID, _("Scrub"), wxT("Scrub"))
{
}
ScrubbingToolBar::~ScrubbingToolBar()
{
}
void ScrubbingToolBar::Create(wxWindow * parent)
{
ToolBar::Create(parent);
}
/// This is a convenience function that allows for button creation in
/// MakeButtons() with fewer arguments
/// Very similar to code in ControlToolBar...
AButton *ScrubbingToolBar::AddButton
(teBmps eEnabledUp, teBmps eEnabledDown, teBmps eDisabled,
int id,
const wxChar *label,
bool toggle)
{
AButton *&r = mButtons[id];
r = ToolBar::MakeButton
(this,
bmpRecoloredUpSmall, bmpRecoloredDownSmall, bmpRecoloredHiliteSmall,
eEnabledUp, eEnabledDown, eDisabled,
wxWindowID(id),
wxDefaultPosition,
toggle,
theTheme.ImageSize( bmpRecoloredUpSmall ));
r->SetLabel(label);
// JKC: Unlike ControlToolBar, does not have a focus rect. Shouldn't it?
// r->SetFocusRect( r->GetRect().Deflate( 4, 4 ) );
Add( r, 0, wxALIGN_CENTER );
return r;
}
void ScrubbingToolBar::Populate()
{
MakeButtonBackgroundsSmall();
/* Buttons */
AddButton(bmpPlay, bmpStop, bmpPlayDisabled, STBStartID,
_("Start scrubbing"), true);
AddButton(bmpScrub, bmpScrub, bmpScrubDisabled, STBScrubID,
_("Scrub"), true);
AddButton(bmpSeek, bmpSeek, bmpSeekDisabled, STBSeekID,
_("Seek"), true);
RegenerateTooltips();
}
void ScrubbingToolBar::UpdatePrefs()
{
RegenerateTooltips();
// Set label to pull in language change
SetLabel(_("Scrubbing"));
// Give base class a chance
ToolBar::UpdatePrefs();
}
void ScrubbingToolBar::RegenerateTooltips()
{
#if wxUSE_TOOLTIPS
/* i18n-hint: These commands assist the user in finding a sound by ear. ...
"Scrubbing" is variable-speed playback, ...
"Seeking" is normal speed playback but with skips
*/
auto project = GetActiveProject();
if (project) {
auto startStop = mButtons[STBStartID];
auto &scrubber = project->GetScrubber();
if(scrubber.HasStartedScrubbing() || scrubber.IsScrubbing()) {
if (scrubber.Seeks())
startStop->SetToolTip(_("Stop seeking"));
else
startStop->SetToolTip(_("Stop scrubbing"));
}
else {
if (scrubber.Seeks())
startStop->SetToolTip(_("Start seeking"));
else
startStop->SetToolTip(_("Start scrubbing"));
}
}
mButtons[STBScrubID]->SetToolTip(_("Scrub"));
mButtons[STBSeekID]->SetToolTip(_("Seek"));
#endif
}
void ScrubbingToolBar::OnButton(wxCommandEvent &event)
{
AudacityProject *p = GetActiveProject();
if (!p) return;
auto &scrubber = p->GetScrubber();
int id = event.GetId();
switch (id) {
case STBStartID:
scrubber.OnStartStop(event);
break;
case STBScrubID:
scrubber.OnScrub(event);
break;
case STBSeekID:
scrubber.OnSeek(event);
break;
default:
wxASSERT(false);
}
EnableDisableButtons();
}
void ScrubbingToolBar::EnableDisableButtons()
{
const auto scrubButton = mButtons[STBScrubID];
scrubButton->SetEnabled(true);
const auto seekButton = mButtons[STBSeekID];
seekButton->SetEnabled(true);
AudacityProject *p = GetActiveProject();
if (!p) return;
auto &scrubber = p->GetScrubber();
if (scrubber.Scrubs())
scrubButton->PushDown();
else
scrubButton->PopUp();
if (scrubber.Seeks())
seekButton->PushDown();
else
seekButton->PopUp();
const auto startButton = mButtons[STBStartID];
if (scrubber.CanScrub())
startButton->Enable();
else
startButton->Disable();
}

View File

@ -97,5 +97,66 @@ class EditToolBar final : public ToolBar {
DECLARE_EVENT_TABLE(); DECLARE_EVENT_TABLE();
}; };
// PRL: to do: move this to its own file
#include <wx/defs.h>
#include "ToolBar.h"
#include "../Theme.h"
#include "../Experimental.h"
class wxCommandEvent;
class wxDC;
class wxImage;
class wxWindow;
class AButton;
enum {
STBStartID,
STBScrubID,
STBSeekID,
STBNumButtons
};
class ScrubbingToolBar final : public ToolBar {
public:
ScrubbingToolBar();
virtual ~ScrubbingToolBar();
void Create(wxWindow *parent);
void OnButton(wxCommandEvent & event);
void Populate();
void Repaint(wxDC * WXUNUSED(dc)) {};
void EnableDisableButtons();
void UpdatePrefs();
void RegenerateTooltips();
private:
AButton *AddButton(teBmps eEnabledUp, teBmps eEnabledDown, teBmps eDisabled,
int id, const wxChar *label, bool toggle = false);
void MakeButtons();
AButton *mButtons[STBNumButtons];
wxImage *upImage;
wxImage *downImage;
wxImage *hiliteImage;
public:
DECLARE_CLASS(EditToolBar);
DECLARE_EVENT_TABLE();
};
#endif #endif

View File

@ -71,6 +71,7 @@ enum
MixerBarID, MixerBarID,
EditBarID, EditBarID,
TranscriptionBarID, TranscriptionBarID,
ScrubbingBarID,
DeviceBarID, DeviceBarID,
SelectionBarID, SelectionBarID,
#ifdef EXPERIMENTAL_SPECTRAL_EDITING #ifdef EXPERIMENTAL_SPECTRAL_EDITING

View File

@ -448,6 +448,7 @@ ToolManager::ToolManager( AudacityProject *parent )
#ifdef EXPERIMENTAL_SPECTRAL_EDITING #ifdef EXPERIMENTAL_SPECTRAL_EDITING
mBars[SpectralSelectionBarID] = new SpectralSelectionBar(); mBars[SpectralSelectionBarID] = new SpectralSelectionBar();
#endif #endif
mBars[ ScrubbingBarID ] = new ScrubbingToolBar();
// We own the timer // We own the timer
mTimer.SetOwner( this ); mTimer.SetOwner( this );

View File

@ -99,7 +99,7 @@ void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc)
else if(auto ruler = dynamic_cast<AdornedRulerPanel*>(&panel)) { else if(auto ruler = dynamic_cast<AdornedRulerPanel*>(&panel)) {
wxASSERT(!mIsMaster); wxASSERT(!mIsMaster);
ruler->DoDrawIndicator(&dc, mLastIndicatorX, !rec, IndicatorMediumWidth, false); ruler->DoDrawIndicator(&dc, mLastIndicatorX, !rec, IndicatorMediumWidth, false, false);
} }
else else
wxASSERT(false); wxASSERT(false);

View File

@ -21,6 +21,7 @@ Paul Licameli split from TrackPanel.cpp
#include "../../commands/CommandFunctors.h" #include "../../commands/CommandFunctors.h"
#include "../../prefs/PlaybackPrefs.h" #include "../../prefs/PlaybackPrefs.h"
#include "../../toolbars/ControlToolBar.h" #include "../../toolbars/ControlToolBar.h"
#include "../../toolbars/EditToolBar.h"
#undef USE_TRANSCRIPTION_TOOLBAR #undef USE_TRANSCRIPTION_TOOLBAR
#ifdef USE_TRANSCRIPTION_TOOLBAR #ifdef USE_TRANSCRIPTION_TOOLBAR
@ -33,9 +34,6 @@ Paul Licameli split from TrackPanel.cpp
#include <wx/dc.h> #include <wx/dc.h>
// Conditional compilation switch for making scrub menu items checkable
#define CHECKABLE_SCRUB_MENU_ITEMS
enum { enum {
// PRL: // PRL:
// Mouse must move at least this far to distinguish ctrl-drag to scrub // Mouse must move at least this far to distinguish ctrl-drag to scrub
@ -165,7 +163,7 @@ private:
void Scrubber::ScrubPoller::Notify() void Scrubber::ScrubPoller::Notify()
{ {
// Call ContinueScrubbing() here in a timer handler // Call Continue functions here in a timer handler
// rather than in SelectionHandleDrag() // rather than in SelectionHandleDrag()
// so that even without drag events, we can instruct the play head to // so that even without drag events, we can instruct the play head to
// keep approaching the mouse cursor, when its maximum speed is limited. // keep approaching the mouse cursor, when its maximum speed is limited.
@ -183,7 +181,6 @@ Scrubber::Scrubber(AudacityProject *project)
, mPaused(true) , mPaused(true)
, mScrubSpeedDisplayCountdown(0) , mScrubSpeedDisplayCountdown(0)
, mScrubStartPosition(-1) , mScrubStartPosition(-1)
, mScrubSeekPress(false)
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
, mSmoothScrollingScrub(false) , mSmoothScrollingScrub(false)
, mLogMaxScrubSpeed(0) , mLogMaxScrubSpeed(0)
@ -221,24 +218,27 @@ namespace {
wxString status; wxString status;
void (Scrubber::*memFn)(wxCommandEvent&); void (Scrubber::*memFn)(wxCommandEvent&);
bool seek; bool seek;
bool (Scrubber::*StatusTest)() const;
const wxString &GetStatus() const { return status; } const wxString &GetStatus() const { return status; }
} menuItems[] = { } menuItems[] = {
/* i18n-hint: These commands assist the user in finding a sound by ear. ... /* i18n-hint: These commands assist the user in finding a sound by ear. ...
"Scrubbing" is variable-speed playback, ... "Scrubbing" is variable-speed playback, ...
"Seeking" is normal speed playback but with skips, ... "Seeking" is normal speed playback but with skips, ...
"Scrolling" keeps the playback position at a fixed place on screen while the waveform moves
*/ */
{ wxT("Scrub"), XO("&Scrub"), XO("Scrubbing"), { wxT("Scrub"), XO("&Scrub"), XO("Scrubbing"),
&Scrubber::OnScrub, false }, &Scrubber::OnScrub, false, &Scrubber::Scrubs },
{ wxT("Seek"), XO("See&k"), XO("Seeking"), { wxT("Seek"), XO("See&k"), XO("Seeking"),
&Scrubber::OnSeek, true }, &Scrubber::OnSeek, true, &Scrubber::Seeks },
{ wxT("StartStopScrubSeek"), XO("Star&t/Stop"), XO(""),
&Scrubber::OnStartStop, true, nullptr },
}; };
enum { nMenuItems = sizeof(menuItems) / sizeof(*menuItems) }; enum { nMenuItems = sizeof(menuItems) / sizeof(*menuItems), StartMenuItem = 2 };
// This never finds the last item:
inline const MenuItem &FindMenuItem(bool seek) inline const MenuItem &FindMenuItem(bool seek)
{ {
return *std::find_if(menuItems, menuItems + nMenuItems, return *std::find_if(menuItems, menuItems + nMenuItems,
@ -252,16 +252,13 @@ namespace {
void Scrubber::MarkScrubStart( void Scrubber::MarkScrubStart(
// Assume xx is relative to the left edge of TrackPanel! // Assume xx is relative to the left edge of TrackPanel!
wxCoord xx, bool smoothScrolling, bool alwaysSeeking wxCoord xx, bool smoothScrolling
) )
{ {
UncheckAllMenuItems();
// Don't actually start scrubbing, but collect some information // Don't actually start scrubbing, but collect some information
// needed for the decision to start scrubbing later when handling // needed for the decision to start scrubbing later when handling
// drag events. // drag events.
mSmoothScrollingScrub = smoothScrolling; mSmoothScrollingScrub = smoothScrolling;
mAlwaysSeeking = alwaysSeeking;
ControlToolBar * const ctb = mProject->GetControlToolBar(); ControlToolBar * const ctb = mProject->GetControlToolBar();
@ -272,14 +269,15 @@ void Scrubber::MarkScrubStart(
// scrubber state // scrubber state
mProject->SetAudioIOToken(0); mProject->SetAudioIOToken(0);
ctb->SetPlay(true, ControlToolBar::PlayAppearance::Scrub); ctb->SetPlay(true, mSeeking
? ControlToolBar::PlayAppearance::Seek
ctb->UpdateStatusBar(mProject); : ControlToolBar::PlayAppearance::Scrub);
mScrubStartPosition = xx; mScrubStartPosition = xx;
ctb->UpdateStatusBar(mProject);
mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis(); mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
CheckMenuItem(); mCancelled = false;
} }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
@ -354,8 +352,9 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
mOptions.minStutter = mOptions.minStutter =
mDragging ? 0.0 : lrint(std::max(0.0, MinStutter) * options.rate); mDragging ? 0.0 : lrint(std::max(0.0, MinStutter) * options.rate);
ControlToolBar::PlayAppearance appearance = ControlToolBar::PlayAppearance appearance = mSeeking
ControlToolBar::PlayAppearance::Scrub; ? ControlToolBar::PlayAppearance::Seek
: ControlToolBar::PlayAppearance::Scrub;
const bool cutPreview = false; const bool cutPreview = false;
const bool backwards = time1 < time0; const bool backwards = time1 < time0;
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
@ -398,12 +397,12 @@ void Scrubber::ContinueScrubbingPoll()
{ {
// Thus scrubbing relies mostly on periodic polling of mouse and keys, // Thus scrubbing relies mostly on periodic polling of mouse and keys,
// not event notifications. But there are a few event handlers that // not event notifications. But there are a few event handlers that
// leave messages for this routine, in mScrubSeekPress and in mPaused. // leave messages for this routine, in mPaused.
// Decide whether to skip play, because either mouse is down now, // Decide whether to skip play, because either mouse is down now,
// or there was a left click event. (This is then a delayed reaction, in a // or there was a left click event. (This is then a delayed reaction, in a
// timer callback, to a left click event detected elsewhere.) // timer callback, to a left click event detected elsewhere.)
const bool seek = PollIsSeeking() || mScrubSeekPress; const bool seek = Seeks();
bool result = false; bool result = false;
if (mPaused) { if (mPaused) {
@ -447,11 +446,6 @@ void Scrubber::ContinueScrubbingPoll()
} }
} }
} }
if (result)
mScrubSeekPress = false;
// else, if seek requested, try again at a later time when we might
// enqueue a long enough stutter
} }
void Scrubber::ContinueScrubbingUI() void Scrubber::ContinueScrubbingUI()
@ -461,19 +455,21 @@ void Scrubber::ContinueScrubbingUI()
if (mDragging && !state.LeftIsDown()) { if (mDragging && !state.LeftIsDown()) {
// Stop and set cursor // Stop and set cursor
mProject->DoPlayStopSelect(true, state.ShiftDown()); mProject->DoPlayStopSelect(true, state.ShiftDown());
wxCommandEvent evt;
mProject->GetControlToolBar()->OnStop(evt);
return; return;
} }
const bool seek = PollIsSeeking(); const bool seek = Seeks();
{ {
// Show the correct status for seeking. // Show the correct status for seeking.
bool backup = mAlwaysSeeking; bool backup = mSeeking;
mAlwaysSeeking = seek; mSeeking = seek;
const auto ctb = mProject->GetControlToolBar(); const auto ctb = mProject->GetControlToolBar();
if (ctb) if (ctb)
ctb->UpdateStatusBar(mProject); ctb->UpdateStatusBar(mProject);
mAlwaysSeeking = backup; mSeeking = backup;
} }
if (seek) if (seek)
@ -498,7 +494,11 @@ void Scrubber::StopScrubbing()
mPoller->Stop(); mPoller->Stop();
UncheckAllMenuItems(); if (HasStartedScrubbing() && !mCancelled) {
const wxMouseState state(::wxGetMouseState());
// Stop and set cursor
mProject->DoPlayStopSelect(true, state.ShiftDown());
}
mScrubStartPosition = -1; mScrubStartPosition = -1;
mDragging = false; mDragging = false;
@ -536,7 +536,7 @@ bool Scrubber::ShouldDrawScrubSpeed()
return IsScrubbing() && return IsScrubbing() &&
!mPaused && ( !mPaused && (
// Draw for (non-scroll) scrub, sometimes, but never for seek // Draw for (non-scroll) scrub, sometimes, but never for seek
(!PollIsSeeking() && mScrubSpeedDisplayCountdown > 0) (!Seeks() && mScrubSpeedDisplayCountdown > 0)
// Draw always for scroll-scrub and for scroll-seek // Draw always for scroll-scrub and for scroll-seek
|| mSmoothScrollingScrub || mSmoothScrollingScrub
); );
@ -598,12 +598,7 @@ void Scrubber::Forwarder::OnMouse(wxMouseEvent &event)
auto ruler = scrubber.mProject->GetRulerPanel(); auto ruler = scrubber.mProject->GetRulerPanel();
auto isScrubbing = scrubber.IsScrubbing(); auto isScrubbing = scrubber.IsScrubbing();
if (isScrubbing && !event.HasAnyModifiers()) { if (isScrubbing && !event.HasAnyModifiers()) {
if(event.LeftDown() || if (event.m_wheelRotation) {
(event.LeftIsDown() && event.Dragging())) {
if (!scrubber.mDragging)
scrubber.mScrubSeekPress = true;
}
else if (event.m_wheelRotation) {
double steps = event.m_wheelRotation / double steps = event.m_wheelRotation /
(event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0); (event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0);
scrubber.HandleScrollWheel(steps); scrubber.HandleScrollWheel(steps);
@ -717,7 +712,7 @@ void ScrubbingOverlay::OnTimer(wxCommandEvent &event)
// Where's the mouse? // Where's the mouse?
position = trackPanel->ScreenToClient(position); position = trackPanel->ScreenToClient(position);
const bool seeking = scrubber.PollIsSeeking(); const bool seeking = scrubber.Seeks();
// Find the text // Find the text
const double maxScrubSpeed = GetScrubber().GetMaxScrubSpeed(); const double maxScrubSpeed = GetScrubber().GetMaxScrubSpeed();
@ -768,15 +763,9 @@ Scrubber &ScrubbingOverlay::GetScrubber()
return mProject->GetScrubber(); return mProject->GetScrubber();
} }
bool Scrubber::PollIsSeeking() void Scrubber::DoScrub()
{ {
return mDragging || (mAlwaysSeeking || ::wxGetMouseState().LeftIsDown()); const bool wasScrubbing = HasStartedScrubbing() || IsScrubbing();
}
void Scrubber::DoScrub(bool seek)
{
const bool wasScrubbing = IsScrubbing();
const bool match = (seek == mAlwaysSeeking);
const bool scroll = PlaybackPrefs::GetPinnedHeadPreference(); const bool scroll = PlaybackPrefs::GetPinnedHeadPreference();
if (!wasScrubbing) { if (!wasScrubbing) {
auto tp = mProject->GetTrackPanel(); auto tp = mProject->GetTrackPanel();
@ -788,33 +777,49 @@ void Scrubber::DoScrub(bool seek)
const auto offset = tp->GetLeftOffset(); const auto offset = tp->GetLeftOffset();
xx = (std::max(offset, std::min(offset + width - 1, xx))); xx = (std::max(offset, std::min(offset + width - 1, xx)));
MarkScrubStart(xx, scroll, seek); MarkScrubStart(xx, scroll);
}
else
mProject->GetControlToolBar()->StopPlaying();
} }
else if(!match) {
mSmoothScrollingScrub = scroll;
mAlwaysSeeking = seek;
UncheckAllMenuItems();
CheckMenuItem();
void Scrubber::OnScrubOrSeek(bool &toToggle, bool &other)
{
toToggle = !toToggle;
if (toToggle)
other = false;
if (HasStartedScrubbing()) {
// Show the correct status. // Show the correct status.
const auto ctb = mProject->GetControlToolBar(); const auto ctb = mProject->GetControlToolBar();
ctb->UpdateStatusBar(mProject); ctb->UpdateStatusBar(mProject);
} }
else {
// This will call back to Scrubber::StopScrubbing auto ruler = mProject->GetRulerPanel();
const auto ctb = mProject->GetControlToolBar(); if (ruler)
ctb->StopPlaying(); // Update button images
} ruler->UpdateButtonStates();
auto scrubbingToolBar = mProject->GetScrubbingToolBar();
scrubbingToolBar->EnableDisableButtons();
scrubbingToolBar->RegenerateTooltips();
CheckMenuItem();
} }
void Scrubber::OnScrub(wxCommandEvent&) void Scrubber::OnScrub(wxCommandEvent&)
{ {
DoScrub(false); OnScrubOrSeek(mScrubbing, mSeeking);
} }
void Scrubber::OnSeek(wxCommandEvent&) void Scrubber::OnSeek(wxCommandEvent&)
{ {
DoScrub(true); OnScrubOrSeek(mSeeking, mScrubbing);
}
void Scrubber::OnStartStop(wxCommandEvent&)
{
DoScrub();
} }
enum { CMD_ID = 8000 }; enum { CMD_ID = 8000 };
@ -822,20 +827,21 @@ enum { CMD_ID = 8000 };
BEGIN_EVENT_TABLE(Scrubber, wxEvtHandler) BEGIN_EVENT_TABLE(Scrubber, wxEvtHandler)
EVT_MENU(CMD_ID, Scrubber::OnScrub) EVT_MENU(CMD_ID, Scrubber::OnScrub)
EVT_MENU(CMD_ID + 1, Scrubber::OnSeek) EVT_MENU(CMD_ID + 1, Scrubber::OnSeek)
EVT_MENU(CMD_ID + 2, Scrubber::OnStartStop)
END_EVENT_TABLE() END_EVENT_TABLE()
BEGIN_EVENT_TABLE(Scrubber::Forwarder, wxEvtHandler) BEGIN_EVENT_TABLE(Scrubber::Forwarder, wxEvtHandler)
EVT_MOUSE_EVENTS(Scrubber::Forwarder::OnMouse) EVT_MOUSE_EVENTS(Scrubber::Forwarder::OnMouse)
END_EVENT_TABLE() END_EVENT_TABLE()
static_assert(nMenuItems == 2, "wrong number of items"); static_assert(nMenuItems == 3, "wrong number of items");
const wxString &Scrubber::GetUntranslatedStateString() const const wxString &Scrubber::GetUntranslatedStateString() const
{ {
static wxString empty; static wxString empty;
if (HasStartedScrubbing()) { if (HasStartedScrubbing()) {
auto &item = FindMenuItem(mAlwaysSeeking); auto &item = FindMenuItem(mSeeking);
return item.status; return item.status;
} }
else else
@ -846,34 +852,42 @@ std::vector<wxString> Scrubber::GetAllUntranslatedStatusStrings()
{ {
using namespace std; using namespace std;
vector<wxString> results; vector<wxString> results;
transform(menuItems, menuItems + nMenuItems, back_inserter(results), for (const auto &item : menuItems) {
mem_fun_ref(&MenuItem::GetStatus)); const auto &status = item.GetStatus();
if (!status.empty())
results.push_back(status);
}
return move(results); return move(results);
} }
bool Scrubber::CanScrub() const bool Scrubber::CanScrub() const
{ {
// Return the enabled state for the menu item that really launches the scrub or seek.
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
return cm->GetEnabled(menuItems[0].name); return cm->GetEnabled(menuItems[StartMenuItem].name);
} }
void Scrubber::AddMenuItems() void Scrubber::AddMenuItems()
{ {
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
auto flags = cm->GetDefaultFlags() | WaveTracksExistFlag; auto flag = WaveTracksExistFlag;
auto mask = cm->GetDefaultMask() | WaveTracksExistFlag; auto flags = cm->GetDefaultFlags() | flag;
auto mask = cm->GetDefaultMask() | flag;
cm->BeginSubMenu(_("Scru&bbing")); cm->BeginSubMenu(_("Scru&bbing"));
for (const auto &item : menuItems) { for (const auto &item : menuItems) {
#ifdef CHECKABLE_SCRUB_MENU_ITEMS if (!item.GetStatus().empty())
cm->AddCheck(item.name, wxGetTranslation(item.label), cm->AddCheck(item.name, wxGetTranslation(item.label),
FNT(Scrubber, this, item.memFn), FNT(Scrubber, this, item.memFn),
false, flags, mask); false,
#else // Less restricted:
AlwaysEnabledFlag, AlwaysEnabledFlag);
else
// The start item
cm->AddItem(item.name, wxGetTranslation(item.label), cm->AddItem(item.name, wxGetTranslation(item.label),
FNT(Scrubber, this, item.memFn), FNT(Scrubber, this, item.memFn),
// More restricted:
flags, mask); flags, mask);
#endif
} }
cm->EndSubMenu(); cm->EndSubMenu();
CheckMenuItem(); CheckMenuItem();
@ -883,42 +897,22 @@ void Scrubber::PopulateMenu(wxMenu &menu)
{ {
int id = CMD_ID; int id = CMD_ID;
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
const MenuItem *checkedItem = const MenuItem *checkedItem = &FindMenuItem(mSeeking);
HasStartedScrubbing()
? &FindMenuItem(mAlwaysSeeking)
: nullptr;
for (const auto &item : menuItems) { for (const auto &item : menuItems) {
if (cm->GetEnabled(item.name)) { if (cm->GetEnabled(item.name)) {
#ifdef CHECKABLE_SCRUB_MENU_ITEMS
menu.AppendCheckItem(id, item.label); menu.AppendCheckItem(id, item.label);
if(&item == checkedItem) if(&item == checkedItem)
menu.FindItem(id)->Check(); menu.FindItem(id)->Check();
#else
menu.Append(id, item.label);
#endif
} }
++id; ++id;
} }
} }
void Scrubber::UncheckAllMenuItems()
{
#ifdef CHECKABLE_SCRUB_MENU_ITEMS
auto cm = mProject->GetCommandManager();
for (const auto &item : menuItems)
cm->Check(item.name, false);
#endif
}
void Scrubber::CheckMenuItem() void Scrubber::CheckMenuItem()
{ {
#ifdef CHECKABLE_SCRUB_MENU_ITEMS
if(HasStartedScrubbing()) {
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
auto item = FindMenuItem(mAlwaysSeeking); cm->Check(menuItems[0].name, mScrubbing);
cm->Check(item.name, true); cm->Check(menuItems[1].name, mSeeking);
}
#endif
} }
#endif #endif

View File

@ -71,11 +71,7 @@ public:
~Scrubber(); ~Scrubber();
// Assume xx is relative to the left edge of TrackPanel! // Assume xx is relative to the left edge of TrackPanel!
void MarkScrubStart( void MarkScrubStart(wxCoord xx, bool smoothScrolling);
wxCoord xx, bool smoothScrolling,
bool alwaysSeeking // if false, can switch seeking or scrubbing
// by mouse button state
);
// Returns true iff the event should be considered consumed by this: // Returns true iff the event should be considered consumed by this:
// Assume xx is relative to the left edge of TrackPanel! // Assume xx is relative to the left edge of TrackPanel!
@ -101,8 +97,14 @@ public:
void SetScrollScrubbing(bool value) void SetScrollScrubbing(bool value)
{ mSmoothScrollingScrub = value; } { mSmoothScrollingScrub = value; }
bool IsAlwaysSeeking() const bool Seeks() const
{ return mAlwaysSeeking; } { return mSeeking; }
bool Scrubs() const
{ return mScrubbing; }
void Cancel()
{ mCancelled = true; }
bool ShouldDrawScrubSpeed(); bool ShouldDrawScrubSpeed();
double FindScrubSpeed(bool seeking, double time) const; double FindScrubSpeed(bool seeking, double time) const;
@ -110,8 +112,6 @@ public:
void HandleScrollWheel(int steps); void HandleScrollWheel(int steps);
bool PollIsSeeking();
// This returns the same as the enabled state of the menu items: // This returns the same as the enabled state of the menu items:
bool CanScrub() const; bool CanScrub() const;
@ -120,10 +120,13 @@ public:
// For popup // For popup
void PopulateMenu(wxMenu &menu); void PopulateMenu(wxMenu &menu);
void OnScrubOrSeek(bool &toToggle, bool &other);
void OnScrub(wxCommandEvent&); void OnScrub(wxCommandEvent&);
void OnSeek(wxCommandEvent&); void OnSeek(wxCommandEvent&);
void OnStartStop(wxCommandEvent&);
// A string to put in the leftmost part of the status bar. // A string to put in the leftmost part of the status bar
// when scrub or seek is in progress, or else empty.
const wxString &GetUntranslatedStateString() const; const wxString &GetUntranslatedStateString() const;
// All possible status strings. // All possible status strings.
@ -133,9 +136,8 @@ public:
bool IsPaused() const; bool IsPaused() const;
private: private:
void DoScrub(bool seek); void DoScrub();
void OnActivateOrDeactivateApp(wxActivateEvent & event); void OnActivateOrDeactivateApp(wxActivateEvent & event);
void UncheckAllMenuItems();
void CheckMenuItem(); void CheckMenuItem();
// I need this because I can't push the scrubber as an event handler // I need this because I can't push the scrubber as an event handler
@ -156,11 +158,17 @@ private:
int mScrubSpeedDisplayCountdown; int mScrubSpeedDisplayCountdown;
wxCoord mScrubStartPosition; wxCoord mScrubStartPosition;
wxCoord mLastScrubPosition {}; wxCoord mLastScrubPosition {};
bool mScrubSeekPress;
bool mSmoothScrollingScrub; bool mSmoothScrollingScrub;
bool mAlwaysSeeking {};
// These hold the three-way choice among click-to-scrub, click-to-seek, or disabled.
// Not both true.
bool mScrubbing {};
bool mSeeking {};
bool mDragging {}; bool mDragging {};
bool mCancelled {};
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
int mLogMaxScrubSpeed; int mLogMaxScrubSpeed;
#endif #endif

View File

@ -106,8 +106,9 @@ class Grabber final : public wxWindow
// not a need to dock/float a toolbar from the keyboard. If this // not a need to dock/float a toolbar from the keyboard. If this
// changes, remove this and add the necessary keyboard movement // changes, remove this and add the necessary keyboard movement
// handling. // handling.
// PRL: Commented out so the ESC key can stop dragging. // Note that AcceptsFocusFromKeyboard() rather than AcceptsFocus()
// bool AcceptsFocus() const {return false;} // is overridden so that ESC can cancel toolbar drag.
bool AcceptsFocusFromKeyboard() const override {return false;}
void PushButton(bool state); void PushButton(bool state);

View File

@ -1791,7 +1791,7 @@ std::pair<wxRect, bool> QuickPlayRulerOverlay::DoGetRectangle(wxSize size)
if (x >= 0) { if (x >= 0) {
// These dimensions are always sufficient, even if a little // These dimensions are always sufficient, even if a little
// excessive for the small triangle: // excessive for the small triangle:
const int width = IndicatorBigWidth(); const int width = IndicatorBigWidth() * 3 / 2;
const auto height = IndicatorHeightForWidth(width); const auto height = IndicatorHeightForWidth(width);
const int indsize = width / 2; const int indsize = width / 2;
@ -1814,12 +1814,14 @@ void QuickPlayRulerOverlay::Draw(OverlayPanel &panel, wxDC &dc)
mOldQPIndicatorPos = mNewQPIndicatorPos; mOldQPIndicatorPos = mNewQPIndicatorPos;
if (mOldQPIndicatorPos >= 0) { if (mOldQPIndicatorPos >= 0) {
auto ruler = GetRuler(); auto ruler = GetRuler();
const auto &scrubber = mPartner.mProject->GetScrubber();
auto scrub = auto scrub =
ruler->mMouseEventState == AdornedRulerPanel::mesNone && ruler->mMouseEventState == AdornedRulerPanel::mesNone &&
(ruler->mPrevZone == AdornedRulerPanel::StatusChoice::EnteringScrubZone || (ruler->mPrevZone == AdornedRulerPanel::StatusChoice::EnteringScrubZone ||
(mPartner.mProject->GetScrubber().HasStartedScrubbing())); (scrubber.HasStartedScrubbing()));
auto seek = scrub && scrubber.Seeks();
auto width = scrub ? IndicatorBigWidth() : IndicatorSmallWidth; auto width = scrub ? IndicatorBigWidth() : IndicatorSmallWidth;
ruler->DoDrawIndicator(&dc, mOldQPIndicatorPos, true, width, scrub); ruler->DoDrawIndicator(&dc, mOldQPIndicatorPos, true, width, scrub, seek);
} }
} }
@ -1918,8 +1920,6 @@ enum {
OnLockPlayRegionID, OnLockPlayRegionID,
OnTogglePinnedStateID, OnTogglePinnedStateID,
OnShowHideScrubbingID,
}; };
BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel) BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel)
@ -1935,9 +1935,6 @@ BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel)
EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll) EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll)
EVT_MENU(OnLockPlayRegionID, AdornedRulerPanel::OnLockPlayRegion) EVT_MENU(OnLockPlayRegionID, AdornedRulerPanel::OnLockPlayRegion)
// Scrub bar menu commands
EVT_MENU(OnShowHideScrubbingID, AdornedRulerPanel::OnToggleScrubbing)
// Pop up menus on Windows // Pop up menus on Windows
EVT_CONTEXT_MENU(AdornedRulerPanel::OnContextMenu) EVT_CONTEXT_MENU(AdornedRulerPanel::OnContextMenu)
@ -1959,8 +1956,6 @@ AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent,
for (auto &button : mButtons) for (auto &button : mButtons)
button = nullptr; button = nullptr;
ReCreateButtons();
SetLabel( _("Timeline") ); SetLabel( _("Timeline") );
SetName(GetLabel()); SetName(GetLabel());
SetBackgroundStyle(wxBG_STYLE_PAINT); SetBackgroundStyle(wxBG_STYLE_PAINT);
@ -1998,8 +1993,6 @@ AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent,
mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false; mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
mQuickPlayEnabled = !!gPrefs->Read(wxT("/QuickPlay/QuickPlayEnabled"), 1L); mQuickPlayEnabled = !!gPrefs->Read(wxT("/QuickPlay/QuickPlayEnabled"), 1L);
UpdatePrefs();
#if wxUSE_TOOLTIPS #if wxUSE_TOOLTIPS
wxToolTip::Enable(true); wxToolTip::Enable(true);
#endif #endif
@ -2026,6 +2019,7 @@ AdornedRulerPanel::~AdornedRulerPanel()
this); this);
} }
#if 0
namespace { namespace {
static const wxChar *scrubEnabledPrefName = wxT("/QuickPlay/ScrubbingEnabled"); static const wxChar *scrubEnabledPrefName = wxT("/QuickPlay/ScrubbingEnabled");
@ -2041,6 +2035,7 @@ namespace {
gPrefs->Write(scrubEnabledPrefName, value); gPrefs->Write(scrubEnabledPrefName, value);
} }
} }
#endif
void AdornedRulerPanel::UpdatePrefs() void AdornedRulerPanel::UpdatePrefs()
{ {
@ -2058,7 +2053,7 @@ void AdornedRulerPanel::UpdatePrefs()
#endif #endif
#endif #endif
mShowScrubbing = ReadScrubEnabledPref(); // mShowScrubbing = ReadScrubEnabledPref();
// Affected by the last // Affected by the last
UpdateRects(); UpdateRects();
@ -2088,9 +2083,10 @@ void AdornedRulerPanel::ReCreateButtons()
// Make the short row of time ruler pushbottons. // Make the short row of time ruler pushbottons.
// Don't bother with sizers. Their sizes and positions are fixed. // Don't bother with sizers. Their sizes and positions are fixed.
wxPoint position{ FocusBorderLeft, FocusBorderTop }; wxPoint position{ FocusBorderLeft, 0 };
size_t iButton = 0; size_t iButton = 0;
const auto size = theTheme.ImageSize( bmpRecoloredUpSmall ); auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
size.y = std::min(size.y, GetRulerHeight(false));
auto buttonMaker = [&] auto buttonMaker = [&]
(wxWindowID id, teBmps bitmap, bool toggle) (wxWindowID id, teBmps bitmap, bool toggle)
@ -2122,6 +2118,40 @@ void AdornedRulerPanel::InvalidateRuler()
mRuler.Invalidate(); mRuler.Invalidate();
} }
namespace {
const wxString StartScrubbingMessage(const Scrubber &scrubber)
{
/* i18n-hint: These commands assist the user in finding a sound by ear. ...
"Scrubbing" is variable-speed playback, ...
"Seeking" is normal speed playback but with skips
*/
if(scrubber.Seeks())
return _("Click or drag to begin seeking");
else
return _("Click or drag to begin scrubbing");
}
const wxString ContinueScrubbingMessage(const Scrubber &scrubber)
{
/* i18n-hint: These commands assist the user in finding a sound by ear. ...
"Scrubbing" is variable-speed playback, ...
"Seeking" is normal speed playback but with skips
*/
if(scrubber.Seeks())
return _("Move to seek");
else
return _("Move to scrub");
}
const wxString ScrubbingMessage(const Scrubber &scrubber)
{
if (scrubber.HasStartedScrubbing())
return ContinueScrubbingMessage(scrubber);
else
return StartScrubbingMessage(scrubber);
}
}
void AdornedRulerPanel::RegenerateTooltips(StatusChoice choice) void AdornedRulerPanel::RegenerateTooltips(StatusChoice choice)
{ {
#if wxUSE_TOOLTIPS #if wxUSE_TOOLTIPS
@ -2140,7 +2170,10 @@ void AdornedRulerPanel::RegenerateTooltips(StatusChoice choice)
} }
break; break;
case StatusChoice::EnteringScrubZone : case StatusChoice::EnteringScrubZone :
this->SetToolTip(_("Scrub Bar")); {
const auto message = ScrubbingMessage(mProject->GetScrubber());
this->SetToolTip(message);
}
break; break;
default: default:
this->SetToolTip(NULL); this->SetToolTip(NULL);
@ -2182,7 +2215,8 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
mNeedButtonUpdate = false; mNeedButtonUpdate = false;
// Do this first time setting of button status texts // Do this first time setting of button status texts
// when we are sure the CommandManager is initialized. // when we are sure the CommandManager is initialized.
UpdateButtonStates(); ReCreateButtons();
UpdatePrefs();
} }
wxPaintDC dc(this); wxPaintDC dc(this);
@ -2421,7 +2455,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
} }
else if (!HasCapture() && inScrubZone) { else if (!HasCapture() && inScrubZone) {
if (evt.LeftDown()) { if (evt.LeftDown()) {
scrubber.MarkScrubStart(evt.m_x, PlaybackPrefs::GetPinnedHeadPreference(), false); scrubber.MarkScrubStart(evt.m_x, PlaybackPrefs::GetPinnedHeadPreference());
UpdateStatusBarAndTooltips(StatusChoice::EnteringScrubZone); UpdateStatusBarAndTooltips(StatusChoice::EnteringScrubZone);
} }
ShowQuickPlayIndicator(); ShowQuickPlayIndicator();
@ -2730,12 +2764,7 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
case StatusChoice::EnteringScrubZone: case StatusChoice::EnteringScrubZone:
{ {
if (scrubbing) { message = ScrubbingMessage(scrubber);
if(!scrubber.IsAlwaysSeeking())
message = _("Click or drag to seek");
}
else
message = _("Click to scrub, Double-Click to scroll, Drag to seek");
} }
break; break;
@ -2749,10 +2778,10 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
RegenerateTooltips(choice); RegenerateTooltips(choice);
} }
void AdornedRulerPanel::OnToggleScrubbing(wxCommandEvent&) void AdornedRulerPanel::OnToggleScrubbing(/*wxCommandEvent&*/)
{ {
mShowScrubbing = !mShowScrubbing; mShowScrubbing = !mShowScrubbing;
WriteScrubEnabledPref(mShowScrubbing); //WriteScrubEnabledPref(mShowScrubbing);
gPrefs->Flush(); gPrefs->Flush();
wxSize size { GetSize().GetWidth(), GetRulerHeight(mShowScrubbing) }; wxSize size { GetSize().GetWidth(), GetRulerHeight(mShowScrubbing) };
SetSize(size); SetSize(size);
@ -2767,6 +2796,13 @@ void AdornedRulerPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event))
void AdornedRulerPanel::UpdateButtonStates() void AdornedRulerPanel::UpdateButtonStates()
{ {
auto common = [this]
(wxWindow *button, const wxString &commandName, const wxString &label){
const auto &fullLabel = ComposeButtonLabel(*mProject, commandName, label);
button->SetLabel(fullLabel);
button->SetToolTip(fullLabel);
};
{ {
bool state = PlaybackPrefs::GetPinnedHeadPreference(); bool state = PlaybackPrefs::GetPinnedHeadPreference();
auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID)); auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
@ -2777,10 +2813,13 @@ void AdornedRulerPanel::UpdateButtonStates()
// (which is, to toggle the state) // (which is, to toggle the state)
? _("Pinned play/record Head") ? _("Pinned play/record Head")
: _("Unpinned play/record Head"); : _("Unpinned play/record Head");
const auto &fullLabel = ComposeButtonLabel(*mProject, wxT("PinnedHead"), label); common(pinButton, wxT("PinnedHead"), label);
pinButton->SetLabel(fullLabel);
pinButton->SetToolTip(fullLabel);
} }
auto &scrubber = mProject->GetScrubber();
if(mShowScrubbing != (scrubber.Scrubs() || scrubber.Seeks()))
OnToggleScrubbing();
} }
void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & event) void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & event)
@ -2858,12 +2897,6 @@ void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
auto cleanup = finally([this]{ PopEventHandler(); }); auto cleanup = finally([this]{ PopEventHandler(); });
wxMenu rulerMenu; wxMenu rulerMenu;
rulerMenu.AppendCheckItem(OnShowHideScrubbingID, _("Scrub Bar"));
if(mShowScrubbing)
rulerMenu.FindItem(OnShowHideScrubbingID)->Check();
rulerMenu.AppendSeparator();
mProject->GetScrubber().PopulateMenu(rulerMenu); mProject->GetScrubber().PopulateMenu(rulerMenu);
PopupMenu(&rulerMenu, pos); PopupMenu(&rulerMenu, pos);
} }
@ -3104,11 +3137,6 @@ void AdornedRulerPanel::DoDrawSelection(wxDC * dc)
dc->DrawRectangle( r ); dc->DrawRectangle( r );
} }
int AdornedRulerPanel::GetRulerHeight()
{
return GetRulerHeight(ReadScrubEnabledPref());
}
int AdornedRulerPanel::GetRulerHeight(bool showScrubBar) int AdornedRulerPanel::GetRulerHeight(bool showScrubBar)
{ {
return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0); return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
@ -3122,14 +3150,46 @@ void AdornedRulerPanel::SetLeftOffset(int offset)
// Draws the play/recording position indicator. // Draws the play/recording position indicator.
void AdornedRulerPanel::DoDrawIndicator void AdornedRulerPanel::DoDrawIndicator
(wxDC * dc, wxCoord xx, bool playing, int width, bool scrub) (wxDC * dc, wxCoord xx, bool playing, int width, bool scrub, bool seek)
{ {
ADCChanger changer(dc); // Undo pen and brush changes at function exit ADCChanger changer(dc); // Undo pen and brush changes at function exit
AColor::IndicatorColor( dc, playing ); AColor::IndicatorColor( dc, playing );
wxPoint tri[ 3 ]; wxPoint tri[ 3 ];
if (scrub) { if (seek) {
auto height = IndicatorHeightForWidth(width);
// Make four triangles
const int TriangleWidth = width * 3 / 8;
// Double-double headed, left-right
auto yy = mShowScrubbing
? mScrubZone.y
: (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
tri[ 0 ].x = xx - IndicatorOffset;
tri[ 0 ].y = yy;
tri[ 1 ].x = xx - IndicatorOffset;
tri[ 1 ].y = yy + height;
tri[ 2 ].x = xx - TriangleWidth;
tri[ 2 ].y = yy + height / 2;
dc->DrawPolygon( 3, tri );
tri[ 0 ].x -= TriangleWidth;
tri[ 1 ].x -= TriangleWidth;
tri[ 2 ].x -= TriangleWidth;
dc->DrawPolygon( 3, tri );
tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
tri[ 2 ].x = xx + TriangleWidth;
dc->DrawPolygon( 3, tri );
tri[ 0 ].x += TriangleWidth;
tri[ 1 ].x += TriangleWidth;
tri[ 2 ].x += TriangleWidth;
dc->DrawPolygon( 3, tri );
}
else if (scrub) {
auto height = IndicatorHeightForWidth(width); auto height = IndicatorHeightForWidth(width);
const int IndicatorHalfWidth = width / 2; const int IndicatorHalfWidth = width / 2;

View File

@ -298,7 +298,7 @@ public:
#endif #endif
public: public:
static int GetRulerHeight(); int GetRulerHeight() { return GetRulerHeight(mShowScrubbing); }
static int GetRulerHeight(bool showScrubBar); static int GetRulerHeight(bool showScrubBar);
wxRect GetInnerRect() const { return mInner; } wxRect GetInnerRect() const { return mInner; }
@ -351,7 +351,7 @@ private:
void DoDrawSelection(wxDC * dc); void DoDrawSelection(wxDC * dc);
public: public:
void DoDrawIndicator(wxDC * dc, wxCoord xx, bool playing, int width, bool scrub); void DoDrawIndicator(wxDC * dc, wxCoord xx, bool playing, int width, bool scrub, bool seek);
void UpdateButtonStates(); void UpdateButtonStates();
private: private:
@ -413,7 +413,7 @@ private:
void OnAutoScroll(wxCommandEvent &evt); void OnAutoScroll(wxCommandEvent &evt);
void OnLockPlayRegion(wxCommandEvent &evt); void OnLockPlayRegion(wxCommandEvent &evt);
void OnToggleScrubbing(wxCommandEvent&); void OnToggleScrubbing(/*wxCommandEvent&*/);
void OnContextMenu(wxContextMenuEvent & WXUNUSED(event)); void OnContextMenu(wxContextMenuEvent & WXUNUSED(event));
@ -446,7 +446,7 @@ private:
friend QuickPlayRulerOverlay; friend QuickPlayRulerOverlay;
wxWindow *mButtons[1]; wxWindow *mButtons[3];
bool mNeedButtonUpdate { true }; bool mNeedButtonUpdate { true };
}; };