All generator effects now use common Duration handling

(Also updated a few windows IDs to fit the pattern)
This commit is contained in:
Leland Lucius 2015-04-19 19:44:10 -05:00
parent cc0b4789a8
commit 5e27710495
11 changed files with 173 additions and 120 deletions

View File

@ -93,8 +93,8 @@ public:
virtual ~EffectHostInterface() {};
virtual double GetDefaultDuration() = 0;
virtual double GetDuration() = 0;
virtual bool SetDuration(double seconds) = 0;
virtual double GetDuration(bool *isSelection = NULL) = 0;
virtual void SetDuration(double seconds) = 0;
virtual bool Apply() = 0;
virtual void Preview() = 0;

View File

@ -98,6 +98,8 @@ int EffectDtmf::GetAudioOutCount()
bool EffectDtmf::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{
double duration = GetDuration();
// all dtmf sequence durations in samples from seconds
// MJS: Note that mDuration is in seconds but will have been quantised to the units of the TTC.
// If this was 'samples' and the project rate was lower than the track rate,
@ -105,7 +107,7 @@ bool EffectDtmf::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames
// However we are making our best efforts at creating what was asked for.
sampleCount nT0 = (sampleCount)floor(mT0 * mSampleRate + 0.5);
sampleCount nT1 = (sampleCount)floor((mT0 + mDuration) * mSampleRate + 0.5);
sampleCount nT1 = (sampleCount)floor((mT0 + duration) * mSampleRate + 0.5);
numSamplesSequence = nT1 - nT0; // needs to be exact number of samples selected
//make under-estimates if anything, and then redistribute the few remaining samples
@ -279,17 +281,6 @@ void EffectDtmf::PopulateOrExchange(ShuttleGui & S)
// value from saved config: this is useful is user wants to
// replace selection with dtmf sequence
bool isSelection = false;
if (mT1 > mT0) {
// there is a selection: let's fit in there...
// MJS: note that this is just for the TTC and is independent of the track rate
// but we do need to make sure we have the right number of samples at the project rate
double quantMT0 = QUANTIZED_TIME(mT0, mProjectRate);
double quantMT1 = QUANTIZED_TIME(mT1, mProjectRate);
mDuration = quantMT1 - quantMT0;
isSelection = true;
}
S.AddSpace(0, 5);
S.StartMultiColumn(2, wxCENTER);
{
@ -301,17 +292,16 @@ void EffectDtmf::PopulateOrExchange(ShuttleGui & S)
vldAmp.SetRange(MIN_Amplitude, MAX_Amplitude);
S.AddTextBox(_("Amplitude (0-1):"), wxT(""), 10)->SetValidator(vldAmp);
bool isSelection;
double duration = GetDuration(&isSelection);
S.AddPrompt(_("Duration:"));
mDtmfDurationT = new
NumericTextCtrl(NumericConverter::TIME,
S.GetParent(),
wxID_ANY,
/* use this instead of "seconds" because if a selection is passed to the
* effect, I want it (mDuration) to be used as the duration, and with
* "seconds" this does not always work properly. For example, it rounds
* down to zero... */
isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
mDuration,
duration,
mProjectRate,
wxDefaultPosition,
wxDefaultSize,
@ -354,7 +344,8 @@ bool EffectDtmf::TransferDataToWindow()
}
mDtmfDutyS->SetValue(dtmfDutyCycle * SCL_DutyCycle);
mDtmfDurationT->SetValue(mDuration);
mDtmfDurationT->SetValue(GetDuration());
UpdateUI();
@ -369,7 +360,7 @@ bool EffectDtmf::TransferDataFromWindow()
}
dtmfDutyCycle = (double) mDtmfDutyS->GetValue() / SCL_DutyCycle;
mDuration = mDtmfDurationT->GetValue();
SetDuration(mDtmfDurationT->GetValue());
// recalculate to make sure all values are up-to-date. This is especially
// important if the user did not change any values in the dialog
@ -390,13 +381,13 @@ void EffectDtmf::Recalculate()
// no tones, all zero: don't do anything
// this should take care of the case where user got an empty
// dtmf sequence into the generator: track won't be generated
mDuration = 0;
SetDuration(0.0);
dtmfTone = 0;
dtmfSilence = mDuration;
dtmfSilence = 0;
} else {
if (dtmfNTones==1) {
// single tone, as long as the sequence
dtmfTone = mDuration;
dtmfTone = GetDuration();
dtmfSilence = 0;
} else {
// Don't be fooled by the fact that you divide the sequence into dtmfNTones:
@ -408,7 +399,7 @@ void EffectDtmf::Recalculate()
// which can be simplified in the one below.
// Then just take the part that belongs to tone or silence.
//
double slot = mDuration / ((double)dtmfNTones + (dtmfDutyCycle / 100.0) - 1);
double slot = GetDuration() / ((double)dtmfNTones + (dtmfDutyCycle / 100.0) - 1);
dtmfTone = slot * (dtmfDutyCycle / 100.0); // seconds
dtmfSilence = slot * (1.0 - (dtmfDutyCycle / 100.0)); // seconds
@ -571,7 +562,7 @@ void EffectDtmf::OnSlider(wxCommandEvent & evt)
void EffectDtmf::OnText(wxCommandEvent & WXUNUSED(evt))
{
mDuration = mDtmfDurationT->GetValue();
SetDuration(mDtmfDurationT->GetValue());
mUIParent->TransferDataFromWindow();
Recalculate();
UpdateUI();

View File

@ -646,26 +646,64 @@ double Effect::GetDefaultDuration()
return 30.0;
}
double Effect::GetDuration()
double Effect::GetDuration(bool *isSelection)
{
if (mT1 > mT0)
{
return mT1 - mT0;
// there is a selection: let's fit in there...
// MJS: note that this is just for the TTC and is independent of the track rate
// but we do need to make sure we have the right number of samples at the project rate
double quantMT0 = QUANTIZED_TIME(mT0, mProjectRate);
double quantMT1 = QUANTIZED_TIME(mT1, mProjectRate);
mDuration = quantMT1 - quantMT0;
if (isSelection)
{
*isSelection = true;
}
return mDuration;
}
if (isSelection)
{
*isSelection = false;
}
GetPrivateConfig(GetCurrentSettingsGroup(), wxT("LastUsedDuration"), mDuration, 0.0);
if (mDuration > 0.0)
{
return mDuration;
}
if (mDuration < 0.0)
{
mDuration = 0.0;
}
if (GetType() == EffectTypeGenerate)
{
return GetDefaultDuration();
mDuration = GetDefaultDuration();
}
return 0;
return mDuration;
}
bool Effect::SetDuration(double seconds)
void Effect::SetDuration(double seconds)
{
if (seconds < 0.0)
{
seconds = 0.0;
}
if (mDuration != seconds)
{
SetPrivateConfig(GetCurrentSettingsGroup(), wxT("LastUsedDuration"), seconds);
}
mDuration = seconds;
return true;
return;
}
bool Effect::Apply()

View File

@ -144,8 +144,8 @@ class AUDACITY_DLL_API Effect : public wxEvtHandler,
// EffectHostInterface implementation
virtual double GetDefaultDuration();
virtual double GetDuration();
virtual bool SetDuration(double duration);
virtual double GetDuration(bool *isSelection = NULL);
virtual void SetDuration(double duration);
virtual bool Apply();
virtual void Preview();
@ -344,7 +344,6 @@ protected:
wxDialog *mUIDialog;
wxWindow *mUIParent;
double mDuration;
sampleCount mSampleCnt;
// type of the tracks on mOutputTracks
@ -373,6 +372,8 @@ protected:
private:
wxWindow *mParent;
double mDuration;
wxArrayPtrVoid mIMap;
wxArrayPtrVoid mOMap;

View File

@ -23,7 +23,7 @@
bool Generator::Process()
{
if (mDuration < 0.0)
if (GetDuration() < 0.0)
return false;
@ -49,7 +49,7 @@ bool Generator::Process()
//make sure there's room.
if (!editClipCanMove &&
track->IsEmpty(mT0, mT1+1.0/track->GetRate()) &&
!track->IsEmpty(mT0, mT0+mDuration-(mT1-mT0)-1.0/track->GetRate()))
!track->IsEmpty(mT0, mT0+GetDuration()-(mT1-mT0)-1.0/track->GetRate()))
{
wxMessageBox(
_("There is not enough room available to generate the audio"),
@ -58,7 +58,7 @@ bool Generator::Process()
return false;
}
if (mDuration > 0.0)
if (GetDuration() > 0.0)
{
// Create a temporary track
std::auto_ptr<WaveTrack> tmp(
@ -74,7 +74,7 @@ bool Generator::Process()
else {
// Transfer the data from the temporary track to the actual one
tmp->Flush();
SetTimeWarper(new StepTimeWarper(mT0+mDuration, mDuration-(mT1-mT0)));
SetTimeWarper(new StepTimeWarper(mT0+GetDuration(), GetDuration()-(mT1-mT0)));
bGoodResult = track->ClearAndPaste(mT0, mT1, &*tmp, true,
false, GetTimeWarper());
}
@ -94,7 +94,7 @@ bool Generator::Process()
ntrack++;
}
else if (t->IsSyncLockSelected()) {
t->SyncLockAdjust(mT1, mT0 + mDuration);
t->SyncLockAdjust(mT1, mT0 + GetDuration());
}
// Move on to the next track
t = iter.Next();
@ -104,7 +104,7 @@ bool Generator::Process()
this->ReplaceProcessedTracks(bGoodResult);
mT1 = mT0 + mDuration; // Update selection.
mT1 = mT0 + GetDuration(); // Update selection.
return true;
}
@ -114,7 +114,7 @@ bool BlockGenerator::GenerateTrack(WaveTrack *tmp,
int ntrack)
{
bool bGoodResult = true;
numSamples = track.TimeToLongSamples(mDuration);
numSamples = track.TimeToLongSamples(GetDuration());
sampleCount i = 0;
float *data = new float[tmp->GetMaxBlockSize()];
sampleCount block = 0;

View File

@ -56,7 +56,6 @@ EffectNoise::EffectNoise()
{
mType = DEF_Type;
mAmp = DEF_Amp;
mDuration = GetDefaultDuration();
y = z = buf0 = buf1 = buf2 = buf3 = buf4 = buf5 = buf6 = 0;
}
@ -222,18 +221,17 @@ void EffectNoise::PopulateOrExchange(ShuttleGui & S)
FloatingPointValidator<double> vldAmp(1, &mAmp, NUM_VAL_NO_TRAILING_ZEROES);
vldAmp.SetRange(MIN_Amp, MAX_Amp);
S.AddTextBox(_("Amplitude (0-1):"), wxT(""), 12)->SetValidator(vldAmp);
S.AddPrompt(_("Duration:"));
bool isSelection;
double duration = GetDuration(&isSelection);
S.AddPrompt(_("Duration:"));
mNoiseDurationT = new
NumericTextCtrl(NumericConverter::TIME,
S.GetParent(),
wxID_ANY,
/* use this instead of "seconds" because if a selection is passed to
* the effect, I want it (nDuration) to be used as the duration, and
* with "seconds" this does not always work properly. For example,
* it rounds down to zero... */
(mT1 > mT0) ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
mDuration,
isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
duration,
mProjectRate,
wxDefaultPosition,
wxDefaultSize,
@ -252,7 +250,7 @@ bool EffectNoise::TransferDataToWindow()
return false;
}
mNoiseDurationT->SetValue(mDuration);
mNoiseDurationT->SetValue(GetDuration());
return true;
}
@ -264,7 +262,7 @@ bool EffectNoise::TransferDataFromWindow()
return false;
}
mDuration = mNoiseDurationT->GetValue();
SetDuration(mNoiseDurationT->GetValue());
return true;
}

View File

@ -54,22 +54,22 @@ void EffectSilence::PopulateOrExchange(ShuttleGui & S)
{
S.StartHorizontalLay();
{
bool isSelection;
double duration = GetDuration(&isSelection);
S.AddPrompt(_("Duration:"));
if (S.GetMode() == eIsCreating)
{
mDurationT = new
NumericTextCtrl(NumericConverter::TIME,
S.GetParent(),
wxID_ANY,
(mT1 > mT0) ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
mDuration,
mProjectRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDurationT->SetName(_("Duration"));
mDurationT->EnableMenu();
}
mDurationT = new
NumericTextCtrl(NumericConverter::TIME,
S.GetParent(),
wxID_ANY,
isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
duration,
mProjectRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDurationT->SetName(_("Duration"));
mDurationT->EnableMenu();
S.AddWindow(mDurationT, wxALIGN_CENTER | wxALL);
}
S.EndHorizontalLay();
@ -81,14 +81,14 @@ void EffectSilence::PopulateOrExchange(ShuttleGui & S)
bool EffectSilence::TransferDataToWindow()
{
mDurationT->SetValue(mDuration);
mDurationT->SetValue(GetDuration());
return true;
}
bool EffectSilence::TransferDataFromWindow()
{
mDuration = mDurationT->GetValue();
SetDuration(mDurationT->GetValue());
return true;
}
@ -97,7 +97,7 @@ bool EffectSilence::GenerateTrack(WaveTrack *tmp,
const WaveTrack & WXUNUSED(track),
int WXUNUSED(ntrack))
{
bool bResult = tmp->InsertSilence(0.0, mDuration);
bool bResult = tmp->InsertSilence(0.0, GetDuration());
wxASSERT(bResult);
return bResult;
}

View File

@ -388,13 +388,16 @@ void EffectToneGen::PopulateOrExchange(ShuttleGui & S)
t->SetValidator(vldAmplitude);
}
bool isSelection;
double duration = GetDuration(&isSelection);
S.AddPrompt(_("Duration:"));
mToneDurationT = new
NumericTextCtrl(NumericConverter::TIME,
S.GetParent(),
wxID_ANY,
(mT1 > mT0) ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
mDuration,
isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
duration,
mProjectRate,
wxDefaultPosition,
wxDefaultSize,
@ -415,7 +418,7 @@ bool EffectToneGen::TransferDataToWindow()
return false;
}
mToneDurationT->SetValue(mDuration);
mToneDurationT->SetValue(GetDuration());
return true;
}
@ -433,7 +436,7 @@ bool EffectToneGen::TransferDataFromWindow()
mAmplitude[1] = mAmplitude[0];
}
mDuration = mToneDurationT->GetValue();
SetDuration(mToneDurationT->GetValue());
return true;
}

