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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ Paul Licameli
#include "../Audacity.h"
#include "SpectrogramSettings.h"
#include "../NumberScale.h"
#include <algorithm>
#include <wx/msgdlg.h>
@ -23,7 +24,6 @@ Paul Licameli
#include "../Prefs.h"
#include "../RealFFTf.h"
#include <algorithm>
#include <cmath>
SpectrogramSettings::Globals::Globals()
@ -161,6 +161,10 @@ const wxArrayString &SpectrogramSettings::GetScaleNames()
// Keep in correspondence with enum SpectrogramSettings::ScaleType:
theArray.Add(_("Linear"));
theArray.Add(_("Logarithmic"));
theArray.Add(_("Mel"));
theArray.Add(_("Bark"));
theArray.Add(_("Erb"));
theArray.Add(_("Undertone"));
}
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
{
#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH

View File

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

View File

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

View File

@ -23,6 +23,7 @@ class ViewInfo;
class AudacityProject;
class TimeTrack;
class SnapManager;
class NumberScale;
class AUDACITY_DLL_API Ruler {
public:
@ -98,6 +99,9 @@ class AUDACITY_DLL_API Ruler {
// Good defaults are provided, but you can override here
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.
// Use this if you have another graphic object obscuring part
// of the ruler's area. The values start and end are interpreted
@ -227,6 +231,8 @@ private:
bool mTwoTone;
bool mUseZoomInfo;
int mLeftOffset;
NumberScale *mpNumberScale;
};
class AUDACITY_DLL_API RulerPanel : public wxPanel {

View File

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

View File

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