1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-11 17:13:37 +02:00
Files
audacity/src/FreqWindow.cpp

1385 lines
38 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
FreqWindow.cpp
Dominic Mazzoni
*******************************************************************//**
\class FreqWindow
\brief Displays a spectrum plot of the waveform. Has options for
selecting parameters of the plot.
Has a feature that finds peaks and reports their value as you move
the mouse around.
*//****************************************************************//**
\class FreqPlot
\brief Works with FreqWindow to dsplay a spectrum plot of the waveform.
This class actually does the graph display.
Has a feature that finds peaks and reports their value as you move
the mouse around.
*//****************************************************************//**
\class SpectrumAnalyst
\brief Used for finding the peaks, for snapping to peaks.
This class is used to do the 'find peaks' snapping both in FreqPlot
and in the spectrogram spectral selection.
*//*******************************************************************/
/*
Salvo Ventura - November 2006
Extended range check for additional FFT windows
*/
#include "Audacity.h"
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#include <wx/brush.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/font.h>
#include <wx/image.h>
#include <wx/dcmemory.h>
#include <wx/msgdlg.h>
#include <wx/file.h>
#include <wx/filedlg.h>
#include <wx/intl.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/statusbr.h>
#include <wx/textfile.h>
#include <math.h>
#include "FreqWindow.h"
#include "AColor.h"
#include "FFT.h"
#include "Internat.h"
#include "PitchName.h"
#include "Prefs.h"
#include "Project.h"
#include "WaveClip.h"
#include "Theme.h"
#include "AllThemeResources.h"
#include "FileDialog.h"
enum {
FirstID = 7000,
FreqExportButtonID,
FreqAlgChoiceID,
FreqSizeChoiceID,
FreqFuncChoiceID,
FreqAxisChoiceID,
ReplotButtonID,
GridOnOffID
};
// These specify the minimum plot window width
#define FREQ_WINDOW_WIDTH 480
#define FREQ_WINDOW_HEIGHT 330
// FreqWindow
BEGIN_EVENT_TABLE(FreqWindow, wxDialog)
EVT_CLOSE(FreqWindow::OnCloseWindow)
EVT_SIZE(FreqWindow::OnSize)
EVT_BUTTON(wxID_CANCEL, FreqWindow::OnCloseButton)
EVT_BUTTON(FreqExportButtonID, FreqWindow::OnExport)
EVT_CHOICE(FreqAlgChoiceID, FreqWindow::OnAlgChoice)
EVT_CHOICE(FreqSizeChoiceID, FreqWindow::OnSizeChoice)
EVT_CHOICE(FreqFuncChoiceID, FreqWindow::OnFuncChoice)
EVT_CHOICE(FreqAxisChoiceID, FreqWindow::OnAxisChoice)
EVT_BUTTON(ReplotButtonID, FreqWindow::OnReplot)
EVT_CHECKBOX(GridOnOffID, FreqWindow::OnGridOnOff)
END_EVENT_TABLE()
SpectrumAnalyst::SpectrumAnalyst()
: mAlg(Spectrum)
, mRate(0.0)
, mWindowSize(0)
{
}
SpectrumAnalyst::~SpectrumAnalyst()
{
}
FreqWindow::FreqWindow(wxWindow * parent, wxWindowID id,
const wxString & title,
const wxPoint & pos):
wxDialog(parent, id, title, pos, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX),
mData(NULL), mBitmap(NULL), mAnalyst(new SpectrumAnalyst())
{
mMouseX = 0;
mMouseY = 0;
mRate = 0;
mDataLen = 0;
mBuffer = NULL;
p = GetActiveProject();
if (!p)
return;
mFreqFont = wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
mArrowCursor = new wxCursor(wxCURSOR_ARROW);
mCrossCursor = new wxCursor(wxCURSOR_CROSS);
mLeftMargin = 40;
mBottomMargin = 20;
gPrefs->Read(wxT("/FreqWindow/DrawGrid"), &mDrawGrid, true);
gPrefs->Read(wxT("/FreqWindow/SizeChoice"), &mSize, 2);
int alg;
gPrefs->Read(wxT("/FreqWindow/AlgChoice"), (&alg), 0);
mAlg = static_cast<SpectrumAnalyst::Algorithm>(alg);
gPrefs->Read(wxT("/FreqWindow/FuncChoice"), &mFunc, 3);
gPrefs->Read(wxT("/FreqWindow/AxisChoice"), &mAxis, 0);
gPrefs->Read(wxT("/GUI/EnvdBRange"), &dBRange, ENV_DB_RANGE);
if(dBRange < 90.)
dBRange = 90.;
mFreqPlot = new FreqPlot(this, 0,
wxDefaultPosition, wxDefaultSize);
wxString algChoiceStrings[5] = { _("Spectrum"),
_("Standard Autocorrelation"),
_("Cuberoot Autocorrelation"),
_("Enhanced Autocorrelation"),
/* i18n-hint: This is a technical term, derived from the word
* "spectrum". Do not translate it unless you are sure you
* know the correct technical word in your language. */
_("Cepstrum")
};
wxStaticText *algLabel = new wxStaticText(this, wxID_ANY,
wxString(_("Algorithm")) + wxT(":"));
mAlgChoice = new wxChoice(this, FreqAlgChoiceID,
wxDefaultPosition, wxDefaultSize,
5, algChoiceStrings);
mAlgChoice->SetName(_("Algorithm"));
mAlgChoice->SetSelection(mAlg);
wxArrayString sizeChoiceStrings;
sizeChoiceStrings.Add(wxT("128"));
sizeChoiceStrings.Add(wxT("256"));
sizeChoiceStrings.Add(wxT("512"));
sizeChoiceStrings.Add(wxT("1024"));
sizeChoiceStrings.Add(wxT("2048"));
sizeChoiceStrings.Add(wxT("4096"));
sizeChoiceStrings.Add(wxT("8192"));
sizeChoiceStrings.Add(wxT("16384"));
sizeChoiceStrings.Add(wxT("32768"));
sizeChoiceStrings.Add(wxT("65536"));
wxStaticText *sizeLabel = new wxStaticText(this, wxID_ANY,
wxString(_("Size")) + wxT(":"));
mSizeChoice = new wxChoice(this, FreqSizeChoiceID,
wxDefaultPosition, wxDefaultSize,
sizeChoiceStrings);
mSizeChoice->SetName(_("Size"));
mSizeChoice->SetSelection(mSize);
int f = NumWindowFuncs();
wxString *funcChoiceStrings = new wxString[f];
for (int i = 0; i < f; i++) {
/* i18n-hint: This refers to a "window function", used in the
* Frequency analyze dialog box. */
funcChoiceStrings[i] = wxString( WindowFuncName(i)) + wxT(" ") + wxString(_("window"));
}
wxStaticText *funcLabel = new wxStaticText(this, wxID_ANY,
wxString(_("Function")) + wxT(":"));
mFuncChoice = new wxChoice(this, FreqFuncChoiceID,
wxDefaultPosition, wxDefaultSize,
f, funcChoiceStrings);
mFuncChoice->SetName(_("Function"));
mFuncChoice->SetSelection(mFunc);
delete[]funcChoiceStrings;
wxString axisChoiceStrings[2] = { _("Linear frequency"),
_("Log frequency")
};
wxStaticText *axisLabel = new wxStaticText(this, wxID_ANY,
wxString(_("Axis")) + wxT(":"));
mAxisChoice = new wxChoice(this, FreqAxisChoiceID,
wxDefaultPosition, wxDefaultSize,
2, axisChoiceStrings);
mAxisChoice->SetName(_("Axis"));
mAxisChoice->SetSelection(mAxis);
// Log-frequency axis works for spectrum plots only.
if (mAlg != SpectrumAnalyst::Spectrum) {
mAxis = 0;
mAxisChoice->Disable();
}
mLogAxis = mAxis?true:false;
mExportButton = new wxButton(this, FreqExportButtonID,
_("&Export..."));
mExportButton->SetName(_("Export"));
mReplotButton = new wxButton(this, ReplotButtonID,
_("&Replot"));
mReplotButton->SetName(_("Replot"));
/* i18n-hint: (verb)*/
mCloseButton = new wxButton(this, wxID_CANCEL,
_("Close"));
mCloseButton->SetName(_("Close"));
mGridOnOff = new wxCheckBox(this, GridOnOffID, _("Grids"),
wxDefaultPosition, wxDefaultSize,
#if defined(__WXGTK__)
// Fixes bug #662
wxALIGN_LEFT);
#else
wxALIGN_RIGHT);
#endif
mGridOnOff->SetName(_("Grids"));
mGridOnOff->SetValue(mDrawGrid);
#ifndef TARGET_CARBON
mCloseButton->SetDefault();
mCloseButton->SetFocus();
#endif
mInfo = new wxStatusBar(this, wxID_ANY, wxST_SIZEGRIP);
// Set minimum sizes
mFreqPlot->SetMinSize( wxSize( FREQ_WINDOW_WIDTH, FREQ_WINDOW_HEIGHT ) );
wxRect r( 0, 0, 0, 0 );
r.Union( algLabel->GetRect() );
r.Union( funcLabel->GetRect() );
algLabel->SetMinSize( r.GetSize() );
funcLabel->SetMinSize( r.GetSize() );
r = wxRect( 0, 0, 0, 0 );
r.Union( mAlgChoice->GetRect() );
r.Union( mFuncChoice->GetRect() );
mAlgChoice->SetMinSize( r.GetSize() );
mFuncChoice->SetMinSize( r.GetSize() );
r = wxRect( 0, 0, 0, 0 );
r.Union( sizeLabel->GetRect() );
r.Union( axisLabel->GetRect() );
sizeLabel->SetMinSize( r.GetSize() );
axisLabel->SetMinSize( r.GetSize() );
r = wxRect( 0, 0, 0, 0 );
r.Union( mSizeChoice->GetRect() );
r.Union( mAxisChoice->GetRect() );
mSizeChoice->SetMinSize( r.GetSize() );
mAxisChoice->SetMinSize( r.GetSize() );
r = wxRect( 0, 0, 0, 0 );
r.Union( mExportButton->GetRect() );
r.Union( mCloseButton->GetRect() );
mExportButton->SetMinSize( r.GetSize() );
mCloseButton->SetMinSize( r.GetSize() );
// Add everything to the sizers
wxBoxSizer *vs = new wxBoxSizer( wxVERTICAL );
wxBoxSizer *hs;
szr = new wxFlexGridSizer(2);
szr->AddGrowableCol( 1, 1 );
szr->AddGrowableRow( 0, 1 );
szr->SetFlexibleDirection( wxBOTH );
vRuler = new RulerPanel(this, wxID_ANY);
vRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
vRuler->ruler.SetOrientation(wxVERTICAL);
vRuler->ruler.SetRange(10.0, -dBRange); // Note inversion for vertical.
vRuler->ruler.SetFormat(Ruler::LinearDBFormat);
vRuler->ruler.SetUnits(_("dB"));
vRuler->ruler.SetLabelEdges(false);
int w, h;
vRuler->ruler.GetMaxSize(&w, NULL);
vRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK
szr->Add( vRuler, 0, wxEXPAND|wxTOP|wxBOTTOM, 1 ); //for border around graph
szr->Add( mFreqPlot, 1, wxEXPAND );
szr->Add(1,1); //spacer on next row, under vRuler
hRuler = new RulerPanel(this, wxID_ANY);
hRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
hRuler->ruler.SetOrientation(wxHORIZONTAL);
hRuler->ruler.SetLog(false); //default for freq axis
hRuler->ruler.SetRange(10, 20000); //dummy values - will get replaced
hRuler->ruler.SetFormat(Ruler::RealFormat);
hRuler->ruler.SetUnits(_("Hz"));
hRuler->ruler.SetFlip(true);
hRuler->ruler.SetLabelEdges(false);
hRuler->ruler.GetMaxSize(NULL, &h);
hRuler->SetMinSize(wxSize(-1, h));
szr->Add( hRuler, 0, wxEXPAND|wxLEFT|wxRIGHT, 1 ); //for border around graph
szr->Add(1,1); //spacer
mInfoText = new wxStaticText(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); //box for info text
szr->Add( mInfoText, 0, wxEXPAND|wxALL, 5);
vs->Add(szr, 1, wxEXPAND|wxALL, 5);
vs->Add( 1, 5, 0 );
wxFlexGridSizer *gs = new wxFlexGridSizer( 2 );
hs = new wxBoxSizer( wxHORIZONTAL );
hs->Add( algLabel, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( mAlgChoice, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( sizeLabel, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( mSizeChoice, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( 10, 1, 0 );
hs->Add( mExportButton, 0, wxALIGN_CENTER | wxALIGN_RIGHT | wxLEFT | wxRIGHT, 5 );
gs->Add( hs, 0, wxALIGN_CENTER_HORIZONTAL | wxLEFT | wxRIGHT , 5 );
gs->Add( mReplotButton, 0, wxALIGN_CENTER | wxALIGN_RIGHT | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
hs = new wxBoxSizer( wxHORIZONTAL );
hs->Add( funcLabel, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( mFuncChoice, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( axisLabel, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( mAxisChoice, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 );
hs->Add( 10, 1, 0 );
hs->Add( mCloseButton, 0, wxALIGN_CENTER | wxALIGN_RIGHT | wxLEFT | wxRIGHT, 5 );
gs->Add( hs, 0, wxALIGN_CENTER_HORIZONTAL | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
gs->Add( mGridOnOff, 0, wxALIGN_CENTER | wxALIGN_RIGHT | wxLEFT | wxRIGHT, 5 );
vs->Add( gs, 0, wxEXPAND | wxBOTTOM, 0 );
vs->Add( mInfo, 0, wxEXPAND | wxBOTTOM, 0 );
SetAutoLayout( false );
SetSizerAndFit( vs );
Layout();
}
FreqWindow::~FreqWindow()
{
delete mFreqPlot;
if (mBitmap)
delete mBitmap;
delete mCloseButton;
delete mAlgChoice;
delete mSizeChoice;
delete mFuncChoice;
delete mArrowCursor;
delete mCrossCursor;
if (mData)
delete[] mData;
if (mBuffer)
delete[] mBuffer;
}
void FreqWindow::GetAudio()
{
int selcount = 0;
int i;
bool warning = false;
//wxLogDebug(wxT("Entering FreqWindow::GetAudio()"));
TrackListIterator iter(p->GetTracks());
Track *t = iter.First();
while (t) {
if (t->GetSelected() && t->GetKind() == Track::Wave) {
WaveTrack *track = (WaveTrack *)t;
if (selcount==0) {
mRate = track->GetRate();
sampleCount start, end;
start = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t0());
end = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t1());
mDataLen = (sampleCount)(end - start);
if (mDataLen > 10485760) {
warning = true;
mDataLen = 10485760;
}
if (mBuffer) {
delete [] mBuffer;
}
mBuffer = new float[mDataLen];
track->Get((samplePtr)mBuffer, floatSample, start, mDataLen);
}
else {
if (track->GetRate() != mRate) {
wxMessageBox(_("To plot the spectrum, all selected tracks must be the same sample rate."));
delete[] mBuffer;
mBuffer = NULL;
return;
}
sampleCount start;
start = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t0());
float *buffer2 = new float[mDataLen];
track->Get((samplePtr)buffer2, floatSample, start, mDataLen);
for(i=0; i<mDataLen; i++)
mBuffer[i] += buffer2[i];
delete[] buffer2;
}
selcount++;
}
t = iter.Next();
}
if (selcount == 0)
return;
if (warning) {
wxString msg;
msg.Printf(_("Too much audio was selected. Only the first %.1f seconds of audio will be analyzed."),
(mDataLen / mRate));
//wxLogDebug(wxT("About to show length warning message"));
wxMessageBox(msg);
//wxLogDebug(wxT("Length warning message done"));
}
//wxLogDebug(wxT("Leaving FreqWindow::GetAudio()"));
}
void FreqWindow::OnSize(wxSizeEvent & WXUNUSED(event))
{
Layout();
mUpdateRect.x = 0;
mUpdateRect.y = 0;
mUpdateRect.SetSize( mFreqPlot->GetSize() );
mPlotRect = mUpdateRect;
DrawPlot();
Refresh(true);
}
void FreqWindow::DrawPlot()
{
if (mUpdateRect.width == 0 || mUpdateRect.height == 0)
{
// Update rect not yet initialized properly
// (can happen during initialization phase on wx2.8)
return;
}
if (mBitmap)
{
delete mBitmap;
mBitmap = NULL;
}
mBitmap = new wxBitmap(mUpdateRect.width, mUpdateRect.height);
wxMemoryDC memDC;
memDC.SelectObject(*mBitmap);
memDC.SetBackground(wxBrush(wxColour(254, 254, 254)));// DONT-THEME Mask colour.
memDC.Clear();
wxRect r = mPlotRect;
memDC.SetPen(*wxBLACK_PEN);
memDC.SetBrush(*wxWHITE_BRUSH);
memDC.DrawRectangle(r);
if (0 == mAnalyst->GetProcessedSize()) {
if (mData && mDataLen < mWindowSize)
memDC.DrawText(_("Not enough data selected."), r.x + 5, r.y + 5);
return;
}
float yTotal = (mYMax - mYMin);
SpectrumAnalyst::Algorithm alg =
SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
int i;
memDC.SetFont(mFreqFont);
// Set up y axis ruler
if (alg == SpectrumAnalyst::Spectrum) {
vRuler->ruler.SetUnits(_("dB"));
vRuler->ruler.SetFormat(Ruler::LinearDBFormat);
} else {
vRuler->ruler.SetUnits(wxT(""));
vRuler->ruler.SetFormat(Ruler::RealFormat);
}
int w1, w2, h;
vRuler->ruler.GetMaxSize(&w1, &h);
vRuler->ruler.SetRange(mYMax, mYMin); // Note inversion for vertical.
vRuler->ruler.GetMaxSize(&w2, &h);
if( w1 != w2 ) // Reduces flicker
{
vRuler->SetSize(wxSize(w2,h));
szr->Layout();
hRuler->Refresh(false);
}
vRuler->Refresh(false);
// Set up x axis ruler
int width = r.width - 2;
float xMin, xMax, xRatio, xStep;
if (alg == SpectrumAnalyst::Spectrum) {
xMin = mRate / mWindowSize;
xMax = mRate / 2;
xRatio = xMax / xMin;
if (mLogAxis)
{
xStep = pow(2.0f, (log(xRatio) / log(2.0f)) / width);
hRuler->ruler.SetLog(true);
}
else
{
xStep = (xMax - xMin) / width;
hRuler->ruler.SetLog(false);
}
hRuler->ruler.SetUnits(_("Hz"));
} else {
xMin = 0;
xMax = mAnalyst->GetProcessedSize() / mRate;
xStep = (xMax - xMin) / width;
hRuler->ruler.SetLog(false);
hRuler->ruler.SetUnits(_("s"));
}
hRuler->ruler.SetRange(xMin, xMax-xStep);
hRuler->Refresh(false);
// Draw the plot
if (alg == SpectrumAnalyst::Spectrum)
memDC.SetPen(wxPen(theTheme.Colour( clrHzPlot ), 1, wxSOLID));
else
memDC.SetPen(wxPen(theTheme.Colour( clrWavelengthPlot), 1, wxSOLID));
float xPos = xMin;
for (i = 0; i < width; i++) {
float y;
if (mLogAxis)
y = mAnalyst->GetProcessedValue(xPos, xPos * xStep);
else
y = mAnalyst->GetProcessedValue(xPos, xPos + xStep);
float ynorm = (y - mYMin) / yTotal;
int lineheight = int (ynorm * (r.height - 1));
if (lineheight > r.height - 2)
lineheight = r.height - 2;
if (ynorm > 0.0)
AColor::Line(memDC, r.x + 1 + i, r.y + r.height - 1 - lineheight,
r.x + 1 + i, r.y + r.height - 1);
if (mLogAxis)
xPos *= xStep;
else
xPos += xStep;
}
// Outline the graph
memDC.SetPen(*wxBLACK_PEN);
memDC.SetBrush(*wxTRANSPARENT_BRUSH);
memDC.DrawRectangle(r);
if(mDrawGrid)
{
hRuler->ruler.DrawGrid(memDC, r.height, true, true, 1, 1);
vRuler->ruler.DrawGrid(memDC, r.width, true, true, 1, 1);
}
memDC.SelectObject( wxNullBitmap );
}
void FreqWindow::PlotMouseEvent(wxMouseEvent & event)
{
if (event.Moving() && (event.m_x != mMouseX || event.m_y != mMouseY)) {
mMouseX = event.m_x;
mMouseY = event.m_y;
wxRect r = mPlotRect;
if (r.Contains(mMouseX, mMouseY))
mFreqPlot->SetCursor(*mCrossCursor);
else
mFreqPlot->SetCursor(*mArrowCursor);
mFreqPlot->Refresh(false);
mInfoText->Refresh();
}
}
void FreqWindow::OnAlgChoice(wxCommandEvent & WXUNUSED(event))
{
// Log-frequency axis works for spectrum plots only.
if (mAlgChoice->GetSelection() == 0) {
mAxisChoice->Enable(true);
mLogAxis = (mAxisChoice->GetSelection())?true:false;
}
else {
mAxisChoice->Disable();
mLogAxis = false;
}
Recalc();
}
void FreqWindow::OnSizeChoice(wxCommandEvent & WXUNUSED(event))
{
Recalc();
}
void FreqWindow::OnFuncChoice(wxCommandEvent & WXUNUSED(event))
{
Recalc();
}
void FreqWindow::OnAxisChoice(wxCommandEvent & WXUNUSED(event))
{
mLogAxis = (mAxisChoice->GetSelection())?true:false;
DrawPlot();
mFreqPlot->Refresh(true);
}
// If f(0)=y0, f(1)=y1, f(2)=y2, and f(3)=y3, this function finds
// the degree-three polynomial which best fits these points and
// returns the value of this polynomial at a value x. Usually
// 0 < x < 3
/* Declare Static functions */
static float CubicInterpolate(float y0, float y1, float y2, float y3, float x);
static float CubicMaximize(float y0, float y1, float y2, float y3, float * max);
float CubicInterpolate(float y0, float y1, float y2, float y3, float x)
{
float a, b, c, d;
a = y0 / -6.0 + y1 / 2.0 - y2 / 2.0 + y3 / 6.0;
b = y0 - 5.0 * y1 / 2.0 + 2.0 * y2 - y3 / 2.0;
c = -11.0 * y0 / 6.0 + 3.0 * y1 - 3.0 * y2 / 2.0 + y3 / 3.0;
d = y0;
float xx = x * x;
float xxx = xx * x;
return (a * xxx + b * xx + c * x + d);
}
float CubicMaximize(float y0, float y1, float y2, float y3, float * max)
{
// Find coefficients of cubic
float a, b, c, d;
a = y0 / -6.0 + y1 / 2.0 - y2 / 2.0 + y3 / 6.0;
b = y0 - 5.0 * y1 / 2.0 + 2.0 * y2 - y3 / 2.0;
c = -11.0 * y0 / 6.0 + 3.0 * y1 - 3.0 * y2 / 2.0 + y3 / 3.0;
d = y0;
// Take derivative
float da, db, dc;
da = 3 * a;
db = 2 * b;
dc = c;
// Find zeroes of derivative using quadratic equation
float discriminant = db * db - 4 * da * dc;
if (discriminant < 0.0)
return float(-1.0); // error
float x1 = (-db + sqrt(discriminant)) / (2 * da);
float x2 = (-db - sqrt(discriminant)) / (2 * da);
// The one which corresponds to a local _maximum_ in the
// cubic is the one we want - the one with a negative
// second derivative
float dda = 2 * da;
float ddb = db;
if (dda * x1 + ddb < 0)
{
*max = a*x1*x1*x1+b*x1*x1+c*x1+d;
return x1;
}
else
{
*max = a*x2*x2*x2+b*x2*x2+c*x2+d;
return x2;
}
}
float SpectrumAnalyst::GetProcessedValue(float freq0, float freq1) const
{
float bin0, bin1, binwidth;
if (mAlg == Spectrum) {
bin0 = freq0 * mWindowSize / mRate;
bin1 = freq1 * mWindowSize / mRate;
} else {
bin0 = freq0 * mRate;
bin1 = freq1 * mRate;
}
binwidth = bin1 - bin0;
float value = float(0.0);
if (binwidth < 1.0) {
float binmid = (bin0 + bin1) / 2.0;
int ibin = int (binmid) - 1;
if (ibin < 1)
ibin = 1;
if (ibin >= GetProcessedSize() - 3)
ibin = std::max(0, GetProcessedSize() - 4);
value = CubicInterpolate(mProcessed[ibin],
mProcessed[ibin + 1],
mProcessed[ibin + 2],
mProcessed[ibin + 3], binmid - ibin);
} else {
if (bin0 < 0)
bin0 = 0;
if (bin1 >= GetProcessedSize())
bin1 = GetProcessedSize() - 1;
if (int (bin1) > int (bin0))
value += mProcessed[int (bin0)] * (int (bin0) + 1 - bin0);
bin0 = 1 + int (bin0);
while (bin0 < int (bin1)) {
value += mProcessed[int (bin0)];
bin0 += 1.0;
}
value += mProcessed[int (bin1)] * (bin1 - int (bin1));
value /= binwidth;
}
return value;
}
float SpectrumAnalyst::FindPeak(float xPos, float *pY) const
{
float bestpeak = 0.0f;
float bestValue = 0.0;
if (GetProcessedSize() > 1) {
bool up = (mProcessed[1] > mProcessed[0]);
float bestdist = 1000000;
for (int bin = 3; bin < GetProcessedSize() - 1; bin++) {
bool nowUp = mProcessed[bin] > mProcessed[bin - 1];
if (!nowUp && up) {
// Local maximum. Find actual value by cubic interpolation
int leftbin = bin - 2;
/*
if (leftbin < 1)
leftbin = 1;
*/
float valueAtMax = 0.0;
float max = leftbin + CubicMaximize(mProcessed[leftbin],
mProcessed[leftbin + 1],
mProcessed[leftbin + 2],
mProcessed[leftbin + 3],
&valueAtMax);
float thispeak;
if (mAlg == Spectrum)
thispeak = max * mRate / mWindowSize;
else
thispeak = max / mRate;
if (fabs(thispeak - xPos) < bestdist) {
bestpeak = thispeak;
bestdist = fabs(thispeak - xPos);
bestValue = valueAtMax;
// Should this test come after the enclosing if?
if (thispeak > xPos)
break;
}
}
up = nowUp;
}
}
if (pY)
*pY = bestValue;
return bestpeak;
}
void FreqWindow::PlotPaint(wxPaintEvent & evt)
{
wxPaintDC dc( (wxWindow *) evt.GetEventObject() );
dc.DrawBitmap( *mBitmap, 0, 0, true );
if( 0 == mAnalyst->GetProcessedSize() )
return;
SpectrumAnalyst::Algorithm alg =
SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
dc.SetFont(mFreqFont);
wxRect r = mPlotRect;
int width = r.width - 2;
float xMin, xMax, xRatio, xStep;
if (alg == SpectrumAnalyst::Spectrum) {
xMin = mRate / mWindowSize;
xMax = mRate / 2;
xRatio = xMax / xMin;
if (mLogAxis)
xStep = pow(2.0f, (log(xRatio) / log(2.0f)) / width);
else
xStep = (xMax - xMin) / width;
} else {
xMin = 0;
xMax = mAnalyst->GetProcessedSize() / mRate;
xStep = (xMax - xMin) / width;
}
float xPos = xMin;
// Find the peak nearest the cursor and plot it
if ( r.Contains(mMouseX, mMouseY) & (mMouseX!=0) & (mMouseX!=r.width-1) ) {
if (mLogAxis)
xPos = xMin * pow(xStep, mMouseX - (r.x + 1));
else
xPos = xMin + xStep * (mMouseX - (r.x + 1));
float bestValue = 0;
float bestpeak = mAnalyst->FindPeak(xPos, &bestValue);
int px;
if (mLogAxis)
px = int (log(bestpeak / xMin) / log(xStep));
else
px = int ((bestpeak - xMin) * width / (xMax - xMin));
dc.SetPen(wxPen(wxColour(160,160,160), 1, wxSOLID));
AColor::Line(dc, r.x + 1 + px, r.y, r.x + 1 + px, r.y + r.height);
// print out info about the cursor location
float value;
if (mLogAxis) {
xPos = xMin * pow(xStep, mMouseX - (r.x + 1));
value = mAnalyst->GetProcessedValue(xPos, xPos * xStep);
} else {
xPos = xMin + xStep * (mMouseX - (r.x + 1));
value = mAnalyst->GetProcessedValue(xPos, xPos + xStep);
}
wxString info;
wxString xpitch;
wxString peakpitch;
const wxChar *xp;
const wxChar *pp;
if (alg == SpectrumAnalyst::Spectrum) {
xpitch = PitchName_Absolute(FreqToMIDInote(xPos));
peakpitch = PitchName_Absolute(FreqToMIDInote(bestpeak));
xp = xpitch.c_str();
pp = peakpitch.c_str();
/* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A#*/
info.Printf(_("Cursor: %d Hz (%s) = %d dB Peak: %d Hz (%s) = %.1f dB"),
int (xPos + 0.5), xp,
int (value + 0.5), int (bestpeak + 0.5),
pp, bestValue);
} else if (xPos > 0.0 && bestpeak > 0.0) {
xpitch = PitchName_Absolute(FreqToMIDInote(1.0 / xPos));
peakpitch = PitchName_Absolute(FreqToMIDInote(1.0 / bestpeak));
xp = xpitch.c_str();
pp = peakpitch.c_str();
/* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A#
* the %.4f are numbers, and 'sec' should be an abbreviation for seconds */
info.Printf(_("Cursor: %.4f sec (%d Hz) (%s) = %f, Peak: %.4f sec (%d Hz) (%s) = %.3f"),
xPos,
int (1.0 / xPos + 0.5),
xp, value, bestpeak, int (1.0 / bestpeak + 0.5), pp, bestValue);
}
mInfoText->SetLabel(info);
}
else
mInfoText->SetLabel(wxT(""));
// Outline the graph
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(r);
}
void FreqWindow::OnCloseWindow(wxCloseEvent & WXUNUSED(event))
{
this->Show(FALSE);
}
void FreqWindow::OnCloseButton(wxCommandEvent & WXUNUSED(event))
{
gPrefs->Write(wxT("/FreqWindow/DrawGrid"), mDrawGrid);
gPrefs->Write(wxT("/FreqWindow/SizeChoice"), mSizeChoice->GetSelection());
gPrefs->Write(wxT("/FreqWindow/AlgChoice"), mAlgChoice->GetSelection());
gPrefs->Write(wxT("/FreqWindow/FuncChoice"), mFuncChoice->GetSelection());
gPrefs->Write(wxT("/FreqWindow/AxisChoice"), mAxisChoice->GetSelection());
gPrefs->Flush();
this->Show(FALSE);
}
void FreqWindow::Plot()
{
//wxLogDebug(wxT("Starting FreqWindow::Plot()"));
if (mData) {
delete[]mData;
mData = NULL;
}
if (mBuffer) {
mData = new float[mDataLen];
for (int i = 0; i < mDataLen; i++)
mData[i] = mBuffer[i];
}
Recalc();
wxSizeEvent dummy;
OnSize( dummy );
//wxLogDebug(wxT("Leaving FreqWindow::Plot()"));
}
void FreqWindow::Recalc()
{
//wxLogDebug(wxT("Starting FreqWindow::Recalc()"));
if (!mData) {
mFreqPlot->Refresh(true);
return;
}
SpectrumAnalyst::Algorithm alg =
SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
int windowFunc = mFuncChoice->GetSelection();
long windowSize = 0;
(mSizeChoice->GetStringSelection()).ToLong(&windowSize);
mWindowSize = windowSize;
//Progress dialog over FFT operation
std::auto_ptr<ProgressDialog> progress
(new ProgressDialog(_("Plot Spectrum"),_("Drawing Spectrum")));
if(!mAnalyst->Calculate(alg, windowFunc, mWindowSize, mRate,
mData, mDataLen,
&mYMin, &mYMax, progress.get())) {
mFreqPlot->Refresh(true);
return;
}
if (alg == SpectrumAnalyst::Spectrum) {
if(mYMin < -dBRange)
mYMin = -dBRange;
if(mYMax <= -dBRange)
mYMax = -dBRange + 10.; // it's all out of range, but show a scale.
else
mYMax += .5;
}
//wxLogDebug(wxT("About to draw plot in FreqWindow::Recalc()"));
DrawPlot();
mFreqPlot->Refresh(true);
}
bool SpectrumAnalyst::Calculate(Algorithm alg, int windowFunc,
int windowSize, double rate,
const float *data, int dataLen,
float *pYMin, float *pYMax,
ProgressDialog *progress)
{
// Wipe old data
mProcessed.resize(0);
mRate = 0.0;
mWindowSize = 0;
// Validate inputs
int f = NumWindowFuncs();
if (!(windowSize >= 32 && windowSize <= 65536 &&
alg >= SpectrumAnalyst::Spectrum &&
alg < SpectrumAnalyst::NumAlgorithms &&
windowFunc >= 0 && windowFunc < f)) {
return false;
}
if (dataLen < windowSize) {
return false;
}
// Now repopulate
mRate = rate;
mWindowSize = windowSize;
mAlg = alg;
int half = mWindowSize / 2;
mProcessed.resize(mWindowSize);
int i;
for (i = 0; i < mWindowSize; i++)
mProcessed[i] = float(0.0);
float *in = new float[mWindowSize];
float *in2 = new float[mWindowSize];
float *out = new float[mWindowSize];
float *out2 = new float[mWindowSize];
float *win = new float[mWindowSize];
// initialize the window
for(int i=0; i<mWindowSize; i++)
win[i] = 1.0;
WindowFunc(windowFunc, mWindowSize, win);
// Scale window such that an amplitude of 1.0 in the time domain
// shows an amplitude of 0dB in the frequency domain
double wss = 0;
for(int i=0; i<mWindowSize; i++)
wss += win[i];
if(wss > 0)
wss = 4.0 / (wss*wss);
else
wss = 1.0;
int start = 0;
int windows = 0;
while (start + mWindowSize <= dataLen) {
for (i = 0; i < mWindowSize; i++)
in[i] = win[i] * data[start + i];
switch (alg) {
case Spectrum:
PowerSpectrum(mWindowSize, in, out);
for (i = 0; i < half; i++)
mProcessed[i] += out[i];
break;
case Autocorrelation:
case CubeRootAutocorrelation:
case EnhancedAutocorrelation:
// Take FFT
#ifdef EXPERIMENTAL_USE_REALFFTF
RealFFT(mWindowSize, in, out, out2);
#else
FFT(mWindowSize, false, in, NULL, out, out2);
#endif
// Compute power
for (i = 0; i < mWindowSize; i++)
in[i] = (out[i] * out[i]) + (out2[i] * out2[i]);
if (alg == Autocorrelation) {
for (i = 0; i < mWindowSize; i++)
in[i] = sqrt(in[i]);
}
if (alg == CubeRootAutocorrelation ||
alg == EnhancedAutocorrelation) {
// Tolonen and Karjalainen recommend taking the cube root
// of the power, instead of the square root
for (i = 0; i < mWindowSize; i++)
in[i] = pow(in[i], 1.0f / 3.0f);
}
// Take FFT
#ifdef EXPERIMENTAL_USE_REALFFTF
RealFFT(mWindowSize, in, out, out2);
#else
FFT(mWindowSize, false, in, NULL, out, out2);
#endif
// Take real part of result
for (i = 0; i < half; i++)
mProcessed[i] += out[i];
break;
case Cepstrum:
#ifdef EXPERIMENTAL_USE_REALFFTF
RealFFT(mWindowSize, in, out, out2);
#else
FFT(mWindowSize, false, in, NULL, out, out2);
#endif
// Compute log power
// Set a sane lower limit assuming maximum time amplitude of 1.0
{
float power;
float minpower = 1e-20*mWindowSize*mWindowSize;
for (i = 0; i < mWindowSize; i++)
{
power = (out[i] * out[i]) + (out2[i] * out2[i]);
if(power < minpower)
in[i] = log(minpower);
else
in[i] = log(power);
}
// Take IFFT
#ifdef EXPERIMENTAL_USE_REALFFTF
InverseRealFFT(mWindowSize, in, NULL, out);
#else
FFT(mWindowSize, true, in, NULL, out, out2);
#endif
// Take real part of result
for (i = 0; i < half; i++)
mProcessed[i] += out[i];
}
break;
default:
wxASSERT(false);
break;
} //switch
start += half;
windows++;
// only update the progress dialogue infrequently to reduce its overhead
// If we do it every time, it spends as much time updating X11 as doing
// the calculations. 10 seems a reasonable compromise on Linux that
// doesn't make it unresponsive, but avoids the slowdown.
if (progress && (windows % 10) == 0)
progress->Update(1 - static_cast<float>(dataLen - start) / dataLen);
}
//wxLogDebug(wxT("Finished updating progress dialogue in SpectrumAnalyst::Recalc()"));
float mYMin = 1000000, mYMax = -1000000;
switch (alg) {
double scale;
case Spectrum:
// Convert to decibels
mYMin = 1000000.;
mYMax = -1000000.;
scale = wss / (double)windows;
for (i = 0; i < half; i++)
{
mProcessed[i] = 10 * log10(mProcessed[i] * scale);
if(mProcessed[i] > mYMax)
mYMax = mProcessed[i];
else if(mProcessed[i] < mYMin)
mYMin = mProcessed[i];
}
break;
case Autocorrelation:
case CubeRootAutocorrelation:
for (i = 0; i < half; i++)
mProcessed[i] = mProcessed[i] / windows;
// Find min/max
mYMin = mProcessed[0];
mYMax = mProcessed[0];
for (i = 1; i < half; i++)
if (mProcessed[i] > mYMax)
mYMax = mProcessed[i];
else if (mProcessed[i] < mYMin)
mYMin = mProcessed[i];
break;
case EnhancedAutocorrelation:
for (i = 0; i < half; i++)
mProcessed[i] = mProcessed[i] / windows;
// Peak Pruning as described by Tolonen and Karjalainen, 2000
// Clip at zero, copy to temp array
for (i = 0; i < half; i++) {
if (mProcessed[i] < 0.0)
mProcessed[i] = float(0.0);
out[i] = mProcessed[i];
}
// Subtract a time-doubled signal (linearly interp.) from the original
// (clipped) signal
for (i = 0; i < half; i++)
if ((i % 2) == 0)
mProcessed[i] -= out[i / 2];
else
mProcessed[i] -= ((out[i / 2] + out[i / 2 + 1]) / 2);
// Clip at zero again
for (i = 0; i < half; i++)
if (mProcessed[i] < 0.0)
mProcessed[i] = float(0.0);
// Find new min/max
mYMin = mProcessed[0];
mYMax = mProcessed[0];
for (i = 1; i < half; i++)
if (mProcessed[i] > mYMax)
mYMax = mProcessed[i];
else if (mProcessed[i] < mYMin)
mYMin = mProcessed[i];
break;
case Cepstrum:
for (i = 0; i < half; i++)
mProcessed[i] = mProcessed[i] / windows;
// Find min/max, ignoring first and last few values
{
int ignore = 4;
mYMin = mProcessed[ignore];
mYMax = mProcessed[ignore];
for (i = ignore + 1; i < half - ignore; i++)
if (mProcessed[i] > mYMax)
mYMax = mProcessed[i];
else if (mProcessed[i] < mYMin)
mYMin = mProcessed[i];
}
break;
default:
wxASSERT(false);
break;
}
delete[]in;
delete[]in2;
delete[]out;
delete[]out2;
delete[]win;
if (pYMin)
*pYMin = mYMin;
if (pYMax)
*pYMax = mYMax;
return true;
}
void FreqWindow::OnExport(wxCommandEvent & WXUNUSED(event))
{
wxString fName = _("spectrum.txt");
fName = FileSelector(_("Export Spectral Data As:"),
wxEmptyString, fName, wxT("txt"), wxT("*.txt"), wxFD_SAVE | wxRESIZE_BORDER, this);
if (fName == wxT(""))
return;
wxTextFile f(fName);
#ifdef __WXMAC__
wxFile *temp = new wxFile();
temp->Create(fName);
delete temp;
#else
f.Create();
#endif
f.Open();
if (!f.IsOpened()) {
wxMessageBox(_("Couldn't write to file: ") + fName);
return;
}
const int processedSize = mAnalyst->GetProcessedSize();
const float *const processed = mAnalyst->GetProcessed();
if (mAlgChoice->GetSelection() == 0) {
f.AddLine(_("Frequency (Hz)\tLevel (dB)"));
for (int i = 1; i < processedSize; i++)
f.AddLine(wxString::
Format(wxT("%f\t%f"), i * mRate / mWindowSize,
processed[i]));
} else {
f.AddLine(_("Lag (seconds)\tFrequency (Hz)\tLevel"));
for (int i = 1; i < processedSize; i++)
f.AddLine(wxString::Format(wxT("%f\t%f\t%f"),
i / mRate, mRate / i, processed[i]));
}
#ifdef __WXMAC__
f.Write(wxTextFileType_Mac);
#else
f.Write();
#endif
f.Close();
}
void FreqWindow::OnReplot(wxCommandEvent & WXUNUSED(event))
{
gPrefs->Read(wxT("/GUI/EnvdBRange"), &dBRange, ENV_DB_RANGE);
if(dBRange < 90.)
dBRange = 90.;
GetAudio();
Plot();
}
void FreqWindow::OnGridOnOff(wxCommandEvent & WXUNUSED(event))
{
if( mGridOnOff->IsChecked() )
mDrawGrid = true;
else
mDrawGrid = false;
Plot();
}
BEGIN_EVENT_TABLE(FreqPlot, wxWindow)
EVT_ERASE_BACKGROUND(FreqPlot::OnErase)
EVT_PAINT(FreqPlot::OnPaint)
EVT_MOUSE_EVENTS(FreqPlot::OnMouseEvent)
END_EVENT_TABLE()
FreqPlot::FreqPlot(wxWindow * parent, wxWindowID id,
const wxPoint & pos,
const wxSize & size):wxWindow(parent, id, pos, size)
{
freqWindow = (FreqWindow *) parent;
}
void FreqPlot::OnErase(wxEraseEvent & WXUNUSED(event))
{
// Ignore it to prevent flashing
}
void FreqPlot::OnPaint(wxPaintEvent & evt)
{
freqWindow->PlotPaint(evt);
}
void FreqPlot::OnMouseEvent(wxMouseEvent & event)
{
freqWindow->PlotMouseEvent(event);
}