/********************************************************************** Audacity: A Digital Audio Editor Meter.cpp Dominic Mazzoni Vaughan Johnson 2004.06.25 refresh rate limited to 30mS, by ChackoN *******************************************************************//** \class Meter \brief VU Meter, for displaying recording/playback level This is a bunch of common code that can display many different forms of VU meters and other displays. But note that a lot of later code here assumes these are MeterToolBar meters, e.g., Meter::StartMonitoring, so these are not as generic/common as originally intended. *//****************************************************************//** \class MeterBar \brief A struct used by Meter to hold the position of one bar. *//****************************************************************//** \class MeterUpdateMsg \brief Message used to update the Meter *//****************************************************************//** \class MeterUpdateQueue \brief Queue of MeterUpdateMsg used to feed the Meter. *//******************************************************************/ #include "../Audacity.h" #include "../AudacityApp.h" #include #include #include #include #include #include #include #include #include #include #if defined(__WXMAC__) #include #endif #include #include "Meter.h" #include "../AudioIO.h" #include "../AColor.h" #include "../ImageManipulation.h" //#include "../../images/MixerImages.h" #include "../Project.h" #include "../toolbars/MeterToolBar.h" #include "../toolbars/ControlToolBar.h" #include "../Prefs.h" #include "../Theme.h" #include "../AllThemeResources.h" #include "../Experimental.h" /* Updates to the meter are passed accross via meter updates, each contained in * a MeterUpdateMsg object */ wxString MeterUpdateMsg::toString() { wxString output; // somewhere to build up a string in output = wxString::Format(wxT("Meter update msg: %i channels, %i samples\n"), \ kMaxMeterBars, numFrames); for (int i = 0; i 0) || (tailPeakCount[i] > 0)) return toString(); } return wxT(""); } // // The Meter passes itself messages via this queue so that it can // communicate between the audio thread and the GUI thread. // This class is as simple as possible in order to be thread-safe // without needing mutexes. // MeterUpdateQueue::MeterUpdateQueue(int maxLen): mBufferSize(maxLen) { mBuffer = new MeterUpdateMsg[mBufferSize]; Clear(); } // destructor MeterUpdateQueue::~MeterUpdateQueue() { delete[] mBuffer; } void MeterUpdateQueue::Clear() { mStart = 0; mEnd = 0; } // Add a message to the end of the queue. Return false if the // queue was full. bool MeterUpdateQueue::Put(MeterUpdateMsg &msg) { int len = (mEnd + mBufferSize - mStart) % mBufferSize; // Never completely fill the queue, because then the // state is ambiguous (mStart==mEnd) if (len >= mBufferSize-1) return false; //wxLogDebug(wxT("Put: %s"), msg.toString().c_str()); mBuffer[mEnd] = msg; mEnd = (mEnd+1)%mBufferSize; return true; } // Get the next message from the start of the queue. // Return false if the queue was empty. bool MeterUpdateQueue::Get(MeterUpdateMsg &msg) { int len = (mEnd + mBufferSize - mStart) % mBufferSize; if (len == 0) return false; msg = mBuffer[mStart]; mStart = (mStart+1)%mBufferSize; return true; } // // Meter class // enum { OnMeterUpdateID = 6000, OnDisableMeterID, OnMonitorID, OnHorizontalID, OnAutomatedInputLevelAdjustmentID, OnVerticalID, OnMultiID, OnEqualizerID, OnWaveformID, OnLinearID, OnDBID, OnClipID, OnFloatID, OnPreferencesID }; BEGIN_EVENT_TABLE(Meter, wxPanel) EVT_TIMER(OnMeterUpdateID, Meter::OnMeterUpdate) EVT_MOUSE_EVENTS(Meter::OnMouse) EVT_ERASE_BACKGROUND(Meter::OnErase) EVT_PAINT(Meter::OnPaint) EVT_SIZE(Meter::OnSize) EVT_MENU(OnDisableMeterID, Meter::OnDisableMeter) EVT_MENU(OnHorizontalID, Meter::OnHorizontal) EVT_MENU(OnVerticalID, Meter::OnVertical) EVT_MENU(OnMultiID, Meter::OnMulti) EVT_MENU(OnEqualizerID, Meter::OnEqualizer) EVT_MENU(OnWaveformID, Meter::OnWaveform) EVT_MENU(OnLinearID, Meter::OnLinear) EVT_MENU(OnDBID, Meter::OnDB) EVT_MENU(OnClipID, Meter::OnClip) EVT_MENU(OnMonitorID, Meter::OnMonitor) #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT EVT_MENU(OnAutomatedInputLevelAdjustmentID, Meter::OnAutomatedInputLevelAdjustment) #endif EVT_MENU(OnFloatID, Meter::OnFloat) EVT_MENU(OnPreferencesID, Meter::OnPreferences) END_EVENT_TABLE() IMPLEMENT_CLASS(Meter, wxPanel) Meter::Meter(wxWindow* parent, wxWindowID id, bool isInput, const wxPoint& pos /*= wxDefaultPosition*/, const wxSize& size /*= wxDefaultSize*/, Style style /*= HorizontalStereo*/, float fDecayRate /*= 60.0f*/) : wxPanel(parent, id, pos, size), mQueue(1024), mWidth(size.x), mHeight(size.y), mIsInput(isInput), mStyle(style), mDB(true), mDBRange(ENV_DB_RANGE), mDecay(true), mDecayRate(fDecayRate), mClip(true), mNumPeakSamplesToClip(3), mPeakHoldDuration(3), mT(0), mRate(0), mNumBars(0), mLayoutValid(false), mBitmap(NULL), mIcon(NULL) { int i; wxColour backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE); mBkgndBrush = wxBrush(backgroundColour, wxSOLID); UpdatePrefs(); mPeakPeakPen = wxPen(theTheme.Colour( clrMeterPeak), 1, wxSOLID); mDisabledPen = wxPen(theTheme.Colour( clrMeterDisabledPen), 1, wxSOLID); mLeftSize = wxSize(0, 0); mRightSize = wxSize(0, 0); if (mIsInput) { mPen = wxPen( theTheme.Colour( clrMeterInputPen ), 1, wxSOLID); mBrush = wxBrush( theTheme.Colour( clrMeterInputBrush ), wxSOLID); mRMSBrush = wxBrush( theTheme.Colour( clrMeterInputRMSBrush ), wxSOLID); mClipBrush = wxBrush( theTheme.Colour( clrMeterInputClipBrush ), wxSOLID); mLightPen = wxPen( theTheme.Colour( clrMeterInputLightPen ), 1, wxSOLID); mDarkPen = wxPen( theTheme.Colour( clrMeterInputDarkPen ), 1, wxSOLID); } else { mPen = wxPen( theTheme.Colour( clrMeterOutputPen ), 1, wxSOLID); mBrush = wxBrush( theTheme.Colour( clrMeterOutputBrush ), wxSOLID); mRMSBrush = wxBrush( theTheme.Colour( clrMeterOutputRMSBrush ), wxSOLID); mClipBrush = wxBrush( theTheme.Colour( clrMeterOutputClipBrush ), wxSOLID); mLightPen = wxPen( theTheme.Colour( clrMeterOutputLightPen ), 1, wxSOLID); mDarkPen = wxPen( theTheme.Colour( clrMeterOutputDarkPen ), 1, wxSOLID); } mDisabledBkgndBrush = wxBrush(theTheme.Colour( clrMeterDisabledBrush), wxSOLID); // mDisabledBkgndBrush = wxBrush( // wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DSHADOW), wxSOLID); // wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT), wxSOLID); if (mMeterDisabled) { mSavedBkgndBrush = mBkgndBrush; mSavedBrush = mBrush; mSavedRMSBrush = mRMSBrush; mBkgndBrush = mDisabledBkgndBrush; mBrush = mDisabledBkgndBrush; mRMSBrush = mDisabledBkgndBrush; } // MixerTrackCluster style has no menu, so disallows SetStyle, so never needs icon. if (mStyle != MixerTrackCluster) CreateIcon(2); mRuler.SetFonts(GetFont(), GetFont(), GetFont()); mTimer.SetOwner(this, OnMeterUpdateID); Reset(44100.0, true); for(i=0; iIsMonitoring()) gAudioIO->StopStream(); delete mIcon; if (mBitmap) delete mBitmap; } void Meter::UpdatePrefs() { mDBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE); mMeterRefreshRate = gPrefs->Read(wxT("/Meter/MeterRefreshRate"), 30); // MixerTrackCluster style has no menu, so disallows disabling the meter. if (mStyle == MixerTrackCluster) mMeterDisabled = 0L; else if (mIsInput) mMeterDisabled = gPrefs->Read(wxT("/Meter/MeterInputDisabled"), (long)0); else mMeterDisabled = gPrefs->Read(wxT("/Meter/MeterOutputDisabled"), (long)0); } void Meter::OnErase(wxEraseEvent &evt) { // Ignore it to prevent flashing } void Meter::OnPaint(wxPaintEvent &evt) { wxPaintDC dc(this); #ifdef __WXMAC__ // Mac OS X automatically double-buffers the screen for you, // so our bitmap is unneccessary HandlePaint(dc); #else if (!mBitmap) mBitmap = new wxBitmap(mWidth, mHeight); wxMemoryDC memDC; memDC.SelectObject(*mBitmap); HandlePaint(memDC); dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE); #endif } void Meter::OnSize(wxSizeEvent &evt) { delete mBitmap; mBitmap = NULL; GetClientSize(&mWidth, &mHeight); //::wxMessageBox(wxString::Format(" mHeight=%d, mWidth=%d", mHeight,mWidth)); mLayoutValid = false; } void Meter::OnMouse(wxMouseEvent &evt) { if (mStyle == MixerTrackCluster) // MixerTrackCluster style has no menu. return; #if wxUSE_TOOLTIPS // Not available in wxX11 if (evt.Leaving()){ GetActiveProject()->TP_DisplayStatusMessage(wxT("")); } else if (evt.Entering()) { // Display the tooltip in the status bar wxToolTip * pTip = this->GetToolTip(); if( pTip ) { wxString tipText = pTip->GetTip(); GetActiveProject()->TP_DisplayStatusMessage(tipText); } } #endif if (evt.RightDown() || (evt.ButtonDown() && mMenuRect.Contains(evt.m_x, evt.m_y))) { wxMenu *menu = new wxMenu(); // Note: these should be kept in the same order as the enum if (mMeterDisabled) menu->Append(OnDisableMeterID, _("Enable Meter")); else menu->Append(OnDisableMeterID, _("Disable Meter")); if (mIsInput) { if (gAudioIO->IsMonitoring()) menu->Append(OnMonitorID, _("Stop Monitoring")); else menu->Append(OnMonitorID, _("Start Monitoring")); #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT if (gAudioIO->AILAIsActive()) menu->Append(OnAutomatedInputLevelAdjustmentID, _("Stop Automated Input Level Adjustment")); else menu->Append(OnAutomatedInputLevelAdjustmentID, _("Start Automated Input Level Adjustment")); bool AVActive; gPrefs->Read(wxT("/AudioIO/AutomatedInputLevelAdjustment"), &AVActive, false); if (!AVActive || !GetActiveProject()->GetControlToolBar()->IsRecordDown()) menu->Enable(OnAutomatedInputLevelAdjustmentID, false); #endif } menu->AppendSeparator(); menu->Append(OnHorizontalID, _("Horizontal Stereo")); menu->Append(OnVerticalID, _("Vertical Stereo")); //menu->Append(OnMultiID, _("Vertical Multichannel")); //menu->Append(OnEqualizerID, _("Equalizer")); //menu->Append(OnWaveformID, _("Waveform")); //menu->Enable(OnHorizontalID + mStyle, false); menu->Enable(mStyle==VerticalStereo? OnVerticalID: OnHorizontalID, false); menu->AppendSeparator(); menu->Append(OnLinearID, _("Linear")); menu->Append(OnDBID, _("dB")); menu->Enable(mDB? OnDBID: OnLinearID, false); //menu->AppendSeparator(); //menu->Append(OnClipID, _("Turn on clipping")); //menu->AppendSeparator(); //menu->Append(OnFloatID, _("Float Window")); menu->AppendSeparator(); menu->Append(OnPreferencesID, _("Preferences...")); if (evt.RightDown()) PopupMenu(menu, evt.m_x, evt.m_y); else PopupMenu(menu, mMenuRect.x + 1, mMenuRect.y + mMenuRect.height + 1); delete menu; } else if (evt.ButtonDown()) { if (mIsInput) StartMonitoring(); } } void Meter::SetStyle(Meter::Style newStyle) { // MixerTrackCluster disallows style change. if (mStyle == MixerTrackCluster) return; mStyle = newStyle; mLayoutValid = false; Refresh(true); } void Meter::Reset(double sampleRate, bool resetClipping) { int j; mT = 0; mRate = sampleRate; for(j=0; jb? a: b; } static int intmin(int a, int b) { return ab? a: b; } static float ClipZeroToOne(float z) { if (z > 1.0) return 1.0; else if (z < 0.0) return 0.0; else return z; } static float ToDB(float v, float range) { double db; if (v > 0) db = 20 * log10(fabs(v)); else db = -999; return ClipZeroToOne((db + range) / range); } void Meter::UpdateDisplay(int numChannels, int numFrames, float *sampleData) { int i, j; float *sptr = sampleData; int num = intmin(numChannels, mNumBars); MeterUpdateMsg msg; msg.numFrames = numFrames; for(j=0; j=MAX_AUDIO) { if (msg.headPeakCount[j]==i) msg.headPeakCount[j]++; msg.tailPeakCount[j]++; if (msg.tailPeakCount[j] > mNumPeakSamplesToClip) msg.clipping[j] = true; } else msg.tailPeakCount[j] = 0; } sptr += numChannels; } for(j=0; j= MAX_AUDIO) { if (msg.headPeakCount[j]==i) msg.headPeakCount[j]++; msg.tailPeakCount[j]++; if (msg.tailPeakCount[j] > mNumPeakSamplesToClip) msg.clipping[j] = true; } else msg.tailPeakCount[j] = 0; } } if (mDB) { for(j=0; j if (mMeterDisabled) return; //wxLogDebug(wxT("Pop: %s"), msg.toString().c_str()); mT += deltaT; for(j=0; j mPeakHoldDuration || mBar[j].peak > mBar[j].peakHold) { mBar[j].peakHold = mBar[j].peak; mBar[j].peakHoldTime = mT; } if (mBar[j].peak > mBar[j].peakPeakHold ) mBar[j].peakPeakHold = mBar[j].peak; if (msg.clipping[j] || mBar[j].tailPeakCount+msg.headPeakCount[j] >= mNumPeakSamplesToClip){ mBar[j].clipping = true; mBar[j].isclipping = true; } mBar[j].tailPeakCount = msg.tailPeakCount[j]; #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT if (mT > gAudioIO->AILAGetLastDecisionTime()) { discarded = false; maxPeak = msg.peak[j] > maxPeak ? msg.peak[j] : maxPeak; printf("%f@%f ", msg.peak[j], mT); } else { discarded = true; printf("%f@%f discarded\n", msg.peak[j], mT); } #endif } } // while if (numChanges > 0) { #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT if (gAudioIO->AILAIsActive() && mIsInput && !discarded) { gAudioIO->AILAProcess(maxPeak); putchar('\n'); } #endif RepaintBarsNow(); } } float Meter::GetMaxPeak() { int j; float maxPeak = 0.; for(j=0; j maxPeak ? mBar[j].peak : maxPeak; return(maxPeak); } double Meter::ToLinearIfDB(double value) { if (mDB) value = pow(10.0, (-(1.0-value)*mDBRange)/20.0); return value; } wxFont Meter::GetFont() { int fontSize = 10; #if defined __WXMSW__ fontSize = 8; #endif return wxFont(fontSize, wxSWISS, wxNORMAL, wxNORMAL); } void Meter::ResetBar(MeterBar *b, bool resetClipping) { b->peak = 0.0; b->rms = 0.0; b->peakHold = 0.0; b->peakHoldTime = 0.0; if (resetClipping) { b->clipping = false; b->peakPeakHold =0.0; } b->isclipping = false; b->tailPeakCount = 0; } bool Meter::IsClipping() { for (int c = 0; c < kMaxMeterBars; c++) if (mBar[c].isclipping) return true; return false; } void Meter::HandleLayout() { int iconWidth = 0; int iconHeight = 0; int menuWidth = 0; int menuHeight = 0; if (mStyle != MixerTrackCluster) { iconWidth = mIcon->GetWidth(); iconHeight = mIcon->GetHeight(); menuWidth = 17; menuHeight = 14; } int width = mWidth; int height = mHeight; int left = 0, top = 0; int right, bottom; int barw, barh; int i; mRuler.SetFlip(true); mRuler.SetLabelEdges(true); switch(mStyle) { default: wxPrintf(wxT("Style not handled yet!\n")); break; case VerticalStereo: mMenuRect = wxRect(mWidth - menuWidth - 5, mHeight - menuHeight - 2, menuWidth, menuHeight); if (mHeight < (menuHeight + iconHeight + 8)) mIconPos = wxPoint(-999, -999); // Don't display else mIconPos = wxPoint(mWidth - iconWidth - 1, 1); width = intmin(mWidth-(iconWidth+2), mWidth-(menuWidth+3)); case MixerTrackCluster: // Doesn't show menu, icon, or L/R labels, // but otherwise like VerticalStereo. if (width >= mLeftSize.x + mRightSize.x + 24) { if (mStyle != MixerTrackCluster) { mLeftTextPos = wxPoint(2, height-2-mLeftSize.y); left += mLeftSize.x+4; } mRightTextPos = wxPoint(width-mLeftSize.x, height-2-mLeftSize.y); width -= mLeftSize.x + mRightSize.x + 8; //vvvvv ...but then -8 in UmixIt? -- for vertical only? } barw = (width-2)/2; barh = height - 4; mNumBars = 2; mBar[0].vert = true; ResetBar(&mBar[0], false); mBar[0].r = wxRect(left + width/2 - barw - 1, 2, barw, barh); if (mClip) { mBar[0].rClip = mBar[0].r; mBar[0].rClip.height = 3; mBar[0].r.y += 4; mBar[0].r.height -= 4; } mBar[1].vert = true; ResetBar(&mBar[1], false); mBar[1].r = wxRect(left + width/2 + 1, 2, barw, barh); if (mClip) { mBar[1].rClip = mBar[1].r; mBar[1].rClip.height = 3; mBar[1].r.y += 4; mBar[1].r.height -= 4; } mRuler.SetOrientation(wxVERTICAL); mRuler.SetBounds(mBar[1].r.x + mBar[1].r.width + 1, mBar[1].r.y, mBar[1].r.x + width, mBar[1].r.y + mBar[1].r.height); if (mDB) { mRuler.SetRange(0, -mDBRange); mRuler.SetFormat(Ruler::LinearDBFormat); } else { mRuler.SetRange(1, 0); mRuler.SetFormat(Ruler::RealFormat); } if (mStyle != MixerTrackCluster) // MixerTrackCluster style has no menu. mRuler.OfflimitsPixels(mMenuRect.y-mBar[1].r.y, mBar[1].r.height); break; case HorizontalStereo: if (mWidth < menuWidth + iconWidth + 8) { mIconPos = wxPoint(-999, -999); // Don't display icon mMenuRect = wxRect(2, mHeight - menuHeight - 2, menuWidth, menuHeight); } else { mIconPos = wxPoint(2, mHeight - iconHeight); mMenuRect = wxRect(iconWidth + 2, mHeight - menuHeight - 5, menuWidth, menuHeight); } height = intmin(height-(menuHeight+3), height-iconHeight) - 2; left = 2 + intmax(mLeftSize.x, mRightSize.x); width -= left; mLeftTextPos = wxPoint(2, (height)/4 - mLeftSize.y/2); mRightTextPos = wxPoint(2, (height*3)/4 - mLeftSize.y/2); barw = width - 4; barh = (height-2)/2; mNumBars = 2; mBar[0].vert = false; ResetBar(&mBar[0], false); mBar[0].r = wxRect(left+2, height/2 - barh - 1, barw, barh); if (mClip) { mBar[0].rClip = mBar[0].r; mBar[0].rClip.x += mBar[0].rClip.width-3; mBar[0].rClip.width = 3; mBar[0].r.width -= 4; } mBar[1].vert = false; ResetBar(&mBar[1], false); mBar[1].r = wxRect(left+2, height/2 + 1, barw, barh); if (mClip) { mBar[1].rClip = mBar[1].r; mBar[1].rClip.x += mBar[1].rClip.width-3; mBar[1].rClip.width = 3; mBar[1].r.width -= 4; } mRuler.SetOrientation(wxHORIZONTAL); mRuler.SetBounds(mBar[1].r.x, mBar[1].r.y + mBar[1].r.height + 1, mBar[1].r.x + mBar[1].r.width, mWidth); if (mDB) { mRuler.SetRange(-mDBRange, 0); mRuler.SetFormat(Ruler::LinearDBFormat); } else { mRuler.SetRange(0, 1); mRuler.SetFormat(Ruler::RealFormat); } mRuler.OfflimitsPixels(0, mMenuRect.x+mMenuRect.width-4); break; case Waveform: mNumBars = 0; break; } if (mNumBars > 0) { // Compute bounding rectangle of all bars (to save time when // blitting just the bars to the screen) left = mBar[0].r.x; top = mBar[0].r.y; right = mBar[0].r.x + mBar[0].r.width; bottom = mBar[0].r.y + mBar[0].r.height; for(i=1; iGetBackgroundColour() ); } #endif dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(mBkgndBrush); dc.DrawRectangle(0, 0, mWidth, mHeight); #endif // MixerTrackCluster style has no icon or menu. if (mStyle != MixerTrackCluster) { dc.DrawBitmap(*mIcon, mIconPos.x, mIconPos.y, true); // Draws a beveled button and a down pointing triangle. // The style and sizing matches the ones in the title // bar of the waveform left-hand-side panels. wxRect r = mMenuRect; AColor::Bevel(dc, true, r); dc.SetBrush(*wxBLACK_BRUSH); dc.SetPen(*wxBLACK_PEN); AColor::Arrow(dc, r.x + 3, r.y + 5, 10); } if (mNumBars>0) mRuler.Draw(dc); // MixerTrackCluster style has no L/R labels. if (mStyle != MixerTrackCluster) { dc.SetFont(GetFont()); dc.DrawText(mLeftText, mLeftTextPos.x, mLeftTextPos.y); dc.DrawText(mRightText, mRightTextPos.x, mRightTextPos.y); } for(i=0; ir; wxRect rRMS = meterBar->r; dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(mBkgndBrush); dc.DrawRectangle(r); AColor::Bevel(dc, false, r); /* AColor::Dark(&dc, false); for(i=0; ivert) AColor::Line(dc, r.x+r.width/2-1, mTick[i], r.x+r.width/2+1, mTick[i]); else AColor::Line(dc, mTick[i], r.y+r.height/2-1, mTick[i], r.y+r.height/2+1); */ dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(mPen); if (meterBar->vert) { int ht = (int)(meterBar->peakHold * r.height + 0.5); AColor::Line(dc, r.x+1, r.y + r.height - ht, r.x+r.width-1, r.y + r.height - ht); if (ht > 1) AColor::Line(dc, r.x+1, r.y + r.height - ht + 1, r.x+r.width-1, r.y + r.height - ht + 1); dc.SetPen(mPeakPeakPen); ht = (int)(meterBar->peakPeakHold * r.height + 0.5); AColor::Line(dc, r.x+1, r.y + r.height - ht, r.x+r.width-1, r.y + r.height - ht); if (ht > 1) AColor::Line(dc, r.x+1, r.y + r.height - ht + 1, r.x+r.width-1, r.y + r.height - ht + 1); dc.SetPen(mPen); ht = (int)(meterBar->peak * r.height + 0.5); r = wxRect(r.x, r.y + r.height - ht, r.width, ht); ht = (int)(meterBar->rms * rRMS.height + 0.5); rRMS = wxRect(rRMS.x, rRMS.y + rRMS.height - ht, rRMS.width, ht); } else { int wd = (int)(meterBar->peakHold * r.width + 0.5); AColor::Line(dc, r.x + wd, r.y + 1, r.x + wd, r.y + r.height - 1); if (wd > 1) AColor::Line(dc, r.x + wd - 1, r.y + 1, r.x + wd - 1, r.y + r.height - 1); dc.SetPen(mPeakPeakPen); wd = (int)(meterBar->peakPeakHold * r.width + 0.5); AColor::Line(dc, r.x + wd, r.y + 1, r.x + wd, r.y + r.height - 1); if (wd > 1) AColor::Line(dc, r.x + wd - 1, r.y + 1, r.x + wd - 1, r.y + r.height - 1); dc.SetPen(mPen); wd = (int)(meterBar->peak * r.width + 0.5); r = wxRect(r.x, r.y, wd, r.height); wd = (int)(meterBar->rms * rRMS.width + 0.5); rRMS = wxRect(rRMS.x, rRMS.y, wd, rRMS.height); } dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(mBrush); dc.DrawRectangle(r); dc.SetBrush(mRMSBrush); dc.DrawRectangle(rRMS); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(mLightPen); AColor::Line(dc, r.x, r.y, r.x + r.width, r.y); AColor::Line(dc, r.x, r.y, r.x, r.y + r.height); dc.SetPen(mDarkPen); AColor::Line(dc, r.x + r.width, r.y, r.x + r.width, r.y + r.height); AColor::Line(dc, r.x, r.y + r.height, r.x + r.width, r.y + r.height); if (mClip) { if (meterBar->clipping) dc.SetBrush(mClipBrush); else dc.SetBrush(mBkgndBrush); dc.SetPen(*wxTRANSPARENT_PEN); dc.DrawRectangle(meterBar->rClip); dc.SetBrush(*wxTRANSPARENT_BRUSH); AColor::Bevel(dc, false, meterBar->rClip); } } bool Meter::IsMeterDisabled() {return mMeterDisabled!=0;} void Meter::StartMonitoring() { if (gAudioIO->IsMonitoring()) gAudioIO->StopStream(); else { if (mMeterDisabled){ wxCommandEvent dummy; OnDisableMeter(dummy); } AudacityProject *p = GetActiveProject(); if (p) { gAudioIO->StartMonitoring(p->GetRate()); MeterToolBar *bar = p->GetMeterToolBar(); if (bar) { Meter *play, *record; bar->GetMeters(&play, &record); gAudioIO->SetMeters(record, play); } } } } // // Pop-up menu handlers // void Meter::OnDisableMeter(wxCommandEvent &evt) { if (mMeterDisabled) //Enable { mLightPen = mSavedLightPen; mDarkPen = mSavedDarkPen; mBkgndBrush = mSavedBkgndBrush; mBrush = mSavedBrush; mRMSBrush = mSavedRMSBrush; mBkgndBrush = mSavedBkgndBrush; mLightPen = mSavedLightPen; Refresh(false); mMeterDisabled = false; } else { if (mIsInput) { if (gAudioIO->IsMonitoring()) gAudioIO->StopStream(); } mSavedLightPen = mLightPen; mSavedDarkPen = mDarkPen; mSavedBkgndBrush = mBkgndBrush; mSavedBrush = mBrush; mSavedRMSBrush = mRMSBrush; mLightPen = mDisabledPen; mDarkPen = mDisabledPen; mBkgndBrush = mDisabledBkgndBrush; mBrush = mDisabledBkgndBrush; mRMSBrush = mDisabledBkgndBrush; mLayoutValid = false; Refresh(false); mMeterDisabled = true; } if (mIsInput) { gPrefs->Write(wxT("/Meter/MeterInputDisabled"), mMeterDisabled); } else { gPrefs->Write(wxT("/Meter/MeterOutputDisabled"), mMeterDisabled); } } void Meter::OnHorizontal(wxCommandEvent &evt) { SetStyle(HorizontalStereo); } void Meter::OnVertical(wxCommandEvent &evt) { SetStyle(VerticalStereo); } void Meter::OnMulti(wxCommandEvent &evt) { SetStyle(VerticalMulti); } void Meter::OnEqualizer(wxCommandEvent &evt) { SetStyle(Equalizer); } void Meter::OnWaveform(wxCommandEvent &evt) { SetStyle(Waveform); } void Meter::OnLinear(wxCommandEvent &evt) { mDB = false; mLayoutValid = false; Refresh(false); } void Meter::OnDB(wxCommandEvent &evt) { mDB = true; mLayoutValid = false; Refresh(false); } void Meter::OnClip(wxCommandEvent &evt) { } void Meter::OnMonitor(wxCommandEvent &evt) { StartMonitoring(); } #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT void Meter::OnAutomatedInputLevelAdjustment(wxCommandEvent &evt) { if (gAudioIO->AILAIsActive()) { gAudioIO->AILADisable(); AudacityProject *p = GetActiveProject(); if (p) p->TP_DisplayStatusMessage(_("Automated Input Level Adjustment stopped as requested by user.")); } else gAudioIO->AILAInitialize(); } #endif void Meter::OnFloat(wxCommandEvent &evt) { } void Meter::OnPreferences(wxCommandEvent &evt) { wxNumberEntryDialog d(this, _("Higher refresh rates make the meter show more frequent\nchanges. A rate of 30 per second or less should prevent\nthe meter affecting audio quality on slower machines."), _("Meter refresh rate per second [1-100]: "), _("Meter Preferences"), mMeterRefreshRate, 1, 100); //#if defined(__WXMAC__) // WXMAC doesn't support wxFRAME_FLOAT_ON_PARENT, so we do // // LL: I've commented this out because if you have, for instance, the meter // toolbar undocked and large and then you open a dialog like an effect, // the dialog may appear behind the dialog and you can't move either one. // // However, I'm leaving it here because I don't remember why I'd included // it in the first place. // SetWindowClass((WindowRef)d.MacGetWindowRef(), kFloatingWindowClass); //#endif if (d.ShowModal() == wxID_OK) { mMeterRefreshRate = d.GetValue(); gPrefs->Write(wxT("/Meter/MeterRefreshRate"), mMeterRefreshRate); } mTimer.Start(1000 / mMeterRefreshRate); } // Indentation settings for Vim and Emacs. // Please do not modify past this point. // // Local Variables: // c-basic-offset: 3 // indent-tabs-mode: nil // End: // // vim: et sts=3 sw=3 //