1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-25 16:48:44 +02:00

Other spectrogram scales, easily defined!! -- and include bug fixes 1038, 1039

This commit is contained in:
Paul Licameli 2015-07-27 22:58:33 -04:00
commit 981acf0bd2
11 changed files with 655 additions and 426 deletions

View File

@ -171,6 +171,7 @@ audacity_SOURCES = \
MixerBoard.h \ MixerBoard.h \
ModuleManager.cpp \ ModuleManager.cpp \
ModuleManager.h \ ModuleManager.h \
NumberScale.h \
PitchName.cpp \ PitchName.cpp \
PitchName.h \ PitchName.h \
PlatformCompatibility.cpp \ PlatformCompatibility.cpp \

279
src/NumberScale.h Normal file
View File

@ -0,0 +1,279 @@
/**********************************************************************
Audacity: A Digital Audio Editor
NumberScale.h
Paul Licameli
**********************************************************************/
#ifndef __AUDACITY_NUMBER_SCALE__
#define __AUDACITY_NUMBER_SCALE__
#include <algorithm>
#include <cmath>
#include <wx/defs.h>
#include <wx/debug.h>
enum NumberScaleType {
nstLinear,
nstLogarithmic,
nstMel,
nstBark,
nstErb,
nstUndertone,
nstNumScaleTypes,
};
class NumberScale
{
public:
NumberScale(NumberScaleType type,
float value0, float value1, float unit)
: mType(type)
{
switch (mType) {
case nstLinear:
{
mValue0 = value0 / unit;
mValue1 = value1 / unit;
mUnit = 1.0;
}
break;
case nstLogarithmic:
{
mValue0 = logf(value0 / unit);
mValue1 = logf(value1 / unit);
mUnit = 1.0;
}
break;
case nstMel:
{
mValue0 = hzToMel(value0);
mValue1 = hzToMel(value1);
mUnit = unit;
}
break;
case nstBark:
{
mValue0 = hzToBark(value0);
mValue1 = hzToBark(value1);
mUnit = unit;
}
break;
case nstErb:
{
mValue0 = hzToErb(value0);
mValue1 = hzToErb(value1);
mUnit = unit;
}
break;
case nstUndertone:
{
mValue0 = hzToUndertone(value0);
mValue1 = hzToUndertone(value1);
mUnit = unit;
}
break;
default:
wxASSERT(false);
}
}
NumberScale Reversal() const
{
NumberScale result(*this);
std::swap(result.mValue0, result.mValue1);
return result;
}
bool operator == (const NumberScale& other) const
{
return mType == other.mType
&& mValue0 == other.mValue0
&& mValue1 == other.mValue1
&& mUnit == other.mUnit;
}
bool operator != (const NumberScale &other) const
{
return !(*this == other);
}
static inline float hzToMel(float hz)
{
return 1127 * log(1 + hz / 700);
}
static inline float melToHz(float mel)
{
return 700 * (exp(mel / 1127) - 1);
}
static inline float hzToBark(float hz)
{
// Traunmueller's formula
const float z1 = 26.81 * hz / (1960 + hz) - 0.53;
if (z1 < 2.0)
return z1 + 0.15 * (2.0 - z1);
else if (z1 > 20.1)
return z1 + 0.22 * (z1 - 20.1);
else
return z1;
}
static inline float barkToHz(float z1)
{
if (z1 < 2.0)
z1 = 2.0 + (z1 - 2.0) / 0.85;
else if (z1 > 20.1)
z1 = 20.1 + (z1 - 20.1) / 1.22;
return 1960 * (z1 + 0.53) / (26.28 - z1);
}
static inline float hzToErb(float hz)
{
return 11.17268 * log(1 + (46.06538 * hz) / (hz + 14678.49));
}
static inline float erbToHz(float erb)
{
return 676170.4 / (47.06538 - exp(0.08950404 * erb)) - 14678.49;
}
static inline float hzToUndertone(float hz)
{
return -1.0 / std::max (1.0f, hz);
}
static inline float undertoneToHz(float u)
{
return -1.0 / u;
}
// Random access
float PositionToValue(float pp) const
{
switch (mType) {
default:
wxASSERT(false);
case nstLinear:
return mValue0 + pp * (mValue1 - mValue0);
case nstLogarithmic:
return exp(mValue0 + pp * (mValue1 - mValue0));
case nstMel:
return melToHz(mValue0 + pp * (mValue1 - mValue0)) / mUnit;
case nstBark:
return barkToHz(mValue0 + pp * (mValue1 - mValue0)) / mUnit;
case nstErb:
return erbToHz(mValue0 + pp * (mValue1 - mValue0)) / mUnit;
case nstUndertone:
return undertoneToHz(mValue0 + pp * (mValue1 - mValue0)) / mUnit;
}
}
// STL-idiom iteration
class Iterator
{
public:
Iterator(NumberScaleType type, float step, float value, float unit)
: mType(type), mStep(step), mValue(value), mUnit(unit)
{
}
float operator * () const
{
switch (mType) {
default:
wxASSERT(false);
case nstLinear:
case nstLogarithmic:
return mValue;
case nstMel:
return melToHz(mValue) / mUnit;
case nstBark:
return barkToHz(mValue) / mUnit;
case nstErb:
return erbToHz(mValue) / mUnit;
case nstUndertone:
return undertoneToHz(mValue) / mUnit;
}
}
Iterator &operator ++()
{
switch (mType) {
case nstLinear:
case nstMel:
case nstBark:
case nstErb:
case nstUndertone:
mValue += mStep;
break;
case nstLogarithmic:
mValue *= mStep;
break;
default:
wxASSERT(false);
}
return *this;
}
private:
const NumberScaleType mType;
const float mStep;
float mValue;
float mUnit;
};
Iterator begin(float nPositions) const
{
switch (mType) {
default:
wxASSERT(false);
case nstLinear:
case nstMel:
case nstBark:
case nstErb:
case nstUndertone:
return Iterator
(mType, (mValue1 - mValue0) / nPositions, mValue0, mUnit);
case nstLogarithmic:
return Iterator
(mType, exp((mValue1 - mValue0) / nPositions), exp(mValue0), mUnit);
}
}
// Inverse
float ValueToPosition(float val) const
{
switch (mType) {
default:
wxASSERT(false);
case nstLinear:
return ((val - mValue0) / (mValue1 - mValue0));
case nstLogarithmic:
return ((log(val) - mValue0) / (mValue1 - mValue0));
case nstMel:
return ((hzToMel(val * mUnit) - mValue0) / (mValue1 - mValue0));
case nstBark:
return ((hzToBark(val * mUnit) - mValue0) / (mValue1 - mValue0));
case nstErb:
return ((hzToErb(val * mUnit) - mValue0) / (mValue1 - mValue0));
case nstUndertone:
return ((hzToUndertone(val * mUnit) - mValue0) / (mValue1 - mValue0));
}
}
private:
const NumberScaleType mType;
float mValue0;
float mValue1;
float mUnit;
};
#endif

View File

