1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-17 00:57: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( bmpAppendRecord, wxImage( 16, 16 ), wxT("AppendRecord"));
DEFINE_IMAGE( bmpAppendRecordDisabled, wxImage( 16, 16 ), wxT("AppendRecordDisabled"));
DEFINE_IMAGE( bmpScrubDisabled, wxImage( 16, 16 ), wxT("ScrubDisabled"));
DEFINE_IMAGE( bmpScrub, wxImage( 16, 16 ), wxT("Scrub"));
DEFINE_IMAGE( bmpScrubDisabled, wxImage( 18, 16 ), wxT("ScrubDisabled"));
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 );
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
auto project = ::GetActiveProject();
auto token = project->GetAudioIOToken();
auto &scrubber = project->GetScrubber();
auto scrubbing = scrubber.HasStartedScrubbing();
if (scrubbing)
scrubber.Cancel();
if((token > 0 &&
gAudioIO->IsAudioTokenActive(token) &&
gAudioIO->GetNumCaptureChannels() == 0) ||
project->GetScrubber().HasStartedScrubbing())
scrubbing)
// ESC out of other play (but not record)
project->OnStop();
else

View File

@ -1798,6 +1798,10 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
if (mListener && captureChannels > 0)
mListener->OnAudioIOStopRecording();
mStreamToken = 0;
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
return 0;
}
@ -2100,6 +2104,10 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
mScrubQueue = 0;
}
#endif
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
}
#ifdef EXPERIMENTAL_MIDI_OUT
@ -2530,6 +2538,9 @@ void AudioIO::StopStream()
// Tell UI to hide sample rate
mListener->OnAudioIORate(0);
}
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
}
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);
/* 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("ShowScrubbingTB"), _("Scrubbing Toolbar"), FN(OnShowScrubbingToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
c->AddSeparator();
@ -1807,6 +1808,8 @@ void AudacityProject::ModifyToolbarMenus()
return;
}
mCommandManager.Check(wxT("ShowScrubbingTB"),
mToolManager->IsVisible(ScrubbingBarID));
mCommandManager.Check(wxT("ShowDeviceTB"),
mToolManager->IsVisible(DeviceBarID));
mCommandManager.Check(wxT("ShowEditTB"),
@ -2292,19 +2295,30 @@ void AudacityProject::OnRecordAppend()
GetControlToolBar()->OnRecord(evt);
}
// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and "OnStopSelect" merged.
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.
void AudacityProject::DoPlayStopSelect(bool click, bool shift)
bool AudacityProject::DoPlayStopSelect(bool click, bool shift)
{
wxCommandEvent evt;
ControlToolBar *toolbar = GetControlToolBar();
//If busy, stop playing, make sure everything is unpaused.
if (gAudioIO->IsStreamActive(GetAudioIOToken())) {
if (GetScrubber().HasStartedScrubbing() ||
gAudioIO->IsStreamActive(GetAudioIOToken())) {
toolbar->SetPlay(false); //Pops
toolbar->SetStop(true); //Pushes stop down
@ -2338,16 +2352,9 @@ void AudacityProject::DoPlayStopSelect(bool click, bool shift)
selection.setT0(time, false);
ModifyState(false); // without bWantsAutoSave
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);
return true;
}
return false;
}
void AudacityProject::OnStopSelect()
@ -2384,7 +2391,6 @@ void AudacityProject::OnTogglePinnedHead()
auto ruler = GetRulerPanel();
if (ruler)
// Update button image
ruler->UpdateButtonStates();
auto &scrubber = GetScrubber();
@ -5439,6 +5445,12 @@ void AudacityProject::OnShowMixerToolBar()
ModifyToolbarMenus();
}
void AudacityProject::OnShowScrubbingToolBar()
{
mToolManager->ShowHide( ScrubbingBarID );
ModifyToolbarMenus();
}
void AudacityProject::OnShowSelectionToolBar()
{
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.
void OnPlayStop();
void DoPlayStopSelect(bool click, bool shift);
bool DoPlayStopSelect(bool click, bool shift);
void OnPlayStopSelect();
void OnPlayOneSecond();
void OnPlayToSelection();
@ -305,6 +305,7 @@ void OnShowSelectionToolBar();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
void OnShowSpectralSelectionToolBar();
#endif
void OnShowScrubbingToolBar();
void OnShowToolsToolBar();
void OnShowTranscriptionToolBar();
void OnResetToolBars();

View File

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

View File

@ -75,6 +75,7 @@ class EditToolBar;
class MeterToolBar;
class MixerToolBar;
class Scrubber;
class ScrubbingToolBar;
class SelectionBar;
class SpectralSelectionBar;
class Toolbar;
@ -442,6 +443,7 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame,
DeviceToolBar *GetDeviceToolBar();
EditToolBar *GetEditToolBar();
MixerToolBar *GetMixerToolBar();
ScrubbingToolBar *GetScrubbingToolBar();
SelectionBar *GetSelectionBar();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
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);
}
else if (captureMode.IsSameAs(wxT("scrubbing")))
{
CaptureToolbar(context.GetProject()->GetToolManager(), ScrubbingBarID, fileName);
}
else if (captureMode.IsSameAs(wxT("trackpanel")))
{
TrackPanel *panel = context.GetProject()->GetTrackPanel();

View File

@ -1,7 +1,7 @@
/**********************************************************************
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.
BassTreble.cpp
@ -12,16 +12,13 @@
\class EffectBassTreble
\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 "BassTreble.h"
#include <math.h>
#include <algorithm>
#include <wx/button.h>
#include <wx/intl.h>
@ -37,21 +34,20 @@ enum
{
ID_Bass = 10000,
ID_Treble,
ID_Level,
ID_Normalize,
ID_Gain,
ID_Link
};
// Define keys, defaults, minimums, and maximums for the effect parameters
//
// Name Type Key Def Min Max Scale
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( Level, double, XO("Level"), -1.0, -30.0, 0.0, 1 );
Param( Normalize, bool, XO("Normalize"), true, false, true, 1 );
// Name Type Key Def Min Max Scale
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( Gain, double, XO("Gain"), 0.0, -30.0, 30.0, 1 );
Param( Link, bool, XO("Link Sliders"), false, false, true, 1 );
// Sliders are integer, so range is x 10
// to allow 1 decimal place resolution
static const int kSliderScale = 10;
#include <wx/arrimpl.cpp>
WX_DEFINE_OBJARRAY(EffectBassTrebleStateArray);
// Used to communicate the type of the filter.
enum kShelfType
@ -61,23 +57,23 @@ enum kShelfType
};
BEGIN_EVENT_TABLE(EffectBassTreble, wxEvtHandler)
EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider)
EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider)
EVT_SLIDER(ID_Level, EffectBassTreble::OnLevelSlider)
EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText)
EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText)
EVT_TEXT(ID_Level, EffectBassTreble::OnLevelText)
EVT_CHECKBOX(ID_Normalize, EffectBassTreble::OnNormalize)
EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider)
EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider)
EVT_SLIDER(ID_Gain, EffectBassTreble::OnGainSlider)
EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText)
EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText)
EVT_TEXT(ID_Gain, EffectBassTreble::OnGainText)
EVT_CHECKBOX(ID_Link, EffectBassTreble::OnLinkCheckbox)
END_EVENT_TABLE()
EffectBassTreble::EffectBassTreble()
{
dB_bass = DEF_Bass;
dB_treble = DEF_Treble;
dB_level = DEF_Level;
mbNormalize = DEF_Normalize;
mBass = DEF_Bass;
mTreble = DEF_Treble;
mGain = DEF_Gain;
mLink = DEF_Link;
SetLinearEffectFlag(false);
SetLinearEffectFlag(true);
}
EffectBassTreble::~EffectBassTreble()
@ -93,7 +89,7 @@ wxString EffectBassTreble::GetSymbol()
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
@ -103,6 +99,16 @@ EffectType EffectBassTreble::GetType()
return EffectTypeProcess;
}
bool EffectBassTreble::SupportsRealtime()
{
#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS)
return true;
#else
return false;
#endif
}
// EffectClientInterface implementation
int EffectBassTreble::GetAudioInCount()
@ -117,61 +123,57 @@ int EffectBassTreble::GetAudioOutCount()
bool EffectBassTreble::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{
if (GetPass() == 1)
{
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);
}
InstanceInit(mMaster, mSampleRate);
return true;
}
sampleCount EffectBassTreble::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen)
{
float *ibuf = inBlock[0];
float *obuf = outBlock[0];
return InstanceProcess(mMaster, inBlock, outBlock, blockLen);
}
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);
}
}
bool EffectBassTreble::RealtimeInitialize()
{
SetBlockSize(512);
return blockLen;
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)
{
parms.Write(KEY_Bass, dB_bass);
parms.Write(KEY_Treble, dB_treble);
parms.Write(KEY_Level, dB_level);
parms.Write(KEY_Normalize, mbNormalize);
parms.Write(KEY_Bass, mBass);
parms.Write(KEY_Treble, mTreble);
parms.Write(KEY_Gain, mGain);
parms.Write(KEY_Link, mLink);
return true;
}
@ -180,144 +182,88 @@ bool EffectBassTreble::SetAutomationParameters(EffectAutomationParameters & parm
{
ReadAndVerifyDouble(Bass);
ReadAndVerifyDouble(Treble);
ReadAndVerifyDouble(Level);
ReadAndVerifyBool(Normalize);
ReadAndVerifyDouble(Gain);
ReadAndVerifyBool(Link);
dB_bass = Bass;
dB_treble = Treble;
dB_level = Level;
mbNormalize = Normalize;
mBass = Bass;
mTreble = Treble;
mGain = Gain;
mLink = Link;
return true;
}
bool EffectBassTreble::CheckWhetherSkipEffect()
{
return (mBass == 0.0 && mTreble == 0.0 && mGain == 0.0);
}
// 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)
{
S.StartVerticalLay(0);
S.SetBorder(5);
S.AddSpace(0, 5);
S.StartStatic(_("Tone controls"));
{
S.StartMultiColumn(3, wxEXPAND);
S.SetStretchyCol(2);
{
S.SetStretchyCol(2);
// Bass control
FloatingPointValidator<double> vldBass(1, &dB_bass);
FloatingPointValidator<double> vldBass(1, &mBass);
vldBass.SetRange(MIN_Bass, MAX_Bass);
mBassT = S.Id(ID_Bass).AddTextBox(_("&Bass (dB):"), wxT(""), 10);
mBassT->SetName(_("Bass (dB):"));
mBassT->SetValidator(vldBass);
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->SetPageSize(30);
// Treble control
FloatingPointValidator<double> vldTreble(1, &dB_treble);
FloatingPointValidator<double> vldTreble(1, &mTreble);
vldTreble.SetRange(MIN_Treble, MAX_Treble);
mTrebleT = S.Id(ID_Treble).AddTextBox(_("&Treble (dB):"), wxT(""), 10);
mTrebleT->SetValidator(vldTreble);
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->SetPageSize(30);
}
S.EndMultiColumn();
}
S.EndStatic();
// Level control
FloatingPointValidator<double> vldLevel(1, &dB_level);
vldLevel.SetRange(MIN_Level, MAX_Level);
mLevelT = S.Id(ID_Level).AddTextBox(_("&Level (dB):"), wxT(""), 10);
mLevelT->SetValidator(vldLevel);
S.StartStatic("Output");
{
S.StartMultiColumn(3, wxEXPAND);
{
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);
mLevelS = S.Id(ID_Level).AddSlider(wxT(""), 0, MAX_Level * kSliderScale, MIN_Level * kSliderScale);
mLevelS->SetName(_("Level"));
mLevelS->SetPageSize(30);
mGainS = S.Id(ID_Gain).AddSlider(wxT(""), 0, MAX_Gain * SCL_Gain, MIN_Gain * SCL_Gain);
mGainS->SetName(_("Level"));
}
S.EndMultiColumn();
// Normalize checkbox
S.StartHorizontalLay(wxLEFT, true);
S.StartMultiColumn(2, wxCENTER);
{
mNormalizeCheckBox = S.Id(ID_Normalize).AddCheckBox(_("&Enable level control"),
DEF_Normalize ? wxT("true") : wxT("false"));
mWarning = S.AddVariableText(wxT(""), false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
// Link checkbox
mLinkCheckBox = S.Id(ID_Link).AddCheckBox(_("Link Volume control to Tone controls"),
DEF_Link ? wxT("true") : wxT("false"));
}
S.EndHorizontalLay();
S.EndMultiColumn();
}
S.EndVerticalLay();
return;
S.EndStatic();
}
bool EffectBassTreble::TransferDataToWindow()
@ -327,12 +273,10 @@ bool EffectBassTreble::TransferDataToWindow()
return false;
}
mBassS->SetValue((int) dB_bass * kSliderScale + 0.5);
mTrebleS->SetValue((int) dB_treble * kSliderScale + 0.5);
mLevelS->SetValue((int) dB_level * kSliderScale + 0.5);
mNormalizeCheckBox->SetValue(mbNormalize);
UpdateUI();
mBassS->SetValue((int) (mBass * SCL_Bass));
mTrebleS->SetValue((int) mTreble *SCL_Treble);
mGainS->SetValue((int) mGain * SCL_Gain);
mLinkCheckBox->SetValue(mLink);
return true;
}
@ -344,18 +288,96 @@ bool EffectBassTreble::TransferDataFromWindow()
return false;
}
mbNormalize = mNormalizeCheckBox->GetValue();
return true;
}
// EffectBassTreble implementation
void EffectBassTreble::Coefficents(double hz, float slope, double gain, int type,
float& a0, float& a1, float& a2,
float& b0, float& b1, float& b2)
void EffectBassTreble::InstanceInit(EffectBassTrebleState & data, float sampleRate)
{
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 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
float out = (b0Bass * in + b1Bass * xn1Bass + b2Bass * xn2Bass -
a1Bass * yn1Bass - a2Bass * yn2Bass) / a0Bass;
xn2Bass = xn1Bass;
xn1Bass = in;
yn2Bass = yn1Bass;
yn1Bass = out;
float out = (data.b0Bass * in + data.b1Bass * data.xn1Bass + data.b2Bass * data.xn2Bass -
data.a1Bass * data.yn1Bass - data.a2Bass * data.yn2Bass) / data.a0Bass;
data.xn2Bass = data.xn1Bass;
data.xn1Bass = in;
data.yn2Bass = data.yn1Bass;
data.yn1Bass = out;
// Treble filter
in = out;
out = (b0Treble * in + b1Treble * xn1Treble + b2Treble * xn2Treble -
a1Treble * yn1Treble - a2Treble * yn2Treble) / a0Treble;
xn2Treble = xn1Treble;
xn1Treble = in;
yn2Treble = yn1Treble;
yn1Treble = out;
out = (data.b0Treble * in + data.b1Treble * data.xn1Treble + data.b2Treble * data.xn2Treble -
data.a1Treble * data.yn1Treble - data.a2Treble * data.yn2Treble) / data.a0Treble;
data.xn2Treble = data.xn1Treble;
data.xn1Treble = in;
data.yn2Treble = data.yn1Treble;
data.yn1Treble = out;
// Retain the maximum value for use in the normalization pass
if(mMax < fabs(out))
mMax = fabs(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))
{
mBassT->GetValidator()->TransferFromWindow();
mBassS->SetValue((int) floor(dB_bass * kSliderScale + 0.5));
UpdateUI();
double oldBass = mBass;
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
if (mLink) UpdateGain(oldBass, kBass);
mBassS->SetValue((int) (mBass * SCL_Bass));
}
void EffectBassTreble::OnTrebleText(wxCommandEvent & WXUNUSED(evt))
{
mTrebleT->GetValidator()->TransferFromWindow();
mTrebleS->SetValue((int) floor(dB_treble * kSliderScale + 0.5));
UpdateUI();
double oldTreble = mTreble;
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
if (mLink) UpdateGain(oldTreble, kTreble);
mTrebleS->SetValue((int) (mTreble * SCL_Treble));
}
void EffectBassTreble::OnLevelText(wxCommandEvent & WXUNUSED(evt))
void EffectBassTreble::OnGainText(wxCommandEvent & WXUNUSED(evt))
{
mLevelT->GetValidator()->TransferFromWindow();
mLevelS->SetValue((int) floor(dB_level * kSliderScale + 0.5));
UpdateUI();
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
mGainS->SetValue((int) (mGain * SCL_Gain));
}
void EffectBassTreble::OnBassSlider(wxCommandEvent & evt)
{
dB_bass = (double) evt.GetInt() / kSliderScale;
double oldBass = mBass;
mBass = (double) evt.GetInt() / SCL_Bass;
mBassT->GetValidator()->TransferToWindow();
UpdateUI();
if (mLink) UpdateGain(oldBass, kBass);
EnableApply(mUIParent->Validate());
}
void EffectBassTreble::OnTrebleSlider(wxCommandEvent & evt)
{
dB_treble = (double) evt.GetInt() / kSliderScale;
double oldTreble = mTreble;
mTreble = (double) evt.GetInt() / SCL_Treble;
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;
mLevelT->GetValidator()->TransferToWindow();
UpdateUI();
mGain = (double) evt.GetInt() / SCL_Gain;
mGainT->GetValidator()->TransferToWindow();
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(R) is copyright (c) 1999-2013 Audacity Team.
Audacity(R) is copyright (c) 1999-2016 Audacity Team.
License: GPL v2. See License.txt.
BassTreble.h (two shelf filters)
@ -12,12 +12,12 @@
#ifndef __AUDACITY_EFFECT_BASS_TREBLE__
#define __AUDACITY_EFFECT_BASS_TREBLE__
#include <wx/checkbox.h>
#include <wx/event.h>
#include <wx/slider.h>
#include <wx/stattext.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include "Effect.h"
@ -25,7 +25,23 @@ class ShuttleGui;
#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:
EffectBassTreble();
@ -39,6 +55,7 @@ public:
// EffectIdentInterface implementation
EffectType GetType() override;
bool SupportsRealtime() override;
// EffectClientInterface implementation
@ -46,57 +63,64 @@ public:
int GetAudioOutCount() override;
bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL) 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 SetAutomationParameters(EffectAutomationParameters & parms) override;
// Effect Implementation
bool Startup() override;
bool InitPass1() override;
bool InitPass2() override;
// Effect Implementation
void PopulateOrExchange(ShuttleGui & S) override;
bool TransferDataToWindow() override;
bool TransferDataFromWindow() override;
bool CheckWhetherSkipEffect() override;
private:
// EffectBassTreble implementation
void Coefficents(double hz, float slope, double gain, int type,
float& a0, float& a1, float& a2, float& b0, float& b1, float& b2);
float DoFilter(float in);
void UpdateUI();
void InstanceInit(EffectBassTrebleState & data, float sampleRate);
sampleCount InstanceProcess(EffectBassTrebleState & data, float **inBlock, float **outBlock, sampleCount blockLen);
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 OnTrebleText(wxCommandEvent & evt);
void OnLevelText(wxCommandEvent & evt);
void OnGainText(wxCommandEvent & evt);
void OnBassSlider(wxCommandEvent & evt);
void OnTrebleSlider(wxCommandEvent & evt);
void OnLevelSlider(wxCommandEvent & evt);
void OnNormalize(wxCommandEvent & evt);
void OnGainSlider(wxCommandEvent & evt);
void OnLinkCheckbox(wxCommandEvent & evt);
// Auto-adjust gain to reduce variation in peak level
void UpdateGain(double oldVal, int control );
private:
float xn1Bass, xn2Bass, yn1Bass, yn2Bass,
wBass, swBass, cwBass, aBass, bBass,
a0Bass, a1Bass, a2Bass, b0Bass, b1Bass, b2Bass;
// High shelf
float xn1Treble, xn2Treble, yn1Treble, yn2Treble,
wTreble, swTreble, cwTreble, aTreble, bTreble,
b0Treble, b1Treble, b2Treble, a0Treble, a1Treble, a2Treble;
EffectBassTrebleState mMaster;
EffectBassTrebleStateArray mSlaves;
double dB_bass, dB_treble, dB_level;
double mMax;
bool mbNormalize;
double mPreGain;
double mBass;
double mTreble;
double mGain;
bool mLink;
wxSlider *mBassS;
wxSlider *mTrebleS;
wxSlider *mLevelS;
wxTextCtrl *mBassT;
wxTextCtrl *mTrebleT;
wxTextCtrl *mLevelT;
wxCheckBox *mNormalizeCheckBox;
wxStaticText *mWarning;
wxSlider *mBassS;
wxSlider *mTrebleS;
wxSlider *mGainS;
wxTextCtrl *mBassT;
wxTextCtrl *mTrebleT;
wxTextCtrl *mGainT;
wxCheckBox *mLinkCheckBox;
DECLARE_EVENT_TABLE();
};

View File

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

View File

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

View File

@ -1,4 +1,4 @@
/**********************************************************************
/**********************************************************************
Audacity: A Digital Audio Editor
@ -330,3 +330,203 @@ void EditToolBar::EnableDisableButtons()
mButtons[ETBSyncLockID]->PopUp();
#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();
};
// 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

View File

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

View File

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

View File

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

View File

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

View File

@ -71,11 +71,7 @@ public:
~Scrubber();
// Assume xx is relative to the left edge of TrackPanel!
void MarkScrubStart(
wxCoord xx, bool smoothScrolling,
bool alwaysSeeking // if false, can switch seeking or scrubbing
// by mouse button state
);
void MarkScrubStart(wxCoord xx, bool smoothScrolling);
// Returns true iff the event should be considered consumed by this:
// Assume xx is relative to the left edge of TrackPanel!
@ -101,8 +97,14 @@ public:
void SetScrollScrubbing(bool value)
{ mSmoothScrollingScrub = value; }
bool IsAlwaysSeeking() const
{ return mAlwaysSeeking; }
bool Seeks() const
{ return mSeeking; }
bool Scrubs() const
{ return mScrubbing; }
void Cancel()
{ mCancelled = true; }
bool ShouldDrawScrubSpeed();
double FindScrubSpeed(bool seeking, double time) const;
@ -110,8 +112,6 @@ public:
void HandleScrollWheel(int steps);
bool PollIsSeeking();
// This returns the same as the enabled state of the menu items:
bool CanScrub() const;
@ -120,10 +120,13 @@ public:
// For popup
void PopulateMenu(wxMenu &menu);
void OnScrubOrSeek(bool &toToggle, bool &other);
void OnScrub(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;
// All possible status strings.
@ -133,9 +136,8 @@ public:
bool IsPaused() const;
private:
void DoScrub(bool seek);
void DoScrub();
void OnActivateOrDeactivateApp(wxActivateEvent & event);
void UncheckAllMenuItems();
void CheckMenuItem();
// I need this because I can't push the scrubber as an event handler
@ -156,11 +158,17 @@ private:
int mScrubSpeedDisplayCountdown;
wxCoord mScrubStartPosition;
wxCoord mLastScrubPosition {};
bool mScrubSeekPress;
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 mCancelled {};
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
int mLogMaxScrubSpeed;
#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
// changes, remove this and add the necessary keyboard movement
// handling.
// PRL: Commented out so the ESC key can stop dragging.
// bool AcceptsFocus() const {return false;}
// Note that AcceptsFocusFromKeyboard() rather than AcceptsFocus()
// is overridden so that ESC can cancel toolbar drag.
bool AcceptsFocusFromKeyboard() const override {return false;}
void PushButton(bool state);

View File

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

View File

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