unsigned WaveTrackVRulerControls::HandleWheelRotation (const TrackPanelMouseEvent &evt, AudacityProject *pProject) { using namespace RefreshCode; const wxMouseEvent &event = evt.event; if (!(event.ShiftDown() || event.CmdDown())) return RefreshNone; // Always stop propagation even if the ruler didn't change. The ruler // is a narrow enough target. evt.event.Skip(false); const auto pTrack = FindTrack(); if (!pTrack) return RefreshNone; wxASSERT(pTrack->GetKind() == Track::Wave); auto steps = evt.steps; const auto wt = static_cast<WaveTrack*>(pTrack.get()); // Assume linked track is wave or null const auto partner = static_cast<WaveTrack*>(wt->GetLink()); const bool isDB = wt->GetDisplay() == WaveTrack::Waveform && wt->GetWaveformSettings().scaleType == WaveformSettings::stLogarithmic; // Special cases for Waveform dB only. // Set the bottom of the dB scale but only if it's visible if (isDB && event.ShiftDown() && event.CmdDown()) { float min, max; wt->GetDisplayBounds(&min, &max); if (!(min < 0.0 && max > 0.0)) return RefreshNone; WaveformSettings &settings = wt->GetIndependentWaveformSettings(); float olddBRange = settings.dBRange; if (steps < 0) // Zoom out settings.NextLowerDBRange(); else settings.NextHigherDBRange(); float newdBRange = settings.dBRange; if (partner) { WaveformSettings &settings = partner->GetIndependentWaveformSettings(); if (steps < 0) // Zoom out settings.NextLowerDBRange(); else settings.NextHigherDBRange(); } // Is y coordinate within the rectangle half-height centered about // the zero level? const auto &rect = evt.rect; const auto zeroLevel = wt->ZeroLevelYCoordinate(rect); const bool fixedMagnification = (4 * std::abs(event.GetY() - zeroLevel) < rect.GetHeight()); if (fixedMagnification) { // Vary the db limit without changing // magnification; that is, peaks and troughs move up and down // rigidly, as parts of the wave near zero are exposed or hidden. const float extreme = (LINEAR_TO_DB(2) + newdBRange) / newdBRange; max = std::min(extreme, max * olddBRange / newdBRange); min = std::max(-extreme, min * olddBRange / newdBRange); wt->SetLastdBRange(); wt->SetDisplayBounds(min, max); if (partner) { partner->SetLastdBRange(); partner->SetDisplayBounds(min, max); } } } else if (event.CmdDown() && !event.ShiftDown()) { const int yy = event.m_y; const auto partner = static_cast<WaveTrack *>(wt); WaveTrackVZoomHandle::DoZoom( pProject, wt, partner, (steps < 0)?kZoomOut:kZoomIn, evt.rect, yy, yy, true); } else if (!event.CmdDown() && event.ShiftDown()) { // Scroll some fixed number of pixels, independent of zoom level or track height: static const float movement = 10.0f; const int height = evt.rect.GetHeight(); const bool spectral = (wt->GetDisplay() == WaveTrack::Spectrum); if (spectral) { const float delta = steps * movement / height; SpectrogramSettings &settings = wt->GetIndependentSpectrogramSettings(); const bool isLinear = settings.scaleType == SpectrogramSettings::stLinear; float bottom, top; wt->GetSpectrumBounds(&bottom, &top); const double rate = wt->GetRate(); const float bound = rate / 2; const NumberScale numberScale(settings.GetScale(bottom, top)); float newTop = std::min(bound, numberScale.PositionToValue(1.0f + delta)); const float newBottom = std::max((isLinear ? 0.0f : 1.0f), numberScale.PositionToValue(numberScale.ValueToPosition(newTop) - 1.0f)); newTop = std::min(bound, numberScale.PositionToValue(numberScale.ValueToPosition(newBottom) + 1.0f)); wt->SetSpectrumBounds(newBottom, newTop); if (partner) partner->SetSpectrumBounds(newBottom, newTop); } else { float topLimit = 2.0; if (isDB) { const float dBRange = wt->GetWaveformSettings().dBRange; topLimit = (LINEAR_TO_DB(topLimit) + dBRange) / dBRange; } const float bottomLimit = -topLimit; float top, bottom; wt->GetDisplayBounds(&bottom, &top); const float range = top - bottom; const float delta = range * steps * movement / height; float newTop = std::min(topLimit, top + delta); const float newBottom = std::max(bottomLimit, newTop - range); newTop = std::min(topLimit, newBottom + range); wt->SetDisplayBounds(newBottom, newTop); if (partner) partner->SetDisplayBounds(newBottom, newTop); } } else return RefreshNone; pProject->ModifyState(true); return RefreshCell | UpdateVRuler; }
// ZoomKind says how to zoom. // If ZoomStart and ZoomEnd are not equal, this may override // the zoomKind and cause a drag-zoom-in. void WaveTrackVZoomHandle::DoZoom (AudacityProject *pProject, WaveTrack *pTrack, int ZoomKind, const wxRect &rect, int zoomStart, int zoomEnd, bool fixedMousePoint) { static const float ZOOMLIMIT = 0.001f; // Assume linked track is wave or null const auto partner = static_cast<WaveTrack *>(pTrack->GetLink()); int height = rect.height; int ypos = rect.y; // Ensure start and end are in order (swap if not). if (zoomEnd < zoomStart) std::swap( zoomStart, zoomEnd ); float min, max, c, minBand = 0; const double rate = pTrack->GetRate(); const float halfrate = rate / 2; float maxFreq = 8000.0; const SpectrogramSettings &settings = pTrack->GetSpectrogramSettings(); NumberScale scale; const bool spectral = (pTrack->GetDisplay() == WaveTrack::Spectrum); const bool spectrumLinear = spectral && (pTrack->GetSpectrogramSettings().scaleType == SpectrogramSettings::stLinear); bool bDragZoom = IsDragZooming(zoomStart, zoomEnd); // Add 100 if spectral to separate the kinds of zoom. const int kSpectral = 100; // Possibly override the zoom kind. if( bDragZoom ) ZoomKind = kZoomInByDrag; // If we are actually zooming a spectrum rather than a wave. ZoomKind += spectral ? kSpectral:0; float top=2.0; float half=0.5; if (spectral) { pTrack->GetSpectrumBounds(&min, &max); scale = (settings.GetScale(min, max)); const auto fftLength = settings.GetFFTLength(); const float binSize = rate / fftLength; maxFreq = gPrefs->Read(wxT("/Spectrum/MaxFreq"), 8000L); // JKC: Following discussions of Bug 1208 I'm allowing zooming in // down to one bin. // const int minBins = // std::min(10, fftLength / 2); //minimum 10 freq bins, unless there are less const int minBins = 1; minBand = minBins * binSize; } else{ pTrack->GetDisplayBounds(&min, &max); const WaveformSettings &settings = pTrack->GetWaveformSettings(); const bool linear = settings.isLinear(); if( !linear ){ top = (LINEAR_TO_DB(2.0) + settings.dBRange) / settings.dBRange; half = (LINEAR_TO_DB(0.5) + settings.dBRange) / settings.dBRange; } } // Compute min and max. switch(ZoomKind) { default: // If we have covered all the cases, this won't happen. // In release builds Audacity will ignore the zoom. wxFAIL_MSG("Zooming Case not implemented by Audacity"); break; case kZoomReset: case kZoom1to1: { // Zoom out full min = -1.0; max = 1.0; } break; case kZoomDiv2: { // Zoom out even more than full :-) // -2.0..+2.0 (or logarithmic equivalent) min = -top; max = top; } break; case kZoomTimes2: { // Zoom in to -0.5..+0.5 min = -half; max = half; } break; case kZoomHalfWave: { // Zoom to show fractionally more than the top half of the wave. min = -0.01f; max = 1.0; } break; case kZoomInByDrag: { const float tmin = min, tmax = max; const float p1 = (zoomStart - ypos) / (float)height; const float p2 = (zoomEnd - ypos) / (float)height; max = (tmax * (1.0 - p1) + tmin * p1); min = (tmax * (1.0 - p2) + tmin * p2); // 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; } } break; case kZoomIn: { // Zoom in centered on cursor if (min < -1.0 || max > 1.0) { min = -1.0; max = 1.0; } else { // Enforce maximum vertical zoom const float oldRange = max - min; const float l = std::max(ZOOMLIMIT, 0.5f * oldRange); const float ratio = l / (max - min); const float p1 = (zoomStart - ypos) / (float)height; const float c = (max * (1.0 - p1) + min * p1); if (fixedMousePoint) min = c - ratio * (1.0f - p1) * oldRange, max = c + ratio * p1 * oldRange; else min = c - 0.5 * l, max = c + 0.5 * l; } } break; case kZoomOut: { // Zoom out if (min <= -1.0 && max >= 1.0) { min = -top; max = top; } else { // limit to +/- 1 range unless already outside that range... float minRange = (min < -1) ? -top : -1.0; float maxRange = (max > 1) ? top : 1.0; // and enforce vertical zoom limits. const float p1 = (zoomStart - ypos) / (float)height; if (fixedMousePoint) { const float oldRange = max - min; const float c = (max * (1.0 - p1) + min * p1); min = std::min(maxRange - ZOOMLIMIT, std::max(minRange, c - 2 * (1.0f - p1) * oldRange)); max = std::max(minRange + ZOOMLIMIT, std::min(maxRange, c + 2 * p1 * oldRange)); } else { const float c = p1 * min + (1 - p1) * max; const float l = (max - min); min = std::min(maxRange - ZOOMLIMIT, std::max(minRange, c - l)); max = std::max(minRange + ZOOMLIMIT, std::min(maxRange, c + l)); } } } break; // VZooming on spectral we don't implement the other zoom presets. // They are also not in the menu. case kZoomReset + kSpectral: { // Zoom out to normal level. min = spectrumLinear ? 0.0f : 1.0f; max = maxFreq; } break; case kZoom1to1 + kSpectral: case kZoomDiv2 + kSpectral: case kZoomTimes2 + kSpectral: case kZoomHalfWave + kSpectral: { // Zoom out full min = spectrumLinear ? 0.0f : 1.0f; max = halfrate; } break; case kZoomInByDrag + kSpectral: { double xmin = 1 - (zoomEnd - ypos) / (float)height; double xmax = 1 - (zoomStart - 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) )); } break; case kZoomIn + kSpectral: { // Center the zoom-in at the click const float p1 = (zoomStart - ypos) / (float)height; const float middle = 1.0f - p1; const float middleValue = scale.PositionToValue(middle); if (fixedMousePoint) { min = std::max(spectrumLinear ? 0.0f : 1.0f, std::min(middleValue - minBand * middle, scale.PositionToValue(0.5f * middle) )); max = std::min(halfrate, std::max(middleValue + minBand * p1, scale.PositionToValue(middle + 0.5f * p1) )); } else { 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) )); } } break; case kZoomOut + kSpectral: { // Zoom out const float p1 = (zoomStart - ypos) / (float)height; // (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 - p1; if (fixedMousePoint) { min = std::max(spectrumLinear ? 0.0f : 1.0f, scale.PositionToValue(-middle)); max = std::min(halfrate, scale.PositionToValue(1.0f + p1)); } else { min = std::max(spectrumLinear ? 0.0f : 1.0f, scale.PositionToValue(middle - 1.0f)); max = std::min(halfrate, scale.PositionToValue(middle + 1.0f)); } } break; } // Now actually apply the zoom. if (spectral) { pTrack->SetSpectrumBounds(min, max); if (partner) partner->SetSpectrumBounds(min, max); } else { pTrack->SetDisplayBounds(min, max); if (partner) partner->SetDisplayBounds(min, max); } zoomEnd = zoomStart = 0; if( pProject ) pProject->ModifyState(true); }