1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-06 15:37:44 +02:00

Paul Licameli's Spectral Editing Patch.

This relies on three new nyquist scripts to actually do the editing.  The peak-snapping code in FrequencyWindow has been extracted into a new class, SpectrumAnalyst, to provide peak-snapping in spectrogram too.
This commit is contained in:
james.k.crook@gmail.com 2014-10-18 14:19:38 +00:00
parent b84fdb82e1
commit 37608c2290
28 changed files with 1342 additions and 279 deletions

@ -0,0 +1,33 @@
;nyquist plug-in
;version 3
;type process
;name "Spectral edit multi tool"
;action "Calculating..."
(defun wet (sig)
(cond
((not (or *f0* *f1*)) (throw 'error-message "Please select frequencies"))
((not *f0*) (highpass2 sig *f1*))
((not *f1*) (lowpass2 sig *f0*))
(t (if (= *f0* *f1*)
(throw 'error-message "Band width is undefined")
(let*
((fc (sqrt (* *f0* *f1*)))
(width (abs (- *f1* *f0*)))
(q (/ fc width)))
(notch2 sig fc q))))))
(defun result (sig)
(let*
((tn (truncate len))
(rate (snd-srate sig))
(transition (truncate (* 0.01 rate)))
(t1 (min transition (/ tn 2)))
(t2 (max (- tn transition) (/ tn 2)))
(breakpoints (list t1 1.0 t2 1.0 tn))
(env (snd-pwl 0.0 rate breakpoints)))
(sum (prod env (wet sig)) (prod (diff 1.0 env) sig))))
(catch 'error-message
(multichan-expand #'result s))

@ -0,0 +1,32 @@
;nyquist plug-in
;version 3
;type process
;name "Spectral edit parametric EQ"
;action "Calculating..."
;control control-gain "Gain (dB)" real "" 0 -24 24
(defun wet (sig gain)
(cond
((not (or *f0* *f1*)) (throw 'debug-message "Please select frequencies"))
((not *f0*) (throw 'debug-message "Bottom frequency is undefined"))
((not *f1*) (throw 'debug-message "Top frequency is undefined"))
(t (let*
((fc (sqrt (* *f0* *f1*)))
(width-octaves (/ (s-log (/ *f1* *f0*)) (s-log 2.0))))
(eq-band sig fc gain (/ width-octaves 2))))))
(defun result (sig)
(let*
((tn (truncate len))
(rate (snd-srate sig))
(transition (truncate (* 0.01 rate)))
(t1 (min transition (/ tn 2)))
(t2 (max (- tn transition) (/ tn 2)))
(breakpoints (list t1 1.0 t2 1.0 tn))
(env (snd-pwl 0.0 rate breakpoints)))
(sum (prod env (wet sig control-gain)) (prod (diff 1.0 env) sig))))
(catch 'debug-message
(multichan-expand #'result s))

@ -0,0 +1,36 @@
;nyquist plug-in
;version 3
;type process
;name "Spectral edit shelves"
;action "Calculating..."
;control control-gain "Gain (dB)" real "" 0 -24 24
(defun mid-shelf (sig lf hf gain)
"Combines high shelf and low shelf filters"
(let* ((invg (- gain)))
(scale (db-to-linear gain)
(eq-highshelf (eq-lowshelf sig lf invg)
hf invg))))
(defun wet (sig gain)
(cond
((not (or *f0* *f1*)) (throw 'debug-message "Please select frequencies"))
((not *f0*) (eq-lowshelf sig *f1* gain))
((not *f1*) (eq-highshelf sig *f0* gain))
(t (mid-shelf sig *f0* *f1* gain))))
(defun result (sig)
(let*
((tn (truncate len))
(rate (snd-srate sig))
(transition (truncate (* 0.01 rate)))
(t1 (min transition (/ tn 2)))
(t2 (max (- tn transition) (/ tn 2)))
(breakpoints (list t1 1.0 t2 1.0 tn))
(env (snd-pwl 0.0 rate breakpoints)))
(sum (prod env (wet sig control-gain)) (prod (diff 1.0 env) sig))))
(catch 'debug-message
(multichan-expand #'result s))

@ -570,14 +570,14 @@ void AColor::DarkMIDIChannel(wxDC * dc, int channel /* 1 - 16 */ )
bool AColor::gradient_inited = 0; bool AColor::gradient_inited = 0;
unsigned char AColor::gradient_pre[2][2][gradientSteps][3]; unsigned char AColor::gradient_pre[ColorGradientTotal][2][gradientSteps][3];
void AColor::PreComputeGradient() { void AColor::PreComputeGradient() {
{ {
if (!gradient_inited) { if (!gradient_inited) {
gradient_inited = 1; gradient_inited = 1;
for (int selected = 0; selected <= 1; selected++) for (int selected = 0; selected < ColorGradientTotal; selected++)
for (int grayscale = 0; grayscale <= 1; grayscale++) { for (int grayscale = 0; grayscale <= 1; grayscale++) {
float r, g, b; float r, g, b;
@ -608,10 +608,24 @@ void AColor::PreComputeGradient() {
b = (gradient[left][2] * lweight) + (gradient[right][2] * rweight); b = (gradient[left][2] * lweight) + (gradient[right][2] * rweight);
} }
if (selected) { switch (selected) {
case ColorGradientUnselected:
// not dimmed
break;
case ColorGradientTimeSelected:
// partly dimmed
r *= 0.88f;
g *= 0.88f;
b *= 0.992f;
break;
case ColorGradientTimeAndFrequencySelected:
// fully dimmed
r *= 0.77f; r *= 0.77f;
g *= 0.77f; g *= 0.77f;
b *= 0.885f; b *= 0.885f;
break;
} }
gradient_pre[selected][grayscale][i][0] = (unsigned char) (255 * r); gradient_pre[selected][grayscale][i][0] = (unsigned char) (255 * r);
gradient_pre[selected][grayscale][i][1] = (unsigned char) (255 * g); gradient_pre[selected][grayscale][i][1] = (unsigned char) (255 * g);

@ -22,6 +22,15 @@ class wxRect;
class AColor { class AColor {
public: public:
enum ColorGradientChoice {
ColorGradientUnselected = 0,
ColorGradientTimeSelected,
ColorGradientTimeAndFrequencySelected,
ColorGradientTotal // keep me last
};
static void Init(); static void Init();
static void ReInit(); static void ReInit();
@ -97,7 +106,7 @@ class AColor {
static bool gradient_inited; static bool gradient_inited;
static const int gradientSteps = 512; static const int gradientSteps = 512;
static unsigned char gradient_pre[2][2][gradientSteps][3]; static unsigned char gradient_pre[ColorGradientTotal][2][gradientSteps][3];
private: private:
static wxPen sparePen; static wxPen sparePen;
@ -107,7 +116,7 @@ class AColor {
}; };
inline void GetColorGradient(float value, inline void GetColorGradient(float value,
bool selected, AColor::ColorGradientChoice selected,
bool grayscale, bool grayscale,
unsigned char *red, unsigned char *red,
unsigned char *green, unsigned char *blue) { unsigned char *green, unsigned char *blue) {

@ -102,6 +102,9 @@
// Won't build on Fedora 17 or Windows VC++, per http://bugzilla.audacityteam.org/show_bug.cgi?id=539. // Won't build on Fedora 17 or Windows VC++, per http://bugzilla.audacityteam.org/show_bug.cgi?id=539.
//#define EXPERIMENTAL_OD_FFMPEG 1 //#define EXPERIMENTAL_OD_FFMPEG 1
// Paul Licameli (PRL) 5 Oct 2014
#define EXPERIMENTAL_SPECTRAL_EDITING
// Philip Van Baren 01 July 2009 // Philip Van Baren 01 July 2009
// Replace RealFFT() and PowerSpectrum function to use (faster) RealFFTf function // Replace RealFFT() and PowerSpectrum function to use (faster) RealFFTf function
#define EXPERIMENTAL_USE_REALFFTF #define EXPERIMENTAL_USE_REALFFTF

@ -107,12 +107,23 @@ BEGIN_EVENT_TABLE(FreqWindow, wxDialog)
EVT_CHECKBOX(GridOnOffID, FreqWindow::OnGridOnOff) EVT_CHECKBOX(GridOnOffID, FreqWindow::OnGridOnOff)
END_EVENT_TABLE() END_EVENT_TABLE()
SpectrumAnalyst::SpectrumAnalyst()
: mAlg(Spectrum)
, mRate(0.0)
, mWindowSize(0)
{
}
SpectrumAnalyst::~SpectrumAnalyst()
{
}
FreqWindow::FreqWindow(wxWindow * parent, wxWindowID id, FreqWindow::FreqWindow(wxWindow * parent, wxWindowID id,
const wxString & title, const wxString & title,
const wxPoint & pos): const wxPoint & pos):
wxDialog(parent, id, title, pos, wxDefaultSize, wxDialog(parent, id, title, pos, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX),
mData(NULL), mProcessed(NULL), mBitmap(NULL) mData(NULL), mBitmap(NULL), mAnalyst(new SpectrumAnalyst())
{ {
mMouseX = 0; mMouseX = 0;
mMouseY = 0; mMouseY = 0;
@ -132,7 +143,11 @@ FreqWindow::FreqWindow(wxWindow * parent, wxWindowID id,
gPrefs->Read(wxT("/FreqWindow/DrawGrid"), &mDrawGrid, true); gPrefs->Read(wxT("/FreqWindow/DrawGrid"), &mDrawGrid, true);
gPrefs->Read(wxT("/FreqWindow/SizeChoice"), &mSize, 2); gPrefs->Read(wxT("/FreqWindow/SizeChoice"), &mSize, 2);
gPrefs->Read(wxT("/FreqWindow/AlgChoice"), &mAlg, 0);
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/FuncChoice"), &mFunc, 3);
gPrefs->Read(wxT("/FreqWindow/AxisChoice"), &mAxis, 0); gPrefs->Read(wxT("/FreqWindow/AxisChoice"), &mAxis, 0);
gPrefs->Read(wxT("/GUI/EnvdBRange"), &dBRange, ENV_DB_RANGE); gPrefs->Read(wxT("/GUI/EnvdBRange"), &dBRange, ENV_DB_RANGE);
@ -214,7 +229,7 @@ FreqWindow::FreqWindow(wxWindow * parent, wxWindowID id,
mAxisChoice->SetSelection(mAxis); mAxisChoice->SetSelection(mAxis);
// Log-frequency axis works for spectrum plots only. // Log-frequency axis works for spectrum plots only.
if (mAlg != 0) { if (mAlg != SpectrumAnalyst::Spectrum) {
mAxis = 0; mAxis = 0;
mAxisChoice->Disable(); mAxisChoice->Disable();
} }
@ -381,8 +396,6 @@ FreqWindow::~FreqWindow()
delete[] mData; delete[] mData;
if (mBuffer) if (mBuffer)
delete[] mBuffer; delete[] mBuffer;
if (mProcessed)
delete[] mProcessed;
} }
void FreqWindow::GetAudio() void FreqWindow::GetAudio()
@ -489,7 +502,7 @@ void FreqWindow::DrawPlot()
memDC.SetBrush(*wxWHITE_BRUSH); memDC.SetBrush(*wxWHITE_BRUSH);
memDC.DrawRectangle(r); memDC.DrawRectangle(r);
if (!mProcessed) { if (0 == mAnalyst->GetProcessedSize()) {
if (mData && mDataLen < mWindowSize) if (mData && mDataLen < mWindowSize)
memDC.DrawText(_("Not enough data selected."), r.x + 5, r.y + 5); memDC.DrawText(_("Not enough data selected."), r.x + 5, r.y + 5);
@ -498,7 +511,8 @@ void FreqWindow::DrawPlot()
float yTotal = (mYMax - mYMin); float yTotal = (mYMax - mYMin);
int alg = mAlgChoice->GetSelection(); SpectrumAnalyst::Algorithm alg =
SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
int i; int i;
@ -506,7 +520,7 @@ void FreqWindow::DrawPlot()
// Set up y axis ruler // Set up y axis ruler
if (alg == 0) { if (alg == SpectrumAnalyst::Spectrum) {
vRuler->ruler.SetUnits(_("dB")); vRuler->ruler.SetUnits(_("dB"));
vRuler->ruler.SetFormat(Ruler::LinearDBFormat); vRuler->ruler.SetFormat(Ruler::LinearDBFormat);
} else { } else {
@ -531,7 +545,7 @@ void FreqWindow::DrawPlot()
float xMin, xMax, xRatio, xStep; float xMin, xMax, xRatio, xStep;
if (alg == 0) { if (alg == SpectrumAnalyst::Spectrum) {
xMin = mRate / mWindowSize; xMin = mRate / mWindowSize;
xMax = mRate / 2; xMax = mRate / 2;
xRatio = xMax / xMin; xRatio = xMax / xMin;
@ -548,7 +562,7 @@ void FreqWindow::DrawPlot()
hRuler->ruler.SetUnits(_("Hz")); hRuler->ruler.SetUnits(_("Hz"));
} else { } else {
xMin = 0; xMin = 0;
xMax = mProcessedSize / mRate; xMax = mAnalyst->GetProcessedSize() / mRate;
xStep = (xMax - xMin) / width; xStep = (xMax - xMin) / width;
hRuler->ruler.SetLog(false); hRuler->ruler.SetLog(false);
hRuler->ruler.SetUnits(_("s")); hRuler->ruler.SetUnits(_("s"));
@ -557,7 +571,7 @@ void FreqWindow::DrawPlot()
hRuler->Refresh(false); hRuler->Refresh(false);
// Draw the plot // Draw the plot
if (alg == 0) if (alg == SpectrumAnalyst::Spectrum)
memDC.SetPen(wxPen(theTheme.Colour( clrHzPlot ), 1, wxSOLID)); memDC.SetPen(wxPen(theTheme.Colour( clrHzPlot ), 1, wxSOLID));
else else
memDC.SetPen(wxPen(theTheme.Colour( clrWavelengthPlot), 1, wxSOLID)); memDC.SetPen(wxPen(theTheme.Colour( clrWavelengthPlot), 1, wxSOLID));
@ -568,9 +582,9 @@ void FreqWindow::DrawPlot()
float y; float y;
if (mLogAxis) if (mLogAxis)
y = GetProcessedValue(xPos, xPos * xStep); y = mAnalyst->GetProcessedValue(xPos, xPos * xStep);
else else
y = GetProcessedValue(xPos, xPos + xStep); y = mAnalyst->GetProcessedValue(xPos, xPos + xStep);
float ynorm = (y - mYMin) / yTotal; float ynorm = (y - mYMin) / yTotal;
@ -725,13 +739,11 @@ float CubicMaximize(float y0, float y1, float y2, float y3, float * max)
} }
} }
float FreqWindow::GetProcessedValue(float freq0, float freq1) float SpectrumAnalyst::GetProcessedValue(float freq0, float freq1) const
{ {
int alg = mAlgChoice->GetSelection();
float bin0, bin1, binwidth; float bin0, bin1, binwidth;
if (alg == 0) { if (mAlg == Spectrum) {
bin0 = freq0 * mWindowSize / mRate; bin0 = freq0 * mWindowSize / mRate;
bin1 = freq1 * mWindowSize / mRate; bin1 = freq1 * mWindowSize / mRate;
} else { } else {
@ -747,8 +759,8 @@ float FreqWindow::GetProcessedValue(float freq0, float freq1)
int ibin = int (binmid) - 1; int ibin = int (binmid) - 1;
if (ibin < 1) if (ibin < 1)
ibin = 1; ibin = 1;
if (ibin >= mProcessedSize - 3) if (ibin >= GetProcessedSize() - 3)
ibin = mProcessedSize - 4; ibin = std::max(0, GetProcessedSize() - 4);
value = CubicInterpolate(mProcessed[ibin], value = CubicInterpolate(mProcessed[ibin],
mProcessed[ibin + 1], mProcessed[ibin + 1],
@ -756,6 +768,11 @@ float FreqWindow::GetProcessedValue(float freq0, float freq1)
mProcessed[ibin + 3], binmid - ibin); mProcessed[ibin + 3], binmid - ibin);
} else { } else {
if (bin0 < 0)
bin0 = 0;
if (bin1 >= GetProcessedSize())
bin1 = GetProcessedSize() - 1;
if (int (bin1) > int (bin0)) if (int (bin1) > int (bin0))
value += mProcessed[int (bin0)] * (int (bin0) + 1 - bin0); value += mProcessed[int (bin0)] * (int (bin0) + 1 - bin0);
bin0 = 1 + int (bin0); bin0 = 1 + int (bin0);
@ -771,15 +788,63 @@ float FreqWindow::GetProcessedValue(float freq0, float freq1)
return value; 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) void FreqWindow::PlotPaint(wxPaintEvent & evt)
{ {
wxPaintDC dc( (wxWindow *) evt.GetEventObject() ); wxPaintDC dc( (wxWindow *) evt.GetEventObject() );
dc.DrawBitmap( *mBitmap, 0, 0, true ); dc.DrawBitmap( *mBitmap, 0, 0, true );
if( mProcessed == NULL ) if( 0 == mAnalyst->GetProcessedSize() )
return; return;
int alg = mAlgChoice->GetSelection(); SpectrumAnalyst::Algorithm alg =
SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
dc.SetFont(mFreqFont); dc.SetFont(mFreqFont);
@ -789,7 +854,7 @@ void FreqWindow::PlotPaint(wxPaintEvent & evt)
float xMin, xMax, xRatio, xStep; float xMin, xMax, xRatio, xStep;
if (alg == 0) { if (alg == SpectrumAnalyst::Spectrum) {
xMin = mRate / mWindowSize; xMin = mRate / mWindowSize;
xMax = mRate / 2; xMax = mRate / 2;
xRatio = xMax / xMin; xRatio = xMax / xMin;
@ -799,52 +864,21 @@ void FreqWindow::PlotPaint(wxPaintEvent & evt)
xStep = (xMax - xMin) / width; xStep = (xMax - xMin) / width;
} else { } else {
xMin = 0; xMin = 0;
xMax = mProcessedSize / mRate; xMax = mAnalyst->GetProcessedSize() / mRate;
xStep = (xMax - xMin) / width; xStep = (xMax - xMin) / width;
} }
float xPos = xMin; float xPos = xMin;
// Find the peak nearest the cursor and plot it // Find the peak nearest the cursor and plot it
float bestpeak = float(0.0);
if ( r.Contains(mMouseX, mMouseY) & (mMouseX!=0) & (mMouseX!=r.width-1) ) { if ( r.Contains(mMouseX, mMouseY) & (mMouseX!=0) & (mMouseX!=r.width-1) ) {
if (mLogAxis) if (mLogAxis)
xPos = xMin * pow(xStep, mMouseX - (r.x + 1)); xPos = xMin * pow(xStep, mMouseX - (r.x + 1));
else else
xPos = xMin + xStep * (mMouseX - (r.x + 1)); xPos = xMin + xStep * (mMouseX - (r.x + 1));
bool up = (mProcessed[1] > mProcessed[0]); float bestValue = 0;
float bestdist = 1000000; float bestpeak = mAnalyst->FindPeak(xPos, &bestValue);
float bestValue = 0.0;
for (int bin = 2; bin < mProcessedSize; 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 (alg == 0)
thispeak = max * mRate / mWindowSize;
else
thispeak = max / mRate;
if (fabs(thispeak - xPos) < bestdist) {
bestpeak = thispeak;
bestdist = fabs(thispeak - xPos);
bestValue = valueAtMax;
if (thispeak > xPos)
break;
}
}
up = nowUp;
}
int px; int px;
if (mLogAxis) if (mLogAxis)
@ -861,10 +895,10 @@ void FreqWindow::PlotPaint(wxPaintEvent & evt)
if (mLogAxis) { if (mLogAxis) {
xPos = xMin * pow(xStep, mMouseX - (r.x + 1)); xPos = xMin * pow(xStep, mMouseX - (r.x + 1));
value = GetProcessedValue(xPos, xPos * xStep); value = mAnalyst->GetProcessedValue(xPos, xPos * xStep);
} else { } else {
xPos = xMin + xStep * (mMouseX - (r.x + 1)); xPos = xMin + xStep * (mMouseX - (r.x + 1));
value = GetProcessedValue(xPos, xPos + xStep); value = mAnalyst->GetProcessedValue(xPos, xPos + xStep);
} }
wxString info; wxString info;
@ -873,7 +907,7 @@ void FreqWindow::PlotPaint(wxPaintEvent & evt)
const wxChar *xp; const wxChar *xp;
const wxChar *pp; const wxChar *pp;
if (alg == 0) { if (alg == SpectrumAnalyst::Spectrum) {
xpitch = PitchName_Absolute(FreqToMIDInote(xPos)); xpitch = PitchName_Absolute(FreqToMIDInote(xPos));
peakpitch = PitchName_Absolute(FreqToMIDInote(bestpeak)); peakpitch = PitchName_Absolute(FreqToMIDInote(bestpeak));
xp = xpitch.c_str(); xp = xpitch.c_str();
@ -946,41 +980,80 @@ void FreqWindow::Plot()
void FreqWindow::Recalc() void FreqWindow::Recalc()
{ {
//wxLogDebug(wxT("Starting FreqWindow::Recalc()")); //wxLogDebug(wxT("Starting FreqWindow::Recalc()"));
if (mProcessed)
delete[] mProcessed;
mProcessed = NULL;
if (!mData) { if (!mData) {
mFreqPlot->Refresh(true); mFreqPlot->Refresh(true);
return; return;
} }
int alg = mAlgChoice->GetSelection(); SpectrumAnalyst::Algorithm alg =
SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
int windowFunc = mFuncChoice->GetSelection(); int windowFunc = mFuncChoice->GetSelection();
long windowSize = 0; long windowSize = 0;
(mSizeChoice->GetStringSelection()).ToLong(&windowSize); (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(); int f = NumWindowFuncs();
if (!(windowSize >= 32 && windowSize <= 65536 && if (!(windowSize >= 32 && windowSize <= 65536 &&
alg >= 0 && alg <= 4 && windowFunc >= 0 && windowFunc < f)) { alg >= SpectrumAnalyst::Spectrum &&
mFreqPlot->Refresh(true); alg < SpectrumAnalyst::NumAlgorithms &&
return; windowFunc >= 0 && windowFunc < f)) {
return false;
} }
if (dataLen < windowSize) {
return false;
}
// Now repopulate
mRate = rate;
mWindowSize = windowSize; mWindowSize = windowSize;
mAlg = alg;
if (mDataLen < mWindowSize) { int half = mWindowSize / 2;
mFreqPlot->Refresh(true); mProcessed.resize(mWindowSize);
return;
}
mProcessed = new float[mWindowSize];
int i; int i;
for (i = 0; i < mWindowSize; i++) for (i = 0; i < mWindowSize; i++)
mProcessed[i] = float(0.0); mProcessed[i] = float(0.0);
int half = mWindowSize / 2;
float *in = new float[mWindowSize]; float *in = new float[mWindowSize];
float *in2 = new float[mWindowSize]; float *in2 = new float[mWindowSize];
@ -1002,26 +1075,23 @@ void FreqWindow::Recalc()
else else
wss = 1.0; wss = 1.0;
//Progress dialog over FFT operation
ProgressDialog *mProgress = new ProgressDialog(_("Plot Spectrum"),_("Drawing Spectrum"));
int start = 0; int start = 0;
int windows = 0; int windows = 0;
while (start + mWindowSize <= mDataLen) { while (start + mWindowSize <= dataLen) {
for (i = 0; i < mWindowSize; i++) for (i = 0; i < mWindowSize; i++)
in[i] = win[i] * mData[start + i]; in[i] = win[i] * data[start + i];
switch (alg) { switch (alg) {
case 0: // Spectrum case Spectrum:
PowerSpectrum(mWindowSize, in, out); PowerSpectrum(mWindowSize, in, out);
for (i = 0; i < half; i++) for (i = 0; i < half; i++)
mProcessed[i] += out[i]; mProcessed[i] += out[i];
break; break;
case 1: case Autocorrelation:
case 2: case CubeRootAutocorrelation:
case 3: // Autocorrelation, Cuberoot AC or Enhanced AC case EnhancedAutocorrelation:
// Take FFT // Take FFT
#ifdef EXPERIMENTAL_USE_REALFFTF #ifdef EXPERIMENTAL_USE_REALFFTF
@ -1033,11 +1103,12 @@ void FreqWindow::Recalc()
for (i = 0; i < mWindowSize; i++) for (i = 0; i < mWindowSize; i++)
in[i] = (out[i] * out[i]) + (out2[i] * out2[i]); in[i] = (out[i] * out[i]) + (out2[i] * out2[i]);
if (alg == 1) { if (alg == Autocorrelation) {
for (i = 0; i < mWindowSize; i++) for (i = 0; i < mWindowSize; i++)
in[i] = sqrt(in[i]); in[i] = sqrt(in[i]);
} }
if (alg == 2 || alg == 3) { if (alg == CubeRootAutocorrelation ||
alg == EnhancedAutocorrelation) {
// Tolonen and Karjalainen recommend taking the cube root // Tolonen and Karjalainen recommend taking the cube root
// of the power, instead of the square root // of the power, instead of the square root
@ -1056,7 +1127,7 @@ void FreqWindow::Recalc()
mProcessed[i] += out[i]; mProcessed[i] += out[i];
break; break;
case 4: // Cepstrum case Cepstrum:
#ifdef EXPERIMENTAL_USE_REALFFTF #ifdef EXPERIMENTAL_USE_REALFFTF
RealFFT(mWindowSize, in, out, out2); RealFFT(mWindowSize, in, out, out2);
#else #else
@ -1065,6 +1136,7 @@ void FreqWindow::Recalc()
// Compute log power // Compute log power
// Set a sane lower limit assuming maximum time amplitude of 1.0 // Set a sane lower limit assuming maximum time amplitude of 1.0
{
float power; float power;
float minpower = 1e-20*mWindowSize*mWindowSize; float minpower = 1e-20*mWindowSize*mWindowSize;
for (i = 0; i < mWindowSize; i++) for (i = 0; i < mWindowSize; i++)
@ -1085,24 +1157,30 @@ void FreqWindow::Recalc()
// Take real part of result // Take real part of result
for (i = 0; i < half; i++) for (i = 0; i < half; i++)
mProcessed[i] += out[i]; mProcessed[i] += out[i];
}
break; break;
default:
wxASSERT(false);
break;
} //switch } //switch
start += half; start += half;
windows++; windows++;
// only update the progress dialogue infrequently to reduce it's overhead // 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 // 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 // the calculations. 10 seems a reasonable compromise on Linux that
// doesn't make it unresponsive, but avoids the slowdown. // doesn't make it unresponsive, but avoids the slowdown.
if ((windows % 10) == 0) if (progress && (windows % 10) == 0)
mProgress->Update(1 - static_cast<float>(mDataLen - start) / mDataLen); progress->Update(1 - static_cast<float>(dataLen - start) / dataLen);
} }
//wxLogDebug(wxT("Finished updating progress dialogue in FreqWindow::Recalc()")); //wxLogDebug(wxT("Finished updating progress dialogue in SpectrumAnalyst::Recalc()"));
float mYMin = 1000000, mYMax = -1000000;
switch (alg) { switch (alg) {
double scale; double scale;
case 0: // Spectrum case Spectrum:
// Convert to decibels // Convert to decibels
mYMin = 1000000.; mYMin = 1000000.;
mYMax = -1000000.; mYMax = -1000000.;
@ -1115,19 +1193,10 @@ void FreqWindow::Recalc()
else if(mProcessed[i] < mYMin) else if(mProcessed[i] < mYMin)
mYMin = mProcessed[i]; mYMin = mProcessed[i];
} }
if(mYMin < -dBRange)
mYMin = -dBRange;
if(mYMax <= -dBRange)
mYMax = -dBRange + 10.; // it's all out of range, but show a scale.
else
mYMax += .5;
mProcessedSize = half;
mYStep = 10;
break; break;
case 1: // Standard Autocorrelation case Autocorrelation:
case 2: // Cuberoot Autocorrelation case CubeRootAutocorrelation:
for (i = 0; i < half; i++) for (i = 0; i < half; i++)
mProcessed[i] = mProcessed[i] / windows; mProcessed[i] = mProcessed[i] / windows;
@ -1139,13 +1208,9 @@ void FreqWindow::Recalc()
mYMax = mProcessed[i]; mYMax = mProcessed[i];
else if (mProcessed[i] < mYMin) else if (mProcessed[i] < mYMin)
mYMin = mProcessed[i]; mYMin = mProcessed[i];
mYStep = 1;
mProcessedSize = half;
break; break;
case 3: // Enhanced Autocorrelation case EnhancedAutocorrelation:
for (i = 0; i < half; i++) for (i = 0; i < half; i++)
mProcessed[i] = mProcessed[i] / windows; mProcessed[i] = mProcessed[i] / windows;
@ -1179,17 +1244,14 @@ void FreqWindow::Recalc()
mYMax = mProcessed[i]; mYMax = mProcessed[i];
else if (mProcessed[i] < mYMin) else if (mProcessed[i] < mYMin)
mYMin = mProcessed[i]; mYMin = mProcessed[i];
mYStep = 1;
mProcessedSize = half;
break; break;
case 4: // Cepstrum case Cepstrum:
for (i = 0; i < half; i++) for (i = 0; i < half; i++)
mProcessed[i] = mProcessed[i] / windows; mProcessed[i] = mProcessed[i] / windows;
// Find min/max, ignoring first and last few values // Find min/max, ignoring first and last few values
{
int ignore = 4; int ignore = 4;
mYMin = mProcessed[ignore]; mYMin = mProcessed[ignore];
mYMax = mProcessed[ignore]; mYMax = mProcessed[ignore];
@ -1198,10 +1260,11 @@ void FreqWindow::Recalc()
mYMax = mProcessed[i]; mYMax = mProcessed[i];
else if (mProcessed[i] < mYMin) else if (mProcessed[i] < mYMin)
mYMin = mProcessed[i]; mYMin = mProcessed[i];
}
break;
mYStep = 1; default:
wxASSERT(false);
mProcessedSize = half;
break; break;
} }
@ -1211,10 +1274,12 @@ void FreqWindow::Recalc()
delete[]out2; delete[]out2;
delete[]win; delete[]win;
//wxLogDebug(wxT("About to draw plot in FreqWindow::Recalc()")); if (pYMin)
DrawPlot(); *pYMin = mYMin;
mFreqPlot->Refresh(true); if (pYMax)
delete mProgress; *pYMax = mYMax;
return true;
} }
void FreqWindow::OnExport(wxCommandEvent & WXUNUSED(event)) void FreqWindow::OnExport(wxCommandEvent & WXUNUSED(event))
@ -1241,17 +1306,19 @@ void FreqWindow::OnExport(wxCommandEvent & WXUNUSED(event))
return; return;
} }
const int processedSize = mAnalyst->GetProcessedSize();
const float *const processed = mAnalyst->GetProcessed();
if (mAlgChoice->GetSelection() == 0) { if (mAlgChoice->GetSelection() == 0) {
f.AddLine(_("Frequency (Hz)\tLevel (dB)")); f.AddLine(_("Frequency (Hz)\tLevel (dB)"));
for (int i = 1; i < mProcessedSize; i++) for (int i = 1; i < processedSize; i++)
f.AddLine(wxString:: f.AddLine(wxString::
Format(wxT("%f\t%f"), i * mRate / mWindowSize, Format(wxT("%f\t%f"), i * mRate / mWindowSize,
mProcessed[i])); processed[i]));
} else { } else {
f.AddLine(_("Lag (seconds)\tFrequency (Hz)\tLevel")); f.AddLine(_("Lag (seconds)\tFrequency (Hz)\tLevel"));
for (int i = 1; i < mProcessedSize; i++) for (int i = 1; i < processedSize; i++)
f.AddLine(wxString::Format(wxT("%f\t%f\t%f"), f.AddLine(wxString::Format(wxT("%f\t%f\t%f"),
i / mRate, mRate / i, mProcessed[i])); i / mRate, mRate / i, processed[i]));
} }
#ifdef __WXMAC__ #ifdef __WXMAC__

@ -11,6 +11,8 @@
#ifndef __AUDACITY_FREQ_WINDOW__ #ifndef __AUDACITY_FREQ_WINDOW__
#define __AUDACITY_FREQ_WINDOW__ #define __AUDACITY_FREQ_WINDOW__
#include <memory>
#include <vector>
#include <wx/brush.h> #include <wx/brush.h>
#include <wx/frame.h> #include <wx/frame.h>
#include <wx/panel.h> #include <wx/panel.h>
@ -32,7 +34,7 @@ class FreqWindow;
class TrackList; class TrackList;
class FreqWindow; class ProgressDialog;
class FreqPlot:public wxWindow { class FreqPlot:public wxWindow {
public: public:
@ -50,6 +52,45 @@ class FreqPlot:public wxWindow {
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };
class SpectrumAnalyst
{
public:
enum Algorithm {
Spectrum,
Autocorrelation,
CubeRootAutocorrelation,
EnhancedAutocorrelation,
Cepstrum,
NumAlgorithms
};
SpectrumAnalyst();
~SpectrumAnalyst();
// Return true iff successful
bool Calculate(Algorithm alg,
int windowFunc, // see FFT.h for values
int windowSize, double rate,
const float *data, int dataLen,
float *pYMin = 0, float *pYMax = 0, // outputs
ProgressDialog *progress = 0);
const float *GetProcessed() const { return &mProcessed[0]; }
int GetProcessedSize() const { return mProcessed.size() / 2; }
float GetProcessedValue(float freq0, float freq1) const;
float FindPeak(float xPos, float *pY) const;
private:
Algorithm mAlg;
double mRate;
int mWindowSize;
std::vector<float> mProcessed;
};
class FreqWindow:public wxDialog { class FreqWindow:public wxDialog {
public: public:
FreqWindow(wxWindow * parent, wxWindowID id, FreqWindow(wxWindow * parent, wxWindowID id,
@ -81,7 +122,7 @@ class FreqWindow:public wxDialog {
float *mBuffer; float *mBuffer;
bool mDrawGrid; bool mDrawGrid;
int mSize; int mSize;
int mAlg; SpectrumAnalyst::Algorithm mAlg;
int mFunc; int mFunc;
int mAxis; int mAxis;
int dBRange; int dBRange;
@ -126,8 +167,6 @@ class FreqWindow:public wxDialog {
int mDataLen; int mDataLen;
float *mData; float *mData;
int mWindowSize; int mWindowSize;
float *mProcessed;
int mProcessedSize;
bool mLogAxis; bool mLogAxis;
float mYMin; float mYMin;
@ -139,7 +178,7 @@ class FreqWindow:public wxDialog {
int mMouseX; int mMouseX;
int mMouseY; int mMouseY;
float GetProcessedValue(float freq0, float freq1); std::auto_ptr<SpectrumAnalyst> mAnalyst;
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };

@ -48,6 +48,9 @@ simplifies construction of menu items.
#include <wx/statusbr.h> #include <wx/statusbr.h>
#include <wx/utils.h> #include <wx/utils.h>
#include "FreqWindow.h"
#include "TrackPanel.h"
#include "Project.h" #include "Project.h"
#include "effects/EffectManager.h" #include "effects/EffectManager.h"

@ -92,6 +92,7 @@ scroll information. It also has some status flags.
#include "Project.h" #include "Project.h"
#include "FreqWindow.h"
#include "AutoRecovery.h" #include "AutoRecovery.h"
#include "AudacityApp.h" #include "AudacityApp.h"
#include "AColor.h" #include "AColor.h"

@ -23,13 +23,12 @@
#include "DirManager.h" #include "DirManager.h"
#include "UndoManager.h" #include "UndoManager.h"
#include "ViewInfo.h" #include "ViewInfo.h"
#include "TrackPanel.h" #include "TrackPanelListener.h"
#include "AudioIO.h" #include "AudioIO.h"
#include "commands/CommandManager.h" #include "commands/CommandManager.h"
#include "effects/EffectManager.h" #include "effects/EffectManager.h"
#include "xml/XMLTagHandler.h" #include "xml/XMLTagHandler.h"
#include "toolbars/SelectionBar.h" #include "toolbars/SelectionBarListener.h"
#include "FreqWindow.h"
#include <wx/defs.h> #include <wx/defs.h>
#include <wx/event.h> #include <wx/event.h>
@ -56,13 +55,16 @@ class RecordingRecoveryHandler;
class TrackList; class TrackList;
class Tags; class Tags;
class TrackPanel;
class FreqWindow;
// toolbar classes // toolbar classes
class ControlToolBar; class ControlToolBar;
class DeviceToolBar; class DeviceToolBar;
class EditToolBar; class EditToolBar;
class MeterToolBar; class MeterToolBar;
class MixerToolBar; class MixerToolBar;
class SelectionToolBar; class SelectionBar;
class Toolbar; class Toolbar;
class ToolManager; class ToolManager;
class ToolsToolBar; class ToolsToolBar;
@ -365,7 +367,7 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame,
LyricsWindow* GetLyricsWindow() { return mLyricsWindow; }; LyricsWindow* GetLyricsWindow() { return mLyricsWindow; };
MixerBoard* GetMixerBoard() { return mMixerBoard; }; MixerBoard* GetMixerBoard() { return mMixerBoard; };
// SelectionBar callback methods // SelectionBarListener callback methods
virtual double AS_GetRate(); virtual double AS_GetRate();
virtual void AS_SetRate(double rate); virtual void AS_SetRate(double rate);

@ -6,26 +6,52 @@
Dominic Mazzoni Dominic Mazzoni
*******************************************************************//**
\class SelectedRegion
\brief Defines a selected portion of a project
This includes starting and ending times, and other optional information
such as a frequency range, but not the set of selected tracks.
Maintains the invariants that ending time is not less than starting time
and that starting and ending frequencies, when both defined, are also
correctly ordered.
*//****************************************************************//**
**********************************************************************/ **********************************************************************/
#ifndef __AUDACITY_SELECTEDREGION__ #ifndef __AUDACITY_SELECTEDREGION__
#define __AUDACITY_SELECTEDREGION__ #define __AUDACITY_SELECTEDREGION__
#include "Audacity.h" #include "Audacity.h"
#include "Experimental.h"
class AUDACITY_DLL_API SelectedRegion { class AUDACITY_DLL_API SelectedRegion {
// Maintains the invariant: t1() >= t0() // Maintains the invariant: t1() >= t0()
public: public:
static const int UndefinedFrequency = -1;
SelectedRegion() SelectedRegion()
: mT0(0.0) : mT0(0.0)
, mT1(0.0) , mT1(0.0)
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
, mF0(UndefinedFrequency)
, mF1(UndefinedFrequency)
#endif
{} {}
SelectedRegion(double t0, double t1) SelectedRegion(double t0, double t1)
: mT0(t0) : mT0(t0)
, mT1(t1) , mT1(t1)
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
, mF0(UndefinedFrequency)
, mF1(UndefinedFrequency)
#endif
{ ensureOrdering(); } { ensureOrdering(); }
@ -37,6 +63,10 @@ public:
SelectedRegion(const SelectedRegion &x) SelectedRegion(const SelectedRegion &x)
: mT0(x.mT0) : mT0(x.mT0)
, mT1(x.mT1) , mT1(x.mT1)
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
, mF0(x.mF0)
, mF1(x.mF1)
#endif
{} {}
SelectedRegion& operator=(const SelectedRegion& x) SelectedRegion& operator=(const SelectedRegion& x)
@ -44,15 +74,34 @@ public:
if (this != &x) { if (this != &x) {
mT0 = x.mT0; mT0 = x.mT0;
mT1 = x.mT1; mT1 = x.mT1;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
mF0 = x.mF0;
mF1 = x.mF1;
#endif
} }
return *this; return *this;
} }
// Accessors
double t0() const { return mT0; } double t0() const { return mT0; }
double t1() const { return mT1; } double t1() const { return mT1; }
double duration() const { return mT1 - mT0; } double duration() const { return mT1 - mT0; }
bool isPoint() const { return mT1 <= mT0; } bool isPoint() const { return mT1 <= mT0; }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
double f0() const { return mF0; }
double f1() const { return mF1; }
double fc() const {
if (mF0 == UndefinedFrequency ||
mF1 == UndefinedFrequency)
return UndefinedFrequency;
else
return sqrt(mF0 * mF1);
};
#endif
// Mutators
// PRL: to do: more integrity checks // PRL: to do: more integrity checks
// Returns true iff the bounds got swapped // Returns true iff the bounds got swapped
@ -105,6 +154,28 @@ public:
void collapseToT1() { mT0 = mT1; } void collapseToT1() { mT0 = mT1; }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Returns true iff the bounds got swapped
bool setF0(double f) {
mF0 = f;
return ensureFrequencyOrdering();
}
// Returns true iff the bounds got swapped
bool setF1(double f) {
mF1 = f;
return ensureFrequencyOrdering();
}
// Returns true iff the bounds got swapped
bool setFrequencies(double f0, double f1)
{
mF0 = f0;
mF1 = f1;
return ensureFrequencyOrdering();
}
#endif
private: private:
bool ensureOrdering() bool ensureOrdering()
{ {
@ -118,8 +189,33 @@ private:
return false; return false;
} }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
bool ensureFrequencyOrdering()
{
if (mF1 < 0)
mF1 = UndefinedFrequency;
if (mF0 < 0)
mF0 = UndefinedFrequency;
if (mF0 != UndefinedFrequency &&
mF1 != UndefinedFrequency &&
mF1 < mF0) {
const double t = mF1;
mF1 = mF0;
mF0 = t;
return true;
}
else
return false;
}
#endif
double mT0; double mT0;
double mT1; double mT1;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
double mF0; // low frequency
double mF1; // high frequency
#endif
}; };

@ -1780,6 +1780,8 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
bool autocorrelation, bool autocorrelation,
bool logF) bool logF)
{ {
enum { MONOCHROME_LINE = 230, COLORED_LINE = 0 };
#if PROFILE_WAVEFORM #if PROFILE_WAVEFORM
# ifdef __WXMSW__ # ifdef __WXMSW__
__time64_t tv0, tv1; __time64_t tv0, tv1;
@ -1794,6 +1796,13 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
double sel0 = viewInfo->selectedRegion.t0(); double sel0 = viewInfo->selectedRegion.t0();
double sel1 = viewInfo->selectedRegion.t1(); double sel1 = viewInfo->selectedRegion.t1();
double freqLo = SelectedRegion::UndefinedFrequency;
double freqHi = SelectedRegion::UndefinedFrequency;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
freqLo = viewInfo->selectedRegion.f0();
freqHi = viewInfo->selectedRegion.f1();
#endif
double tOffset = clip->GetOffset(); double tOffset = clip->GetOffset();
double rate = clip->GetRate(); double rate = clip->GetRate();
double sps = 1./rate; double sps = 1./rate;
@ -1922,7 +1931,7 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
minFreq = GetSpectrumLogMinFreq(ifreq/1000.0); minFreq = GetSpectrumLogMinFreq(ifreq/1000.0);
if(minFreq < 1) if(minFreq < 1)
// Paul L: I suspect this line is now unreachable // Paul L: I suspect this line is now unreachable
minFreq = ifreq/1000.0; minFreq = 1.0;
} }
bool usePxCache = false; bool usePxCache = false;
@ -1953,9 +1962,15 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
#endif #endif
} }
// PRL: Must the following two be integers?
int minSamples = int ((double)minFreq * (double)windowSize / rate + 0.5); // units are fft bins int minSamples = int ((double)minFreq * (double)windowSize / rate + 0.5); // units are fft bins
int maxSamples = int ((double)maxFreq * (double)windowSize / rate + 0.5); int maxSamples = int ((double)maxFreq * (double)windowSize / rate + 0.5);
float binPerPx = float(maxSamples - minSamples) / float(mid.height); float binPerPx = float(maxSamples - minSamples) / float(mid.height);
float selBinLo = freqLo * (double)windowSize / rate;
float selBinHi = freqHi * (double)windowSize / rate;
float selBinCenter =
((freqLo < 0 || freqHi < 0) ? -1 : sqrt(freqLo * freqHi))
* (double)windowSize / rate;
int x = 0; int x = 0;
sampleCount w1 = (sampleCount) ((t0*rate + x *rate *tstep) + .5); sampleCount w1 = (sampleCount) ((t0*rate + x *rate *tstep) + .5);
@ -2024,15 +2039,31 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
if (!logF) if (!logF)
{ {
for (int yy = 0; yy < mid.height; yy++) { for (int yy = 0; yy < mid.height; yy++) {
bool selflag = (ssel0 <= w0 && w1 < ssel1); float bin0 = float (yy) * binPerPx + minSamples;
float bin1 = float (yy + 1) * binPerPx + minSamples;
bool centerLine = false;
AColor::ColorGradientChoice selected =
AColor::ColorGradientUnselected;
if (ssel0 <= w0 && w1 < ssel1)
{
if (selBinCenter >= 0 &&
bin0 <= selBinCenter &&
selBinCenter < bin1)
centerLine = true;
else if((selBinLo < 0 || selBinLo < bin1) &&
(selBinHi < 0 || selBinHi > bin0))
selected =
AColor::ColorGradientTimeAndFrequencySelected;
else
selected =
AColor::ColorGradientTimeSelected;
}
unsigned char rv, gv, bv; unsigned char rv, gv, bv;
float value; float value;
if(!usePxCache) { if(!usePxCache) {
float bin0 = float (yy) * binPerPx + minSamples;
float bin1 = float (yy + 1) * binPerPx + minSamples;
if (int (bin1) == int (bin0)) if (int (bin1) == int (bin0))
value = freq[half * x + int (bin0)]; value = freq[half * x + int (bin0)];
else { else {
@ -2069,7 +2100,11 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
else else
value = clip->mSpecPxCache->values[x * mid.height + yy]; value = clip->mSpecPxCache->values[x * mid.height + yy];
GetColorGradient(value, selflag, mIsGrayscale, &rv, &gv, &bv); if(centerLine)
// Draw center frequency line
rv = gv = bv = (mIsGrayscale ? MONOCHROME_LINE : COLORED_LINE);
else
GetColorGradient(value, selected, mIsGrayscale, &rv, &gv, &bv);
int px = ((mid.height - 1 - yy) * mid.width + x) * 3; int px = ((mid.height - 1 - yy) * mid.width + x) * 3;
data[px++] = rv; data[px++] = rv;
@ -2079,7 +2114,6 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
} }
else //logF else //logF
{ {
bool selflag = (ssel0 <= w0 && w1 < ssel1);
unsigned char rv, gv, bv; unsigned char rv, gv, bv;
float value; float value;
int x0=x*half; int x0=x*half;
@ -2151,7 +2185,6 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
float yy2 = yy2_base; float yy2 = yy2_base;
double exp_scale_per_height = exp(scale/mid.height); double exp_scale_per_height = exp(scale/mid.height);
for (int yy = 0; yy < mid.height; yy++) { for (int yy = 0; yy < mid.height; yy++) {
if(!usePxCache) {
if (int(yy2)>=half) if (int(yy2)>=half)
yy2=half-1; yy2=half-1;
if (yy2<0) if (yy2<0)
@ -2165,6 +2198,26 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
yy3=0; yy3=0;
float bin1 = float(yy3); float bin1 = float(yy3);
bool centerLine = false;
AColor::ColorGradientChoice selected =
AColor::ColorGradientUnselected;
if (ssel0 <= w0 && w1 < ssel1)
{
if (selBinCenter >= 0 &&
bin0 <= selBinCenter &&
selBinCenter < bin1)
centerLine = true;
else if((selBinLo < 0 || selBinLo < bin1) &&
(selBinHi < 0 || selBinHi > bin0))
selected =
AColor::ColorGradientTimeAndFrequencySelected;
else
selected =
AColor::ColorGradientTimeSelected;
}
if(!usePxCache) {
#ifdef EXPERIMENTAL_FIND_NOTES #ifdef EXPERIMENTAL_FIND_NOTES
if (mFftFindNotes) { if (mFftFindNotes) {
if (it < maximas) { if (it < maximas) {
@ -2204,12 +2257,16 @@ void TrackArtist::DrawClipSpectrum(WaveTrack *track,
if (value < 0.0) if (value < 0.0)
value = float(0.0); value = float(0.0);
clip->mSpecPxCache->values[x * mid.height + yy] = value; clip->mSpecPxCache->values[x * mid.height + yy] = value;
yy2 = yy2_base;
} }
else else
value = clip->mSpecPxCache->values[x * mid.height + yy]; value = clip->mSpecPxCache->values[x * mid.height + yy];
yy2 = yy2_base;
GetColorGradient(value, selflag, mIsGrayscale, &rv, &gv, &bv); if(centerLine)
// Draw center frequency line
rv = gv = bv = (mIsGrayscale ? MONOCHROME_LINE : COLORED_LINE);
else
GetColorGradient(value, selected, mIsGrayscale, &rv, &gv, &bv);
#ifdef EXPERIMENTAL_FFT_Y_GRID #ifdef EXPERIMENTAL_FFT_Y_GRID
if (mFftYGrid && yGrid[yy]) { if (mFftYGrid && yGrid[yy]) {

@ -184,6 +184,8 @@ is time to refresh some aspect of the screen.
#include <wx/intl.h> #include <wx/intl.h>
#include <wx/image.h> #include <wx/image.h>
#include "FreqWindow.h" // for SpectrumAnalyst
#include "AColor.h" #include "AColor.h"
#include "AllThemeResources.h" #include "AllThemeResources.h"
#include "AudacityApp.h" #include "AudacityApp.h"
@ -459,6 +461,7 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
mTrackArtist(NULL), mTrackArtist(NULL),
mBacking(NULL), mBacking(NULL),
mRefreshBacking(false), mRefreshBacking(false),
mConverter(),
mAutoScrolling(false), mAutoScrolling(false),
mVertScrollRemainder(0), mVertScrollRemainder(0),
vrulerSize(36,0) vrulerSize(36,0)
@ -561,6 +564,11 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
wxCommandEventHandler(TrackPanel::OnTrackListUpdated), wxCommandEventHandler(TrackPanel::OnTrackListUpdated),
NULL, NULL,
this); this);
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
mFreqSelMode = FREQ_SEL_INVALID;
mFrequencySnapper.reset(new SpectrumAnalyst());
#endif
} }
TrackPanel::~TrackPanel() TrackPanel::~TrackPanel()
@ -1509,6 +1517,7 @@ bool TrackPanel::SetCursorByActivity( )
#endif #endif
case IsOverCutLine: case IsOverCutLine:
SetCursor( unsafe ? *mDisabledCursor : *mArrowCursor); SetCursor( unsafe ? *mDisabledCursor : *mArrowCursor);
// what, no return true here? -- PRL
default: default:
break; break;
} }
@ -1595,6 +1604,11 @@ void TrackPanel::SetCursorAndTipWhenInLabelTrack( LabelTrack * pLT,
} }
} }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Seems 4 is too small to work at the top. Why?
enum { FREQ_SNAP_DISTANCE = 10 };
#endif
// The select tool can have different cursors and prompts depending on what // The select tool can have different cursors and prompts depending on what
// we hover over, most notably when hovering over the selction boundaries. // we hover over, most notably when hovering over the selction boundaries.
// Determine and set the cursor and tip accordingly. // Determine and set the cursor and tip accordingly.
@ -1678,6 +1692,97 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t,
*ppTip = _("Click and drag to move right selection boundary."); *ppTip = _("Click and drag to move right selection boundary.");
SetCursor(*mAdjustRightSelectionCursor); SetCursor(*mAdjustRightSelectionCursor);
} }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
else if (mAdjustSelectionEdges &&
!mViewInfo->selectedRegion.isPoint() &&
t->GetKind() == Track::Wave)
{
const WaveTrack *wt = static_cast<WaveTrack*>(t);
const int display = wt->GetDisplay();
const bool logF = display == WaveTrack::SpectrumLogDisplay;
if(logF || display == WaveTrack::SpectrumDisplay) {
double centerFrequency = mViewInfo->selectedRegion.fc();
if (event.AltDown()) {
if (event.ShiftDown()) {
// Even if top or bottom is not defined, it un-snaps
*ppTip = _("Click and drag to adjust boundaries of selected frequency band.");
return; // default cursor
}
else if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) {
*ppTip = _("Move to snap center to peak frequencies, then click and drag to adjust band width.");
return; // default cursor
}
else if (centerFrequency >= 0) {
// PRL: Todo: fix message for Mac, say "Command" ?
*ppTip =
_("Click and drag to adjust band width about a fixed center, or hit Control to move the center.");
return; // default cursor
}
}
else {
// Is the cursor over the geometric mean frequency?
bool adjustCenter = false;
if (centerFrequency >= 0) {
const wxInt64 centerSel =
FrequencyToPosition(centerFrequency, r.y, r.height,
wt->GetRate(), logF);
adjustCenter =
within(event.m_y, centerSel, SELECTION_RESIZE_REGION);
}
if (adjustCenter) {
*ppTip =
_("Click and drag to adjust center selection frequency.");
SetCursor(*mEnvelopeCursor); // For want of a better...
return;
}
// Is the cursor over the bottom selection boundary?
bool adjust_bottom = false;
if (mViewInfo->selectedRegion.f0() < 0)
// Should we un-snap the bottom from "all"?
adjust_bottom =
within(event.m_y, r.y + r.height, FREQ_SNAP_DISTANCE);
else {
const wxInt64 bottomSel =
FrequencyToPosition(mViewInfo->selectedRegion.f0(),
r.y, r.height,
wt->GetRate(), logF);
adjust_bottom =
within(event.m_y, bottomSel, SELECTION_RESIZE_REGION);
}
if (adjust_bottom) {
*ppTip =
_("Click and drag to adjust bottom selection frequency.");
SetCursor(*mEnvelopeCursor); // For want of a better...
return;
}
// Is the cursor over the top selection boundary?
bool adjust_top = false;
if (mViewInfo->selectedRegion.f1() < 0)
// Should we un-snap the top from "all"?
adjust_top =
within(event.m_y, r.y, FREQ_SNAP_DISTANCE);
else {
const wxInt64 topSel =
FrequencyToPosition(mViewInfo->selectedRegion.f1(),
r.y, r.height,
wt->GetRate(), logF);
adjust_top =
within(event.m_y, topSel, SELECTION_RESIZE_REGION);
}
if (adjust_top) {
*ppTip =
_("Click and drag to adjust top selection frequency.");
SetCursor(*mEnvelopeCursor); // For want of a better...
return;
}
}
}
}
#endif
#ifdef USE_MIDI #ifdef USE_MIDI
else if (HitTestStretch(t, r, event)) { else if (HitTestStretch(t, r, event)) {
*ppTip = _("Click and drag to stretch within selected region."); *ppTip = _("Click and drag to stretch within selected region.");
@ -1856,6 +1961,11 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
//Send the new selection state to the undo/redo stack: //Send the new selection state to the undo/redo stack:
MakeParentModifyState(false); MakeParentModifyState(false);
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// This stops center snapping with mouse movement
mFreqSelMode = FREQ_SEL_INVALID;
#endif
} else if (event.LeftDClick() && !event.ShiftDown()) { } else if (event.LeftDClick() && !event.ShiftDown()) {
if (!mCapturedTrack) { if (!mCapturedTrack) {
wxRect r; wxRect r;
@ -1896,6 +2006,21 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
SetCapturedTrack( NULL ); SetCapturedTrack( NULL );
MakeParentModifyState(false); MakeParentModifyState(false);
} }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
else if (!event.IsButton() && event.AltDown()) {
// Enter center-snapping mode,
// or readjust if there already
if ((mFreqSelMode == FREQ_SEL_SNAPPING_CENTER
// Already in this selection mode
||
mFreqSelCenter < 0
// No defined center
)
&&
!mViewInfo->selectedRegion.isPoint())
MoveSnappingFreqSelection(event.m_y, r.y, r.height, t);
}
#endif
done: done:
SelectionHandleDrag(event, t); SelectionHandleDrag(event, t);
@ -1940,6 +2065,63 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
bool stretch = HitTestStretch(pTrack, r, event); bool stretch = HitTestStretch(pTrack, r, event);
#endif #endif
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
if (event.AltDown() && pTrack &&
pTrack->GetKind() == Track::Wave) {
const WaveTrack* wt = static_cast<WaveTrack*>(pTrack);
const int display = wt->GetDisplay();
const bool logF = display == WaveTrack::SpectrumLogDisplay;
if (logF || display == WaveTrack::SpectrumDisplay) {
mFreqSelTrack = wt;
const double rate = wt->GetRate();
// If the Alt button is down, modify the current frequency selection.
// If not shift-alt, and center is defined, drag the width;
// otherwise edit the selection boundary nearest the mouse click.
bool dragWidth = false;
mFreqSelCenter = mViewInfo->selectedRegion.fc();
if (!event.ShiftDown() && mFreqSelCenter >= 0)
dragWidth = true;
double selend =
PositionToFrequency(false, event.m_y, r.y, r.height, rate, logF);
double high =
mViewInfo->selectedRegion.f1() < 0
? rate / 2 : mViewInfo->selectedRegion.f1();
double low =
mViewInfo->selectedRegion.f0() < 0
? 0 : mViewInfo->selectedRegion.f0();
if (logF)
selend = log(std::max(1.0, selend)),
high = log(std::max(1.0, high)),
low = log(std::max(1.0, low));
const bool adjustLow = (fabs (selend - low) < fabs (selend - high));
if (adjustLow) {
mFreqSelMode = FREQ_SEL_BOTTOM_FREE;
mFreqSelStart = mViewInfo->selectedRegion.f1();
}
else {
mFreqSelMode = FREQ_SEL_TOP_FREE;
mFreqSelStart = mViewInfo->selectedRegion.f0();
}
// Do not adjust time boundaries
mSelStart = -1;
ExtendFreqSelection(event.m_y, r.y, r.height, dragWidth);
UpdateSelectionDisplay();
// Frequency selection doesn't persist (yet?), so skip this:
// MakeParentModifyState(false);
return;
}
}
else
#endif
if (event.ShiftDown() if (event.ShiftDown()
#ifdef USE_MIDI #ifdef USE_MIDI
&& !stretch && !stretch
@ -1978,8 +2160,14 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
else else
mSelStart = mViewInfo->selectedRegion.t0(); mSelStart = mViewInfo->selectedRegion.t0();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// If drag starts, change time selection only
mFreqSelMode = FREQ_SEL_INVALID;
#endif
// If the shift button is down, extend the current selection. // If the shift button is down, extend the current selection.
ExtendSelection(event.m_x, r.x, pTrack); ExtendSelection(event.m_x, r.x, pTrack);
UpdateSelectionDisplay();
MakeParentModifyState(false); MakeParentModifyState(false);
return; return;
} }
@ -2030,26 +2218,130 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
} }
//Make sure you are within the selected track //Make sure you are within the selected track
if (pTrack && pTrack->GetSelected()) { if (pTrack && pTrack->GetSelected()) {
// Adjusting selection edges can be turned off in the
// preferences now
if (mAdjustSelectionEdges) {
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Check for dragging of top, bottom, or center
if (!mViewInfo->selectedRegion.isPoint() &&
pTrack->GetKind() == Track::Wave) {
const WaveTrack *wt = static_cast<WaveTrack*>(pTrack);
const int display = wt->GetDisplay();
const bool logF = display == WaveTrack::SpectrumLogDisplay;
if (logF || display == WaveTrack::SpectrumDisplay) {
// Adjust frequency selection, no modifier keys
// Is the cursor over the geometric mean frequency?
bool adjustCenter = false;
mFreqSelCenter = mViewInfo->selectedRegion.fc();
if (mFreqSelCenter >= 0) {
const wxInt64 centerSel =
FrequencyToPosition(mFreqSelCenter, r.y, r.height,
wt->GetRate(), logF);
adjustCenter =
within(event.m_y, centerSel, SELECTION_RESIZE_REGION);
}
if (adjustCenter) {
// Keep width constant
// Disable extension of time selection, unless also near
// left or right boundary as tested later
mSelStart = -1;
mFreqSelMode = FREQ_SEL_DRAG_CENTER;
mFreqSelTrack = wt;
// High (and low) must be defined if center is
// Use this to remember the starting ratio:
mFreqSelStart = mViewInfo->selectedRegion.f1() / mFreqSelCenter;
startNewSelection = false;
}
if (startNewSelection) {
// Is the cursor over the bottom selection boundary?
bool adjust_bottom = false;
if (mViewInfo->selectedRegion.f0() < 0)
// Should we un-snap the bottom from "all"?
adjust_bottom =
within(event.m_y, r.y + r.height, FREQ_SNAP_DISTANCE);
else {
const wxInt64 bottomSel =
FrequencyToPosition(mViewInfo->selectedRegion.f0(),
r.y, r.height,
wt->GetRate(), logF);
adjust_bottom =
within(event.m_y, bottomSel, SELECTION_RESIZE_REGION);
}
if (adjust_bottom) {
// Pin top edge
// Disable extension of time selection, unless also near
// left or right boundary as tested later
mSelStart = -1;
mFreqSelMode = FREQ_SEL_BOTTOM_FREE;
mFreqSelTrack = wt;
mFreqSelStart = mViewInfo->selectedRegion.f1();
startNewSelection = false;
}
}
if (startNewSelection) {
// Is the cursor over the bottom selection boundary?
bool adjust_top = false;
if (mViewInfo->selectedRegion.f1() < 0)
// Should we un-snap the top from "all"?
adjust_top =
within(event.m_y, r.y, FREQ_SNAP_DISTANCE);
else {
const wxInt64 topSel =
FrequencyToPosition(mViewInfo->selectedRegion.f1(),
r.y, r.height,
wt->GetRate(), logF);
adjust_top =
within(event.m_y, topSel, SELECTION_RESIZE_REGION);
}
if (adjust_top) {
// Pin bottom edge
// Disable extension of time selection, unless also near
// left or right boundary as tested later
mSelStart = -1;
mFreqSelMode = FREQ_SEL_TOP_FREE;
mFreqSelTrack = wt;
mFreqSelStart = mViewInfo->selectedRegion.f0();
startNewSelection = false;
}
}
} // spectrum type
} // wave track
#endif
wxInt64 leftSel = TimeToPosition(mViewInfo->selectedRegion.t0(), r.x); wxInt64 leftSel = TimeToPosition(mViewInfo->selectedRegion.t0(), r.x);
wxInt64 rightSel = TimeToPosition(mViewInfo->selectedRegion.t1(), r.x); wxInt64 rightSel = TimeToPosition(mViewInfo->selectedRegion.t1(), r.x);
wxASSERT(leftSel <= rightSel); wxASSERT(leftSel <= rightSel);
// Adjusting selection edges can be turned off in the
// preferences now
if (!mAdjustSelectionEdges) {
}
// Is the cursor over the left selection boundary? // Is the cursor over the left selection boundary?
else if (within(event.m_x, leftSel, SELECTION_RESIZE_REGION)) { if (within(event.m_x, leftSel, SELECTION_RESIZE_REGION)) {
// Pin the right selection boundary // Pin the right selection boundary
mSelStart = mViewInfo->selectedRegion.t1(); mSelStart = mViewInfo->selectedRegion.t1();
if (startNewSelection) {
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Enable time extension only
mFreqSelMode = FREQ_SEL_INVALID;
#endif
startNewSelection = false; startNewSelection = false;
} }
}
// Is the cursor over the right selection boundary? // Is the cursor over the right selection boundary?
else if (within(event.m_x, rightSel, SELECTION_RESIZE_REGION)) { else if (within(event.m_x, rightSel, SELECTION_RESIZE_REGION)) {
// Pin the left selection boundary // Pin the left selection boundary
mSelStart = mViewInfo->selectedRegion.t0(); mSelStart = mViewInfo->selectedRegion.t0();
if (startNewSelection) {
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Enable time extension only
mFreqSelMode = FREQ_SEL_INVALID;
#endif
startNewSelection = false; startNewSelection = false;
} }
} }
}
}
//Determine if user clicked on a label track. //Determine if user clicked on a label track.
if (pTrack && (pTrack->GetKind() == Track::Label)) if (pTrack && (pTrack->GetKind() == Track::Label))
@ -2160,6 +2452,9 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
if (startNewSelection) { if (startNewSelection) {
// If we didn't move a selection boundary, start a new selection // If we didn't move a selection boundary, start a new selection
SelectNone(); SelectNone();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
StartFreqSelection (event.m_y, r.y, r.height, pTrack);
#endif
StartSelection(event.m_x, r.x); StartSelection(event.m_x, r.x);
mTracks->Select(pTrack); mTracks->Select(pTrack);
SetFocusedTrack(pTrack); SetFocusedTrack(pTrack);
@ -2203,6 +2498,10 @@ void TrackPanel::StartSelection(int mouseXCoordinate, int trackLeftEdge)
void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge, void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge,
Track *pTrack) Track *pTrack)
{ {
if (mSelStart < 0)
// Must be dragging frequency bounds only.
return;
double selend = PositionToTime(mouseXCoordinate, trackLeftEdge); double selend = PositionToTime(mouseXCoordinate, trackLeftEdge);
clip_bottom(selend, 0.0); clip_bottom(selend, 0.0);
@ -2255,7 +2554,10 @@ void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge,
//On-Demand: check to see if there is an OD thing associated with this track. If so we want to update the focal point for the task. //On-Demand: check to see if there is an OD thing associated with this track. If so we want to update the focal point for the task.
if (pTrack && (pTrack->GetKind() == Track::Wave) && ODManager::IsInstanceCreated()) if (pTrack && (pTrack->GetKind() == Track::Wave) && ODManager::IsInstanceCreated())
ODManager::Instance()->DemandTrackUpdate((WaveTrack*)pTrack,sel0); //sel0 is sometimes less than mSelStart ODManager::Instance()->DemandTrackUpdate((WaveTrack*)pTrack,sel0); //sel0 is sometimes less than mSelStart
}
void TrackPanel::UpdateSelectionDisplay()
{
// Full refresh since the label area may need to indicate // Full refresh since the label area may need to indicate
// newly selected tracks. // newly selected tracks.
Refresh(false); Refresh(false);
@ -2267,6 +2569,210 @@ void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge,
DisplaySelection(); DisplaySelection();
} }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
namespace {
inline double findMaxRatio(double center, double rate)
{
const double minFrequency = 1.0;
const double maxFrequency = (rate / 2.0);
const double frequency =
std::min(maxFrequency,
std::max(minFrequency, center));
return
std::min(frequency / minFrequency, maxFrequency / frequency);
}
}
void TrackPanel::StartSnappingFreqSelection (WaveTrack *pTrack)
{
static const sampleCount minLength = 8;
mFreqSelMode = FREQ_SEL_SNAPPING_CENTER;
const double rate = pTrack->GetRate();
// Grab samples, just for this track, at these times
std::vector<float> frequencySnappingData;
const sampleCount start =
pTrack->TimeToLongSamples(mViewInfo->selectedRegion.t0());
const sampleCount end =
pTrack->TimeToLongSamples(mViewInfo->selectedRegion.t1());
const sampleCount length =
std::min(sampleCount(frequencySnappingData.max_size()),
std::min(sampleCount(10485760), // as in FreqWindow.cpp
end - start));
const sampleCount effectiveLength = std::max(minLength, length);
frequencySnappingData.resize(effectiveLength, 0.0f);
pTrack->Get(
reinterpret_cast<samplePtr>(&frequencySnappingData[0]),
floatSample, start, length);
// Use same settings as are now used for spectrogram display,
// except, shrink the window as needed so we get some answers
int windowSize = mTrackArtist->GetSpectrumWindowSize();
while(windowSize > effectiveLength)
windowSize >>= 1;
int windowType;
gPrefs->Read(wxT("/Spectrum/WindowType"), &windowType, 3);
mFrequencySnapper->Calculate(
SpectrumAnalyst::Spectrum, windowType, windowSize, rate,
&frequencySnappingData[0], length);
// We can now throw away the sample data but we keep the spectrum.
}
void TrackPanel::MoveSnappingFreqSelection (int mouseYCoordinate,
int trackTopEdge,
int trackHeight, Track *pTrack)
{
if (pTrack &&
pTrack->GetSelected() &&
pTrack->GetKind() == Track::Wave)
{
WaveTrack* wt = static_cast<WaveTrack*>(pTrack);
const int display = wt->GetDisplay();
const bool logF = display == WaveTrack::SpectrumLogDisplay;
if (logF || display== WaveTrack::SpectrumDisplay)
{
const double rate = wt->GetRate();
if (mFreqSelMode != FREQ_SEL_SNAPPING_CENTER)
StartSnappingFreqSelection(wt);
const double frequency =
PositionToFrequency(false, mouseYCoordinate,
trackTopEdge, trackHeight, rate, logF);
const double snappedFrequency =
mFrequencySnapper->FindPeak(frequency, NULL);
const double maxRatio = findMaxRatio(snappedFrequency, rate);
double ratio = 2.0; // An arbitrary octave on each side, at most
if (mViewInfo->selectedRegion.f1() >= mViewInfo->selectedRegion.f0() &&
mViewInfo->selectedRegion.f0() >= 0)
// Preserve already chosen ratio instead
ratio = sqrt(mViewInfo->selectedRegion.f1() /
mViewInfo->selectedRegion.f0());
ratio = std::min(ratio, maxRatio);
// Set up what the alt-click code expects to find
mFreqSelCenter = snappedFrequency;
mViewInfo->selectedRegion.setFrequencies(
snappedFrequency / ratio, snappedFrequency * ratio);
mFreqSelTrack = wt;
// SelectNone();
// mTracks->Select(pTrack);
SetFocusedTrack(pTrack);
}
}
}
void TrackPanel::StartFreqSelection (int mouseYCoordinate, int trackTopEdge,
int trackHeight, Track *pTrack)
{
mFreqSelTrack = 0;
mFreqSelMode = FREQ_SEL_INVALID;
mFreqSelStart = mFreqSelCenter = SelectedRegion::UndefinedFrequency;
if (pTrack &&
pTrack->GetKind() == Track::Wave)
{
const WaveTrack* wt = static_cast<WaveTrack*>(pTrack);
const int display = wt->GetDisplay();
const bool logF = display == WaveTrack::SpectrumLogDisplay;
if (logF || display == WaveTrack::SpectrumDisplay)
{
mFreqSelTrack = wt;
mFreqSelMode = FREQ_SEL_FREE;
const double rate = wt->GetRate();
mFreqSelStart =
PositionToFrequency(false, mouseYCoordinate,
trackTopEdge, trackHeight, rate, logF);
}
}
mViewInfo->selectedRegion.setFrequencies(mFreqSelStart, mFreqSelStart);
}
void TrackPanel::ExtendFreqSelection(int mouseYCoordinate, int trackTopEdge,
int trackHeight, bool dragWidth)
{
// When dragWidth is true, and not dragging the center,
// adjust both top and bottom about geometric mean.
if (mFreqSelMode == FREQ_SEL_INVALID ||
mFreqSelMode == FREQ_SEL_SNAPPING_CENTER)
return;
// Extension happens only when dragging in the same track in which we
// started, and that is of a spectrogram display type.
const WaveTrack* wt = mFreqSelTrack;
const int display = wt->GetDisplay();
const bool logF = display == WaveTrack::SpectrumLogDisplay;
const double rate = wt->GetRate();
const double frequency =
PositionToFrequency(true, mouseYCoordinate,
trackTopEdge, trackHeight, rate, logF);
if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) {
if (frequency == rate || frequency < 1.0)
// snapped to top or bottom
mViewInfo->selectedRegion.setFrequencies(
SelectedRegion::UndefinedFrequency,
SelectedRegion::UndefinedFrequency);
else {
// mFreqSelStart holds the ratio of top to center
const double maxRatio = findMaxRatio(frequency, rate);
const double ratio = std::min(maxRatio, mFreqSelStart);
mViewInfo->selectedRegion.setFrequencies(
frequency / ratio, frequency * ratio);
}
}
else if (dragWidth && mFreqSelCenter >= 0) {
if (frequency == rate || frequency < 1.0)
// snapped to top or bottom
mViewInfo->selectedRegion.setFrequencies(
SelectedRegion::UndefinedFrequency,
SelectedRegion::UndefinedFrequency);
else {
const double maxRatio = findMaxRatio(mFreqSelCenter, rate);
double ratio = frequency / mFreqSelCenter;
if (ratio < 1.0)
ratio = 1.0 / ratio;
ratio = std::min(maxRatio, ratio);
mViewInfo->selectedRegion.setFrequencies(
mFreqSelCenter / ratio, mFreqSelCenter * ratio);
}
}
else {
const bool bottomDefined =
!(mFreqSelMode == FREQ_SEL_TOP_FREE && mFreqSelStart < 0);
const bool topDefined =
!(mFreqSelMode == FREQ_SEL_BOTTOM_FREE && mFreqSelStart < 0);
if (!bottomDefined || (topDefined && mFreqSelStart < frequency)) {
// Adjust top
if (frequency == rate)
// snapped high; upper frequency is undefined
mViewInfo->selectedRegion.setF1(SelectedRegion::UndefinedFrequency);
else
mViewInfo->selectedRegion.setF1(std::max(1.0, frequency));
mViewInfo->selectedRegion.setF0(mFreqSelStart);
}
else {
// Adjust bottom
if (frequency < 1.0)
// snapped low; lower frequency is undefined
mViewInfo->selectedRegion.setF0(SelectedRegion::UndefinedFrequency);
else
mViewInfo->selectedRegion.setF0(std::min(rate / 2.0, frequency));
mViewInfo->selectedRegion.setF1(mFreqSelStart);
}
}
}
#endif
#ifdef USE_MIDI #ifdef USE_MIDI
void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge, void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge,
Track *pTrack) Track *pTrack)
@ -2404,6 +2910,9 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
// Can someone make this value of '5' configurable in // Can someone make this value of '5' configurable in
// preferences? // preferences?
const int minimumSizedSelection = 5; //measured in pixels const int minimumSizedSelection = 5; //measured in pixels
// Might be dragging frequency bounds only, test
if (mSelStart >= 0) {
wxInt64 SelStart=TimeToPosition( mSelStart, r.x); //cvt time to pixels. wxInt64 SelStart=TimeToPosition( mSelStart, r.x); //cvt time to pixels.
// Abandon this drag if selecting < 5 pixels. // Abandon this drag if selecting < 5 pixels.
if (wxLongLong(SelStart-x).Abs() < minimumSizedSelection if (wxLongLong(SelStart-x).Abs() < minimumSizedSelection
@ -2412,26 +2921,28 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
#endif // once stretching starts, it's ok to move even 1 pixel #endif // once stretching starts, it's ok to move even 1 pixel
) )
return; return;
}
// Handle which tracks are selected // Handle which tracks are selected
Track *sTrack = pTrack;
if (Track *eTrack = FindTrack(x, y, false, false, NULL)) { if (Track *eTrack = FindTrack(x, y, false, false, NULL)) {
// Swap the track pointers if needed // Swap the track pointers if needed
if (eTrack->GetIndex() < pTrack->GetIndex()) { if (eTrack->GetIndex() < sTrack->GetIndex()) {
Track *t = eTrack; Track *t = eTrack;
eTrack = pTrack; eTrack = sTrack;
pTrack = t; sTrack = t;
} }
TrackListIterator iter(mTracks); TrackListIterator iter(mTracks);
pTrack = iter.StartWith(pTrack); sTrack = iter.StartWith(sTrack);
do { do {
mTracks->Select(pTrack); mTracks->Select(sTrack);
if (pTrack == eTrack) { if (sTrack == eTrack) {
break; break;
} }
pTrack = iter.Next(); sTrack = iter.Next();
} while (pTrack); } while (sTrack);
} }
#ifdef USE_MIDI #ifdef USE_MIDI
if (mStretching) { if (mStretching) {
@ -2443,7 +2954,15 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
return; return;
} }
#endif #endif
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
if (mFreqSelTrack == pTrack)
ExtendFreqSelection(y, r.y, r.height,
(event.AltDown() && !event.ShiftDown()));
#endif
ExtendSelection(x, r.x, clickedTrack); ExtendSelection(x, r.x, clickedTrack);
UpdateSelectionDisplay();
} }
/// Converts a position (mouse X coordinate) to /// Converts a position (mouse X coordinate) to
@ -2466,6 +2985,84 @@ wxInt64 TrackPanel::TimeToPosition(double projectTime,
trackLeftEdge); trackLeftEdge);
} }
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
/// Converts a position (mouse Y coordinate) to
/// frequency, in Hz.
double TrackPanel::PositionToFrequency(bool maySnap,
wxInt64 mouseYCoordinate,
wxInt64 trackTopEdge,
int trackHeight,
double rate,
bool logF) const
{
// Handle snapping
if (maySnap &&
mouseYCoordinate - trackTopEdge < FREQ_SNAP_DISTANCE)
return rate;
if (maySnap &&
trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
return -1;
const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
const int freq = lrint(rate/2.);
if (logF)
{
const double maxFreq =
std::min(freq, mTrackArtist->GetSpectrumLogMaxFreq(freq));
const double minFreq =
std::max(1, mTrackArtist->GetSpectrumLogMinFreq(1));
return exp(p * log(minFreq) + (1.0 - p) * log(maxFreq));
}
else
{
const double maxFreq =
std::min(freq, mTrackArtist->GetSpectrumMaxFreq(freq));
const double minFreq =
std::max(0, mTrackArtist->GetSpectrumMinFreq(0));
return p * minFreq + (1.0 - p) * maxFreq;
}
}
/// Converts a frequency to screen y position.
wxInt64 TrackPanel::FrequencyToPosition(double frequency,
wxInt64 trackTopEdge,
int trackHeight,
double rate,
bool logF) const
{
const int freq = lrint(rate/2.);
double p = 0;
if (logF)
{
const double maxFreq =
std::min(freq, mTrackArtist->GetSpectrumLogMaxFreq(freq));
const double minFreq =
std::max(1, mTrackArtist->GetSpectrumLogMinFreq(1));
if (maxFreq > minFreq)
{
const double
logFrequency = log(frequency < 1.0 ? 1.0 : frequency),
logMinFreq = log(minFreq),
logMaxFreq = log(maxFreq);
p = (logFrequency - logMinFreq) / (logMaxFreq - logMinFreq);
}
}
else
{
const double maxFreq =
std::min(freq, mTrackArtist->GetSpectrumMaxFreq(freq));
const double minFreq =
std::max(0, mTrackArtist->GetSpectrumMinFreq(0));
if (maxFreq > minFreq)
p = (frequency - minFreq) / (maxFreq - minFreq);
}
return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
}
#endif
/// HandleEnvelope gets called when the user is changing the /// HandleEnvelope gets called when the user is changing the
/// amplitude envelope on a track. /// amplitude envelope on a track.
void TrackPanel::HandleEnvelope(wxMouseEvent & event) void TrackPanel::HandleEnvelope(wxMouseEvent & event)
@ -5025,8 +5622,21 @@ void TrackPanel::OnCaptureKey(wxCommandEvent & event)
/// Allow typing into LabelTracks. /// Allow typing into LabelTracks.
void TrackPanel::OnKeyDown(wxKeyEvent & event) void TrackPanel::OnKeyDown(wxKeyEvent & event)
{ {
// Only deal with LabelTracks
Track *t = GetFocusedTrack(); Track *t = GetFocusedTrack();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Test very special conditions for freeing up the frequency selection
// for renewed center snapping
if (t &&
t->GetKind() == Track::Wave &&
event.GetKeyCode() == WXK_CONTROL &&
event.AltDown()) {
StartSnappingFreqSelection(static_cast<WaveTrack*>(t));
// And don't skip event, yet
}
#endif
// Only deal with LabelTracks
if (!t || t->GetKind() != Track::Label) { if (!t || t->GetKind() != Track::Label) {
event.Skip(); event.Skip();
return; return;

@ -11,12 +11,15 @@
#ifndef __AUDACITY_TRACK_PANEL__ #ifndef __AUDACITY_TRACK_PANEL__
#define __AUDACITY_TRACK_PANEL__ #define __AUDACITY_TRACK_PANEL__
#include <memory>
#include <wx/dcmemory.h> #include <wx/dcmemory.h>
#include <wx/dynarray.h> #include <wx/dynarray.h>
#include <wx/panel.h> #include <wx/panel.h>
#include <wx/timer.h> #include <wx/timer.h>
#include <wx/window.h> #include <wx/window.h>
#include "Experimental.h"
#include "Sequence.h" //Stm: included for the sampleCount declaration #include "Sequence.h" //Stm: included for the sampleCount declaration
#include "WaveClip.h" #include "WaveClip.h"
#include "WaveTrack.h" #include "WaveTrack.h"
@ -27,6 +30,7 @@ class wxMenu;
class wxRect; class wxRect;
class LabelTrack; class LabelTrack;
class SpectrumAnalyst;
class TrackPanel; class TrackPanel;
class TrackArtist; class TrackArtist;
class Ruler; class Ruler;
@ -54,31 +58,8 @@ class AUDACITY_DLL_API TrackClip
WX_DECLARE_OBJARRAY(TrackClip, TrackClipArray); WX_DECLARE_OBJARRAY(TrackClip, TrackClipArray);
class AUDACITY_DLL_API TrackPanelListener { // Declared elsewhere, to reduce compilation dependencies
class TrackPanelListener;
public:
TrackPanelListener(){};
virtual ~TrackPanelListener(){};
virtual void TP_DisplaySelection() = 0;
virtual void TP_DisplayStatusMessage(wxString msg) = 0;
virtual int TP_GetCurrentTool() = 0;
virtual ToolsToolBar * TP_GetToolsToolBar() = 0;
virtual ControlToolBar * TP_GetControlToolBar() = 0;
virtual void TP_OnPlayKey() = 0;
virtual void TP_PushState(wxString shortDesc, wxString longDesc,
int flags = PUSH_AUTOSAVE | PUSH_CALC_SPACE) = 0;
virtual void TP_ModifyState(bool bWantsAutoSave) = 0; // if true, writes auto-save file. Should set only if you really want the state change restored after
// a crash, as it can take many seconds for large (eg. 10 track-hours) projects
virtual void TP_RedrawScrollbars() = 0;
virtual void TP_ScrollLeft() = 0;
virtual void TP_ScrollRight() = 0;
virtual void TP_ScrollWindow(double scrollto) = 0;
virtual void TP_ScrollUpDown(int delta) = 0;
virtual void TP_HandleResize() = 0;
};
// //
// TrackInfo sliders: we keep a pool of sliders, and attach them to tracks as // TrackInfo sliders: we keep a pool of sliders, and attach them to tracks as
@ -334,6 +315,19 @@ class AUDACITY_DLL_API TrackPanel:public wxPanel {
virtual void StartSelection (int mouseXCoordinate, int trackLeftEdge); virtual void StartSelection (int mouseXCoordinate, int trackLeftEdge);
virtual void ExtendSelection(int mouseXCoordinate, int trackLeftEdge, virtual void ExtendSelection(int mouseXCoordinate, int trackLeftEdge,
Track *pTrack); Track *pTrack);
virtual void UpdateSelectionDisplay();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
virtual void StartSnappingFreqSelection (WaveTrack *pTrack);
virtual void MoveSnappingFreqSelection (int mouseYCoordinate,
int trackTopEdge,
int trackHeight, Track *pTrack);
virtual void StartFreqSelection (int mouseYCoordinate, int trackTopEdge,
int trackHeight, Track *pTrack);
virtual void ExtendFreqSelection(int mouseYCoordinate, int trackTopEdge,
int trackHeight, bool dragWidth);
#endif
virtual void SelectTracksByLabel( LabelTrack *t ); virtual void SelectTracksByLabel( LabelTrack *t );
virtual void SelectTrackLength(Track *t); virtual void SelectTrackLength(Track *t);
@ -563,6 +557,21 @@ protected:
double mSelStart; double mSelStart;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
enum {
FREQ_SEL_INVALID,
FREQ_SEL_SNAPPING_CENTER,
FREQ_SEL_TOP_FREE,
FREQ_SEL_BOTTOM_FREE,
FREQ_SEL_DRAG_CENTER,
FREQ_SEL_FREE
} mFreqSelMode;
double mFreqSelStart;
double mFreqSelCenter; // Used when dragging the width about fixed center
const WaveTrack *mFreqSelTrack;
std::auto_ptr<SpectrumAnalyst> mFrequencySnapper;
#endif
Track *mCapturedTrack; Track *mCapturedTrack;
Envelope *mCapturedEnvelope; Envelope *mCapturedEnvelope;
WaveClip *mCapturedClip; WaveClip *mCapturedClip;
@ -628,6 +637,20 @@ protected:
wxInt64 TimeToPosition(double time, wxInt64 TimeToPosition(double time,
wxInt64 trackLeftEdge) const; wxInt64 trackLeftEdge) const;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
double PositionToFrequency(bool maySnap,
wxInt64 mouseYCoordinate,
wxInt64 trackTopEdge,
int trackHeight,
double rate,
bool logF) const;
wxInt64 FrequencyToPosition(double frequency,
wxInt64 trackTopEdge,
int trackHeight,
double rate,
bool logF) const;
#endif
int mInitialTrackHeight; int mInitialTrackHeight;
int mInitialUpperTrackHeight; int mInitialUpperTrackHeight;
bool mAutoScrolling; bool mAutoScrolling;
@ -734,6 +757,8 @@ protected:
//This constant determines the size of the horizontal region (in pixels) around //This constant determines the size of the horizontal region (in pixels) around
//the right and left selection bounds that can be used for horizontal selection adjusting //the right and left selection bounds that can be used for horizontal selection adjusting
//(or, vertical distance around top and bottom bounds in spectrograms,
// for vertical selection adjusting)
#define SELECTION_RESIZE_REGION 3 #define SELECTION_RESIZE_REGION 3
#define SMOOTHING_KERNEL_RADIUS 3 #define SMOOTHING_KERNEL_RADIUS 3

@ -401,7 +401,7 @@ class AUDACITY_DLL_API WaveTrack: public Track {
mLastDisplay=mDisplay; // remember last display mode for wave and wavedb so they can remap mLastDisplay=mDisplay; // remember last display mode for wave and wavedb so they can remap
mDisplay = display; mDisplay = display;
} }
int GetDisplay() {return mDisplay;} int GetDisplay() const {return mDisplay;}
int GetLastDisplay() {return mLastDisplay;} int GetLastDisplay() {return mLastDisplay;}
void GetDisplayBounds(float *min, float *max); void GetDisplayBounds(float *min, float *max);

@ -17,6 +17,7 @@
*//*******************************************************************/ *//*******************************************************************/
#include "GetProjectInfoCommand.h" #include "GetProjectInfoCommand.h"
#include "../TrackPanel.h"
#include "../Project.h" #include "../Project.h"
#include "../Track.h" #include "../Track.h"
#include "../WaveTrack.h" #include "../WaveTrack.h"

@ -17,6 +17,7 @@
*//*******************************************************************/ *//*******************************************************************/
#include "GetTrackInfoCommand.h" #include "GetTrackInfoCommand.h"
#include "../TrackPanel.h"
#include "../Project.h" #include "../Project.h"
#include "../Track.h" #include "../Track.h"
#include "../WaveTrack.h" #include "../WaveTrack.h"

@ -26,6 +26,7 @@
#include <wx/valtext.h> #include <wx/valtext.h>
#include "../Audacity.h" #include "../Audacity.h"
#include "../LabelTrack.h"
#include "../Envelope.h" #include "../Envelope.h"
#include "../LabelTrack.h" #include "../LabelTrack.h"
#include "../Prefs.h" #include "../Prefs.h"

@ -104,6 +104,10 @@ bool Effect::DoEffect(wxWindow *parent, int flags,
mTracks = list; mTracks = list;
mT0 = t0; mT0 = t0;
mT1 = t1; mT1 = t1;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
mF0 = selectedRegion->f0();
mF1 = selectedRegion->f1();
#endif
CountWaveTracks(); CountWaveTracks();
// Note: Init may read parameters from preferences // Note: Init may read parameters from preferences

@ -21,6 +21,7 @@
class wxDialog; class wxDialog;
class wxWindow; class wxWindow;
#include "../Experimental.h"
#include "../WaveTrack.h" #include "../WaveTrack.h"
#include "../Shuttle.h" #include "../Shuttle.h"
#include "../ShuttleGui.h" #include "../ShuttleGui.h"
@ -215,6 +216,10 @@ class AUDACITY_DLL_API Effect {
TrackList *mOutputTracks; // used only if CopyInputTracks() is called. TrackList *mOutputTracks; // used only if CopyInputTracks() is called.
double mT0; double mT0;
double mT1; double mT1;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
double mF0;
double mF1;
#endif
TimeWarper *mWarper; TimeWarper *mWarper;
// //

@ -778,6 +778,24 @@ bool EffectNyquist::ProcessOne()
wxString cmd; wxString cmd;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
{
static const wxString varName(wxT("*F0*"));
if (mF0 < 0)
cmd += wxString::Format(wxT("(setf %s nil)\n"), varName);
else
cmd += wxString::Format(wxT("(setf %s (float %g))\n"), varName, mF0);
}
{
static const wxString varName(wxT("*F1*"));
if (mF1 < 0)
cmd += wxString::Format(wxT("(setf %s nil)\n"), varName);
else
cmd += wxString::Format(wxT("(setf %s (float %g))\n"), varName, mF1);
}
#endif
if (mDebug) { if (mDebug) {
cmd += wxT("(setf *tracenable* T)\n"); cmd += wxT("(setf *tracenable* T)\n");
if (mExternal) { if (mExternal) {

@ -18,6 +18,7 @@
#include <wx/combobox.h> #include <wx/combobox.h>
#include <wx/log.h> #include <wx/log.h>
#include <wx/process.h> #include <wx/process.h>
#include <wx/sizer.h>
#include <wx/textctrl.h> #include <wx/textctrl.h>
#include <FileDialog.h> #include <FileDialog.h>
#include "Export.h" #include "Export.h"

@ -33,6 +33,7 @@
*//********************************************************************/ *//********************************************************************/
#include "../Audacity.h" #include "../Audacity.h"
#include "../Experimental.h"
#include <wx/defs.h> #include <wx/defs.h>
#include <wx/intl.h> #include <wx/intl.h>
@ -105,6 +106,14 @@ void MousePrefs::CreateList()
AddItem(_("Left-Double-Click"), _("Select"), _("Select Clip or Entire Track")); AddItem(_("Left-Double-Click"), _("Select"), _("Select Clip or Entire Track"));
AddItem(_("Ctrl-Left-Click"), _("Select"), _("Set Selection Point and Play")); AddItem(_("Ctrl-Left-Click"), _("Select"), _("Set Selection Point and Play"));
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Spectral selection
AddItem(_("Alt-Shift-Left-Click"), _("Select"), _("Adjust high or low frequency"));
AddItem(_("Alt-Left-Drag"), _("Select"), _("Adjust bandwidth"));
AddItem(_("Alt-Ctrl"), _("Select"), _("Unpin center frequency"));
AddItem(_("Alt-Move"), _("Select"), _("Snap center frequency to peaks"));
#endif
AddItem(_("Left-Click"), _("Zoom"), _("Zoom in on Point")); AddItem(_("Left-Click"), _("Zoom"), _("Zoom in on Point"));
AddItem(_("Left-Drag"), _("Zoom"), _("Zoom in on a Range"), _("same as right-drag")); AddItem(_("Left-Drag"), _("Zoom"), _("Zoom in on a Range"), _("same as right-drag"));
AddItem(_("Right-Click"), _("Zoom"), _("Zoom out one step")); AddItem(_("Right-Click"), _("Zoom"), _("Zoom out one step"));
@ -131,6 +140,14 @@ void MousePrefs::CreateList()
AddItem(_("Right-Click"), _("Multi"), _("Zoom out one step"), _("same as zoom tool")); AddItem(_("Right-Click"), _("Multi"), _("Zoom out one step"), _("same as zoom tool"));
AddItem(_("Right-Drag"), _("Multi"), _("Zoom in on a Range"), _("same as zoom tool")); AddItem(_("Right-Drag"), _("Multi"), _("Zoom in on a Range"), _("same as zoom tool"));
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
// Spectral selection
AddItem(_("Alt-Shift-Left-Click"), _("Multi"), _("Adjust high or low frequency"), _("same as select tool"));
AddItem(_("Alt-Left-Drag"), _("Multi"), _("Adjust bandwidth"), _("same as select tool"));
AddItem(_("Alt-Ctrl"), _("Multi"), _("Unpin center frequency"), _("same as select tool"));
AddItem(_("Alt-Move"), _("Multi"), _("Snap center frequency to peaks"), _("same as select tool"));
#endif
AddItem(_("Wheel-Rotate"), _("Any"), _("Scroll up or down")); AddItem(_("Wheel-Rotate"), _("Any"), _("Scroll up or down"));
AddItem(_("Shift-Wheel-Rotate"),_("Any"), _("Scroll left or right")); AddItem(_("Shift-Wheel-Rotate"),_("Any"), _("Scroll left or right"));
AddItem(_("Ctrl-Wheel-Rotate"), _("Any"), _("Zoom in or out on Mouse Pointer")); AddItem(_("Ctrl-Wheel-Rotate"), _("Any"), _("Zoom in or out on Mouse Pointer"));

@ -33,6 +33,7 @@
#include "DeviceToolBar.h" #include "DeviceToolBar.h"
#include "ToolDock.h" #include "ToolDock.h"
#include "../TrackPanel.h"
#include "../AColor.h" #include "../AColor.h"
#include "../AllThemeResources.h" #include "../AllThemeResources.h"

@ -43,6 +43,7 @@ with changes in the SelectionBar.
#endif #endif
#include <wx/statline.h> #include <wx/statline.h>
#include "SelectionBarListener.h"
#include "SelectionBar.h" #include "SelectionBar.h"
#include "../AudacityApp.h" #include "../AudacityApp.h"

@ -24,24 +24,9 @@ class wxDC;
class wxRadioButton; class wxRadioButton;
class wxSizeEvent; class wxSizeEvent;
class SelectionBarListener;
class TimeTextCtrl; class TimeTextCtrl;
class AUDACITY_DLL_API SelectionBarListener {
public:
SelectionBarListener(){};
virtual ~SelectionBarListener(){};
virtual double AS_GetRate() = 0;
virtual void AS_SetRate(double rate) = 0;
virtual int AS_GetSnapTo() = 0;
virtual void AS_SetSnapTo(int snap) = 0;
virtual const wxString & AS_GetSelectionFormat() = 0;
virtual void AS_SetSelectionFormat(const wxString & format) = 0;
virtual void AS_ModifySelection(double &start, double &end, bool done) = 0;
};
class SelectionBar:public ToolBar { class SelectionBar:public ToolBar {
public: public:

@ -27,6 +27,7 @@
#include <wx/intl.h> #include <wx/intl.h>
#endif // WX_PRECOMP #endif // WX_PRECOMP
#include "../Envelope.h"
#include "TranscriptionToolBar.h" #include "TranscriptionToolBar.h"
#include "ControlToolBar.h" #include "ControlToolBar.h"