📄 meter.cpp
字号:
/********************************************************************** Audacity: A Digital Audio Editor Meter.cpp Dominic Mazzoni 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. 2004.06.25 refresh rate limited to 30mS, by ChackoN**********************************************************************/#include "../Audacity.h"#include <wx/defs.h>#include <wx/dcmemory.h>#include <wx/image.h>#include <wx/intl.h>#include <wx/menu.h>#include <wx/settings.h>#include <wx/textdlg.h>#include <wx/numdlg.h>#include <wx/tooltip.h>#include <wx/msgdlg.h>#include <math.h>#include "Meter.h"#include "../AudioIO.h"#include "../AColor.h"#include "../ImageManipulation.h"#include "../../images/MixerImages.h"#include "../Project.h"#include "../MeterToolBar.h"#include "../Prefs.h"//// 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();}// destructorMeterUpdateQueue::~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; 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, 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_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) 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*/): wxPanel(parent, id, pos, size), mQueue(1024), mWidth(size.x), mHeight(size.y), mIsInput(isInput), mStyle(HorizontalStereo), mDB(true), mDBRange(60), mDecay(true), mDecayRate(60), mClip(true), mNumPeakSamplesToClip(3), mPeakHoldDuration(3), mT(0), mRate(0), mNumBars(0), mLayoutValid(false), mBitmap(NULL), mBackgroundBitmap(NULL), mIcon(NULL){ int i; wxColour backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE); mBkgndBrush = wxBrush(backgroundColour, wxSOLID); mMeterRefreshRate = gPrefs->Read(wxT("/Meter/MeterRefreshRate"), 30); if (mIsInput) { mMeterDisabled = gPrefs->Read(wxT("/Meter/MeterInputDisabled"), (long)0); } else { mMeterDisabled = gPrefs->Read(wxT("/Meter/MeterOutputDisabled"), (long)0); }// CreateIcon(2); wxColour origColour(204, 204, 204); mPeakPeakPen = wxPen(wxColour(102, 102, 255), 1, wxSOLID); mDisabledPen = wxPen(wxColour(192, 192, 192), 1, wxSOLID); /* i18n-hint: One-letter abbreviation for Left, in VU Meter */ mLeftText = _("L"); /* i18n-hint: One-letter abbreviation for Right, in VU Meter */ mRightText = _("R"); mLeftSize = wxSize(0, 0); mRightSize = wxSize(0, 0); if (mIsInput) { mPen = wxPen(wxColour(204, 70, 70), 1, wxSOLID); mBrush = wxBrush(wxColour(204, 70, 70), wxSOLID); mRMSBrush = wxBrush(wxColour(255, 102, 102), wxSOLID); mClipBrush = wxBrush(wxColour(255, 53, 53), wxSOLID); mLightPen = wxPen(wxColour(255, 153, 153), 1, wxSOLID); mDarkPen = wxPen(wxColour(153, 61, 61), 1, wxSOLID); } else { mPen = wxPen(wxColour(70, 204, 70), 1, wxSOLID); mBrush = wxBrush(wxColour(70, 204, 70), wxSOLID); mRMSBrush = wxBrush(wxColour(102, 255, 102), wxSOLID); mClipBrush = wxBrush(wxColour(255, 53, 53), wxSOLID); mLightPen = wxPen(wxColour(153, 255, 153), 1, wxSOLID); mDarkPen = wxPen(wxColour(61, 164, 61), 1, wxSOLID); } mDisabledBkgndBrush = wxBrush(wxColour(160, 160, 160), 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; } CreateIcon(2); mRuler.SetFonts(GetFont(), GetFont()); mTimer.SetOwner(this, OnMeterUpdateID); Reset(44100.0, true); for(i=0; i<kMaxMeterBars; i++) mBar[i].clipping = false;}void Meter::CreateIcon(int aquaOffset){ wxColour backgroundColour = mBkgndBrush.GetColour();// wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DFACE);// mBkgndBrush = wxBrush(backgroundColour, wxSOLID); wxImage *image, *alpha; if (mIcon) { delete mIcon; mIcon = NULL; } if (mIsInput) { image = new wxImage(wxBitmap(Mic).ConvertToImage()); alpha = new wxImage(wxBitmap(MicAlpha).ConvertToImage()); } else { image = new wxImage(wxBitmap(Speaker).ConvertToImage()); alpha = new wxImage(wxBitmap(SpeakerAlpha).ConvertToImage()); } wxImage *bkgnd = CreateSysBackground(25, 25, aquaOffset, backgroundColour); wxImage *final = OverlayImage(bkgnd, image, alpha, 0, 0); mIcon = new wxBitmap(final); delete image; delete alpha; delete bkgnd; delete final;}Meter::~Meter(){ delete mIcon; if (mBitmap) delete mBitmap; if (mBackgroundBitmap) delete mBackgroundBitmap;}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 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.Inside(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")); } 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){ mStyle = newStyle; mLayoutValid = false; Refresh(true);}void Meter::Reset(double sampleRate, bool resetClipping){ int j; mT = 0; mRate = sampleRate; for(j=0; j<mNumBars; j++) ResetBar(&mBar[j], resetClipping); // wxTimers seem to be a little unreliable - sometimes they stop for // no good reason, so this "primes" it every now and then... mTimer.Stop(); // While it's stopped, empty the queue MeterUpdateMsg msg; while(mQueue.Get(msg)) { } mTimer.Start(25); // every 25 ms -> ~40 updates per second mLayoutValid = false; Refresh(false);}static float floatMax(float a, float b){ return a>b? a: b;}static int intmin(int a, int b){ return a<b? a: b;}static int intmax(int a, int b){ return a>b? 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<mNumBars; j++) { msg.peak[j] = 0; msg.rms[j] = 0; msg.clipping[j] = false; msg.headPeakCount[j] = 0; msg.tailPeakCount[j] = 0; } for(i=0; i<numFrames; i++) { for(j=0; j<num; j++) { msg.peak[j] = floatMax(msg.peak[j], sptr[j]); msg.rms[j] += sptr[j]*sptr[j]; // In addition to looking for mNumPeakSamplesToClip peaked // samples in a row, also send the number of peaked samples // at the head and tail, in case there's a run of // Send the number of peaked samples at the head and tail, // in case there's a run of peaked samples that crosses // block boundaries if (fabs(sptr[j])>=1.0) { 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<mNumBars; j++) msg.rms[j] = sqrt(msg.rms[j]/numFrames); if (mDB) { for(j=0; j<mNumBars; j++) { msg.peak[j] = ToDB(msg.peak[j], mDBRange); msg.rms[j] = ToDB(msg.rms[j], mDBRange); } } mQueue.Put(msg);}void Meter::OnMeterUpdate(wxTimerEvent &evt){ MeterUpdateMsg msg; int numChanges = 0; // There may have been several update messages since the last // time we got to this function. Catch up to real-time by // popping them off until there are none left. It is necessary // to process all of them, otherwise we won't handle peaks and // peak-hold bars correctly. while(mQueue.Get(msg)) { numChanges++; double deltaT = msg.numFrames / mRate; int j; if (mMeterDisabled) return; mT += deltaT; for(j=0; j<mNumBars; j++) { if (mDecay) { if (mDB) { float decayAmount = mDecayRate * deltaT / mDBRange; mBar[j].peak = floatMax(msg.peak[j], mBar[j].peak - decayAmount); } else { double decayAmount = mDecayRate * deltaT; double decayFactor = pow(10.0, -decayAmount/20); mBar[j].peak = floatMax(msg.peak[j], mBar[j].peak * decayFactor); } } else mBar[j].peak = msg.peak[j]; // This smooths out the RMS signal mBar[j].rms = mBar[j].rms * 0.9 + msg.rms[j] * 0.1; if (mT - mBar[j].peakHoldTime > 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] ||
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -