diff --git a/src/Makefile.am b/src/Makefile.am index 0df9b9025..023f1ec44 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -171,6 +171,7 @@ audacity_SOURCES = \ MixerBoard.h \ ModuleManager.cpp \ ModuleManager.h \ + NumberScale.h \ PitchName.cpp \ PitchName.h \ PlatformCompatibility.cpp \ diff --git a/src/NumberScale.h b/src/NumberScale.h new file mode 100644 index 000000000..b68a557f5 --- /dev/null +++ b/src/NumberScale.h @@ -0,0 +1,279 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +NumberScale.h + +Paul Licameli + +**********************************************************************/ + +#ifndef __AUDACITY_NUMBER_SCALE__ +#define __AUDACITY_NUMBER_SCALE__ + +#include +#include +#include +#include + +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 diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index b23940050..a521ede78 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -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); diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index ea113b475..f419eb49d 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -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(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(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(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(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(mCapturedTrack); WaveTrack *partner = static_cast(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 { diff --git a/src/TrackPanel.h b/src/TrackPanel.h index c93eaab3c..d5cdc578c 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -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 { diff --git a/src/prefs/SpectrogramSettings.cpp b/src/prefs/SpectrogramSettings.cpp index 173d2f58a..abf529428 100644 --- a/src/prefs/SpectrogramSettings.cpp +++ b/src/prefs/SpectrogramSettings.cpp @@ -15,6 +15,7 @@ Paul Licameli #include "../Audacity.h" #include "SpectrogramSettings.h" +#include "../NumberScale.h" #include #include @@ -23,7 +24,6 @@ Paul Licameli #include "../Prefs.h" #include "../RealFFTf.h" -#include #include 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 diff --git a/src/prefs/SpectrogramSettings.h b/src/prefs/SpectrogramSettings.h index 3f4a34dca..a15f36093 100644 --- a/src/prefs/SpectrogramSettings.h +++ b/src/prefs/SpectrogramSettings.h @@ -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; diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index 994e15fe8..d0c5a6eb6 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -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; diff --git a/src/widgets/Ruler.h b/src/widgets/Ruler.h index 935659e81..60b988d72 100644 --- a/src/widgets/Ruler.h +++ b/src/widgets/Ruler.h @@ -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 { diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 6ce73f628..30aeb05e2 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -533,6 +533,7 @@ + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index 566f52817..e767fbf09 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -1685,6 +1685,9 @@ src/prefs + + src +