@ -168,6 +168,7 @@ audio tracks.
#include "AColor.h" #include "AColor.h"
#include "BlockFile.h" #include "BlockFile.h"
#include "Envelope.h" #include "Envelope.h"
#include "NumberScale.h"
#include "Track.h" #include "Track.h"
#include "WaveTrack.h" #include "WaveTrack.h"
#include "LabelTrack.h" #include "LabelTrack.h"
@ -829,6 +830,10 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & rect)
} }
break; break;
case SpectrogramSettings::stLogarithmic: case SpectrogramSettings::stLogarithmic:
case SpectrogramSettings::stMel:
case SpectrogramSettings::stBark:
case SpectrogramSettings::stErb:
case SpectrogramSettings::stUndertone:
{ {
// SpectrumLog // SpectrumLog
@ -852,6 +857,9 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & rect)
vruler->SetRange(maxFreq, minFreq); vruler->SetRange(maxFreq, minFreq);
vruler->SetUnits(wxT("")); vruler->SetUnits(wxT(""));
vruler->SetLog(true); vruler->SetLog(true);
NumberScale scale
(wt->GetSpectrogramSettings().GetScale(wt->GetRate(), false, false).Reversal());
vruler->SetNumberScale(&scale);
} }
break; break;
} }
@ -2036,7 +2044,6 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
const int display = track->GetDisplay(); const int display = track->GetDisplay();
const bool autocorrelation = (WaveTrack::PitchDisplay == display); const bool autocorrelation = (WaveTrack::PitchDisplay == display);
const bool logF = settings.scaleType == SpectrogramSettings::stLogarithmic;
enum { DASH_LENGTH = 10 /* pixels */ }; enum { DASH_LENGTH = 10 /* pixels */ };
@ -2115,15 +2122,7 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
scaleType == SpectrogramSettings::stLinear scaleType == SpectrogramSettings::stLinear
? settings.GetMaxFreq(rate) : settings.GetLogMaxFreq(rate); ? settings.GetMaxFreq(rate) : settings.GetLogMaxFreq(rate);
float minBin = ((double)minFreq / binUnit); const NumberScale numberScale(settings.GetScale(rate, true, autocorrelation));
float maxBin = ((double)maxFreq / binUnit);
float binPerPx = float(maxBin - minBin) / float(mid.height);
const float
// e=exp(1.0f),
lmin = logf(float(minFreq)),
lmax = logf(float(maxFreq)),
scale = lmax - lmin;
#ifdef EXPERIMENTAL_FFT_Y_GRID #ifdef EXPERIMENTAL_FFT_Y_GRID
const float const float
@ -2204,135 +2203,124 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
int *indexes = new int[maxTableSize]; int *indexes = new int[maxTableSize];
#endif //EXPERIMENTAL_FIND_NOTES #endif //EXPERIMENTAL_FIND_NOTES
for (int xx = 0; xx < hiddenMid.width; ++xx) for (int xx = 0; xx < hiddenMid.width; ++xx) {
{ NumberScale::Iterator it = numberScale.begin(mid.height);
if (!logF) { float nextBin = std::max(0.0f, std::min(float(half - 1), *it));
for (int yy = 0; yy < hiddenMid.height; ++yy) { for (int yy = 0; yy < hiddenMid.height; ++yy) {
float bin0 = float(yy) * binPerPx + minBin; const float bin = nextBin;
float bin1 = float(yy + 1) * binPerPx + minBin; nextBin = std::max(0.0f, std::min(float(half - 1), *++it));
if (settings.scaleType != SpectrogramSettings::stLogarithmic) {
const float value = findValue const float value = findValue
(freq + half * xx, bin0, bin1, half, autocorrelation, gain, range); (freq + half * xx, bin, nextBin, half, autocorrelation, gain, range);
clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value; clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
} }
} else {
else { // Do we need this legacy experiment still?
#ifdef EXPERIMENTAL_FIND_NOTES #ifdef EXPERIMENTAL_FIND_NOTES
int maximas=0; int maximas = 0;
const int x0 = half * xx; const int x0 = half * x;
if (fftFindNotes) { if (fftFindNotes) {
for (int i = maxTableSize-1; i >= 0; i--) for (int i = maxTableSize - 1; i >= 0; i--)
indexes[i]=-1; indexes[i] = -1;
// Build a table of (most) values, put the index in it. // Build a table of (most) values, put the index in it.
for (int i = int(i0); i < int(i1); i++) { for (int i = int(i0); i < int(i1); i++) {
float freqi=freq[x0 + int(i)]; float freqi = freq[x0 + int(i)];
int value=int((freqi+gain+range)/range*(maxTableSize-1)); int value = int((freqi + gain + range) / range*(maxTableSize - 1));
if (value < 0) if (value < 0)
value=0; value = 0;
if (value >= maxTableSize) if (value >= maxTableSize)
value=maxTableSize-1; value = maxTableSize - 1;
indexes[value]=i; indexes[value] = i;
} }
// Build from the indices an array of maxima. // Build from the indices an array of maxima.
for (int i = maxTableSize - 1; i >= 0; i--) { for (int i = maxTableSize - 1; i >= 0; i--) {
int index = indexes[i]; int index = indexes[i];
if (index >= 0) { if (index >= 0) {
float freqi = freq[x0 + index]; float freqi = freq[x0 + index];
if (freqi < findNotesMinA) if (freqi < findNotesMinA)
break;
bool ok = true;
for (int m = 0; m < maximas; m++) {
// Avoid to store very close maxima.
float maxm = maxima[m];
if (maxm / index < minDistance && index / maxm < minDistance) {
ok = false;
break; break;
bool ok = true;
for (int m = 0; m < maximas; m++) {
// Avoid to store very close maxima.
float maxm = maxima[m];
if (maxm / index < minDistance && index / maxm < minDistance) {
ok = false;
break;
}
}
if (ok) {
maxima[maximas++] = index;
if (maximas >= numberOfMaxima)
break;
} }
} }
if (ok) {
maxima[maximas++] = index;
if (maximas >= numberOfMaxima)
break;
}
} }
}
// The f2pix helper macro converts a frequency into a pixel coordinate. // The f2pix helper macro converts a frequency into a pixel coordinate.
#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*hiddenMid.height #define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*hiddenMid.height
// Possibly quantize the maxima frequencies and create the pixel block limits. // Possibly quantize the maxima frequencies and create the pixel block limits.
for (int i=0; i < maximas; i++) { for (int i = 0; i < maximas; i++) {
int index=maxima[i]; int index = maxima[i];
float f = float(index)*bin2f; float f = float(index)*bin2f;
if (findNotesQuantize) if (findNotesQuantize)
{ f = expf(int(log(f/440)/log2*12-0.5)/12.0f*log2)*440; {
maxima[i] = f*f2bin; f = expf(int(log(f / 440) / log2 * 12 - 0.5) / 12.0f*log2) * 440;
maxima[i] = f*f2bin;
}
float f0 = expf((log(f / 440) / log2 * 24 - 1) / 24.0f*log2) * 440;
maxima0[i] = f2pix(f0);
float f1 = expf((log(f / 440) / log2 * 24 + 1) / 24.0f*log2) * 440;
maxima1[i] = f2pix(f1);
} }
float f0 = expf((log(f/440)/log2*24-1)/24.0f*log2)*440;
maxima0[i] = f2pix(f0);
float f1 = expf((log(f/440)/log2*24+1)/24.0f*log2)*440;
maxima1[i] = f2pix(f1);
} }
} int it = 0;
int it=0; int oldBin0 = -1;
int oldBin0=-1; bool inMaximum = false;
bool inMaximum = false;
#endif //EXPERIMENTAL_FIND_NOTES #endif //EXPERIMENTAL_FIND_NOTES
double yy2_base = exp(lmin) / binUnit;
float yy2 = yy2_base;
double exp_scale_per_height = exp(scale / hiddenMid.height);
for (int yy = 0; yy < hiddenMid.height; ++yy) {
if (int(yy2) >= half)
yy2=half-1;
if (yy2<0)
yy2=0;
float bin0 = float(yy2);
yy2_base *= exp_scale_per_height;
float yy3 = yy2_base;
if (int(yy3)>=half)
yy3=half-1;
if (yy3<0)
yy3=0;
float bin1 = float(yy3);
float value; float value;
#ifdef EXPERIMENTAL_FIND_NOTES #ifdef EXPERIMENTAL_FIND_NOTES
if (fftFindNotes) { if (fftFindNotes) {
if (it < maximas) { if (it < maximas) {
float i0=maxima0[it]; float i0 = maxima0[it];
if (yy >= i0) if (yy >= i0)
inMaximum = true; inMaximum = true;
if (inMaximum) { if (inMaximum) {
float i1=maxima1[it]; float i1 = maxima1[it];
if (yy+1 <= i1) { if (yy + 1 <= i1) {
value=findValue(freq + x0, bin0, bin1, half, autocorrelation, gain, range); value = findValue(freq + x0, bin, nextBin, half, autocorrelation, gain, range);
if (value < findNotesMinA) if (value < findNotesMinA)
value = minColor; value = minColor;
} else { }
else {
it++; it++;
inMaximum = false; inMaximum = false;
value = minColor; value = minColor;
} }
} else { }
else {
value = minColor; value = minColor;
} }
} else }
else
value = minColor; value = minColor;
} else }
else
#endif //EXPERIMENTAL_FIND_NOTES #endif //EXPERIMENTAL_FIND_NOTES
{ {
value = findValue value = findValue
(freq + half * xx, bin0, bin1, half, autocorrelation, gain, range); (freq + half * xx, bin, nextBin, half, autocorrelation, gain, range);
} }
clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value; clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
yy2 = yy2_base; } // logF
} // each yy } // each yy
} // is logF
} // each xx } // each xx
} // updating cache } // updating cache
float selBinLo = freqLo / binUnit; float selBinLo = freqLo / binUnit;
@ -2384,85 +2372,42 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
(zoomInfo.PositionToTime(xx + 1, -leftOffset) - tOffset) (zoomInfo.PositionToTime(xx + 1, -leftOffset) - tOffset)
); );
// TODO: The logF and non-logF case are very similar. NumberScale::Iterator it = numberScale.begin(mid.height);
// They should be merged and simplified. float nextBin = std::max(0.0f, std::min(float(half - 1), *it));
if (!logF) for (int yy = 0; yy < hiddenMid.height; ++yy) {
{ const float bin = nextBin;
for (int yy = 0; yy < hiddenMid.height; ++yy) { nextBin = std::max(0.0f, std::min(float(half - 1), *++it));
float bin0 = float(yy) * binPerPx + minBin;
float bin1 = float(yy + 1) * binPerPx + minBin;
// For spectral selection, determine what colour // For spectral selection, determine what colour
// set to use. We use a darker selection if // set to use. We use a darker selection if
// in both spectral range and time range. // in both spectral range and time range.
AColor::ColorGradientChoice selected = AColor::ColorGradientChoice selected = AColor::ColorGradientUnselected;
AColor::ColorGradientUnselected; // If we are in the time selected range, then we may use a different color set.
// If we are in the time selected range, then we may use a different color set. if (ssel0 <= w0 && w1 < ssel1)
if (ssel0 <= w0 && w1 < ssel1) selected =
selected = ChooseColorSet(bin0, bin1, selBinLo, selBinCenter, selBinHi, ChooseColorSet(bin, nextBin, selBinLo, selBinCenter, selBinHi,
(xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral); (xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
const float value = uncached
unsigned char rv, gv, bv; ? findValue(uncached, bin, nextBin, half, autocorrelation, gain, range)
const float value = uncached : clip->mSpecPxCache->values[correctedX * hiddenMid.height + yy];
? findValue(uncached, bin0, bin1, half, autocorrelation, gain, range) unsigned char rv, gv, bv;
: clip->mSpecPxCache->values[correctedX * hiddenMid.height + yy]; GetColorGradient(value, selected, isGrayscale, &rv, &gv, &bv);
GetColorGradient(value, selected, isGrayscale, &rv, &gv, &bv);
int px = ((mid.height - 1 - yy) * mid.width + xx) * 3;
data[px++] = rv;
data[px++] = gv;
data[px] = bv;
}
}
else //logF
{
double yy2_base=exp(lmin)/binUnit;
float yy2 = yy2_base;
double exp_scale_per_height = exp(scale / hiddenMid.height);
for (int yy = 0; yy < hiddenMid.height; ++yy) {
if (int(yy2)>=half)
yy2=half-1;
if (yy2<0)
yy2=0;
float bin0 = float(yy2);
yy2_base *= exp_scale_per_height;
float yy3 = yy2_base;
if (int(yy3)>=half)
yy3=half-1;
if (yy3<0)
yy3=0;
float bin1 = float(yy3);
AColor::ColorGradientChoice selected = AColor::ColorGradientUnselected;
// If we are in the time selected range, then we may use a different color set.
if (ssel0 <= w0 && w1 < ssel1)
selected = ChooseColorSet(
bin0, bin1, selBinLo, selBinCenter, selBinHi,
(xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
unsigned char rv, gv, bv;
const float value = uncached
? findValue(uncached, bin0, bin1, half, autocorrelation, gain, range)
: clip->mSpecPxCache->values[correctedX * hiddenMid.height + yy];
GetColorGradient(value, selected, isGrayscale, &rv, &gv, &bv);
#ifdef EXPERIMENTAL_FFT_Y_GRID #ifdef EXPERIMENTAL_FFT_Y_GRID
if (fftYGrid && yGrid[yy]) { if (fftYGrid && yGrid[yy]) {
rv /= 1.1f; rv /= 1.1f;
gv /= 1.1f; gv /= 1.1f;
bv /= 1.1f; bv /= 1.1f;
} }
#endif //EXPERIMENTAL_FFT_Y_GRID #endif //EXPERIMENTAL_FFT_Y_GRID
int px = ((mid.height - 1 - yy) * mid.width + xx) * 3; int px = ((mid.height - 1 - yy) * mid.width + xx) * 3;
data[px++] = rv; data[px++] = rv;
data[px++] = gv; data[px++] = gv;
data[px] = bv; data[px] = bv;
} // each yy
yy2 = yy2_base; } // each xx
}
}
}
wxBitmap converted = wxBitmap(*image); wxBitmap converted = wxBitmap(*image);

View File

@ -201,6 +201,7 @@ is time to refresh some aspect of the screen.
#include "MixerBoard.h" #include "MixerBoard.h"
#include "NoteTrack.h" #include "NoteTrack.h"
#include "NumberScale.h"
#include "Prefs.h" #include "Prefs.h"
#include "Project.h" #include "Project.h"
#include "Snap.h" #include "Snap.h"
@ -1802,22 +1803,15 @@ void TrackPanel::SetCursorAndTipWhenInLabelTrack( LabelTrack * pLT,
namespace { namespace {
// This returns true if we're a spectral editing track. // This returns true if we're a spectral editing track.
inline bool isSpectralSelectionTrack(const Track *pTrack, bool *pLogf = NULL) { inline bool isSpectralSelectionTrack(const Track *pTrack) {
if (pTrack && if (pTrack &&
pTrack->GetKind() == Track::Wave) { pTrack->GetKind() == Track::Wave) {
const WaveTrack *const wt = static_cast<const WaveTrack*>(pTrack); const WaveTrack *const wt = static_cast<const WaveTrack*>(pTrack);
const SpectrogramSettings &settings = wt->GetSpectrogramSettings(); const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
const int display = wt->GetDisplay(); const int display = wt->GetDisplay();
if (pLogf) {
const bool logF =
settings.scaleType == SpectrogramSettings::stLogarithmic;
*pLogf = logF;
}
return (display == WaveTrack::Spectrum) && settings.SpectralSelectionEnabled(); return (display == WaveTrack::Spectrum) && settings.SpectralSelectionEnabled();
} }
else { else {
if (pLogf)
*pLogf = false;
return false; return false;
} }
} }
@ -1941,9 +1935,8 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t,
const bool bShiftDown = event.ShiftDown(); const bool bShiftDown = event.ShiftDown();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING #ifdef EXPERIMENTAL_SPECTRAL_EDITING
bool logF;
if ( (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) && if ( (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
isSpectralSelectionTrack(t, &logF)) { isSpectralSelectionTrack(t)) {
// Not shift-down, but center frequency snapping toggle is on // Not shift-down, but center frequency snapping toggle is on
*ppTip = _("Click and drag to set frequency bandwidth."); *ppTip = _("Click and drag to set frequency bandwidth.");
*ppCursor = mEnvelopeCursor; *ppCursor = mEnvelopeCursor;
@ -2679,9 +2672,8 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
// preferences now // preferences now
if (mAdjustSelectionEdges) { if (mAdjustSelectionEdges) {
#ifdef EXPERIMENTAL_SPECTRAL_EDITING #ifdef EXPERIMENTAL_SPECTRAL_EDITING
bool logF;
if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER && if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
isSpectralSelectionTrack(pTrack, &logF)) { isSpectralSelectionTrack(pTrack)) {
// Ignore whether we are inside the time selection. // Ignore whether we are inside the time selection.
// Exit center-snapping, start dragging the width. // Exit center-snapping, start dragging the width.
mFreqSelMode = FREQ_SEL_PINNED_CENTER; mFreqSelMode = FREQ_SEL_PINNED_CENTER;
@ -3072,10 +3064,9 @@ void TrackPanel::MoveSnappingFreqSelection (int mouseYCoordinate,
int trackTopEdge, int trackTopEdge,
int trackHeight, Track *pTrack) int trackHeight, Track *pTrack)
{ {
bool logF;
if (pTrack && if (pTrack &&
pTrack->GetSelected() && pTrack->GetSelected() &&
isSpectralSelectionTrack(pTrack, &logF)) { isSpectralSelectionTrack(pTrack)) {
WaveTrack *const wt = static_cast<WaveTrack*>(pTrack); WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
// PRL: // PRL:
// What happens if center snapping selection began in one spectrogram track, // What happens if center snapping selection began in one spectrogram track,
@ -3086,7 +3077,7 @@ void TrackPanel::MoveSnappingFreqSelection (int mouseYCoordinate,
const double rate = wt->GetRate(); const double rate = wt->GetRate();
const double frequency = const double frequency =
PositionToFrequency(wt, false, mouseYCoordinate, PositionToFrequency(wt, false, mouseYCoordinate,
trackTopEdge, trackHeight, logF); trackTopEdge, trackHeight);
const double snappedFrequency = const double snappedFrequency =
mFrequencySnapper->FindPeak(frequency, NULL); mFrequencySnapper->FindPeak(frequency, NULL);
const double maxRatio = findMaxRatio(snappedFrequency, rate); const double maxRatio = findMaxRatio(snappedFrequency, rate);
@ -3118,13 +3109,12 @@ void TrackPanel::StartFreqSelection (int mouseYCoordinate, int trackTopEdge,
mFreqSelMode = FREQ_SEL_INVALID; mFreqSelMode = FREQ_SEL_INVALID;
mFreqSelPin = SelectedRegion::UndefinedFrequency; mFreqSelPin = SelectedRegion::UndefinedFrequency;
bool logF; if (isSpectralSelectionTrack(pTrack)) {
if (isSpectralSelectionTrack(pTrack, &logF)) {
mFreqSelTrack = static_cast<WaveTrack*>(pTrack); mFreqSelTrack = static_cast<WaveTrack*>(pTrack);
mFreqSelMode = FREQ_SEL_FREE; mFreqSelMode = FREQ_SEL_FREE;
mFreqSelPin = mFreqSelPin =
PositionToFrequency(mFreqSelTrack, false, mouseYCoordinate, PositionToFrequency(mFreqSelTrack, false, mouseYCoordinate,
trackTopEdge, trackHeight, logF); trackTopEdge, trackHeight);
mViewInfo->selectedRegion.setFrequencies(mFreqSelPin, mFreqSelPin); mViewInfo->selectedRegion.setFrequencies(mFreqSelPin, mFreqSelPin);
} }
} }
@ -3143,12 +3133,10 @@ void TrackPanel::ExtendFreqSelection(int mouseYCoordinate, int trackTopEdge,
// started, and that is of a spectrogram display type. // started, and that is of a spectrogram display type.
const WaveTrack* wt = mFreqSelTrack; const WaveTrack* wt = mFreqSelTrack;
const bool logF =
wt->GetSpectrogramSettings().scaleType == SpectrogramSettings::stLogarithmic;
const double rate = wt->GetRate(); const double rate = wt->GetRate();
const double frequency = const double frequency =
PositionToFrequency(wt, true, mouseYCoordinate, PositionToFrequency(wt, true, mouseYCoordinate,
trackTopEdge, trackHeight, logF); trackTopEdge, trackHeight);
// Dragging center? // Dragging center?
if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) { if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) {
@ -3527,8 +3515,7 @@ double TrackPanel::PositionToFrequency(const WaveTrack *wt,
bool maySnap, bool maySnap,
wxInt64 mouseYCoordinate, wxInt64 mouseYCoordinate,
wxInt64 trackTopEdge, wxInt64 trackTopEdge,
int trackHeight, int trackHeight) const
bool logF) const
{ {
const double rate = wt->GetRate(); const double rate = wt->GetRate();
@ -3540,57 +3527,23 @@ double TrackPanel::PositionToFrequency(const WaveTrack *wt,
trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE) trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
return -1; return -1;
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
const NumberScale numberScale(settings.GetScale(rate, false, false));
const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight; const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
return numberScale.PositionToValue(1.0 - p);
if (logF)
{
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
const double maxFreq = settings.GetLogMaxFreq(rate);
const double minFreq = settings.GetLogMinFreq(rate);
return exp(p * log(minFreq) + (1.0 - p) * log(maxFreq));
}
else
{
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
const double maxFreq = settings.GetMaxFreq(rate);
const double minFreq = settings.GetMinFreq(rate);
return p * minFreq + (1.0 - p) * maxFreq;
}
} }
/// Converts a frequency to screen y position. /// Converts a frequency to screen y position.
wxInt64 TrackPanel::FrequencyToPosition(const WaveTrack *wt, wxInt64 TrackPanel::FrequencyToPosition(const WaveTrack *wt,
double frequency, double frequency,
wxInt64 trackTopEdge, wxInt64 trackTopEdge,
int trackHeight, int trackHeight) const
bool logF) const
{ {
const double rate = wt->GetRate(); const double rate = wt->GetRate();
double p = 0;
if (logF)
{
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
const double maxFreq = settings.GetLogMaxFreq(rate);
const double minFreq = settings.GetLogMinFreq(rate);
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 SpectrogramSettings &settings = wt->GetSpectrogramSettings();
const double maxFreq = settings.GetMaxFreq(rate);
const double minFreq = settings.GetMinFreq(rate);
if (maxFreq > minFreq)
p = (frequency - minFreq) / (maxFreq - minFreq);
}
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
const NumberScale numberScale(settings.GetScale(rate, false, false));
const float p = numberScale.ValueToPosition(frequency);
return trackTopEdge + wxInt64((1.0 - p) * trackHeight); return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
} }
#endif #endif
@ -3671,18 +3624,17 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
bool chooseTime = true; bool chooseTime = true;
bool chooseBottom = true; bool chooseBottom = true;
bool chooseCenter = false; bool chooseCenter = false;
bool logF;
// Consider adjustment of frequencies only if mouse is // Consider adjustment of frequencies only if mouse is
// within the time boundaries // within the time boundaries
if (!mViewInfo->selectedRegion.isPoint() && if (!mViewInfo->selectedRegion.isPoint() &&
t0 <= selend && selend < t1 && t0 <= selend && selend < t1 &&
isSpectralSelectionTrack(pTrack, &logF)) { isSpectralSelectionTrack(pTrack)) {
const WaveTrack *const wt = static_cast<const WaveTrack*>(pTrack); const WaveTrack *const wt = static_cast<const WaveTrack*>(pTrack);
const wxInt64 bottomSel = (f0 >= 0) const wxInt64 bottomSel = (f0 >= 0)
? FrequencyToPosition(wt, f0, rect.y, rect.height, logF) ? FrequencyToPosition(wt, f0, rect.y, rect.height)
: rect.y + rect.height; : rect.y + rect.height;
const wxInt64 topSel = (f1 >= 0) const wxInt64 topSel = (f1 >= 0)
? FrequencyToPosition(wt, f1, rect.y, rect.height, logF) ? FrequencyToPosition(wt, f1, rect.y, rect.height)
: rect.y; : rect.y;
wxInt64 signedBottomDist = int(event.m_y - bottomSel); wxInt64 signedBottomDist = int(event.m_y - bottomSel);
wxInt64 verticalDist = abs(signedBottomDist); wxInt64 verticalDist = abs(signedBottomDist);
@ -3700,7 +3652,7 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
#endif #endif
) { ) {
const wxInt64 centerSel = const wxInt64 centerSel =
FrequencyToPosition(wt, fc, rect.y, rect.height, logF); FrequencyToPosition(wt, fc, rect.y, rect.height);
const wxInt64 centerDist = abs(int(event.m_y - centerSel)); const wxInt64 centerDist = abs(int(event.m_y - centerSel));
if (centerDist < verticalDist) if (centerDist < verticalDist)
chooseCenter = true, verticalDist = centerDist, chooseCenter = true, verticalDist = centerDist,
@ -4641,7 +4593,6 @@ void TrackPanel::HandleVZoomDrag( wxMouseEvent & event )
/// - Zoom in; ensure we don't go too large. /// - Zoom in; ensure we don't go too large.
void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event ) void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event )
{ {
int minBins = 0;
if (!mCapturedTrack) if (!mCapturedTrack)
return; return;
@ -4671,6 +4622,7 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event )
// don't do anything if track is not wave // don't do anything if track is not wave
if (mCapturedTrack->GetKind() != Track::Wave) if (mCapturedTrack->GetKind() != Track::Wave)
return; return;
WaveTrack *track = static_cast<WaveTrack*>(mCapturedTrack); WaveTrack *track = static_cast<WaveTrack*>(mCapturedTrack);
WaveTrack *partner = static_cast<WaveTrack *>(mTracks->GetLink(track)); WaveTrack *partner = static_cast<WaveTrack *>(mTracks->GetLink(track));
int height = track->GetHeight(); int height = track->GetHeight();
@ -4683,223 +4635,173 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event )
mZoomStart = temp; mZoomStart = temp;
} }
float min, max, c, l, binSize = 0.0; float min, max, c, l, minBand = 0;
const double rate = track->GetRate(); const double rate = track->GetRate();
const float halfrate = rate / 2;
const SpectrogramSettings &settings = track->GetSpectrogramSettings();
NumberScale scale(track->GetSpectrogramSettings().GetScale(rate, false, false));
const bool spectral = (track->GetDisplay() == WaveTrack::Spectrum); const bool spectral = (track->GetDisplay() == WaveTrack::Spectrum);
const bool spectrumLinear = spectral && const bool spectrumLinear = spectral &&
(track->GetSpectrogramSettings().scaleType == SpectrogramSettings::stLinear); (track->GetSpectrogramSettings().scaleType == SpectrogramSettings::stLinear);
const bool spectrumLog = spectral &&
(track->GetSpectrogramSettings().scaleType == SpectrogramSettings::stLogarithmic); if (spectral) {
if (spectrumLinear) { if (spectrumLinear) {
const SpectrogramSettings &settings = track->GetSpectrogramSettings(); min = settings.GetMinFreq(rate);
min = settings.GetMinFreq(rate); max = settings.GetMaxFreq(rate);
max = settings.GetMaxFreq(rate); }
else {
min = settings.GetLogMinFreq(rate);
max = settings.GetLogMaxFreq(rate);
}
const int fftLength = settings.GetFFTLength(false); const int fftLength = settings.GetFFTLength(false);
binSize = rate / fftLength; const float binSize = rate / fftLength;
minBins = std::min(10, fftLength / 2); //minimum 10 freq bins, unless there are less const int minBins =
} std::min(10, fftLength / 2); //minimum 10 freq bins, unless there are less
else if (spectrumLog) { minBand = minBins * binSize;
const SpectrogramSettings &settings = track->GetSpectrogramSettings();
min = settings.GetLogMinFreq(rate);
max = settings.GetLogMaxFreq(rate);
const int fftLength = settings.GetFFTLength(false);
binSize = rate / fftLength;
minBins = std::min(10, fftLength / 2); //minimum 10 freq bins, unless there are less
} }
else else
track->GetDisplayBounds(&min, &max); track->GetDisplayBounds(&min, &max);
if (IsDragZooming()) { if (IsDragZooming()) {
// Drag Zoom // Drag Zoom
float p1, p2, tmin, tmax; const float tmin = min, tmax = max;
tmin=min;
tmax=max;
if(spectrumLog) { if (spectral) {
double xmin = 1-(mZoomEnd - ypos) / (float)height; double xmin = 1 - (mZoomEnd - ypos) / (float)height;
double xmax = 1-(mZoomStart - ypos) / (float)height; double xmax = 1 - (mZoomStart - ypos) / (float)height;
double lmin=log10(tmin), lmax=log10(tmax); const float middle = (xmin + xmax) / 2;
double d=lmax-lmin; const float middleValue = scale.PositionToValue(middle);
min=std::max(1.0, pow(10, xmin*d+lmin));
max=std::min(rate/2.0, pow(10, xmax*d+lmin)); min = std::max(spectrumLinear ? 0.0f : 1.0f,
// Enforce vertical zoom limits std::min(middleValue - minBand / 2,
// done in the linear freq domain for now, but not too far out scale.PositionToValue(xmin)
if(max < min + minBins * binSize) ));
max = min + minBins * binSize; max = std::min(halfrate,
if(max > rate/2.) { std::max(middleValue + minBand / 2,
max = rate/2.; scale.PositionToValue(xmax)
min = max - minBins * binSize; ));
}
} }
else { else {
p1 = (mZoomStart - ypos) / (float)height; const float p1 = (mZoomStart - ypos) / (float)height;
p2 = (mZoomEnd - ypos) / (float)height; const float p2 = (mZoomEnd - ypos) / (float)height;
max = (tmax * (1.0-p1) + tmin * p1); max = (tmax * (1.0-p1) + tmin * p1);
min = (tmax * (1.0-p2) + tmin * p2); min = (tmax * (1.0-p2) + tmin * p2);
// Enforce vertical zoom limits // Waveform view - allow zooming down to a range of ZOOMLIMIT
if(spectrumLinear) { if (max - min < ZOOMLIMIT) { // if user attempts to go smaller...
if(min < 0.) c = (min+max)/2; // ...set centre of view to centre of dragged area and top/bottom to ZOOMLIMIT/2 above/below
min = 0.; min = c - ZOOMLIMIT/2.0;
if(max < min + minBins * binSize) max = c + ZOOMLIMIT/2.0;
max = min + minBins * binSize;
if(max > rate/2.) {
max = rate/2.;
min = max - minBins * binSize;
}
}
else {
// Waveform view - allow zooming down to a range of ZOOMLIMIT
if (max - min < ZOOMLIMIT) { // if user attempts to go smaller...
c = (min+max)/2; // ...set centre of view to centre of dragged area and top/bottom to ZOOMLIMIT/2 above/below
min = c - ZOOMLIMIT/2.0;
max = c + ZOOMLIMIT/2.0;
}
} }
} }
} }
else if (event.ShiftDown() || event.RightUp()) { else if (event.ShiftDown() || event.RightUp()) {
// Zoom OUT // Zoom OUT
// Zoom out to -1.0...1.0 first, then, and only if (spectral) {
// then, if they click again, allow one more
// zoom out.
if (spectrumLinear) {
if (event.ShiftDown() && event.RightUp()) { if (event.ShiftDown() && event.RightUp()) {
// Zoom out full // Zoom out full
min = 0.0; min = spectrumLinear ? 0.0f : 1.0f;
max = rate/2.; max = halfrate;
} }
else { else {
// Zoom out // Zoom out
c = 0.5*(min+max);
l = (c - min); // (Used to zoom out centered at midline, ignoring the click, if linear view.
if(c - 2*l <= 0) { // I think it is better to be consistent. PRL)
min = 0.0; // Center zoom-out at the midline
max = std::min( rate/2., 2. * max); const float middle = // spectrumLinear ? 0.5f :
} 1.0f - (mZoomStart - ypos) / (float)height;
else {
min = std::max( 0.0f, c - 2*l); min = std::max(spectrumLinear ? 0.0f : 1.0f, scale.PositionToValue(middle - 1.0f));
max = std::min( float(rate)/2, c + 2*l); max = std::min(halfrate, scale.PositionToValue(middle + 1.0f));
}
} }
} }
else { else {
if(spectrumLog) { // Zoom out to -1.0...1.0 first, then, and only
if (event.ShiftDown() && event.RightUp()) { // then, if they click again, allow one more
// Zoom out full // zoom out.
min = 1.0; if (event.ShiftDown() && event.RightUp()) {
max = rate/2.; // Zoom out full
} min = -1.0;
else { max = 1.0;
// Zoom out
float p1;
p1 = (mZoomStart - ypos) / (float)height;
c = 1.0-p1;
double xmin = c - 1.;
double xmax = c + 1.;
double lmin = log10(min), lmax = log10(max);
double d = lmax-lmin;
min = std::max(1.0f,float(pow(10, xmin*d+lmin)));
max = std::min(rate/2., pow(10, xmax*d+lmin));
}
} }
else { else {
if (event.ShiftDown() && event.RightUp()) { // Zoom out
// Zoom out full if (min <= -1.0 && max >= 1.0) {
min = -1.0; min = -2.0;
max = 1.0; max = 2.0;
} }
else { else {
// Zoom out c = 0.5*(min + max);
if (min <= -1.0 && max >= 1.0) { l = (c - min);
min = -2.0; // limit to +/- 1 range unless already outside that range...
max = 2.0; float minRange = (min < -1) ? -2.0 : -1.0;
} float maxRange = (max > 1) ? 2.0 : 1.0;
else { // and enforce vertical zoom limits.
c = 0.5*(min+max); min = std::min(maxRange - ZOOMLIMIT, std::max(minRange, c - 2 * l));
l = (c - min); max = std::max(minRange + ZOOMLIMIT, std::min(maxRange, c + 2 * l));
// limit to +/- 1 range unless already outside that range...
float minRange = (min < -1) ? -2.0 : -1.0;
float maxRange = (max > 1) ? 2.0 : 1.0;
// and enforce vertical zoom limits.
min = std::min(maxRange - ZOOMLIMIT, std::max(minRange, c - 2*l));
max = std::max(minRange + ZOOMLIMIT, std::min(maxRange, c + 2*l));
}
} }
} }
} }
} }
else { else {
// Zoom IN // Zoom IN
float p1; if (spectral) {
if (spectrumLinear) { // Center the zoom-in at the click
c = 0.5*(min+max); const float middle = 1.0f - (mZoomStart - ypos) / (float)height;
// Enforce maximum vertical zoom const float middleValue = scale.PositionToValue(middle);
l = std::max( minBins * binSize, (c - min));
p1 = (mZoomStart - ypos) / (float)height; min = std::max(spectrumLinear ? 0.0f : 1.0f,
c = (max * (1.0-p1) + min * p1); std::min(middleValue - minBand / 2,
min = std::max( 0.0, c - 0.5*l); scale.PositionToValue(middle - 0.25f)
max = std::min( float(rate)/2, min + l); ));
max = std::min(halfrate,
std::max(middleValue + minBand / 2,
scale.PositionToValue(middle + 0.25f)
));
} }
else { else {
if(spectrumLog) { // Zoom in centered on cursor
p1 = (mZoomStart - ypos) / (float)height; float p1;
c = 1.0-p1; if (min < -1.0 || max > 1.0) {
double xmin = c - 0.25; min = -1.0;
double xmax = c + 0.25; max = 1.0;
double lmin = log10(min), lmax = log10(max);
double d = lmax-lmin;
min = std::max(1.0f, float(pow(10, xmin*d+lmin)));
max = std::min(rate/2., pow(10, xmax*d+lmin));
// Enforce vertical zoom limits
// done in the linear freq domain for now, but not too far out
if(max < min + minBins * binSize)
max = min + minBins * binSize;
if(max > rate/2.) {
max = rate/2.;
min = max - minBins * binSize;
}
} }
else { else {
// Zoom in centered on cursor c = 0.5*(min + max);
if (min < -1.0 || max > 1.0) { // Enforce maximum vertical zoom
min = -1.0; l = std::max(ZOOMLIMIT, (c - min));
max = 1.0;
}
else {
c = 0.5*(min+max);
// Enforce maximum vertical zoom
l = std::max( ZOOMLIMIT, (c - min));
p1 = (mZoomStart - ypos) / (float)height; p1 = (mZoomStart - ypos) / (float)height;
c = (max * (1.0-p1) + min * p1); c = (max * (1.0 - p1) + min * p1);
min = c - 0.5*l; min = c - 0.5*l;
max = c + 0.5*l; max = c + 0.5*l;
}
} }
} }
} }
if (spectrumLinear) { if (spectral) {
SpectrogramSettings &settings = track->GetSpectrogramSettings(); if (spectrumLinear) {
settings.SetMinFreq(min); SpectrogramSettings &settings = track->GetSpectrogramSettings();
settings.SetMaxFreq(max);
if (partner) {
// To do: share memory with reference counting?
SpectrogramSettings &settings = partner->GetSpectrogramSettings();
settings.SetMinFreq(min); settings.SetMinFreq(min);
settings.SetMaxFreq(max); settings.SetMaxFreq(max);
if (partner) {
// To do: share memory with reference counting?
SpectrogramSettings &settings = partner->GetSpectrogramSettings();
settings.SetMinFreq(min);
settings.SetMaxFreq(max);
}
} }
} else {
else if(spectrumLog) { SpectrogramSettings &settings = track->GetSpectrogramSettings();
SpectrogramSettings &settings = track->GetSpectrogramSettings();
settings.SetLogMinFreq(min);
settings.SetLogMaxFreq(max);
if (partner) {
// To do: share memory with reference counting?
SpectrogramSettings &settings = partner->GetSpectrogramSettings();
settings.SetLogMinFreq(min); settings.SetLogMinFreq(min);
settings.SetLogMaxFreq(max); settings.SetLogMaxFreq(max);
if (partner) {
// To do: share memory with reference counting?
SpectrogramSettings &settings = partner->GetSpectrogramSettings();
settings.SetLogMinFreq(min);
settings.SetLogMaxFreq(max);
}
} }
} }
else { else {

View File

@ -698,13 +698,11 @@ protected:
bool maySnap, bool maySnap,
wxInt64 mouseYCoordinate, wxInt64 mouseYCoordinate,
wxInt64 trackTopEdge, wxInt64 trackTopEdge,
int trackHeight, int trackHeight) const;
bool logF) const;
wxInt64 FrequencyToPosition(const WaveTrack *wt, wxInt64 FrequencyToPosition(const WaveTrack *wt,
double frequency, double frequency,
wxInt64 trackTopEdge, wxInt64 trackTopEdge,
int trackHeight, int trackHeight) const;
bool logF) const;
#endif #endif
enum SelectionBoundary { enum SelectionBoundary {

View File

@ -15,6 +15,7 @@ Paul Licameli
#include "../Audacity.h" #include "../Audacity.h"
#include "SpectrogramSettings.h" #include "SpectrogramSettings.h"
#include "../NumberScale.h"
#include <algorithm> #include <algorithm>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
@ -23,7 +24,6 @@ Paul Licameli
#include "../Prefs.h" #include "../Prefs.h"
#include "../RealFFTf.h" #include "../RealFFTf.h"
#include <algorithm>
#include <cmath> #include <cmath>
SpectrogramSettings::Globals::Globals() SpectrogramSettings::Globals::Globals()
@ -161,6 +161,10 @@ const wxArrayString &SpectrogramSettings::GetScaleNames()
// Keep in correspondence with enum SpectrogramSettings::ScaleType: // Keep in correspondence with enum SpectrogramSettings::ScaleType:
theArray.Add(_("Linear")); theArray.Add(_("Linear"));
theArray.Add(_("Logarithmic")); theArray.Add(_("Logarithmic"));
theArray.Add(_("Mel"));
theArray.Add(_("Bark"));
theArray.Add(_("Erb"));
theArray.Add(_("Undertone"));
} }
return theArray; return theArray;
@ -524,6 +528,59 @@ int SpectrogramSettings::GetFFTLength(bool autocorrelation) const
; ;
} }
NumberScale SpectrogramSettings::GetScale
(double rate, bool bins, bool autocorrelation) const
{
int minFreq, maxFreq;
NumberScaleType type = nstLinear;
const int half = GetFFTLength(autocorrelation) / 2;
// Don't assume the correspondence of the enums will remain direct in the future.
// Do this switch.
switch (scaleType) {
default:
wxASSERT(false);
case stLinear:
type = nstLinear; break;
case stLogarithmic:
type = nstLogarithmic; break;
case stMel:
type = nstMel; break;
case stBark:
type = nstBark; break;
case stErb:
type = nstErb; break;
case stUndertone:
type = nstUndertone; break;
}
switch (scaleType) {
default:
wxASSERT(false);
case stLinear:
minFreq = GetMinFreq(rate);
maxFreq = GetMaxFreq(rate);
break;
case stLogarithmic:
case stMel:
case stBark:
case stErb:
minFreq = GetLogMinFreq(rate);
maxFreq = GetLogMaxFreq(rate);
break;
case stUndertone:
{
const float bin2 = rate / half;
minFreq = std::max(int(0.5 + bin2), GetLogMinFreq(rate));
maxFreq = GetLogMaxFreq(rate);
}
break;
}
return NumberScale(type, minFreq, maxFreq,
bins ? rate / (2 * half) : 1.0f);
}
bool SpectrogramSettings::SpectralSelectionEnabled() const bool SpectrogramSettings::SpectralSelectionEnabled() const
{ {
#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH #ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH

View File

@ -16,6 +16,7 @@ Paul Licameli
#undef SPECTRAL_SELECTION_GLOBAL_SWITCH #undef SPECTRAL_SELECTION_GLOBAL_SWITCH
struct FFTParam; struct FFTParam;
class NumberScale;
class SpectrumPrefs; class SpectrumPrefs;
class wxArrayString; class wxArrayString;
@ -53,6 +54,10 @@ public:
enum ScaleType { enum ScaleType {
stLinear, stLinear,
stLogarithmic, stLogarithmic,
stMel,
stBark,
stErb,
stUndertone,
stNumScaleTypes, stNumScaleTypes,
}; };
@ -80,6 +85,10 @@ public:
void ConvertToEnumeratedWindowSizes(); void ConvertToEnumeratedWindowSizes();
void ConvertToActualWindowSizes(); void ConvertToActualWindowSizes();
// If "bins" is false, units are Hz
NumberScale SpectrogramSettings::GetScale
(double rate, bool bins, bool autocorrelation) const;
private: private:
int minFreq; int minFreq;
int maxFreq; int maxFreq;

View File

@ -76,6 +76,7 @@ array of Ruler::Label.
#include "../TimeTrack.h" #include "../TimeTrack.h"
#include "../TrackPanel.h" #include "../TrackPanel.h"
#include "../Menus.h" #include "../Menus.h"
#include "../NumberScale.h"
#include "../Prefs.h" #include "../Prefs.h"
#include "../Snap.h" #include "../Snap.h"
@ -97,6 +98,7 @@ using std::max;
// //
Ruler::Ruler() Ruler::Ruler()
: mpNumberScale(0)
{ {
mMin = mHiddenMin = 0.0; mMin = mHiddenMin = 0.0;
mMax = mHiddenMax = 100.0; mMax = mHiddenMax = 100.0;
@ -177,6 +179,8 @@ Ruler::~Ruler()
delete[] mMinorLabels; delete[] mMinorLabels;
if (mMinorMinorLabels) if (mMinorMinorLabels)
delete[] mMinorMinorLabels; delete[] mMinorMinorLabels;
delete mpNumberScale;
} }
void Ruler::SetTwoTone(bool twoTone) void Ruler::SetTwoTone(bool twoTone)
@ -319,6 +323,23 @@ void Ruler::SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxF
Invalidate(); Invalidate();
} }
void Ruler::SetNumberScale(const NumberScale *pScale)
{
if (!pScale) {
if (mpNumberScale) {
delete mpNumberScale;
Invalidate();
}
}
else {
if (!mpNumberScale || *mpNumberScale != *pScale) {
delete mpNumberScale;
mpNumberScale = new NumberScale(*pScale);
Invalidate();
}
}
}
void Ruler::OfflimitsPixels(int start, int end) void Ruler::OfflimitsPixels(int start, int end)
{ {
int i; int i;
@ -1165,13 +1186,17 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo
} }
else { else {
// log case // log case
mDigits=2; //TODO: implement dynamic digit computation
NumberScale numberScale(mpNumberScale
? *mpNumberScale
: NumberScale(nstLogarithmic, mMin, mMax, 1.0f)
);
mDigits=2; //TODO: implement dynamic digit computation
double loLog = log10(mMin); double loLog = log10(mMin);
double hiLog = log10(mMax); double hiLog = log10(mMax);
double scale = mLength/(hiLog - loLog);
int loDecade = (int) floor(loLog); int loDecade = (int) floor(loLog);
int pos;
double val; double val;
double startDecade = pow(10., (double)loDecade); double startDecade = pow(10., (double)loDecade);
@ -1179,12 +1204,12 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo
double decade = startDecade; double decade = startDecade;
double delta=hiLog-loLog, steps=fabs(delta); double delta=hiLog-loLog, steps=fabs(delta);
double step = delta>=0 ? 10 : 0.1; double step = delta>=0 ? 10 : 0.1;
double rMin=wxMin(mMin, mMax), rMax=wxMax(mMin, mMax); double rMin=std::min(mMin, mMax), rMax=std::max(mMin, mMax);
for(i=0; i<=steps; i++) for(i=0; i<=steps; i++)
{ // if(i!=0) { // if(i!=0)
{ val = decade; { val = decade;
if(val > rMin && val < rMax) { if(val >= rMin && val < rMax) {
pos = (int)(((log10(val) - loLog)*scale)+0.5); const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
Tick(pos, val, true, false); Tick(pos, val, true, false);
} }
} }
@ -1204,7 +1229,7 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo
for(j=start; j!=end; j+=mstep) { for(j=start; j!=end; j+=mstep) {
val = decade * j; val = decade * j;
if(val >= rMin && val < rMax) { if(val >= rMin && val < rMax) {
pos = (int)(((log10(val) - loLog)*scale)+0.5); const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
Tick(pos, val, false, true); Tick(pos, val, false, true);
} }
} }
@ -1219,13 +1244,16 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo
{ start=100; end= 10; mstep=-1; { start=100; end= 10; mstep=-1;
} }
steps++; steps++;
for(i=0; i<=steps; i++) { for (i = 0; i <= steps; i++) {
for(int f=start; f!=int(end); f+=mstep) { // PRL: Bug1038. Don't label 1.6, rounded, as a duplicate tick for "2"
if (int(f/10)!=f/10.0f) { if (!(mFormat == IntFormat && decade < 10.0)) {
val = decade * f/10; for (int f = start; f != int(end); f += mstep) {
if(val >= rMin && val < rMax) { if (int(f / 10) != f / 10.0f) {
pos = (int)(((log10(val) - loLog)*scale)+0.5); val = decade * f / 10;
Tick(pos, val, false, false); if (val >= rMin && val < rMax) {
const int pos(0.5 + mLength * numberScale.ValueToPosition(val));
Tick(pos, val, false, false);
}
} }
} }
} }
@ -1904,8 +1932,8 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
TrackPanel *tp = mProject->GetTrackPanel(); TrackPanel *tp = mProject->GetTrackPanel();
int mousePosX, width, height; int mousePosX, width, height;
tp->GetTracksUsableArea(&width, &height); tp->GetTracksUsableArea(&width, &height);
mousePosX = wxMax(evt.GetX(), tp->GetLeftOffset()); mousePosX = std::max(evt.GetX(), tp->GetLeftOffset());
mousePosX = wxMin(mousePosX, tp->GetLeftOffset() + width - 1); mousePosX = std::min(mousePosX, tp->GetLeftOffset() + width - 1);
bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart); bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd); bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
@ -1920,7 +1948,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
mLastMouseX = mousePosX; mLastMouseX = mousePosX;
mQuickPlayPos = Pos2Time(mousePosX); mQuickPlayPos = Pos2Time(mousePosX);
// If not looping, restrict selection to end of project // If not looping, restrict selection to end of project
if (!evt.ShiftDown()) mQuickPlayPos = wxMin(t1, mQuickPlayPos); if (!evt.ShiftDown()) mQuickPlayPos = std::min(t1, mQuickPlayPos);
if (evt.Leaving()) { if (evt.Leaving()) {
@ -2487,7 +2515,7 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc, bool clear)
TrackPanel *tp = mProject->GetTrackPanel(); TrackPanel *tp = mProject->GetTrackPanel();
wxClientDC cdc(tp); wxClientDC cdc(tp);
double latestEnd = wxMax(mProject->GetTracks()->GetEndTime(), mProject->GetSel1()); double latestEnd = std::max(mProject->GetTracks()->GetEndTime(), mProject->GetSel1());
if (clear || (mQuickPlayPos >= latestEnd)) { if (clear || (mQuickPlayPos >= latestEnd)) {
tp->TrackPanel::DrawQuickPlayIndicator(cdc, -1); tp->TrackPanel::DrawQuickPlayIndicator(cdc, -1);
return; return;

View File

@ -23,6 +23,7 @@ class ViewInfo;
class AudacityProject; class AudacityProject;
class TimeTrack; class TimeTrack;
class SnapManager; class SnapManager;
class NumberScale;
class AUDACITY_DLL_API Ruler { class AUDACITY_DLL_API Ruler {
public: public:
@ -98,6 +99,9 @@ class AUDACITY_DLL_API Ruler {
// Good defaults are provided, but you can override here // Good defaults are provided, but you can override here
void SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont); void SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont);
// Copies *pScale if it is not NULL
void SetNumberScale(const NumberScale *pScale);
// The ruler will not draw text within this (pixel) range. // The ruler will not draw text within this (pixel) range.
// Use this if you have another graphic object obscuring part // Use this if you have another graphic object obscuring part
// of the ruler's area. The values start and end are interpreted // of the ruler's area. The values start and end are interpreted
@ -227,6 +231,8 @@ private:
bool mTwoTone; bool mTwoTone;
bool mUseZoomInfo; bool mUseZoomInfo;
int mLeftOffset; int mLeftOffset;
NumberScale *mpNumberScale;
}; };
class AUDACITY_DLL_API RulerPanel : public wxPanel { class AUDACITY_DLL_API RulerPanel : public wxPanel {

View File

@ -533,6 +533,7 @@
<ClInclude Include="..\..\..\src\import\MultiFormatReader.h" /> <ClInclude Include="..\..\..\src\import\MultiFormatReader.h" />
<ClInclude Include="..\..\..\src\import\SpecPowerMeter.h" /> <ClInclude Include="..\..\..\src\import\SpecPowerMeter.h" />
<ClInclude Include="..\..\..\src\ModuleManager.h" /> <ClInclude Include="..\..\..\src\ModuleManager.h" />
<ClInclude Include="..\..\..\src\NumberScale.h" />
<ClInclude Include="..\..\..\src\prefs\SpectrogramSettings.h" /> <ClInclude Include="..\..\..\src\prefs\SpectrogramSettings.h" />
<ClInclude Include="..\..\..\src\RevisionIdent.h" /> <ClInclude Include="..\..\..\src\RevisionIdent.h" />
<ClInclude Include="..\..\..\src\SelectedRegion.h" /> <ClInclude Include="..\..\..\src\SelectedRegion.h" />

View File

@ -1685,6 +1685,9 @@
<ClInclude Include="..\..\..\src\prefs\SpectrogramSettings.h"> <ClInclude Include="..\..\..\src\prefs\SpectrogramSettings.h">
<Filter>src/prefs</Filter> <Filter>src/prefs</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\src\NumberScale.h">
<Filter>src</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="..\..\audacity.ico"> <Image Include="..\..\audacity.ico">