View File

@ -880,15 +880,15 @@ private:
enum
{
ID_DURATION = 20000,
ID_SLIDERS = 21000,
ID_Duration = 20000,
ID_Sliders = 21000,
};
DEFINE_LOCAL_EVENT_TYPE(EVT_SIZEWINDOW);
DEFINE_LOCAL_EVENT_TYPE(EVT_UPDATEDISPLAY);
BEGIN_EVENT_TABLE(VSTEffect, wxEvtHandler)
EVT_COMMAND_RANGE(ID_SLIDERS, ID_SLIDERS + 999, wxEVT_COMMAND_SLIDER_UPDATED, VSTEffect::OnSlider)
EVT_COMMAND_RANGE(ID_Sliders, ID_Sliders + 999, wxEVT_COMMAND_SLIDER_UPDATED, VSTEffect::OnSlider)
// Events from the audioMaster callback
EVT_COMMAND(wxID_ANY, EVT_SIZEWINDOW, VSTEffect::OnSizeWindow)
@ -2207,6 +2207,16 @@ bool VSTEffect::IsGraphicalUI()
bool VSTEffect::ValidateUI()
{
if (!mParent->Validate() || !mParent->TransferDataFromWindow())
{
return false;
}
if (GetType() == EffectTypeGenerate)
{
mHost->SetDuration(mDuration->GetValue());
}
return true;
}
@ -3492,17 +3502,21 @@ void VSTEffect::BuildPlain()
// Add the duration control for generators
if (GetType() == EffectTypeGenerate)
{
bool isSelection;
double duration = mHost->GetDuration(&isSelection);
wxControl *item = new wxStaticText(scroller, 0, _("Duration:"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
mDuration = new NumericTextCtrl(NumericConverter::TIME,
scroller,
ID_DURATION,
_("hh:mm:ss + milliseconds"),
mHost->GetDuration(),
mSampleRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDuration = new
NumericTextCtrl(NumericConverter::TIME,
scroller,
ID_Duration,
isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
duration,
mSampleRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDuration->SetName(_("Duration"));
mDuration->EnableMenu();
gridSizer->Add(mDuration, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
@ -3543,7 +3557,7 @@ void VSTEffect::BuildPlain()
gridSizer->Add(mNames[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
mSliders[i] = new wxSlider(scroller,
ID_SLIDERS + i,
ID_Sliders + i,
0,
0,
1000,
@ -3644,7 +3658,7 @@ void VSTEffect::OnSizeWindow(wxCommandEvent & evt)
void VSTEffect::OnSlider(wxCommandEvent & evt)
{
wxSlider *s = (wxSlider *) evt.GetEventObject();
int i = s->GetId() - ID_SLIDERS;
int i = s->GetId() - ID_Sliders;
callSetParameter(i, s->GetValue() / 1000.0);

View File

@ -361,10 +361,10 @@ void LadspaEffectOptionsDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
enum
{
ID_DURATION = 20000,
ID_TOGGLES = 21000,
ID_SLIDERS = 22000,
ID_TEXTS = 23000,
ID_Duration = 20000,
ID_Toggles = 21000,
ID_Sliders = 22000,
ID_Texts = 23000,
};
///////////////////////////////////////////////////////////////////////////////
@ -374,9 +374,9 @@ enum
///////////////////////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE(LadspaEffect, wxEvtHandler)
EVT_COMMAND_RANGE(ID_TOGGLES, ID_TOGGLES + 999, wxEVT_COMMAND_CHECKBOX_CLICKED, LadspaEffect::OnCheckBox)
EVT_COMMAND_RANGE(ID_SLIDERS, ID_SLIDERS + 999, wxEVT_COMMAND_SLIDER_UPDATED, LadspaEffect::OnSlider)
EVT_COMMAND_RANGE(ID_TEXTS, ID_TEXTS + 999, wxEVT_COMMAND_TEXT_UPDATED, LadspaEffect::OnTextCtrl)
EVT_COMMAND_RANGE(ID_Toggles, ID_Toggles + 999, wxEVT_COMMAND_CHECKBOX_CLICKED, LadspaEffect::OnCheckBox)
EVT_COMMAND_RANGE(ID_Sliders, ID_Sliders + 999, wxEVT_COMMAND_SLIDER_UPDATED, LadspaEffect::OnSlider)
EVT_COMMAND_RANGE(ID_Texts, ID_Texts + 999, wxEVT_COMMAND_TEXT_UPDATED, LadspaEffect::OnTextCtrl)
END_EVENT_TABLE()
LadspaEffect::LadspaEffect(const wxString & path, int index)
@ -1035,17 +1035,21 @@ bool LadspaEffect::PopulateUI(wxWindow *parent)
// Add the duration control for generators
if (GetType() == EffectTypeGenerate)
{
bool isSelection;
double duration = mHost->GetDuration(&isSelection);
item = new wxStaticText(w, 0, _("Duration:"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
mDuration = new NumericTextCtrl(NumericConverter::TIME,
w,
ID_DURATION,
_("hh:mm:ss + milliseconds"),
mHost->GetDuration(),
mSampleRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDuration = new
NumericTextCtrl(NumericConverter::TIME,
w,
ID_Duration,
isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
duration,
mSampleRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDuration->SetName(_("Duration"));
mDuration->EnableMenu();
gridSizer->Add(mDuration, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
@ -1071,7 +1075,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent)
if (LADSPA_IS_HINT_TOGGLED(hint.HintDescriptor))
{
mToggles[p] = new wxCheckBox(w, ID_TOGGLES + p, wxT(""));
mToggles[p] = new wxCheckBox(w, ID_Toggles + p, wxT(""));
mToggles[p]->SetName(labelText);
mToggles[p]->SetValue(mInputControls[p] > 0);
gridSizer->Add(mToggles[p], 0, wxALL, 5);
@ -1126,7 +1130,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent)
// Don't specify a value at creation time. This prevents unwanted events
// being sent to the OnTextCtrl() handler before the associated slider
// has been created.
mFields[p] = new wxTextCtrl(w, ID_TEXTS + p);
mFields[p] = new wxTextCtrl(w, ID_Texts + p);
mFields[p]->SetName(labelText);
gridSizer->Add(mFields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
@ -1149,7 +1153,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent)
gridSizer->Add(1, 1, 0);
}
mSliders[p] = new wxSlider(w, ID_SLIDERS + p,
mSliders[p] = new wxSlider(w, ID_Sliders + p,
0, 0, 1000,
wxDefaultPosition,
wxSize(200, -1));
@ -1472,14 +1476,14 @@ void LadspaEffect::FreeInstance(LADSPA_Handle handle)
void LadspaEffect::OnCheckBox(wxCommandEvent & evt)
{
int p = evt.GetId() - ID_TOGGLES;
int p = evt.GetId() - ID_Toggles;
mInputControls[p] = mToggles[p]->GetValue();
}
void LadspaEffect::OnSlider(wxCommandEvent & evt)
{
int p = evt.GetId() - ID_SLIDERS;
int p = evt.GetId() - ID_Sliders;
float val;
float lower = float(0.0);
@ -1516,7 +1520,7 @@ void LadspaEffect::OnSlider(wxCommandEvent & evt)
void LadspaEffect::OnTextCtrl(wxCommandEvent & evt)
{
LadspaEffect *that = reinterpret_cast<LadspaEffect *>(this);
int p = evt.GetId() - ID_TEXTS;
int p = evt.GetId() - ID_Texts;
float val;
float lower = float(0.0);

View File

@ -1078,7 +1078,7 @@ bool LV2Effect::ValidateUI()
if (GetType() == EffectTypeGenerate)
{
mHost->SetDuration(/* ... */ 0.0 /* ... */ );
mHost->SetDuration(mDuration->GetValue());
}
return true;
@ -1481,17 +1481,21 @@ bool LV2Effect::BuildPlain()
wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
bool isSelection;
double duration = mHost->GetDuration(&isSelection);
wxWindow *item = new wxStaticText(w, 0, _("&Duration:"));
sizer->Add(item, 0, wxALIGN_CENTER | wxALL, 5);
mDuration = new NumericTextCtrl(NumericConverter::TIME,
w,
ID_Duration,
_("hh:mm:ss + milliseconds"),
mHost->GetDuration(),
mSampleRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDuration = new
NumericTextCtrl(NumericConverter::TIME,
w,
ID_Duration,
isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"),
duration,
mSampleRate,
wxDefaultPosition,
wxDefaultSize,
true);
mDuration->SetName(_("Duration"));
mDuration->EnableMenu();
sizer->Add(mDuration, 0, wxALIGN_CENTER | wxALL, 5);