/********************************************************************** Audacity: A Digital Audio Editor AutoDuck.cpp Markus Meyer *******************************************************************//** \class EffectAutoDuck \brief Implements the Auto Ducking effect *******************************************************************/ #include #include #include #include #include #include #include "../Audacity.h" #include "../Prefs.h" #include "../Internat.h" #include "../Theme.h" #include "../AllThemeResources.h" #include "../AColor.h" #include "AutoDuck.h" #include /* * Default values for effect params */ #define PARAM_DEFAULT_DUCK_AMOUNT_DB -12.0 #define PARAM_DEFAULT_OUTER_FADE_DOWN_LEN 0.5 #define PARAM_DEFAULT_INNER_FADE_DOWN_LEN 0 #define PARAM_DEFAULT_OUTER_FADE_UP_LEN 0.5 #define PARAM_DEFAULT_INNER_FADE_UP_LEN 0 #define PARAM_DEFAULT_THRESHOLD_DB -30.0 #define PARAM_DEFAULT_MAXIMUM_PAUSE 1.0 /* * Common constants */ #define BUF_SIZE 4096 // number of samples to process at once #define RMS_WINDOW_SIZE 100 // samples in circular RMS window buffer /* * A auto duck region and an array of auto duck regions */ struct AutoDuckRegion { AutoDuckRegion(double t0, double t1) { this->t0 = t0; this->t1 = t1; } double t0; double t1; }; WX_DECLARE_OBJARRAY(AutoDuckRegion, AutoDuckRegionArray); WX_DEFINE_OBJARRAY(AutoDuckRegionArray); /* * Effect implementation */ EffectAutoDuck::EffectAutoDuck() { SetEffectFlags(BUILTIN_EFFECT | PROCESS_EFFECT | ADVANCED_EFFECT); gPrefs->Read(wxT("/Effects/AutoDuck/DuckAmountDb"), &mDuckAmountDb, PARAM_DEFAULT_DUCK_AMOUNT_DB); gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeDownLen"), &mInnerFadeDownLen, PARAM_DEFAULT_INNER_FADE_DOWN_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeUpLen"), &mInnerFadeUpLen, PARAM_DEFAULT_INNER_FADE_UP_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeDownLen"), &mOuterFadeDownLen, PARAM_DEFAULT_OUTER_FADE_DOWN_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeUpLen"), &mOuterFadeUpLen, PARAM_DEFAULT_OUTER_FADE_UP_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/ThresholdDb"), &mThresholdDb, PARAM_DEFAULT_THRESHOLD_DB); gPrefs->Read(wxT("/Effects/AutoDuck/MaximumPause"), &mMaximumPause, PARAM_DEFAULT_MAXIMUM_PAUSE); mControlTrack = NULL; } bool EffectAutoDuck::Init() { gPrefs->Read(wxT("/Effects/AutoDuck/DuckAmountDb"), &mDuckAmountDb, PARAM_DEFAULT_DUCK_AMOUNT_DB); gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeDownLen"), &mInnerFadeDownLen, PARAM_DEFAULT_INNER_FADE_DOWN_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeUpLen"), &mInnerFadeUpLen, PARAM_DEFAULT_INNER_FADE_UP_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeDownLen"), &mOuterFadeDownLen, PARAM_DEFAULT_OUTER_FADE_DOWN_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeUpLen"), &mOuterFadeUpLen, PARAM_DEFAULT_OUTER_FADE_UP_LEN); gPrefs->Read(wxT("/Effects/AutoDuck/ThresholdDb"), &mThresholdDb, PARAM_DEFAULT_THRESHOLD_DB); gPrefs->Read(wxT("/Effects/AutoDuck/MaximumPause"), &mMaximumPause, PARAM_DEFAULT_MAXIMUM_PAUSE); mControlTrack = NULL; TrackListIterator iter(mTracks); Track *t = iter.First(); bool lastWasSelectedWaveTrack = false; WaveTrack *controlTrackCandidate = NULL; while(t) { if (lastWasSelectedWaveTrack && !t->GetSelected() && t->GetKind() == Track::Wave) { // This could be the control track, so remember it controlTrackCandidate = (WaveTrack*)t; } lastWasSelectedWaveTrack = false; if (t->GetSelected()) { if (t->GetKind() == Track::Wave) { lastWasSelectedWaveTrack = true; } else { wxMessageBox( _("You selected a track which does not contain audio. AutoDuck can only process audio tracks."), _("Auto Duck"), wxICON_ERROR, mParent); return false; } } t = iter.Next(); } if (!controlTrackCandidate) { wxMessageBox( _("Auto Duck needs a control track which must be placed below the selected track(s)."), _("Auto Duck"), wxICON_ERROR, mParent); return false; } mControlTrack = controlTrackCandidate; return true; } bool EffectAutoDuck::TransferParameters( Shuttle & shuttle ) { shuttle.TransferDouble(wxT("DuckAmountDb"), mDuckAmountDb, PARAM_DEFAULT_DUCK_AMOUNT_DB); shuttle.TransferDouble(wxT("InnerFadeDownLen"), mInnerFadeDownLen, PARAM_DEFAULT_INNER_FADE_DOWN_LEN); shuttle.TransferDouble(wxT("InnerFadeUpLen"), mInnerFadeUpLen, PARAM_DEFAULT_INNER_FADE_UP_LEN); shuttle.TransferDouble(wxT("OuterFadeDownLen"), mOuterFadeDownLen, PARAM_DEFAULT_OUTER_FADE_DOWN_LEN); shuttle.TransferDouble(wxT("OuterFadeUpLen"), mOuterFadeUpLen, PARAM_DEFAULT_OUTER_FADE_UP_LEN); shuttle.TransferDouble(wxT("ThresholdDb"), mThresholdDb, PARAM_DEFAULT_THRESHOLD_DB); shuttle.TransferDouble(wxT("MaximumPause"), mMaximumPause, PARAM_DEFAULT_MAXIMUM_PAUSE); return true; } bool EffectAutoDuck::CheckWhetherSkipEffect() { return false; } void EffectAutoDuck::End() { mControlTrack = NULL; } bool EffectAutoDuck::PromptUser() { EffectAutoDuckDialog dlog(this, mParent); if (dlog.ShowModal() != wxID_OK) return false; // user cancelled dialog gPrefs->Write(wxT("/Effects/AutoDuck/DuckAmountDb"), mDuckAmountDb); gPrefs->Write(wxT("/Effects/AutoDuck/OuterFadeDownLen"), mOuterFadeDownLen); gPrefs->Write(wxT("/Effects/AutoDuck/OuterFadeUpLen"), mOuterFadeUpLen); gPrefs->Write(wxT("/Effects/AutoDuck/InnerFadeDownLen"), mInnerFadeDownLen); gPrefs->Write(wxT("/Effects/AutoDuck/InnerFadeUpLen"), mInnerFadeUpLen); gPrefs->Write(wxT("/Effects/AutoDuck/ThresholdDb"), mThresholdDb); gPrefs->Write(wxT("/Effects/AutoDuck/MaximumPause"), mMaximumPause); return true; } bool EffectAutoDuck::Process() { int i; if (GetNumWaveTracks() == 0 || !mControlTrack) return false; bool cancel = false; sampleCount start = mControlTrack->TimeToLongSamples(mT0 + mOuterFadeDownLen); sampleCount end = mControlTrack->TimeToLongSamples(mT1 - mOuterFadeUpLen); if (end <= start) return false; // the minimum number of samples we have to wait until the maximum // pause has been exceeded double maxPause = mMaximumPause; // We don't fade in until we have time enough to actually fade out again if (maxPause < mOuterFadeDownLen + mOuterFadeUpLen) maxPause = mOuterFadeDownLen + mOuterFadeUpLen; sampleCount minSamplesPause = mControlTrack->TimeToLongSamples(maxPause); double threshold = pow(10.0, mThresholdDb/20); // adjust the threshold so we can compare it to the rmsSum value threshold = threshold * threshold * RMS_WINDOW_SIZE; int rmsPos = 0; float rmsSum = 0; float *rmsWindow = new float[RMS_WINDOW_SIZE]; for (i = 0; i < RMS_WINDOW_SIZE; i++) rmsWindow[i] = 0; float *buf = new float[BUF_SIZE]; bool inDuckRegion = false; // initialize the following two variables to prevent compiler warning double duckRegionStart = 0; sampleCount curSamplesPause = 0; // to make the progress bar appear more natural, we first look for all // duck regions and apply them all at once afterwards AutoDuckRegionArray regions; sampleCount pos = start; while (pos < end) { sampleCount len = end - pos; if (len > BUF_SIZE) len = BUF_SIZE; mControlTrack->Get((samplePtr)buf, floatSample, pos, (sampleCount)len); for (i = pos; i < pos + len; i++) { rmsSum -= rmsWindow[rmsPos]; rmsWindow[rmsPos] = buf[i - pos] * buf[i - pos]; rmsSum += rmsWindow[rmsPos]; rmsPos = (rmsPos + 1) % RMS_WINDOW_SIZE; bool thresholdExceeded = rmsSum > threshold; if (thresholdExceeded) { // everytime the threshold is exceeded, reset our count for // the number of pause samples curSamplesPause = 0; if (!inDuckRegion) { // the threshold has been exceeded for the first time, so // let the duck region begin here inDuckRegion = true; duckRegionStart = mControlTrack->LongSamplesToTime(i); } } if (!thresholdExceeded && inDuckRegion) { // the threshold has not been exceeded and we are in a duck // region, but only fade in if the maximum pause has been // exceeded curSamplesPause += 1; if (curSamplesPause >= minSamplesPause) { // do the actual duck fade and reset all values double duckRegionEnd = mControlTrack->LongSamplesToTime(i - curSamplesPause); regions.Add(AutoDuckRegion( duckRegionStart - mOuterFadeDownLen, duckRegionEnd + mOuterFadeUpLen)); inDuckRegion = false; } } } pos += len; if (TotalProgress( ((double)(pos-start)) / (end-start) / (GetNumWaveTracks() + 1) )) { cancel = true; break; } } // apply last duck fade, if any if (inDuckRegion) { double duckRegionEnd = mControlTrack->LongSamplesToTime(end - curSamplesPause); regions.Add(AutoDuckRegion( duckRegionStart - mOuterFadeDownLen, duckRegionEnd + mOuterFadeUpLen)); } delete[] buf; delete[] rmsWindow; if (!cancel) { this->CopyInputTracks(); // Set up mOutputTracks. SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); Track *iterTrack = iter.First(); int trackNumber = 0; while (iterTrack) { wxASSERT(iterTrack->GetKind() == Track::Wave); WaveTrack* t = (WaveTrack*)iterTrack; for (i = 0; i < (int)regions.GetCount(); i++) { const AutoDuckRegion& region = regions[i]; if (ApplyDuckFade(trackNumber, t, region.t0, region.t1)) { cancel = true; break; } } if (cancel) break; iterTrack = iter.Next(); trackNumber++; } } this->ReplaceProcessedTracks(!cancel); return !cancel; } // this currently does an exponential fade bool EffectAutoDuck::ApplyDuckFade(int trackNumber, WaveTrack* t, double t0, double t1) { bool cancel = false; int start = t->TimeToLongSamples(t0); int end = t->TimeToLongSamples(t1); float *buf = new float[BUF_SIZE]; int pos = start; int fadeDownSamples = t->TimeToLongSamples( mOuterFadeDownLen + mInnerFadeDownLen); if (fadeDownSamples < 1) fadeDownSamples = 1; int fadeUpSamples = t->TimeToLongSamples( mOuterFadeUpLen + mInnerFadeUpLen); if (fadeUpSamples < 1) fadeUpSamples = 1; float fadeDownStep = mDuckAmountDb / fadeDownSamples; float fadeUpStep = mDuckAmountDb / fadeUpSamples; while (pos < end) { sampleCount len = end - pos; if (len > BUF_SIZE) len = BUF_SIZE; t->Get((samplePtr)buf, floatSample, pos, (sampleCount)len); for (int i = pos; i < pos + len; i++) { float gainDown = fadeDownStep * (i - start); float gainUp = fadeUpStep * (end - i);; float gain; if (gainDown > gainUp) gain = gainDown; else gain = gainUp; if (gain < mDuckAmountDb) gain = mDuckAmountDb; buf[i - pos] *= pow(10.0, gain / 20.0); } t->Set((samplePtr)buf, floatSample, pos, (sampleCount)len); pos += len; float curTime = t->LongSamplesToTime(pos); float fractionFinished = (curTime - mT0) / (mT1 - mT0); if (TotalProgress( (trackNumber + 1 + fractionFinished) / (GetNumWaveTracks() + 1) )) { cancel = true; break; } } delete[] buf; return cancel; } /* * Effect dialog implementation */ #define ID_DUCK_AMOUNT_DB 10001 #define ID_THRESHOLD_DB 10002 #define ID_INNER_FADE_DOWN_LEN 10003 #define ID_INNER_FADE_UP_LEN 10004 #define ID_OUTER_FADE_DOWN_LEN 10005 #define ID_OUTER_FADE_UP_LEN 10006 #define ID_MAXIMUM_PAUSE 10007 #define ID_PANEL 10008 BEGIN_EVENT_TABLE(EffectAutoDuckDialog, wxDialog) EVT_BUTTON(wxID_OK, EffectAutoDuckDialog::OnOk) EVT_BUTTON(wxID_CANCEL, EffectAutoDuckDialog::OnCancel) EVT_TEXT(ID_DUCK_AMOUNT_DB, EffectAutoDuckDialog::OnValueChanged) EVT_TEXT(ID_THRESHOLD_DB, EffectAutoDuckDialog::OnValueChanged) EVT_TEXT(ID_INNER_FADE_DOWN_LEN, EffectAutoDuckDialog::OnValueChanged) EVT_TEXT(ID_INNER_FADE_UP_LEN, EffectAutoDuckDialog::OnValueChanged) EVT_TEXT(ID_OUTER_FADE_DOWN_LEN, EffectAutoDuckDialog::OnValueChanged) EVT_TEXT(ID_OUTER_FADE_UP_LEN, EffectAutoDuckDialog::OnValueChanged) EVT_TEXT(ID_MAXIMUM_PAUSE, EffectAutoDuckDialog::OnValueChanged) END_EVENT_TABLE() EffectAutoDuckDialog::EffectAutoDuckDialog(EffectAutoDuck* effect, wxWindow *parent) : wxDialog(parent, -1, _("Auto Duck"), wxDefaultPosition, wxDefaultSize) { mEffect = effect; wxTextValidator vld(wxFILTER_NUMERIC); ShuttleGui S(this, eIsCreating); S.SetBorder(5); S.StartVerticalLay(true); { S.StartHorizontalLay(wxCENTER, false); { S.AddTitle(_("Auto Duck by Markus Meyer")); } S.EndHorizontalLay(); S.StartHorizontalLay(wxCENTER, false); { // Add a little space } S.EndHorizontalLay(); mPanel = (EffectAutoDuckPanel*) S.AddWindow(new EffectAutoDuckPanel(this, ID_PANEL)); S.StartHorizontalLay(wxCENTER, false); { // Add a little space } S.EndHorizontalLay(); S.StartMultiColumn(6, wxCENTER); { mDuckAmountDbBox = S.Id(ID_DUCK_AMOUNT_DB).AddTextBox( _("Duck amount:"), Internat::ToDisplayString(mEffect->mDuckAmountDb), 10); S.AddUnits(_("dB")); mDuckAmountDbBox->SetValidator(vld); mMaximumPauseBox = S.Id(ID_MAXIMUM_PAUSE).AddTextBox( _("Maximum pause:"), Internat::ToDisplayString(mEffect->mMaximumPause), 10); S.AddUnits(_("seconds")); mMaximumPauseBox->SetValidator(vld); mOuterFadeDownLenBox = S.Id(ID_OUTER_FADE_DOWN_LEN).AddTextBox( _("Outer fade down length:"), Internat::ToDisplayString(mEffect->mOuterFadeDownLen), 10); S.AddUnits(_("seconds")); mOuterFadeDownLenBox->SetValidator(vld); mOuterFadeUpLenBox = S.Id(ID_OUTER_FADE_UP_LEN).AddTextBox( _("Outer fade up length:"), Internat::ToDisplayString(mEffect->mOuterFadeUpLen), 10); S.AddUnits(_("seconds")); mOuterFadeUpLenBox->SetValidator(vld); mInnerFadeDownLenBox = S.Id(ID_INNER_FADE_DOWN_LEN).AddTextBox( _("Inner fade down length:"), Internat::ToDisplayString(mEffect->mInnerFadeDownLen), 10); S.AddUnits(_("seconds")); mInnerFadeDownLenBox->SetValidator(vld); mInnerFadeUpLenBox = S.Id(ID_INNER_FADE_UP_LEN).AddTextBox( _("Inner fade up length:"), Internat::ToDisplayString(mEffect->mInnerFadeUpLen), 10); S.AddUnits(_("seconds")); mInnerFadeUpLenBox->SetValidator(vld); } S.EndMultiColumn(); S.StartMultiColumn(3, wxCENTER); { mThresholdDbBox = S.Id(ID_THRESHOLD_DB).AddTextBox( _("Threshold:"), Internat::ToDisplayString(mEffect->mThresholdDb), 10); S.AddUnits(_("dB")); mThresholdDbBox->SetValidator(vld); } S.EndMultiColumn(); } S.EndVerticalLay(); S.AddStandardButtons(); Layout(); Fit(); SetMinSize(GetSize()); Center(); } void EffectAutoDuckDialog::OnOk(wxCommandEvent& evt) { double duckAmountDb = 0, thresholdDb = 0; double innerFadeDownLen = 0, innerFadeUpLen = 0; double outerFadeDownLen = 0, outerFadeUpLen = 0; double maximumPause = 0; bool success = mDuckAmountDbBox->GetValue().ToDouble(&duckAmountDb) && duckAmountDb > -100 && duckAmountDb < 0 && mThresholdDbBox->GetValue().ToDouble(&thresholdDb) && thresholdDb > -100 && thresholdDb < 0 && mInnerFadeDownLenBox->GetValue().ToDouble(&innerFadeDownLen) && innerFadeDownLen >= 0 && innerFadeDownLen < 1000 && mInnerFadeUpLenBox->GetValue().ToDouble(&innerFadeUpLen) && innerFadeUpLen >= 0 && innerFadeUpLen < 1000 && mOuterFadeDownLenBox->GetValue().ToDouble(&outerFadeDownLen) && outerFadeDownLen >= 0 && outerFadeDownLen < 1000 && mOuterFadeUpLenBox->GetValue().ToDouble(&outerFadeUpLen) && outerFadeUpLen >= 0 && outerFadeUpLen < 1000 && mMaximumPauseBox->GetValue().ToDouble(&maximumPause) && maximumPause >= 0 && maximumPause < 1000; if (!success) { wxMessageBox(_("Please enter valid values."), _("Auto Duck"), wxICON_ERROR, this); return; } mEffect->mDuckAmountDb = duckAmountDb; mEffect->mThresholdDb = thresholdDb; mEffect->mInnerFadeDownLen = innerFadeDownLen; mEffect->mInnerFadeUpLen = innerFadeUpLen; mEffect->mOuterFadeDownLen = outerFadeDownLen; mEffect->mOuterFadeUpLen = outerFadeUpLen; mEffect->mMaximumPause = maximumPause; EndModal(wxID_OK); } void EffectAutoDuckDialog::OnCancel(wxCommandEvent& evt) { EndModal(wxID_CANCEL); } void EffectAutoDuckDialog::OnValueChanged(wxCommandEvent& evt) { mPanel->Refresh(false); } /* * Effect dialog panel implementation */ #define CONTROL_POINT_REGION 10 // pixel distance to click on a control point #define CONTROL_POINT_MIN_MOVE 5 // min mouse move until value is changed #define TEXT_DISTANCE 15 // pixel distance text <-> center of control point #define FADE_DOWN_START 150 // x coordinate #define FADE_UP_START 450 // x coordinate #define DUCK_AMOUNT_START 50 // y coordinate #define MAX_DUCK_AMOUNT 0 // db #define MIN_DUCK_AMOUNT -24 // db #define MIN_FADE 0 // seconds #define MAX_FADE 3 // seconds #define FADE_SCALE 40 // scale factor for second -> pixel conversion #define DUCK_AMOUNT_SCALE 8 // scale factor for db -> pixel conversion static int GetDistance(const wxPoint& first, const wxPoint& second) { int distanceX = abs(first.x - second.x); int distanceY = abs(first.y - second.y); if (distanceX > distanceY) return distanceX; else return distanceY; } BEGIN_EVENT_TABLE(EffectAutoDuckPanel, wxPanel) EVT_PAINT(EffectAutoDuckPanel::OnPaint) EVT_MOUSE_CAPTURE_CHANGED(EffectAutoDuckPanel::OnMouseCaptureChanged) EVT_MOUSE_CAPTURE_LOST(EffectAutoDuckPanel::OnMouseCaptureLost) EVT_LEFT_DOWN(EffectAutoDuckPanel::OnLeftDown) EVT_LEFT_UP(EffectAutoDuckPanel::OnLeftUp) EVT_MOTION(EffectAutoDuckPanel::OnMotion) END_EVENT_TABLE() EffectAutoDuckPanel::EffectAutoDuckPanel(EffectAutoDuckDialog* parent, wxWindowID id) : wxPanel(parent, id, wxDefaultPosition, wxSize(600, 300)) { mParent = parent; mCurrentControlPoint = none; mBackgroundBitmap = NULL; ResetControlPoints(); } EffectAutoDuckPanel::~EffectAutoDuckPanel() { if (mBackgroundBitmap) delete mBackgroundBitmap; } void EffectAutoDuckPanel::ResetControlPoints() { mControlPoints[innerFadeDown] = wxPoint(-100,-100); mControlPoints[innerFadeUp] = wxPoint(-100,-100); mControlPoints[outerFadeDown] = wxPoint(-100,-100); mControlPoints[outerFadeUp] = wxPoint(-100,-100); mControlPoints[duckAmount] = wxPoint(-100,-100); } void EffectAutoDuckPanel::OnPaint(wxPaintEvent& evt) { int clientWidth, clientHeight; GetSize(&clientWidth, &clientHeight); if (!mBackgroundBitmap || mBackgroundBitmap->GetWidth() != clientWidth || mBackgroundBitmap->GetHeight() != clientHeight) { if (mBackgroundBitmap) delete mBackgroundBitmap; mBackgroundBitmap = new wxBitmap(clientWidth, clientHeight); } wxMemoryDC dc; dc.SelectObject(*mBackgroundBitmap); dc.SetBrush(*wxWHITE_BRUSH); dc.SetPen(*wxBLACK_PEN); dc.DrawRectangle(0, 0, clientWidth, clientHeight); dc.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)); dc.SetTextForeground(*wxBLACK); dc.SetTextBackground(*wxWHITE); double duckAmountDb = 0; double innerFadeDownLen = 0; double innerFadeUpLen = 0; double outerFadeDownLen = 0; double outerFadeUpLen = 0; mParent->mDuckAmountDbBox->GetValue().ToDouble(&duckAmountDb); mParent->mInnerFadeDownLenBox->GetValue().ToDouble(&innerFadeDownLen); mParent->mInnerFadeUpLenBox->GetValue().ToDouble(&innerFadeUpLen); mParent->mOuterFadeDownLenBox->GetValue().ToDouble(&outerFadeDownLen); mParent->mOuterFadeUpLenBox->GetValue().ToDouble(&outerFadeUpLen); if (innerFadeDownLen < MIN_FADE || innerFadeDownLen > MAX_FADE || innerFadeUpLen < MIN_FADE || innerFadeUpLen > MAX_FADE || outerFadeDownLen < MIN_FADE || outerFadeDownLen > MAX_FADE || outerFadeUpLen < MIN_FADE || outerFadeUpLen > MAX_FADE || duckAmountDb < MIN_DUCK_AMOUNT || duckAmountDb > MAX_DUCK_AMOUNT) { // values are out of range, no preview available wxString message = wxString::Format(_("Preview not available")); int textWidth = 0, textHeight = 0; dc.GetTextExtent(message, &textWidth, &textHeight); dc.DrawText(message, (clientWidth - textWidth) / 2, (clientHeight - textHeight) / 2); ResetControlPoints(); } else { // draw preview dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(wxPen(theTheme.Colour(clrGraphLines), 3, wxSOLID)); wxPoint points[6]; points[0].x = 10; points[0].y = DUCK_AMOUNT_START; points[1].x = FADE_DOWN_START - (int)(outerFadeDownLen * FADE_SCALE); points[1].y = DUCK_AMOUNT_START; points[2].x = FADE_DOWN_START + (int)(innerFadeDownLen * FADE_SCALE); points[2].y = DUCK_AMOUNT_START - (int)(duckAmountDb * DUCK_AMOUNT_SCALE); points[3].x = FADE_UP_START - (int)(innerFadeUpLen * FADE_SCALE); points[3].y = DUCK_AMOUNT_START - (int)(duckAmountDb * DUCK_AMOUNT_SCALE); points[4].x = FADE_UP_START + (int)(outerFadeUpLen * FADE_SCALE); points[4].y = DUCK_AMOUNT_START; points[5].x = clientWidth - 10; points[5].y = DUCK_AMOUNT_START; dc.DrawLines(6, points); dc.SetPen(wxPen(*wxBLACK, 1, wxDOT)); AColor::Line(dc, FADE_DOWN_START, 10, FADE_DOWN_START, clientHeight - 10); AColor::Line(dc, FADE_UP_START, 10, FADE_UP_START, clientHeight - 10); dc.SetPen(AColor::envelopePen); dc.SetBrush(*wxWHITE_BRUSH); mControlPoints[outerFadeDown] = points[1]; mControlPoints[innerFadeDown] = points[2]; mControlPoints[innerFadeUp] = points[3]; mControlPoints[outerFadeUp] = points[4]; mControlPoints[duckAmount] = wxPoint( (points[2].x + points[3].x) / 2, points[2].y); for (int i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++) { EControlPoint cp = (EControlPoint)i; int digits; float value; if (cp == innerFadeDown) { value = innerFadeDownLen; digits = 2; } else if (cp == innerFadeUp) { value = innerFadeUpLen; digits = 2; } else if (cp == outerFadeDown) { value = outerFadeDownLen; digits = 2; } else if (cp == outerFadeUp) { value = outerFadeUpLen; digits = 2; } else { value = duckAmountDb; digits = 1; } wxString valueStr = Internat::ToDisplayString(value, digits); valueStr += wxT(" "); if (cp == duckAmount) valueStr += _("dB"); // i18n-hint: short form of 'decibels' else valueStr += _("s"); // i18n-hint: short form of 'seconds' int textWidth = 0, textHeight = 0; GetTextExtent(valueStr, &textWidth, &textHeight); int textPosX = mControlPoints[i].x - textWidth / 2; int textPosY = mControlPoints[i].y; if (cp == duckAmount || cp == outerFadeDown || cp == outerFadeUp) textPosY -= TEXT_DISTANCE + textHeight; else textPosY += TEXT_DISTANCE; dc.DrawText(valueStr, textPosX, textPosY); dc.DrawEllipse(mControlPoints[i].x - 3, mControlPoints[i].y - 3, 6, 6); } } // copy background buffer to paint dc wxPaintDC paintDC(this); paintDC.Blit(0, 0, clientWidth, clientHeight, &dc, 0, 0); // clean up: necessary to free resources on Windows dc.SetPen(wxNullPen); dc.SetBrush(wxNullBrush); dc.SetFont(wxNullFont); dc.SelectObject(wxNullBitmap); } void EffectAutoDuckPanel::OnMouseCaptureChanged( wxMouseCaptureChangedEvent &evt) { SetCursor(wxNullCursor); mCurrentControlPoint = none; } void EffectAutoDuckPanel::OnMouseCaptureLost( wxMouseCaptureLostEvent &evt) { mCurrentControlPoint = none; if (HasCapture()) { ReleaseMouse(); } } EffectAutoDuckPanel::EControlPoint EffectAutoDuckPanel::GetNearestControlPoint(const wxPoint& pt) { int dist[AUTO_DUCK_PANEL_NUM_CONTROL_POINTS]; int i; for (i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++) dist[i] = GetDistance(pt, mControlPoints[i]); int curMinimum = 0; for (i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++) if (dist[i] < dist[curMinimum]) curMinimum = i; if (dist[curMinimum] <= CONTROL_POINT_REGION) return (EControlPoint)curMinimum; else return none; } void EffectAutoDuckPanel::OnLeftDown(wxMouseEvent &evt) { EControlPoint nearest = GetNearestControlPoint(evt.GetPosition()); if (nearest != none) { // this control point has been clicked mMouseDownPoint = evt.GetPosition(); mCurrentControlPoint = nearest; mControlPointMoveActivated = false; for (int i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++) mMoveStartControlPoints[i] = mControlPoints[i]; CaptureMouse(); } } void EffectAutoDuckPanel::OnLeftUp(wxMouseEvent &evt) { if (mCurrentControlPoint != none) { mCurrentControlPoint = none; ReleaseMouse(); } } void EffectAutoDuckPanel::OnMotion(wxMouseEvent &evt) { switch (GetNearestControlPoint(evt.GetPosition())) { case none: SetCursor(wxNullCursor); break; case innerFadeDown: case innerFadeUp: case outerFadeDown: case outerFadeUp: SetCursor(wxCursor(wxCURSOR_SIZEWE)); break; case duckAmount: SetCursor(wxCursor(wxCURSOR_SIZENS)); break; } if (mCurrentControlPoint != none) { if (!mControlPointMoveActivated) { int dist; if (mCurrentControlPoint == duckAmount) dist = abs(evt.GetY() - mMouseDownPoint.y); else dist = abs(evt.GetX() - mMouseDownPoint.x); if (dist >= CONTROL_POINT_MIN_MOVE) mControlPointMoveActivated = true; } if (mControlPointMoveActivated) { int dist; if (mCurrentControlPoint == duckAmount) dist = abs(evt.GetY() - mMoveStartControlPoints[mCurrentControlPoint].y); else dist = abs(evt.GetX() - mMoveStartControlPoints[mCurrentControlPoint].x); // TODO: Get rid of unused 'dist' var within this scope? float newValue; switch (mCurrentControlPoint) { case outerFadeDown: newValue = ((double)(FADE_DOWN_START - evt.GetX())) / FADE_SCALE; if (newValue < MIN_FADE) newValue = MIN_FADE; if (newValue > MAX_FADE) newValue = MAX_FADE; mParent->mOuterFadeDownLenBox->SetValue( Internat::ToDisplayString(newValue)); break; case outerFadeUp: newValue = ((double)(evt.GetX() - FADE_UP_START)) / FADE_SCALE; if (newValue < MIN_FADE) newValue = MIN_FADE; if (newValue > MAX_FADE) newValue = MAX_FADE; mParent->mOuterFadeUpLenBox->SetValue( Internat::ToDisplayString(newValue)); break; case innerFadeDown: newValue = ((double)(evt.GetX() - FADE_DOWN_START)) / FADE_SCALE; if (newValue < MIN_FADE) newValue = MIN_FADE; if (newValue > MAX_FADE) newValue = MAX_FADE; mParent->mInnerFadeDownLenBox->SetValue( Internat::ToDisplayString(newValue)); break; case innerFadeUp: newValue = ((double)(FADE_UP_START - evt.GetX())) / FADE_SCALE; if (newValue < MIN_FADE) newValue = MIN_FADE; if (newValue > MAX_FADE) newValue = MAX_FADE; mParent->mInnerFadeUpLenBox->SetValue( Internat::ToDisplayString(newValue)); break; case duckAmount: newValue = ((double)(DUCK_AMOUNT_START - evt.GetY())) / DUCK_AMOUNT_SCALE; if (newValue < MIN_DUCK_AMOUNT) newValue = MIN_DUCK_AMOUNT; if (newValue > MAX_DUCK_AMOUNT) newValue = MAX_DUCK_AMOUNT; mParent->mDuckAmountDbBox->SetValue( Internat::ToDisplayString(newValue)); break; case none: wxASSERT(false); // should not happen } Refresh(false); } } }