diff --git a/src/Envelope.cpp b/src/Envelope.cpp index 61b719e47..e2eec3ffd 100644 --- a/src/Envelope.cpp +++ b/src/Envelope.cpp @@ -27,6 +27,7 @@ a draggable point type. *//*******************************************************************/ #include "Envelope.h" +#include "ViewInfo.h" #include @@ -65,6 +66,8 @@ Envelope::Envelope() mMaxValue = 2.0; mButton = wxMOUSE_BTN_NONE; + + mSearchGuess = -1; } Envelope::~Envelope() @@ -199,13 +202,9 @@ static void DrawPoint(wxDC & dc, const wxRect & r, int x, int y, bool top) } /// TODO: This should probably move to track artist. -void Envelope::DrawPoints(wxDC & dc, const wxRect & r, double h, double pps, bool dB, +void Envelope::DrawPoints(wxDC & dc, const wxRect & r, const ZoomInfo &zoomInfo, bool dB, float zoomMin, float zoomMax) { - h -= mOffset; - - wxASSERT( pps > 0 ); - double tright = h + (r.width / pps); // TODO: Cache the gPrefs value. Reading it every time is inefficient. double dBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE); @@ -213,7 +212,9 @@ void Envelope::DrawPoints(wxDC & dc, const wxRect & r, double h, double pps, boo dc.SetBrush(*wxWHITE_BRUSH); for (int i = 0; i < (int)mEnv.Count(); i++) { - if (mEnv[i]->GetT() >= h && mEnv[i]->GetT() <= tright) { + const double time = mEnv[i]->GetT() + mOffset; + const wxInt64 position = zoomInfo.TimeToPosition(time); + if (position >= 0 && position < r.width) { // Change colour if this is the draggable point... if (i == mDragPoint) { dc.SetPen(AColor::envelopePen); @@ -221,7 +222,7 @@ void Envelope::DrawPoints(wxDC & dc, const wxRect & r, double h, double pps, boo } double v = mEnv[i]->GetVal(); - int x = int ((mEnv[i]->GetT() - h) * pps); + int x = int(position); int y, y2; y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB, @@ -317,9 +318,10 @@ void Envelope::WriteXML(XMLWriter &xmlFile) xmlFile.EndTag(wxT("envelope")); } -#ifndef SQR -#define SQR(X) ((X)*(X)) -#endif +namespace +{ +inline int SQR(int x) { return x * x; } +} /// ValueOfPixel() converts a y position on screen to an envelope value. /// @param y - y position, usually of the mouse.relative to the clip. @@ -353,7 +355,7 @@ float Envelope::ValueOfPixel( int y, int height, bool upper, bool dB, /// We have an upper and lower envelope line. /// Also we may be showing an inner envelope (at 0.5 the range). bool Envelope::HandleMouseButtonDown(wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, + const ZoomInfo &zoomInfo, bool dB, float zoomMin, float zoomMax) { int ctr = (int)(r.height * zoomMax / (zoomMax - zoomMin)); @@ -363,10 +365,8 @@ bool Envelope::HandleMouseButtonDown(wxMouseEvent & event, wxRect & r, if(clip_y < 0) clip_y = 0; //keeps point in rect r, even if mouse isn't if(clip_y > r.GetBottom()) clip_y = r.GetBottom(); - double tleft = h - mOffset; - double tright = tleft + (r.width / pps); int bestNum = -1; - int bestDist = 10; // Must be within 10 pixel radius. + int bestDistSqr = 100; // Must be within 10 pixel radius. // TODO: Cache the gPrefs value. Reading it every time is inefficient. double dBr = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE); @@ -382,22 +382,25 @@ bool Envelope::HandleMouseButtonDown(wxMouseEvent & event, wxRect & r, // TODO: extract this into a function FindNearestControlPoint() // TODO: also fix it so that we can drag the last point on an envelope. for (int i = 0; i < len; i++) { //search for control point nearest click - if (mEnv[i]->GetT() >= tleft && mEnv[i]->GetT() <= tright) { + const double time = mEnv[i]->GetT() + mOffset; + const wxInt64 position = zoomInfo.TimeToPosition(time); + if (position >= 0 && position < r.width) { - int x = int ((mEnv[i]->GetT() + mOffset - h) * pps) + r.x; + int x = int (position); int y[4]; int numControlPoints; // Outer control points - y[0] = GetWaveYPos( mEnv[i]->GetVal(), zoomMin, zoomMax, r.height, + double value = mEnv[i]->GetVal(); + y[0] = GetWaveYPos(value, zoomMin, zoomMax, r.height, dB, true, dBr, false); - y[1] = GetWaveYPos( -mEnv[i]->GetVal(), zoomMin, zoomMax, r.height, + y[1] = GetWaveYPos(-value, zoomMin, zoomMax, r.height, dB, true, dBr, false); // Inner control points(contour) - y[2] = GetWaveYPos( mEnv[i]->GetVal(), zoomMin, zoomMax, r.height, + y[2] = GetWaveYPos(value, zoomMin, zoomMax, r.height, dB, false, dBr, false); - y[3] = GetWaveYPos( -mEnv[i]->GetVal()-.00000001, zoomMin, zoomMax, + y[3] = GetWaveYPos(-value -.00000001, zoomMin, zoomMax, r.height, dB, false, dBr, false); numControlPoints = 4; @@ -408,12 +411,13 @@ bool Envelope::HandleMouseButtonDown(wxMouseEvent & event, wxRect & r, if (!mMirror) numControlPoints = 1; + const int deltaXSquared = SQR(x - (event.m_x - r.x)); for(int j=0; j 1); } } @@ -425,12 +429,12 @@ bool Envelope::HandleMouseButtonDown(wxMouseEvent & event, wxRect & r, } else { // TODO: Extract this into a function CreateNewPoint - double when = h + (event.m_x - r.x) / pps - mOffset; + const double when = zoomInfo.PositionToTime(event.m_x, r.x); // if (when <= 0 || when >= mTrackLen) // return false; - double v = GetValueAtX( event.m_x, r, h, pps ); + const double v = GetValue( when ); int ct = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB, false, dBr, false) ; @@ -456,16 +460,14 @@ bool Envelope::HandleMouseButtonDown(wxMouseEvent & event, wxRect & r, double newVal = ValueOfPixel(clip_y, r.height, upper, dB, zoomMin, zoomMax); - mDragPoint = Insert(when, newVal); + mDragPoint = Insert(when - mOffset, newVal); mDirty = true; } mUpper = upper; - mInitialWhen = mEnv[mDragPoint]->GetT(); mInitialVal = mEnv[mDragPoint]->GetVal(); - mInitialX = event.m_x; mInitialY = event.m_y+mContourOffset; return true; @@ -500,8 +502,8 @@ void Envelope::MarkDragPointForDeletion() } void Envelope::MoveDraggedPoint( wxMouseEvent & event, wxRect & r, - double WXUNUSED(h), double pps, bool dB, - float zoomMin, float zoomMax) + const ZoomInfo &zoomInfo, bool dB, + float zoomMin, float zoomMax) { int clip_y = event.m_y - r.y; if(clip_y < 0) clip_y = 0; @@ -509,13 +511,12 @@ void Envelope::MoveDraggedPoint( wxMouseEvent & event, wxRect & r, double newVal = ValueOfPixel(clip_y, r.height, mUpper, dB, zoomMin, zoomMax); - wxASSERT( pps > 0 ); // We no longer tolerate multiple envelope points at the same t. // epsilon is less than the time offset of a single sample // TODO: However because mTrackEpsilon assumes 200KHz this use // of epsilon is a tad bogus. What we need to do instead is delete // a duplicated point on a mouse up. - double newWhen = mInitialWhen + (event.m_x - mInitialX) / pps; + double newWhen = zoomInfo.PositionToTime(event.m_x, r.x) - mOffset; // We'll limit the drag point time to be between those of the preceding // and next envelope point. @@ -536,7 +537,7 @@ void Envelope::MoveDraggedPoint( wxMouseEvent & event, wxRect & r, } bool Envelope::HandleDragging( wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, + const ZoomInfo &zoomInfo, bool dB, float zoomMin, float zoomMax, float WXUNUSED(eMin), float WXUNUSED(eMax)) { @@ -550,7 +551,7 @@ bool Envelope::HandleDragging( wxMouseEvent & event, wxRect & r, // IF we're in the rect THEN we're not deleting this point (anymore). mIsDeleting = false; // ...we're dragging it. - MoveDraggedPoint( event, r,h,pps,dB, zoomMin, zoomMax); + MoveDraggedPoint( event, r, zoomInfo, dB, zoomMin, zoomMax); return true; } @@ -563,11 +564,7 @@ bool Envelope::HandleDragging( wxMouseEvent & event, wxRect & r, } // Exit dragging mode and deletes dragged point if neccessary. -bool Envelope::HandleMouseButtonUp( wxMouseEvent & WXUNUSED(event), wxRect & WXUNUSED(r), - double WXUNUSED(h), - double WXUNUSED(pps), bool WXUNUSED(dB), - float WXUNUSED(zoomMin), - float WXUNUSED(zoomMax) ) +bool Envelope::HandleMouseButtonUp() { if (mIsDeleting) { delete mEnv[mDragPoint]; @@ -586,18 +583,17 @@ void Envelope::Delete( int point ) // Returns true if parent needs to be redrawn bool Envelope::MouseEvent(wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, + const ZoomInfo &zoomInfo, bool dB, float zoomMin, float zoomMax) { if (event.ButtonDown() && mButton == wxMOUSE_BTN_NONE) - return HandleMouseButtonDown( event, r, h, pps,dB, + return HandleMouseButtonDown( event, r, zoomInfo,dB, zoomMin, zoomMax); if (event.Dragging() && mDragPoint >= 0) - return HandleDragging( event, r, h, pps,dB, + return HandleDragging( event, r, zoomInfo,dB, zoomMin, zoomMax); if (event.ButtonUp() && event.GetButton() == mButton) - return HandleMouseButtonUp( event, r, h, pps, dB, - zoomMin, zoomMax); + return HandleMouseButtonUp(); return false; } @@ -1023,28 +1019,45 @@ void Envelope::SetTrackLen(double trackLen) // Accessors double Envelope::GetValue(double t) const { + // t is absolute time double temp; GetValues(&temp, 1, t, 1.0); return temp; } -// 'X' is in pixels and relative to track. -double Envelope::GetValueAtX(int x, const wxRect & r, double h, double pps) -{ - // Convert x to time. - double t = (x - r.x) / pps + h ;//-mOffset; - return GetValue(t); -} - -/// @param Lo returns index before this time. -/// @param Hi returns index after this time. +/// @param Lo returns last index at or before this time. +/// @param Hi returns first index after this time. void Envelope::BinarySearchForTime( int &Lo, int &Hi, double t ) const { Lo = 0; Hi = mEnv.Count() - 1; // JC: Do we have a problem if the envelope only has one point?? - wxASSERT( Hi > Lo ); + wxASSERT(Hi > Lo); + + // Optimizations for the usual pattern of repeated calls with + // small increases of t. + { + if (mSearchGuess >= 0 && mSearchGuess < int(mEnv.Count()) - 1) { + if (t >= mEnv[mSearchGuess]->GetT() && + t < mEnv[1 + mSearchGuess]->GetT()) { + Lo = mSearchGuess; + Hi = 1 + mSearchGuess; + return; + } + } + + ++mSearchGuess; + if (mSearchGuess >= 0 && mSearchGuess < int(mEnv.Count()) - 1) { + if (t >= mEnv[mSearchGuess]->GetT() && + t < mEnv[1 + mSearchGuess]->GetT()) { + Lo = mSearchGuess; + Hi = 1 + mSearchGuess; + return; + } + } + } + while (Hi > (Lo + 1)) { int mid = (Lo + Hi) / 2; if (t < mEnv[mid]->GetT()) @@ -1053,6 +1066,8 @@ void Envelope::BinarySearchForTime( int &Lo, int &Hi, double t ) const Lo = mid; } wxASSERT( Hi == ( Lo+1 )); + + mSearchGuess = Lo; } /// GetInterpolationStartValueAtPoint() is used to select either the @@ -1072,6 +1087,7 @@ double Envelope::GetInterpolationStartValueAtPoint( int iPoint ) const void Envelope::GetValues(double *buffer, int bufferLen, double t0, double tstep) const { + // Convert t0 from absolute to clip-relative time t0 -= mOffset; // JC: If bufferLen ==0 we have probably just allocated a zero sized buffer. @@ -1154,6 +1170,13 @@ void Envelope::GetValues(double *buffer, int bufferLen, } } +void Envelope::GetValues + (double *buffer, int bufferLen, int leftOffset, const ZoomInfo &zoomInfo) const +{ + for (int xx = 0; xx < bufferLen; ++xx) + buffer[xx] = GetValue(zoomInfo.PositionToTime(xx, -leftOffset)); +} + int Envelope::NumberOfPointsAfter(double t) { if( t >= mEnv[mEnv.Count()-1]->GetT() ) diff --git a/src/Envelope.h b/src/Envelope.h index c1e25bc59..508c5c76d 100644 --- a/src/Envelope.h +++ b/src/Envelope.h @@ -29,6 +29,8 @@ class wxTextFile; class DirManager; class Envelope; +class ZoomInfo; + #define ENV_DB_RANGE 60 class EnvPoint : public XMLTagHandler { @@ -116,23 +118,21 @@ class Envelope : public XMLTagHandler { virtual XMLTagHandler *HandleXMLChild(const wxChar *tag); virtual void WriteXML(XMLWriter &xmlFile); - void DrawPoints(wxDC & dc, const wxRect & r, double h, double pps, bool dB, + void DrawPoints(wxDC & dc, const wxRect & r, const ZoomInfo &zoomInfo, bool dB, float zoomMin=-1.0, float zoomMax=1.0); // Event Handlers // Each ofthese returns true if parents needs to be redrawn bool MouseEvent(wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, + const ZoomInfo &zoomInfo, bool dB, float zoomMin=-1.0, float zoomMax=1.0); bool HandleMouseButtonDown( wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, + const ZoomInfo &zoomInfo, bool dB, float zoomMin=-1.0, float zoomMax=1.0); bool HandleDragging( wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, + const ZoomInfo &zoomInfo, bool dB, float zoomMin=-1.0, float zoomMax=1.0, float eMin=0., float eMax=2.); - bool HandleMouseButtonUp( wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, - float zoomMin=-1.0, float zoomMax=1.0); + bool HandleMouseButtonUp(); void GetEventParams( int &height, bool &upper, bool dB, wxMouseEvent & event, wxRect & r, float &zoomMin, float &zoomMax); @@ -151,8 +151,6 @@ class Envelope : public XMLTagHandler { // Accessors /** \brief Get envelope value at time t */ double GetValue(double t) const; - /** \brief Get envelope value at pixel X */ - double GetValueAtX(int x, const wxRect & r, double h, double pps); /** \brief Get many envelope points at once. * @@ -160,6 +158,11 @@ class Envelope : public XMLTagHandler { * more than one value in a row. */ void GetValues(double *buffer, int len, double t0, double tstep) const; + /** \brief Get many envelope points at once, but don't assume uniform time step. + */ + void GetValues + (double *buffer, int bufferLen, int leftOffset, const ZoomInfo &zoomInfo) const; + int NumberOfPointsAfter(double t); double NextPointAfter(double t); @@ -202,7 +205,7 @@ private: void BinarySearchForTime( int &Lo, int &Hi, double t ) const; double GetInterpolationStartValueAtPoint( int iPoint ) const; void MoveDraggedPoint( wxMouseEvent & event, wxRect & r, - double h, double pps, bool dB, + const ZoomInfo &zoomInfo, bool dB, float zoomMin, float zoomMax); // Possibly inline functions: @@ -228,12 +231,10 @@ private: /** \brief Number of pixels contour is from the true envelope. */ int mContourOffset; - double mInitialWhen; double mInitialVal; // These are used in dragging. int mDragPoint; - int mInitialX; int mInitialY; bool mUpper; bool mIsDeleting; @@ -248,6 +249,8 @@ private: double lastIntegral_t1; double lastIntegral_result; + mutable int mSearchGuess; + }; inline double EnvPoint::ClampValue(double val) diff --git a/src/LabelDialog.h b/src/LabelDialog.h index 29640d620..1aa46b7f2 100644 --- a/src/LabelDialog.h +++ b/src/LabelDialog.h @@ -25,7 +25,7 @@ class TrackList; class RowData; class EmptyLabelRenderer; class LabelTrack; -struct ViewInfo; +class ViewInfo; WX_DEFINE_ARRAY(RowData *, RowDataArray); diff --git a/src/LabelTrack.cpp b/src/LabelTrack.cpp index b688f86db..c8c786c03 100644 --- a/src/LabelTrack.cpp +++ b/src/LabelTrack.cpp @@ -444,7 +444,7 @@ void LabelTrack::ComputeTextPosition(const wxRect & r, int index) /// ComputeLayout determines which row each label /// should be placed on, and reserves space for it. /// Function assumes that the labels are sorted. -void LabelTrack::ComputeLayout(const wxRect & r, double h, double pps) +void LabelTrack::ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo) { int i; int iRow; @@ -461,15 +461,17 @@ void LabelTrack::ComputeLayout(const wxRect & r, double h, double pps) const int nRows = wxMin((r.height / yRowHeight) + 1, MAX_NUM_ROWS); // Initially none of the rows have been used. // So set a value that is less than any valid value. - const int xStart = r.x -(int)(h*pps) -100; - for(i=0;igetT0() - h) * pps); - int x1 = r.x + (int) ((mLabels[i]->getT1() - h) * pps); + const int x = zoomInfo.TimeToPosition(mLabels[i]->getT0(), r.x); + const int x1 = zoomInfo.TimeToPosition(mLabels[i]->getT1(), r.x); int y = r.y; mLabels[i]->x=x; @@ -727,8 +729,9 @@ bool LabelTrack::CalcCursorX(wxWindow * parent, int * x) /// Draw calls other functions to draw the LabelTrack. /// @param dc the device context /// @param r the LabelTrack rectangle. -void LabelTrack::Draw(wxDC & dc, const wxRect & r, double h, double pps, - double sel0, double sel1) +void LabelTrack::Draw(wxDC & dc, const wxRect & r, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo) { if(msFont.Ok()) dc.SetFont(msFont); @@ -738,7 +741,7 @@ void LabelTrack::Draw(wxDC & dc, const wxRect & r, double h, double pps, TrackArtist::DrawBackgroundWithSelection(&dc, r, this, AColor::labelSelectedBrush, AColor::labelUnselectedBrush, - sel0, sel1, h, pps); + selectedRegion, zoomInfo); int i; @@ -765,7 +768,7 @@ void LabelTrack::Draw(wxDC & dc, const wxRect & r, double h, double pps, // happens with a new label track. dc.GetTextExtent(wxT("Demo Text x^y"), &textWidth, &textHeight); mTextHeight = (int)textHeight; - ComputeLayout( r, h , pps ); + ComputeLayout( r, zoomInfo ); dc.SetTextForeground(theTheme.Colour( clrLabelTrackText)); dc.SetBackgroundMode(wxTRANSPARENT); dc.SetBrush(AColor::labelTextNormalBrush); @@ -1382,7 +1385,7 @@ static int Constrain( int value, int min, int max ) /// HandleMouse gets called with every mouse move or click. /// bool LabelTrack::HandleMouse(const wxMouseEvent & evt, - wxRect & r, double h, double pps, + wxRect & r, const ZoomInfo &zoomInfo, SelectedRegion *newSel) { if(evt.LeftUp()) @@ -1454,7 +1457,7 @@ bool LabelTrack::HandleMouse(const wxMouseEvent & evt, bool bLabelMoving = mbIsMoving; bLabelMoving ^= evt.ShiftDown(); bLabelMoving |= mMouseOverLabelLeft==mMouseOverLabelRight; - double fNewX = h + x / pps; + double fNewX = zoomInfo.PositionToTime(x, 0); if( bLabelMoving ) { MayMoveLabel( mMouseOverLabelLeft, -1, fNewX ); @@ -1552,7 +1555,7 @@ bool LabelTrack::HandleMouse(const wxMouseEvent & evt, { t = mLabels[mMouseOverLabelLeft]->getT0(); } - mxMouseDisplacement = (int)((((t-h) * pps) + r.x )-evt.m_x); + mxMouseDisplacement = zoomInfo.TimeToPosition(t, r.x) - evt.m_x; return false; } @@ -1584,7 +1587,7 @@ bool LabelTrack::HandleMouse(const wxMouseEvent & evt, if (mSelIndex != -1) { if (!OverTextBox(mLabels[mSelIndex], evt.m_x, evt.m_y)) mSelIndex = -1; - double t = h + (evt.m_x - r.x) / pps; + double t = zoomInfo.PositionToTime(evt.m_x, r.x); *newSel = SelectedRegion(t, t); } diff --git a/src/LabelTrack.h b/src/LabelTrack.h index 6129b06cc..f270760db 100644 --- a/src/LabelTrack.h +++ b/src/LabelTrack.h @@ -36,6 +36,7 @@ class TrackList; class AudacityProject; class DirManager; class TimeWarper; +class ZoomInfo; class LabelStruct @@ -125,8 +126,9 @@ class AUDACITY_DLL_API LabelTrack : public Track static void ResetFont(); - void Draw(wxDC & dc, const wxRect & r, double h, double pps, - double sel0, double sel1); + void Draw(wxDC & dc, const wxRect & r, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo); int getSelectedIndex() const { return mSelIndex; } @@ -177,7 +179,7 @@ class AUDACITY_DLL_API LabelTrack : public Track void SetWrongDragging(bool rightFlag) { mRightDragging = rightFlag; } void SetDrawCursor(bool drawCursorFlag) { mDrawCursor = drawCursorFlag; } - bool HandleMouse(const wxMouseEvent & evt, wxRect & r, double h, double pps, + bool HandleMouse(const wxMouseEvent & evt, wxRect & r, const ZoomInfo &zoomInfo, SelectedRegion *newSel); bool CaptureKey(wxKeyEvent & event); @@ -260,7 +262,7 @@ class AUDACITY_DLL_API LabelTrack : public Track // Set in copied label tracks double mClipLen; - void ComputeLayout(const wxRect & r, double h, double pps); + void ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo); void ComputeTextPosition(const wxRect & r, int index); void SetCurrentCursorPosition(wxDC & dc, int xPos); diff --git a/src/Menus.cpp b/src/Menus.cpp index b969785ce..2352113de 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -1770,10 +1770,10 @@ wxUint32 AudacityProject::GetUpdateFlags() if (mUndoManager.RedoAvailable()) flags |= RedoAvailableFlag; - if (GetZoom() < gMaxZoom && (flags & TracksExistFlag)) + if (ZoomInAvailable() && (flags & TracksExistFlag)) flags |= ZoomInAvailableFlag; - if (GetZoom() > gMinZoom && (flags & TracksExistFlag)) + if (ZoomOutAvailable() && (flags & TracksExistFlag)) flags |= ZoomOutAvailableFlag; if ((flags & LabelTracksExistFlag) && LabelTrack::IsTextClipSupported()) @@ -4882,7 +4882,7 @@ void AudacityProject::ZoomInByFactor( double ZoomFactor ) { // LLL: Handling positioning differently when audio is active if (gAudioIO->IsStreamActive(GetAudioIOToken()) != 0) { - Zoom(mViewInfo.zoom * ZoomFactor); + ZoomBy(ZoomFactor); mTrackPanel->ScrollIntoView(gAudioIO->GetStreamTime()); mTrackPanel->Refresh(false); return; @@ -4915,7 +4915,7 @@ void AudacityProject::ZoomInByFactor( double ZoomFactor ) (mViewInfo.h + mViewInfo.screen - mViewInfo.selectedRegion.t0()) / 2; // Zoom in - Zoom(mViewInfo.zoom *= ZoomFactor); + ZoomBy(ZoomFactor); // Recenter on selCenter TP_ScrollWindow(selCenter - mViewInfo.screen / 2); @@ -4925,7 +4925,7 @@ void AudacityProject::ZoomInByFactor( double ZoomFactor ) double origLeft = mViewInfo.h; double origWidth = mViewInfo.screen; - Zoom(mViewInfo.zoom *= ZoomFactor); + ZoomBy(ZoomFactor); double newh = origLeft + (origWidth - mViewInfo.screen) / 2; @@ -4957,7 +4957,7 @@ void AudacityProject::ZoomOutByFactor( double ZoomFactor ) double origLeft = mViewInfo.h; double origWidth = mViewInfo.screen; - Zoom(mViewInfo.zoom *=ZoomFactor); + ZoomBy(ZoomFactor); double newh = origLeft + (origWidth - mViewInfo.screen) / 2; // newh = (newh > 0) ? newh : 0; @@ -4965,6 +4965,8 @@ void AudacityProject::ZoomOutByFactor( double ZoomFactor ) } +// this is unused: +#if 0 static double OldZooms[2]={ 44100.0/512.0, 4410.0/512.0 }; void AudacityProject::OnZoomToggle() { @@ -4984,11 +4986,12 @@ void AudacityProject::OnZoomToggle() double newh = origLeft + (origWidth - mViewInfo.screen) / 2; TP_ScrollWindow(newh); } +#endif void AudacityProject::OnZoomNormal() { - Zoom(44100.0 / 512.0); + Zoom(ZoomInfo::GetDefaultZoom()); mTrackPanel->Refresh(false); } @@ -5074,7 +5077,7 @@ void AudacityProject::OnZoomSel() // where the selected region may be scrolled off the left of the screen. // I know this isn't right, but until the real rounding or 1-off issue is // found, this will have to work. - Zoom(((mViewInfo.zoom * mViewInfo.screen) - 1) / denom); + Zoom((mViewInfo.GetScreenWidth() - 1) / denom); TP_ScrollWindow(mViewInfo.selectedRegion.t0()); } diff --git a/src/Menus.h b/src/Menus.h index ad0e47cc3..2b5fe80e0 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -259,7 +259,7 @@ void OnSelectAllTracks(); void OnZoomIn(); void OnZoomOut(); -void OnZoomToggle(); +// void OnZoomToggle(); void OnZoomNormal(); void OnZoomFit(); void OnZoomFitV(); diff --git a/src/Printing.cpp b/src/Printing.cpp index 0ce8710df..3e9f8974d 100644 --- a/src/Printing.cpp +++ b/src/Printing.cpp @@ -15,6 +15,7 @@ #include "Audacity.h" +#include "Printing.h" #include #include @@ -29,7 +30,6 @@ #include "ViewInfo.h" #include "WaveTrack.h" #include "widgets/Ruler.h" -#include "Printing.h" // Globals, so that we remember settings from session to session static wxPrintData *gPrintData = NULL; @@ -80,13 +80,8 @@ bool AudacityPrintout::OnPrintPage(int WXUNUSED(page)) TrackArtist artist; artist.SetBackgroundBrushes(*wxWHITE_BRUSH, *wxWHITE_BRUSH, *wxWHITE_PEN, *wxWHITE_PEN); - ViewInfo viewInfo; - viewInfo.selectedRegion = SelectedRegion(); - viewInfo.vpos = 0; - viewInfo.h = 0.0; - viewInfo.screen = mTracks->GetEndTime() - viewInfo.h; - viewInfo.total = viewInfo.screen; - viewInfo.zoom = width / viewInfo.screen; + const double screenDuration = mTracks->GetEndTime(); + ZoomInfo zoomInfo(0.0, screenDuration, width / screenDuration); int y = rulerPageHeight; TrackListIterator iter(mTracks); @@ -98,7 +93,7 @@ bool AudacityPrintout::OnPrintPage(int WXUNUSED(page)) r.width = width; r.height = (int)(n->GetHeight() * scale); - artist.DrawTrack(n, *dc, r, &viewInfo, false, false, false, false); + artist.DrawTrack(n, *dc, r, SelectedRegion(), zoomInfo, false, false, false, false); dc->SetPen(*wxBLACK_PEN); AColor::Line(*dc, 0, r.y, width, r.y); diff --git a/src/Project.cpp b/src/Project.cpp index 7bd975546..f77e89872 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -780,6 +780,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, mTimerRecordCanceled(false), mMenuClose(false) , mbInitializingScrollbar(false) + , mViewInfo(0.0, 1.0, ZoomInfo::GetDefaultZoom()) { // Note that the first field of the status bar is a dummy, and it's width is set // to zero latter in the code. This field is needed for wxWidgets 2.8.12 because @@ -810,26 +811,6 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, // Initialize view info (shared with TrackPanel) // - // Selection - mViewInfo.selectedRegion = SelectedRegion(); - - // Horizontal scrollbar - mViewInfo.total = 1.0; - mViewInfo.screen = 1.0; - mViewInfo.h = 0.0; - mViewInfo.zoom = 44100.0 / 512.0; - - // Vertical scrollbar - mViewInfo.track = NULL; - mViewInfo.vpos = 0; - - mViewInfo.scrollStep = 16; - - mViewInfo.sbarH = 0; - mViewInfo.sbarScreen = 1; - mViewInfo.sbarTotal = 1; - mViewInfo.sbarScale = 1.0; - UpdatePrefs(); mLockPlayRegion = false; @@ -1470,19 +1451,22 @@ double AudacityProject::ScrollingLowerBoundTime() const : 0; } +wxInt64 AudacityProject::PixelWidthBeforeTime(double scrollto) const +{ + const double lowerBound = ScrollingLowerBoundTime(); + return + mViewInfo.TimeToPosition(scrollto, 0 + , true + ) - + mViewInfo.TimeToPosition(lowerBound, 0 + , true + ); +} + void AudacityProject::SetHorizontalThumb(double scrollto) { - const double timeOffset = -ScrollingLowerBoundTime(); - int pos = (int) ( - (scrollto + timeOffset) * mViewInfo.zoom * mViewInfo.sbarScale - ); - int max = mHsbar->GetRange() - mHsbar->GetThumbSize(); - - if (pos > max) - pos = max; - else if (pos < 0) - pos = 0; - + wxInt64 max = mHsbar->GetRange() - mHsbar->GetThumbSize(); + int pos = std::min(max, std::max(wxInt64(0), PixelWidthBeforeTime(scrollto))); mHsbar->SetThumbPosition(pos); } @@ -1546,7 +1530,7 @@ void AudacityProject::FixScrollbars() double LastTime = std::max(mTracks->GetEndTime(), mViewInfo.selectedRegion.t1()); - mViewInfo.screen = ((double) panelWidth) / mViewInfo.zoom; + mViewInfo.SetScreenWidth(panelWidth); const double halfScreen = mViewInfo.screen / 2.0; // If we can scroll beyond zero, @@ -1570,9 +1554,9 @@ void AudacityProject::FixScrollbars() rescroll = true; } - mViewInfo.sbarTotal = (wxInt64) (mViewInfo.total * mViewInfo.zoom); - mViewInfo.sbarScreen = (wxInt64) (mViewInfo.screen * mViewInfo.zoom); - mViewInfo.sbarH = (wxInt64) (mViewInfo.h * mViewInfo.zoom); + mViewInfo.sbarTotal = (wxInt64) (mViewInfo.GetTotalWidth()); + mViewInfo.sbarScreen = (wxInt64) (mViewInfo.GetScreenWidth()); + mViewInfo.sbarH = (wxInt64) (mViewInfo.GetBeforeScreenWidth()); int lastv = mViewInfo.vpos; // PRL: Can someone else find a more elegant solution to bug 812, than @@ -1593,7 +1577,7 @@ void AudacityProject::FixScrollbars() bool oldhstate; bool oldvstate; - bool newhstate = mViewInfo.screen < mViewInfo.total; + bool newhstate = !mViewInfo.ZoomedAll(); bool newvstate = panelHeight < totalHeight; #ifdef __WXGTK__ @@ -1604,7 +1588,7 @@ void AudacityProject::FixScrollbars() #else oldhstate = mHsbar->IsEnabled(); oldvstate = mVsbar->IsEnabled(); - mHsbar->Enable(mViewInfo.screen < mViewInfo.total); + mHsbar->Enable(!mViewInfo.ZoomedAll()); mVsbar->Enable(panelHeight < totalHeight); #endif @@ -1614,7 +1598,7 @@ void AudacityProject::FixScrollbars() refresh = true; rescroll = false; } - if (mViewInfo.screen >= mViewInfo.total && mViewInfo.sbarH != 0) { + if (mViewInfo.ZoomedAll() && mViewInfo.sbarH != 0) { mViewInfo.sbarH = 0; refresh = true; @@ -1642,8 +1626,7 @@ void AudacityProject::FixScrollbars() int scaledSbarH = (int)(mViewInfo.sbarH * mViewInfo.sbarScale); int scaledSbarScreen = (int)(mViewInfo.sbarScreen * mViewInfo.sbarScale); int scaledSbarTotal = (int)(mViewInfo.sbarTotal * mViewInfo.sbarScale); - int offset; - offset = -lowerBound * mViewInfo.zoom * mViewInfo.sbarScale; + const int offset = mViewInfo.sbarScale * PixelWidthBeforeTime(0.0); mHsbar->SetScrollbar(scaledSbarH + offset, scaledSbarScreen, scaledSbarTotal, scaledSbarScreen, TRUE); @@ -1657,7 +1640,7 @@ void AudacityProject::FixScrollbars() panelHeight / mViewInfo.scrollStep, TRUE); mVsbar->Refresh(); - if (refresh || (rescroll && mViewInfo.screen < mViewInfo.total)) { + if (refresh || (rescroll && !mViewInfo.ZoomedAll())) { mTrackPanel->Refresh(false); } @@ -1835,25 +1818,18 @@ void AudacityProject::OnScroll(wxScrollEvent & WXUNUSED(event)) const wxInt64 hlast = mViewInfo.sbarH; const double lowerBound = ScrollingLowerBoundTime(); - const wxInt64 offset = 0.5 + -lowerBound * mViewInfo.zoom; + const wxInt64 offset = PixelWidthBeforeTime(0.0); mViewInfo.sbarH = (wxInt64)(mHsbar->GetThumbPosition() / mViewInfo.sbarScale) - offset; - if (mViewInfo.sbarH != hlast) { - mViewInfo.h = mViewInfo.sbarH / mViewInfo.zoom; - - if (mViewInfo.h > mViewInfo.total - mViewInfo.screen) - mViewInfo.h = mViewInfo.total - mViewInfo.screen; - - if (mViewInfo.h < lowerBound) - mViewInfo.h = lowerBound; - } - + if (mViewInfo.sbarH != hlast) + mViewInfo.SetBeforeScreenWidth(mViewInfo.sbarH, lowerBound); if (mScrollBeyondZero) { enum { SCROLL_PIXEL_TOLERANCE = 10 }; - if (fabs(mViewInfo.h * mViewInfo.zoom) < SCROLL_PIXEL_TOLERANCE) { + if (abs(mViewInfo.TimeToPosition(0.0, 0 + )) < SCROLL_PIXEL_TOLERANCE) { // Snap the scrollbar to 0 mViewInfo.h = 0; SetHorizontalThumb(0.0); @@ -2902,6 +2878,12 @@ bool AudacityProject::HandleXMLTag(const wxChar *tag, const wxChar **attrs) if (!value || !XMLValueChecker::IsGoodString(value)) break; + if (mViewInfo.ReadXMLAttribute(attr, value)) { + // We need to save vpos now and restore it below + longVpos = std::max(longVpos, long(mViewInfo.vpos)); + continue; + } + if (!wxStrcmp(attr, wxT("datadir"))) { // @@ -2995,20 +2977,6 @@ bool AudacityProject::HandleXMLTag(const wxChar *tag, const wxChar **attrs) requiredTags++; } - else if (mViewInfo.selectedRegion - .HandleXMLAttribute(attr, value, wxT("sel0"), wxT("sel1"))) { - } - - else if (!wxStrcmp(attr, wxT("vpos"))) - // Just assign a variable, put the value in its place later - wxString(value).ToLong(&longVpos); - - else if (!wxStrcmp(attr, wxT("h"))) - Internat::CompatibleToDouble(value, &mViewInfo.h); - - else if (!wxStrcmp(attr, wxT("zoom"))) - Internat::CompatibleToDouble(value, &mViewInfo.zoom); - else if (!wxStrcmp(attr, wxT("rate"))) { Internat::CompatibleToDouble(value, &mRate); GetSelectionBar()->SetRate(mRate); @@ -3202,12 +3170,8 @@ void AudacityProject::WriteXML(XMLWriter &xmlFile) xmlFile.WriteAttr(wxT("projname"), projName); xmlFile.WriteAttr(wxT("version"), wxT(AUDACITY_FILE_FORMAT_VERSION)); xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING); - mViewInfo.selectedRegion - .WriteXMLAttributes(xmlFile, wxT("sel0"), wxT("sel1")); - // PRL: to do: persistence of other fields of the selection - xmlFile.WriteAttr(wxT("vpos"), mViewInfo.vpos); - xmlFile.WriteAttr(wxT("h"), mViewInfo.h, 10); - xmlFile.WriteAttr(wxT("zoom"), mViewInfo.zoom, 10); + + mViewInfo.WriteXMLAttributes(xmlFile); xmlFile.WriteAttr(wxT("rate"), mRate); xmlFile.WriteAttr(wxT("snapto"), GetSnapTo() ? wxT("on") : wxT("off")); xmlFile.WriteAttr(wxT("selectionformat"), GetSelectionFormat()); @@ -4148,12 +4112,14 @@ void AudacityProject::SelectNone() // Utility function called by other zoom methods void AudacityProject::Zoom(double level) { - if (level > gMaxZoom) - level = gMaxZoom; - if (level <= gMinZoom) - level = gMinZoom; + mViewInfo.SetZoom(level); + FixScrollbars(); +} - mViewInfo.zoom = level; +// Utility function called by other zoom methods +void AudacityProject::ZoomBy(double multiplier) +{ + mViewInfo.ZoomBy(multiplier); FixScrollbars(); } diff --git a/src/Project.h b/src/Project.h index c741eac83..8793bde25 100644 --- a/src/Project.h +++ b/src/Project.h @@ -152,9 +152,11 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame, sampleFormat GetDefaultFormat() { return mDefaultFormat; } double GetRate() { return mRate; } - double GetZoom() { return mViewInfo.zoom; } + bool ZoomInAvailable() const { return mViewInfo.ZoomInAvailable(); } + bool ZoomOutAvailable() const { return mViewInfo.ZoomOutAvailable(); } double GetSel0() { return mViewInfo.selectedRegion.t0(); } double GetSel1() { return mViewInfo.selectedRegion.t1(); } + const ZoomInfo &GetZoomInfo() const { return mViewInfo; } Track *GetFirstVisible(); void UpdateFirstVisible(); @@ -308,6 +310,7 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame, void SelectNone(); void SelectAllIfNone(); void Zoom(double level); + void ZoomBy(double multiplier); void Rewind(bool shift); void SkipEnd(bool shift); void EditByLabel( WaveTrack::EditFunction action, bool bSyncLockedTracks ); @@ -354,6 +357,8 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame, void SafeDisplayStatusMessage(const wxChar *msg); double ScrollingLowerBoundTime() const; + // How many pixels are covered by the period from lowermost scrollable time, to the given time: + wxInt64 PixelWidthBeforeTime(double scrollto) const; void SetHorizontalThumb(double scrollto); // TrackPanel access diff --git a/src/Screenshot.cpp b/src/Screenshot.cpp index 890416132..94fc80c33 100644 --- a/src/Screenshot.cpp +++ b/src/Screenshot.cpp @@ -661,7 +661,7 @@ void ScreenFrame::TimeZoom(double seconds) { int width, height; mContext.GetProject()->GetClientSize(&width, &height); - mContext.GetProject()->mViewInfo.zoom = (0.75 * width) / seconds; + mContext.GetProject()->mViewInfo.SetZoom((0.75 * width) / seconds); mContext.GetProject()->RedrawProject(); } diff --git a/src/ShuttleGui.h b/src/ShuttleGui.h index 4885b64c5..3839c7ab0 100644 --- a/src/ShuttleGui.h +++ b/src/ShuttleGui.h @@ -325,7 +325,7 @@ class GuiWaveTrack; class AdornedRulerPanel; class RulerPanel; class AttachableScrollBar; -struct ViewInfo; +class ViewInfo; #include // to get wxSB_HORIZONTAL // CreateStdButtonSizer defs...should probably move to widgets subdir diff --git a/src/Snap.cpp b/src/Snap.cpp index c28f27d50..951c81197 100644 --- a/src/Snap.cpp +++ b/src/Snap.cpp @@ -8,14 +8,14 @@ **********************************************************************/ +#include "Snap.h" + +#include #include #include "LabelTrack.h" -#include "Prefs.h" #include "Project.h" -#include "Snap.h" #include "TrackPanel.h" -#include "WaveTrack.h" #include "widgets/NumericTextCtrl.h" // Change this to "true" to snap to nearest and "false" to snap to previous @@ -29,8 +29,10 @@ static int CompareSnapPoints(SnapPoint *s1, SnapPoint *s2) } SnapManager::SnapManager(TrackList *tracks, TrackClipArray *exclusions, - double zoom, int pixelTolerance, bool noTimeSnap) + const ZoomInfo &zoomInfo, int pixelTolerance, bool noTimeSnap) : mConverter(NumericConverter::TIME) + , mPixelTolerance(std::max(0, pixelTolerance)) + , mZoomInfo(zoomInfo) { int i; @@ -50,13 +52,7 @@ SnapManager::SnapManager(TrackList *tracks, TrackClipArray *exclusions, } mSnapPoints = new SnapPointArray(CompareSnapPoints); - if (zoom > 0 && pixelTolerance > 0) - mTolerance = pixelTolerance / zoom; - else { - // This shouldn't happen, but we don't want to crash if we get - // illegal values. The net effect of this is to never snap. - mTolerance = 0.0; - } + // Two time points closer than this are considered the same mEpsilon = 1 / 44100.0; @@ -135,9 +131,12 @@ double SnapManager::Get(int index) } // Returns the difference in time between t and the point at a given index -double SnapManager::Diff(double t, int index) +wxInt64 SnapManager::PixelDiff(double t, int index) { - return fabs(t - Get(index)); + return abs( + mZoomInfo.TimeToPosition(t, 0) - + mZoomInfo.TimeToPosition(Get(index), 0) + ); } // Find the index where this SnapPoint should go in @@ -168,7 +167,7 @@ int SnapManager::Find(double t) next++; // Now return whichever one is closer to time t - if (next < len && Diff(t, next) < Diff(t, index)) + if (next < len && PixelDiff(t, next) < PixelDiff(t, index)) return next; else return index; @@ -189,7 +188,7 @@ bool SnapManager::SnapToPoints(Track *currentTrack, int index = Find(t); // If it's too far away, just give up now - if (Diff(t, index) >= mTolerance) + if (PixelDiff(t, index) >= mPixelTolerance) return false; // Otherwise, search left and right for all of the points @@ -198,10 +197,10 @@ bool SnapManager::SnapToPoints(Track *currentTrack, int right = index; int i; - while(left > 0 && Diff(t, left-1) < mTolerance) + while(left > 0 && PixelDiff(t, left-1) < mPixelTolerance) left--; - while(right < len-1 && Diff(t, right+1) < mTolerance) + while(right < len-1 && PixelDiff(t, right+1) < mPixelTolerance) right++; if (left == index && right == index) { diff --git a/src/Snap.h b/src/Snap.h index a4d32ca7c..a3e9fe82c 100644 --- a/src/Snap.h +++ b/src/Snap.h @@ -22,6 +22,7 @@ #include "widgets/NumericTextCtrl.h" class TrackClipArray; +class ZoomInfo; enum { @@ -43,9 +44,9 @@ class SnapPoint { WX_DEFINE_SORTED_ARRAY(SnapPoint *, SnapPointArray); class SnapManager { - public: +public: SnapManager(TrackList *tracks, TrackClipArray *exclusions, - double zoom, int pixelTolerance, bool noTimeSnap = false); + const ZoomInfo &zoomInfo, int pixelTolerance, bool noTimeSnap = false); ~SnapManager(); @@ -54,34 +55,35 @@ class SnapManager { // Pass rightEdge=true if this is the right edge of a selection, // and false if it's the left edge. bool Snap(Track *currentTrack, - double t, - bool rightEdge, - double *out_t, - bool *snappedPoint, - bool *snappedTime); + double t, + bool rightEdge, + double *out_t, + bool *snappedPoint, + bool *snappedTime); static wxArrayString GetSnapLabels(); static wxArrayString GetSnapValues(); static const wxString & GetSnapValue(int index); static int GetSnapIndex(const wxString & value); - private: +private: void CondListAdd(double t, Track *tr); double Get(int index); - double Diff(double t, int index); + wxInt64 PixelDiff(double t, int index); int Find(double t, int i0, int i1); int Find(double t); bool SnapToPoints(Track *currentTrack, double t, bool rightEdge, - double *out_t); + double *out_t); double mEpsilon; - double mTolerance; - double mZoom; SnapPointArray *mSnapPoints; // Info for snap-to-time NumericConverter mConverter; bool mSnapToTime; + + const wxInt64 mPixelTolerance; + const ZoomInfo &mZoomInfo; }; #endif diff --git a/src/TimeTrack.cpp b/src/TimeTrack.cpp index ee8fb0465..ea121cf4a 100644 --- a/src/TimeTrack.cpp +++ b/src/TimeTrack.cpp @@ -13,15 +13,16 @@ *//*******************************************************************/ +#include "Audacity.h" +#include "TimeTrack.h" #include -#include "Audacity.h" #include "AColor.h" -#include "TimeTrack.h" #include "widgets/Ruler.h" #include "Prefs.h" #include "Internat.h" #include "Resample.h" +#include "ViewInfo.h" //TODO-MB: are these sensible values? #define TIMETRACK_MIN 0.01 @@ -52,7 +53,8 @@ TimeTrack::TimeTrack(DirManager *projDirManager): SetDefaultName(_("Time Track")); SetName(GetDefaultName()); - mRuler = new Ruler(); + mRuler = new Ruler; + mRuler->SetUseZoomInfo(0); mRuler->SetLabelEdges(false); mRuler->SetFormat(Ruler::TimeFormat); @@ -76,7 +78,8 @@ TimeTrack::TimeTrack(TimeTrack &orig): mEnvelope->Paste(0.0, orig.mEnvelope); ///@TODO: Give Ruler:: a copy-constructor instead of this? - mRuler = new Ruler(); + mRuler = new Ruler; + mRuler->SetUseZoomInfo(0); mRuler->SetLabelEdges(false); mRuler->SetFormat(Ruler::TimeFormat); @@ -223,19 +226,15 @@ void TimeTrack::WriteXML(XMLWriter &xmlFile) xmlFile.EndTag(wxT("timetrack")); } -void TimeTrack::Draw(wxDC & dc, const wxRect & r, double h, double pps) +void TimeTrack::Draw(wxDC & dc, const wxRect & r, const ZoomInfo &zoomInfo) { - double tstep = 1.0 / pps; // Seconds per point - double t0 = h; - double t1 = h + (r.width * tstep); - - //Make sure t1 (the right bound) is greater than 0 - if (t1 < 0.0) - t1 = 0.0; - - //make sure t1 is greater than t0 - if (t0 > t1) - t0 = t1; + double min = zoomInfo.PositionToTime(0); + double max = zoomInfo.PositionToTime(r.width); + if (min > max) + { + wxASSERT(false); + min = max; + } dc.SetBrush(blankBrush); dc.SetPen(blankPen); @@ -246,8 +245,6 @@ void TimeTrack::Draw(wxDC & dc, const wxRect & r, double h, double pps) // Draw the Ruler mRuler->SetBounds(r.x, r.y, r.x + r.width - 1, r.y + r.height - 1); - double min = t0; - double max = min + r.width / pps; mRuler->SetRange(min, max); mRuler->SetFlip(false); // If we don't do this, the Ruler doesn't redraw itself when the envelope is modified. // I have no idea why! @@ -258,7 +255,7 @@ void TimeTrack::Draw(wxDC & dc, const wxRect & r, double h, double pps) mRuler->Draw(dc, this); double *envValues = new double[mid.width]; - GetEnvelope()->GetValues(envValues, mid.width, t0, tstep); + GetEnvelope()->GetValues(envValues, mid.width, 0, zoomInfo); dc.SetPen(AColor::envelopePen); diff --git a/src/TimeTrack.h b/src/TimeTrack.h index 4525c99df..26ba7d022 100644 --- a/src/TimeTrack.h +++ b/src/TimeTrack.h @@ -21,6 +21,7 @@ class wxRect; class wxDC; class Envelope; class Ruler; +class ZoomInfo; class TimeTrack: public Track { @@ -49,7 +50,7 @@ class TimeTrack: public Track { virtual double GetStartTime() const { return 0.0; } virtual double GetEndTime() const { return 0.0; } - void Draw(wxDC & dc, const wxRect & r, double h, double pps); + void Draw(wxDC & dc, const wxRect & r, const ZoomInfo &zoomInfo); // XMLTagHandler callback methods for loading and saving diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index 2f832ac3f..78422e7f5 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -99,7 +99,7 @@ TrackPanel::DoDrawIndicator(); AdornedRulerPanel::DrawIndicator(); [not part of TrackPanel graphics] draw indicator on each track TrackPanel::DoDrawCursor(); - draw cursor on each track [at mviewInfo->selectedRegion.t0()] + draw cursor on each track [at selectedRegion.t0()] AdornedRulerPanel::DrawCursor(); [not part of TrackPanel graphics] TrackPanel::DisplaySelection(); \endcode @@ -247,10 +247,10 @@ const int notePos[12] = { 1, 6, 11, 16, 21, 27, // IPITCH_TO_Y above, which computes coordinates relative to GetBottom() // Note the -NOTE_MARGIN, which leaves a little margin to draw notes that // are out of bounds. I'm not sure why the -2 is necessary. -int TrackArtist::GetBottom(NoteTrack *t, const wxRect &r) +int TrackArtist::GetBottom(NoteTrack *t, const wxRect &rect) { int bottomNote = t->GetBottomNote(); - int bottom = r.y + r.height - 2 - t->GetNoteMargin() + + int bottom = rect.y + rect.height - 2 - t->GetNoteMargin() + ((bottomNote / 12) * octaveHeight + notePos[bottomNote % 12]); return bottom; @@ -270,7 +270,7 @@ TrackArtist::TrackArtist() UpdatePrefs(); SetColours(); - vruler = new Ruler(); + vruler = new Ruler; } TrackArtist::~TrackArtist() @@ -316,14 +316,15 @@ void TrackArtist::DrawTracks(TrackList * tracks, Track * start, wxDC & dc, wxRegion & reg, - wxRect & r, + wxRect & rect, wxRect & clip, - ViewInfo * viewInfo, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo, bool drawEnvelope, - bool drawSamples, + bool bigPoints, bool drawSliders) { - wxRect trackRect = r; + wxRect trackRect = rect; wxRect stereoTrackRect; TrackListIterator iter(tracks); @@ -341,10 +342,10 @@ void TrackArtist::DrawTracks(TrackList * tracks, // Change the +0 to +1 or +2 to see the bounding box mInsetLeft = 1+0; mInsetTop = 5+0; mInsetRight = 6+0; mInsetBottom = 2+0; - // This just show what the passed in rectanges enclose + // This just shows what the passed in rectangles enclose dc.SetPen(wxColour(*wxGREEN)); dc.SetBrush(*wxTRANSPARENT_BRUSH); - dc.DrawRectangle(r); + dc.DrawRectangle(rect); dc.SetPen(wxColour(*wxBLUE)); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle(clip); @@ -354,7 +355,7 @@ void TrackArtist::DrawTracks(TrackList * tracks, t = iter.StartWith(start); while (t) { - trackRect.y = t->GetY() - viewInfo->vpos; + trackRect.y = t->GetY() - zoomInfo.vpos; trackRect.height = t->GetHeight(); if (trackRect.y > clip.GetBottom() && !t->GetLinked()) { @@ -405,13 +406,14 @@ void TrackArtist::DrawTracks(TrackList * tracks, rr.y += mInsetTop; rr.width -= (mInsetLeft + mInsetRight); rr.height -= (mInsetTop + mInsetBottom); - DrawTrack(t, dc, rr, viewInfo, - drawEnvelope, drawSamples, drawSliders, hasSolo); + DrawTrack(t, dc, rr, + selectedRegion, zoomInfo, + drawEnvelope, bigPoints, drawSliders, hasSolo); } #ifdef EXPERIMENTAL_OUTPUT_DISPLAY if(MONO_WAVE_PAN(t)){ - trackRect.y = t->GetY(true) - viewInfo->vpos; + trackRect.y = t->GetY(true) - zoomInfo.vpos; trackRect.height = t->GetHeight(true); stereoTrackRect = trackRect; stereoTrackRect.y -= t->GetHeight(); @@ -423,8 +425,8 @@ void TrackArtist::DrawTracks(TrackList * tracks, rr.y += mInsetTop; rr.width -= (mInsetLeft + mInsetRight); rr.height -= (mInsetTop + mInsetBottom); - DrawTrack(t, dc, rr, viewInfo, - drawEnvelope, drawSamples, drawSliders, hasSolo); + DrawTrack(t, dc, rr, zoomInfo, + drawEnvelope, bigPoints, drawSliders, hasSolo); } } #endif @@ -435,10 +437,11 @@ void TrackArtist::DrawTracks(TrackList * tracks, void TrackArtist::DrawTrack(const Track * t, wxDC & dc, - const wxRect & r, - const ViewInfo * viewInfo, + const wxRect & rect, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo, bool drawEnvelope, - bool drawSamples, + bool bigPoints, bool drawSliders, bool hasSolo) { @@ -454,19 +457,19 @@ void TrackArtist::DrawTrack(const Track * t, switch (wt->GetDisplay()) { case WaveTrack::WaveformDisplay: - DrawWaveform(wt, dc, r, viewInfo, - drawEnvelope, drawSamples, drawSliders, false, muted); + DrawWaveform(wt, dc, rect, selectedRegion, zoomInfo, + drawEnvelope, bigPoints, drawSliders, false, muted); break; case WaveTrack::WaveformDBDisplay: - DrawWaveform(wt, dc, r, viewInfo, - drawEnvelope, drawSamples, drawSliders, true, muted); + DrawWaveform(wt, dc, rect, selectedRegion, zoomInfo, + drawEnvelope, bigPoints, drawSliders, true, muted); break; case WaveTrack::SpectrumDisplay: case WaveTrack::SpectrumLogDisplay: case WaveTrack::SpectralSelectionDisplay: case WaveTrack::SpectralSelectionLogDisplay: case WaveTrack::PitchDisplay: - DrawSpectrum(wt, dc, r, viewInfo); + DrawSpectrum(wt, dc, rect, selectedRegion, zoomInfo); break; } if (mbShowTrackNameInWaveform && @@ -475,7 +478,7 @@ void TrackArtist::DrawTrack(const Track * t, wxFont labelFont(12, wxSWISS, wxNORMAL, wxNORMAL); dc.SetFont(labelFont); dc.SetTextForeground(wxColour(255, 255, 0)); - dc.DrawText (wt->GetName(), r.x+10, r.y); // move right 10 pixels to avoid overwriting <- symbol + dc.DrawText (wt->GetName(), rect.x+10, rect.y); // move right 10 pixels to avoid overwriting <- symbol } break; // case Wave } @@ -483,27 +486,27 @@ void TrackArtist::DrawTrack(const Track * t, case Track::Note: { bool muted = (hasSolo || t->GetMute()) && !t->GetSolo(); - DrawNoteTrack((NoteTrack *)t, dc, r, viewInfo, muted); + DrawNoteTrack((NoteTrack *)t, dc, rect, selectedRegion, zoomInfo, muted); break; } #endif // USE_MIDI case Track::Label: - DrawLabelTrack((LabelTrack *)t, dc, r, viewInfo); + DrawLabelTrack((LabelTrack *)t, dc, rect, selectedRegion, zoomInfo); break; case Track::Time: - DrawTimeTrack((TimeTrack *)t, dc, r, viewInfo); + DrawTimeTrack((TimeTrack *)t, dc, rect, zoomInfo); break; } } -void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) +void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & rect) { int kind = t->GetKind(); // Label and Time tracks do not have a vruler // But give it a beveled area if (kind == Track::Label) { - wxRect bev = r; + wxRect bev = rect; bev.Inflate(-1, -1); bev.width += 1; AColor::BevelTrackInfo(*dc, true, bev); @@ -513,15 +516,15 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) // Time tracks if (kind == Track::Time) { - wxRect bev = r; + wxRect bev = rect; bev.Inflate(-1, -1); bev.width += 1; AColor::BevelTrackInfo(*dc, true, bev); // Right align the ruler - wxRect rr = r; + wxRect rr = rect; rr.width--; - if (t->vrulerSize.GetWidth() < r.GetWidth()) { + if (t->vrulerSize.GetWidth() < rect.GetWidth()) { int adj = rr.GetWidth() - t->vrulerSize.GetWidth(); rr.x += adj; rr.width -= adj; @@ -537,7 +540,7 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) // All waves have a ruler in the info panel // The ruler needs a bevelled surround. if (kind == Track::Wave) { - wxRect bev = r; + wxRect bev = rect; bev.Inflate(-1, -1); bev.width += 1; AColor::BevelTrackInfo(*dc, true, bev); @@ -548,9 +551,9 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) } // Right align the ruler - wxRect rr = r; + wxRect rr = rect; rr.width--; - if (t->vrulerSize.GetWidth() < r.GetWidth()) { + if (t->vrulerSize.GetWidth() < rect.GetWidth()) { int adj = rr.GetWidth() - t->vrulerSize.GetWidth(); rr.x += adj; rr.width -= adj; @@ -566,23 +569,23 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) #ifdef USE_MIDI // The note track draws a vertical keyboard to label pitches if (kind == Track::Note) { - UpdateVRuler(t, r); + UpdateVRuler(t, rect); dc->SetPen(*wxTRANSPARENT_PEN); dc->SetBrush(*wxWHITE_BRUSH); - wxRect bev = r; + wxRect bev = rect; bev.x++; bev.y++; bev.width--; bev.height--; dc->DrawRectangle(bev); - r.y += 2; - r.height -= 2; + rect.y += 2; + rect.height -= 2; - //int bottom = GetBottom((NoteTrack *) t, r); + //int bottom = GetBottom((NoteTrack *) t, rect); NoteTrack *track = (NoteTrack *) t; - track->PrepareIPitchToY(r); + track->PrepareIPitchToY(rect); wxPen hilitePen; hilitePen.SetColour(120, 120, 120); @@ -603,23 +606,23 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) int obottom = track->GetOctaveBottom(octave); int marg = track->GetNoteMargin(); //IPITCH_TO_Y(octave * 12) + PITCH_HEIGHT + 1; - while (obottom >= r.y) { + while (obottom >= rect.y) { dc->SetPen(*wxBLACK_PEN); for (int white = 0; white < 7; white++) { int pos = track->GetWhitePos(white); - if (obottom - pos > r.y + marg + 1 && + if (obottom - pos > rect.y + marg + 1 && // don't draw too close to margin line -- it's annoying - obottom - pos < r.y + r.height - marg - 3) - AColor::Line(*dc, r.x, obottom - pos, - r.x + r.width, obottom - pos); + obottom - pos < rect.y + rect.height - marg - 3) + AColor::Line(*dc, rect.x, obottom - pos, + rect.x + rect.width, obottom - pos); } - wxRect br = r; + wxRect br = rect; br.height = track->GetPitchHeight(); br.x++; br.width = 17; for (int black = 0; black < 5; black++) { br.y = obottom - track->GetBlackPos(black); - if (br.y > r.y + marg - 2 && br.y + br.height < r.y + r.height - marg) { + if (br.y > rect.y + marg - 2 && br.y + br.height < rect.y + rect.height - marg) { dc->SetPen(hilitePen); dc->DrawRectangle(br); dc->SetPen(*wxBLACK_PEN); @@ -639,10 +642,10 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) s.Printf(wxT("C%d"), octave - 1); wxCoord width, height; dc->GetTextExtent(s, &width, &height); - if (obottom - height + 4 > r.y && - obottom + 4 < r.y + r.height) { + if (obottom - height + 4 > rect.y && + obottom + 4 < rect.y + rect.height) { dc->SetTextForeground(wxColour(60, 60, 255)); - dc->DrawText(s, r.x + r.width - width, + dc->DrawText(s, rect.x + rect.width - width, obottom - height + 2); } } @@ -651,19 +654,19 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) // draw lines delineating the out-of-bounds margins dc->SetPen(*wxBLACK_PEN); // you would think the -1 offset here should be -2 to match the - // adjustment to r.y (see above), but -1 produces correct output - AColor::Line(*dc, r.x, r.y + marg - 1, r.x + r.width, r.y + marg - 1); + // adjustment to rect.y (see above), but -1 produces correct output + AColor::Line(*dc, rect.x, rect.y + marg - 1, rect.x + rect.width, rect.y + marg - 1); // since the margin gives us the bottom of the line, // the extra -1 gets us to the top - AColor::Line(*dc, r.x, r.y + r.height - marg - 1, - r.x + r.width, r.y + r.height - marg - 1); + AColor::Line(*dc, rect.x, rect.y + rect.height - marg - 1, + rect.x + rect.width, rect.y + rect.height - marg - 1); } #endif // USE_MIDI } -void TrackArtist::UpdateVRuler(Track *t, wxRect & r) +void TrackArtist::UpdateVRuler(Track *t, wxRect & rect) { // Label tracks do not have a vruler if (t->GetKind() == Track::Label) { @@ -677,7 +680,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) min = tt->GetRangeLower() * 100.0; max = tt->GetRangeUpper() * 100.0; - vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1); + vruler->SetBounds(rect.x, rect.y+1, rect.x + rect.width, rect.y + rect.height-1); vruler->SetOrientation(wxVERTICAL); vruler->SetRange(max, min); vruler->SetFormat((tt->GetDisplayLog()) ? Ruler::RealLogFormat : Ruler::RealFormat); @@ -719,7 +722,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) wt->SetDisplayBounds(min, max); } - vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1); + vruler->SetBounds(rect.x, rect.y+1, rect.x + rect.width, rect.y + rect.height-1); vruler->SetOrientation(wxVERTICAL); vruler->SetRange(max, min); vruler->SetFormat(Ruler::RealFormat); @@ -760,7 +763,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) if (max > 0) { int top = 0; float topval = 0; - int bot = r.height; + int bot = rect.height; float botval = -mdBrange; if (min < 0) { @@ -780,7 +783,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) botval = -((1-min)*mdBrange); } - vruler->SetBounds(r.x, r.y+top+1, r.x + r.width, r.y + bot-1); + vruler->SetBounds(rect.x, rect.y+top+1, rect.x + rect.width, rect.y + bot-1); vruler->SetOrientation(wxVERTICAL); vruler->SetRange(topval, botval); } @@ -796,7 +799,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) { // Spectrum - if (r.height < 60) + if (rect.height < 60) return; double rate = wt->GetRate(); @@ -815,7 +818,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) we will use Hz if maxFreq is < 2000, otherwise we represent kHz, and append to the numbers a "k" */ - vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1); + vruler->SetBounds(rect.x, rect.y+1, rect.x + rect.width, rect.y + rect.height-1); vruler->SetOrientation(wxVERTICAL); vruler->SetFormat(Ruler::RealFormat); vruler->SetLabelEdges(true); @@ -836,7 +839,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) { // SpectrumLog - if (r.height < 10) + if (rect.height < 10) return; double rate = wt->GetRate(); @@ -855,7 +858,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) we will use Hz if maxFreq is < 2000, otherwise we represent kHz, and append to the numbers a "k" */ - vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1); + vruler->SetBounds(rect.x, rect.y+1, rect.x + rect.width, rect.y + rect.height-1); vruler->SetOrientation(wxVERTICAL); vruler->SetFormat(Ruler::IntFormat); vruler->SetLabelEdges(true); @@ -872,7 +875,7 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) // The note track isn't drawing a ruler at all! // But it needs to! else if (t->GetKind() == Track::Note) { - vruler->SetBounds(r.x, r.y+1, r.x + 1, r.y + r.height-1); + vruler->SetBounds(rect.x, rect.y+1, rect.x + 1, rect.y + rect.height-1); vruler->SetOrientation(wxVERTICAL); } #endif // USE_MIDI @@ -941,11 +944,11 @@ float FromDB(float value, double dBRange) return pow(10.0, ((fabs(value) * dBRange) - dBRange) / 20.0)*sign; } -float ValueOfPixel(int y, int height, bool offset, +float ValueOfPixel(int yy, int height, bool offset, bool dB, double dBRange, float zoomMin, float zoomMax) { wxASSERT(height > 0); - float v = zoomMax - (y / (float)height) * (zoomMax - zoomMin); + float v = zoomMax - (yy / (float)height) * (zoomMax - zoomMin); if (offset) { if (v > 0.0) v += .5; @@ -959,7 +962,7 @@ float ValueOfPixel(int y, int height, bool offset, return v; } -void TrackArtist::DrawNegativeOffsetTrackArrows(wxDC &dc, const wxRect &r) +void TrackArtist::DrawNegativeOffsetTrackArrows(wxDC &dc, const wxRect &rect) { // Draws two black arrows on the left side of the track to // indicate the user that the track has been time-shifted @@ -967,31 +970,34 @@ void TrackArtist::DrawNegativeOffsetTrackArrows(wxDC &dc, const wxRect &r) dc.SetPen(*wxBLACK_PEN); AColor::Line(dc, - r.x + 2, r.y + 6, - r.x + 8, r.y + 6); + rect.x + 2, rect.y + 6, + rect.x + 8, rect.y + 6); AColor::Line(dc, - r.x + 2, r.y + 6, - r.x + 6, r.y + 2); + rect.x + 2, rect.y + 6, + rect.x + 6, rect.y + 2); AColor::Line(dc, - r.x + 2, r.y + 6, - r.x + 6, r.y + 10); + rect.x + 2, rect.y + 6, + rect.x + 6, rect.y + 10); AColor::Line(dc, - r.x + 2, r.y + r.height - 8, - r.x + 8, r.y + r.height - 8); + rect.x + 2, rect.y + rect.height - 8, + rect.x + 8, rect.y + rect.height - 8); AColor::Line(dc, - r.x + 2, r.y + r.height - 8, - r.x + 6, r.y + r.height - 4); + rect.x + 2, rect.y + rect.height - 8, + rect.x + 6, rect.y + rect.height - 4); AColor::Line(dc, - r.x + 2, r.y + r.height - 8, - r.x + 6, r.y + r.height - 12); + rect.x + 2, rect.y + rect.height - 8, + rect.x + 6, rect.y + rect.height - 12); } -void TrackArtist::DrawWaveformBackground(wxDC &dc, const wxRect &r, const double env[], +void TrackArtist::DrawWaveformBackground(wxDC &dc, int leftOffset, const wxRect &rect, + const double env[], float zoomMin, float zoomMax, bool dB, - const sampleCount where[], - sampleCount ssel0, sampleCount ssel1, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo, bool drawEnvelope, bool bIsSyncLockSelected) { + const double t0 = selectedRegion.t0(), t1 = selectedRegion.t1(); + // Visually (one vertical slice of the waveform background, on its side; // the "*" is the actual waveform background we're drawing // @@ -1001,33 +1007,35 @@ void TrackArtist::DrawWaveformBackground(wxDC &dc, const wxRect &r, const double // | | | | // maxtop maxbot mintop minbot - int h = r.height; + int h = rect.height; int halfHeight = wxMax(h / 2, 1); int maxtop, lmaxtop = 0; int mintop, lmintop = 0; int maxbot, lmaxbot = 0; int minbot, lminbot = 0; bool sel, lsel = false; - int x, lx = 0; + int xx, lx = 0; int l, w; dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(blankBrush); - dc.DrawRectangle(r); + dc.DrawRectangle(rect); - for (x = 0; x < r.width; x++) { + double time = zoomInfo.PositionToTime(0, -leftOffset), nextTime; + for (xx = 0; xx < rect.width; ++xx, time = nextTime) { + nextTime = zoomInfo.PositionToTime(xx + 1, -leftOffset); // First we compute the truncated shape of the waveform background. // If drawEnvelope is true, then we compute the lower border of the // envelope. - maxtop = GetWaveYPos(env[x], zoomMin, zoomMax, + maxtop = GetWaveYPos(env[xx], zoomMin, zoomMax, h, dB, true, mdBrange, true); - maxbot = GetWaveYPos(env[x], zoomMin, zoomMax, + maxbot = GetWaveYPos(env[xx], zoomMin, zoomMax, h, dB, false, mdBrange, true); - mintop = GetWaveYPos(-env[x], zoomMin, zoomMax, + mintop = GetWaveYPos(-env[xx], zoomMin, zoomMax, h, dB, false, mdBrange, true); - minbot = GetWaveYPos(-env[x], zoomMin, zoomMax, + minbot = GetWaveYPos(-env[xx], zoomMin, zoomMax, h, dB, true, mdBrange, true); // Make sure it's odd so that a that max and min mirror each other @@ -1040,7 +1048,7 @@ void TrackArtist::DrawWaveformBackground(wxDC &dc, const wxRect &r, const double } // We don't draw selection color for sync-lock selected tracks. - sel = (ssel0 <= where[x] && where[x + 1] < ssel1) && !bIsSyncLockSelected; + sel = (t0 <= time && nextTime < t1) && !bIsSyncLockSelected; if (lmaxtop == maxtop && lmintop == mintop && @@ -1052,14 +1060,14 @@ void TrackArtist::DrawWaveformBackground(wxDC &dc, const wxRect &r, const double dc.SetBrush(lsel ? selectedBrush : unselectedBrush); - l = r.x + lx; - w = x - lx; + l = rect.x + lx; + w = xx - lx; if (lmaxbot < lmintop - 1) { - dc.DrawRectangle(l, r.y + lmaxtop, w, lmaxbot - lmaxtop); - dc.DrawRectangle(l, r.y + lmintop, w, lminbot - lmintop); + dc.DrawRectangle(l, rect.y + lmaxtop, w, lmaxbot - lmaxtop); + dc.DrawRectangle(l, rect.y + lmintop, w, lminbot - lmintop); } else { - dc.DrawRectangle(l, r.y + lmaxtop, w, lminbot - lmaxtop); + dc.DrawRectangle(l, rect.y + lmaxtop, w, lminbot - lmaxtop); } lmaxtop = maxtop; @@ -1067,69 +1075,60 @@ void TrackArtist::DrawWaveformBackground(wxDC &dc, const wxRect &r, const double lmaxbot = maxbot; lminbot = minbot; lsel = sel; - lx = x; + lx = xx; } dc.SetBrush(lsel ? selectedBrush : unselectedBrush); - l = r.x + lx; - w = x - lx; + l = rect.x + lx; + w = xx - lx; if (lmaxbot < lmintop - 1) { - dc.DrawRectangle(l, r.y + lmaxtop, w, lmaxbot - lmaxtop); - dc.DrawRectangle(l, r.y + lmintop, w, lminbot - lmintop); + dc.DrawRectangle(l, rect.y + lmaxtop, w, lmaxbot - lmaxtop); + dc.DrawRectangle(l, rect.y + lmintop, w, lminbot - lmintop); } else { - dc.DrawRectangle(l, r.y + lmaxtop, w, lminbot - lmaxtop); + dc.DrawRectangle(l, rect.y + lmaxtop, w, lminbot - lmaxtop); } // If sync-lock selected, draw in linked graphics. - if (bIsSyncLockSelected && ssel0 < ssel1) { - // Find the beginning/end of the selection - int begin, end; - for (x = 0; x < r.width && where[x] < ssel0; ++x); - begin = x; - for (; x < r.width && where[x] < ssel1; ++x); - end = x; - DrawSyncLockTiles(&dc, wxRect(r.x + begin, r.y, end - 1 - begin, r.height)); + if (bIsSyncLockSelected && t0 < t1) { + const int begin = std::max(0, std::min(rect.width, int(zoomInfo.TimeToPosition(t0, -leftOffset)))); + const int end = std::max(0, std::min(rect.width, int(zoomInfo.TimeToPosition(t1, -leftOffset)))); + DrawSyncLockTiles(&dc, wxRect(rect.x + begin, rect.y, end - 1 - begin, rect.height)); } //OK, the display bounds are between min and max, which - //is spread across r.height. Draw the line at the proper place. + //is spread across rect.height. Draw the line at the proper place. if (zoomMin < 0 && zoomMax > 0) { int half = (int)((zoomMax / (zoomMax - zoomMin)) * h); dc.SetPen(*wxBLACK_PEN); - AColor::Line(dc, r.x, r.y + half, r.x + r.width, r.y + half); + AColor::Line(dc, rect.x, rect.y + half, rect.x + rect.width, rect.y + half); } } -void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], - float zoomMin, float zoomMax, bool dB, - const WaveDisplay &display, bool /* showProgress */, bool muted +void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect & rect, const double env[], + float zoomMin, float zoomMax, bool dB, + const float *min, const float *max, const float *rms, const int *bl, + bool /* showProgress */, bool muted #ifdef EXPERIMENTAL_OUTPUT_DISPLAY - , const float gain + , const float gain #endif - ) +) { - const float *const min = display.min; - const float *const max = display.max; - const float *const rms = display.rms; - const int *const bl = display.bl; - // Display a line representing the // min and max of the samples in this region int lasth1 = std::numeric_limits::max(); int lasth2 = std::numeric_limits::min(); int h1; int h2; - int *r1 = new int[r.width]; - int *r2 = new int[r.width]; + int *r1 = new int[rect.width]; + int *r2 = new int[rect.width]; int *clipped = NULL; int clipcnt = 0; - int x; if (mShowClipping) { - clipped = new int[r.width]; + clipped = new int[rect.width]; } long pixAnimOffset = (long)fabs((double)(wxDateTime::Now().GetTicks() * -10)) + @@ -1139,14 +1138,14 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], bool drawWaveform = true; dc.SetPen(muted ? muteSamplePen : samplePen); - for (x = 0; x < r.width; x++) { - int xx = r.x + x; + for (int x0 = 0; x0 < rect.width; ++x0) { + int xx = rect.x + x0; double v; #ifdef EXPERIMENTAL_OUTPUT_DISPLAY //JWA: "gain" variable passed to function includes the pan value and is used below 4/14/13 - v = min[x] * env[x] * gain; + v = min[x0] * env[x0] * gain; #else - v = min[x] * env[x]; + v = min[x0] * env[x0]; #endif if (clipped && mShowClipping && (v <= -MAX_AUDIO)) { @@ -1155,12 +1154,12 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], } } h1 = GetWaveYPos(v, zoomMin, zoomMax, - r.height, dB, true, mdBrange, true); + rect.height, dB, true, mdBrange, true); #ifdef EXPERIMENTAL_OUTPUT_DISPLAY - v = max[x] * env[x] * gain; + v = max[x0] * env[x0] * gain; #else - v = max[x] * env[x]; + v = max[x0] * env[x0]; #endif if (clipped && mShowClipping && (v >= MAX_AUDIO)) { @@ -1169,11 +1168,11 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], } } h2 = GetWaveYPos(v, zoomMin, zoomMax, - r.height, dB, true, mdBrange, true); + rect.height, dB, true, mdBrange, true); // JKC: This adjustment to h1 and h2 ensures that the drawn // waveform is continuous. - if (x > 0) { + if (x0 > 0) { if (h1 < lasth2) { h1 = lasth2 - 1; } @@ -1185,38 +1184,38 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], lasth2 = h2; #ifdef EXPERIMENTAL_OUTPUT_DISPLAY - r1[x] = GetWaveYPos(-rms[x] * env[x]*gain, zoomMin, zoomMax, - r.height, dB, true, mdBrange, true); - r2[x] = GetWaveYPos(rms[x] * env[x]*gain, zoomMin, zoomMax, - r.height, dB, true, mdBrange, true); + r1[x0] = GetWaveYPos(-rms[x0] * env[x0]*gain, zoomMin, zoomMax, + rect.height, dB, true, mdBrange, true); + r2[x0] = GetWaveYPos(rms[xx0 * env[x0]*gain, zoomMin, zoomMax, + rect.height, dB, true, mdBrange, true); #else - r1[x] = GetWaveYPos(-rms[x] * env[x], zoomMin, zoomMax, - r.height, dB, true, mdBrange, true); - r2[x] = GetWaveYPos(rms[x] * env[x], zoomMin, zoomMax, - r.height, dB, true, mdBrange, true); + r1[x0] = GetWaveYPos(-rms[x0] * env[x0], zoomMin, zoomMax, + rect.height, dB, true, mdBrange, true); + r2[x0] = GetWaveYPos(rms[x0] * env[x0], zoomMin, zoomMax, + rect.height, dB, true, mdBrange, true); #endif // Make sure the rms isn't larger than the waveform min/max - if (r1[x] > h1 - 1) { - r1[x] = h1 - 1; + if (r1[x0] > h1 - 1) { + r1[x0] = h1 - 1; } - if (r2[x] < h2 + 1) { - r2[x] = h2 + 1; + if (r2[x0] < h2 + 1) { + r2[x0] = h2 + 1; } - if (r2[x] > r1[x]) { - r2[x] = r1[x]; + if (r2[x0] > r1[x0]) { + r2[x0] = r1[x0]; } - if (bl[x] <= -1) { + if (bl[x0] <= -1) { if (drawStripes) { // TODO:unify with buffer drawing. - dc.SetPen((bl[x] % 2) ? muteSamplePen : samplePen); - for (int y = 0; y < r.height / 25 + 1; y++) { + dc.SetPen((bl[x0] % 2) ? muteSamplePen : samplePen); + for (int yy = 0; yy < rect.height / 25 + 1; ++yy) { // we are drawing over the buffer, but I think DrawLine takes care of this. AColor::Line(dc, xx, - r.y + 25 * y + (x /*+pixAnimOffset*/) % 25, + rect.y + 25 * yy + (x0 /*+pixAnimOffset*/) % 25, xx, - r.y + 25 * y + (x /*+pixAnimOffset*/) % 25 + 6); //take the min so we don't draw past the edge + rect.y + 25 * yy + (x0 /*+pixAnimOffset*/) % 25 + 6); //take the min so we don't draw past the edge } } @@ -1225,10 +1224,10 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], if (drawWaveform) { int triX; dc.SetPen(samplePen); - triX = fabs((double)((x + pixAnimOffset) % (2 * r.height)) - r.height) + r.height; - for (int y = 0; y < r.height; y++) { - if ((y + triX) % r.height == 0) { - dc.DrawPoint(xx, r.y + y); + triX = fabs((double)((x0 + pixAnimOffset) % (2 * rect.height)) - rect.height) + rect.height; + for (int yy = 0; yy < rect.height; ++yy) { + if ((yy + triX) % rect.height == 0) { + dc.DrawPoint(xx, rect.y + yy); } } } @@ -1237,18 +1236,18 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], dc.SetPen(muted ? muteSamplePen : samplePen); } else { - AColor::Line(dc, xx, r.y + h2, xx, r.y + h1); + AColor::Line(dc, xx, rect.y + h2, xx, rect.y + h1); } } // Stroke rms over the min-max dc.SetPen(muted ? muteRmsPen : rmsPen); - for (int x = 0; x < r.width; x++) { - int xx = r.x + x; - if (bl[x] <= -1) { + for (int x0 = 0; x0 < rect.width; ++x0) { + int xx = rect.x + x0; + if (bl[x0] <= -1) { } - else if (r1[x] != r2[x]) { - AColor::Line(dc, xx, r.y + r2[x], xx, r.y + r1[x]); + else if (r1[x0] != r2[x0]) { + AColor::Line(dc, xx, rect.y + r2[x0], xx, rect.y + r1[x0]); } } @@ -1257,7 +1256,7 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], dc.SetPen(muted ? muteClippedPen : clippedPen); while (--clipcnt >= 0) { int xx = clipped[clipcnt]; - AColor::Line(dc, xx, r.y, xx, r.y + r.height); + AColor::Line(dc, xx, rect.y, xx, rect.y + rect.height); } } @@ -1269,26 +1268,25 @@ void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], delete [] r2; } -void TrackArtist::DrawIndividualSamples(wxDC &dc, const wxRect &r, +void TrackArtist::DrawIndividualSamples(wxDC &dc, int leftOffset, const wxRect &rect, float zoomMin, float zoomMax, bool dB, WaveClip *clip, - double t0, double pps, double WXUNUSED(h), - bool drawSamples, bool showPoints, bool muted) + const ZoomInfo &zoomInfo, + bool bigPoints, bool showPoints, bool muted) { + const double toffset = clip->GetOffset(); double rate = clip->GetRate(); - sampleCount s0 = (sampleCount) (t0 * rate + 0.5); - sampleCount slen = (sampleCount) (r.width * rate / pps + 0.5); - sampleCount snSamples = clip->GetNumSamples(); - - slen += 4; - - if (s0 > snSamples) { + const double t0 = std::max(0.0, zoomInfo.PositionToTime(0, -leftOffset) - toffset); + const sampleCount s0 = sampleCount(floor(t0 * rate)); + const sampleCount snSamples = clip->GetNumSamples(); + if (s0 > snSamples) return; - } - if (s0 + slen > snSamples) { - slen = snSamples - s0; - } + const double t1 = zoomInfo.PositionToTime(rect.width - 1, -leftOffset) - toffset; + const sampleCount s1 = sampleCount(ceil(t1 * rate)); + const sampleCount slen = std::min(snSamples - s0, s1 - s0 + 1); + if (slen <= 0) + return; float *buffer = new float[slen]; clip->GetSamples((samplePtr)buffer, floatSample, s0, slen); @@ -1299,61 +1297,49 @@ void TrackArtist::DrawIndividualSamples(wxDC &dc, const wxRect &r, int clipcnt = 0; sampleCount s; - if (mShowClipping) { + if (mShowClipping) clipped = new int[slen]; - } dc.SetPen(muted ? muteSamplePen : samplePen); for (s = 0; s < slen; s++) { - double tt = (s / rate); - - // MB: (s0/rate - t0) is the distance from the left edge of the screen - // to the first sample. - int xx = (int)rint((tt + s0 / rate - t0) * pps); - - if (xx < -10000) { - xx = -10000; - } - if (xx > 10000) { - xx = 10000; - } - + const double time = toffset + (s + s0) / rate; + const int xx = // An offset into the rectangle rect + std::max(-10000, std::min(10000, + int(zoomInfo.TimeToPosition(time, -leftOffset)))); xpos[s] = xx; - // t0 + clip->GetOffset() is 'h' (the absolute time of the left edge) for 'r'. - tt = buffer[s] * clip->GetEnvelope()->GetValueAtX(xx + r.x, r, t0 + clip->GetOffset(), pps); + const double tt = buffer[s] * clip->GetEnvelope()->GetValue(time); + if (clipped && mShowClipping && ((tt <= -MAX_AUDIO) || (tt >= MAX_AUDIO))) clipped[clipcnt++] = xx; - ypos[s] = GetWaveYPos(tt, zoomMin, zoomMax, - r.height, dB, true, mdBrange, false); - if (ypos[s] < -1) { - ypos[s] = -1; - } - if (ypos[s] > r.height) { - ypos[s] = r.height; - } + ypos[s] = + std::max(-1, + std::min(rect.height, + GetWaveYPos(tt, zoomMin, zoomMax, + rect.height, dB, true, mdBrange, false))); } // Draw lines for (s = 0; s < slen - 1; s++) { AColor::Line(dc, - r.x + xpos[s], r.y + ypos[s], - r.x + xpos[s + 1], r.y + ypos[s + 1]); + rect.x + xpos[s], rect.y + ypos[s], + rect.x + xpos[s + 1], rect.y + ypos[s + 1]); } - if (showPoints) { - // Draw points - int tickSize= drawSamples ? 4 : 3;// Bigger ellipses when draggable. + if (showPoints) + { + // Draw points where spacing is enough + const int tickSize = bigPoints ? 4 : 3;// Bigger ellipses when draggable. wxRect pr; pr.width = tickSize; pr.height = tickSize; //different colour when draggable. - dc.SetBrush( drawSamples ? dragsampleBrush : sampleBrush); + dc.SetBrush( bigPoints ? dragsampleBrush : sampleBrush); for (s = 0; s < slen; s++) { - if (ypos[s] >= 0 && ypos[s] < r.height) { - pr.x = r.x + xpos[s] - tickSize/2; - pr.y = r.y + ypos[s] - tickSize/2; + if (ypos[s] >= 0 && ypos[s] < rect.height) { + pr.x = rect.x + xpos[s] - tickSize/2; + pr.y = rect.y + ypos[s] - tickSize/2; dc.DrawEllipse(pr); } } @@ -1364,7 +1350,7 @@ void TrackArtist::DrawIndividualSamples(wxDC &dc, const wxRect &r, dc.SetPen(muted ? muteClippedPen : clippedPen); while (--clipcnt >= 0) { s = clipped[clipcnt]; - AColor::Line(dc, r.x + s, r.y, r.x + s, r.y + r.height); + AColor::Line(dc, rect.x + s, rect.y, rect.x + s, rect.y + rect.height); } } @@ -1377,24 +1363,24 @@ void TrackArtist::DrawIndividualSamples(wxDC &dc, const wxRect &r, delete[]ypos; } -void TrackArtist::DrawEnvelope(wxDC &dc, const wxRect &r, const double env[], +void TrackArtist::DrawEnvelope(wxDC &dc, const wxRect &rect, const double env[], float zoomMin, float zoomMax, bool dB) { - int h = r.height; + int h = rect.height; dc.SetPen(AColor::envelopePen); - for (int x = 0; x < r.width; x++) { - int cenvTop = GetWaveYPos(env[x], zoomMin, zoomMax, + for (int x0 = 0; x0 < rect.width; ++x0) { + int cenvTop = GetWaveYPos(env[x0], zoomMin, zoomMax, h, dB, true, mdBrange, true); - int cenvBot = GetWaveYPos(-env[x], zoomMin, zoomMax, + int cenvBot = GetWaveYPos(-env[x0], zoomMin, zoomMax, h, dB, true, mdBrange, true); - int envTop = GetWaveYPos(env[x], zoomMin, zoomMax, + int envTop = GetWaveYPos(env[x0], zoomMin, zoomMax, h, dB, true, mdBrange, false); - int envBot = GetWaveYPos(-env[x], zoomMin, zoomMax, + int envBot = GetWaveYPos(-env[x0], zoomMin, zoomMax, h, dB, true, mdBrange, false); // Make the collision at zero actually look solid @@ -1404,23 +1390,23 @@ void TrackArtist::DrawEnvelope(wxDC &dc, const wxRect &r, const double env[], cenvBot = value + 4; } - DrawEnvLine(dc, r, x, envTop, cenvTop, true); - DrawEnvLine(dc, r, x, envBot, cenvBot, false); + DrawEnvLine(dc, rect, x0, envTop, cenvTop, true); + DrawEnvLine(dc, rect, x0, envBot, cenvBot, false); } } -void TrackArtist::DrawEnvLine(wxDC &dc, const wxRect &r, int x, int y, int cy, bool top) +void TrackArtist::DrawEnvLine(wxDC &dc, const wxRect &rect, int x0, int y0, int cy, bool top) { - int xx = r.x + x; - int yy = r.y + cy; + int xx = rect.x + x0; + int yy = rect.y + cy; - if (y < 0) { - if (x % 4 != 3) { + if (y0 < 0) { + if (x0 % 4 != 3) { AColor::Line(dc, xx, yy, xx, yy + 3); } } - else if (y > r.height) { - if (x % 4 != 3) { + else if (y0 > rect.height) { + if (x0 % 4 != 3) { AColor::Line(dc, xx, yy - 3, xx, yy); } } @@ -1436,21 +1422,21 @@ void TrackArtist::DrawEnvLine(wxDC &dc, const wxRect &r, int x, int y, int cy, b void TrackArtist::DrawWaveform(WaveTrack *track, wxDC & dc, - const wxRect & r, - const ViewInfo *viewInfo, + const wxRect & rect, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo, bool drawEnvelope, - bool drawSamples, + bool bigPoints, bool drawSliders, bool dB, bool muted) { - DrawBackgroundWithSelection(&dc, r, track, blankSelectedBrush, blankBrush, - viewInfo->selectedRegion.t0(), viewInfo->selectedRegion.t1(), - viewInfo->h, viewInfo->zoom); + DrawBackgroundWithSelection(&dc, rect, track, blankSelectedBrush, blankBrush, + selectedRegion, zoomInfo); for (WaveClipList::compatibility_iterator it = track->GetClipIterator(); it; it = it->GetNext()) - DrawClipWaveform(track, it->GetData(), dc, r, viewInfo, - drawEnvelope, drawSamples, + DrawClipWaveform(track, it->GetData(), dc, rect, selectedRegion, zoomInfo, + drawEnvelope, bigPoints, dB, muted); // Update cache for locations, e.g. cutlines and merge points @@ -1458,25 +1444,25 @@ void TrackArtist::DrawWaveform(WaveTrack *track, for (int i = 0; iGetNumCachedLocations(); i++) { WaveTrack::Location loc = track->GetCachedLocation(i); - double x = (loc.pos - viewInfo->h) * viewInfo->zoom; - if (x >= 0 && x < r.width) { + const int xx = zoomInfo.TimeToPosition(loc.pos); + if (xx >= 0 && xx < rect.width) { dc.SetPen(*wxGREY_PEN); - AColor::Line(dc, (int) (r.x + x - 1), r.y, (int) (r.x + x - 1), r.y + r.height); + AColor::Line(dc, (int) (rect.x + xx - 1), rect.y, (int) (rect.x + xx - 1), rect.y + rect.height); if (loc.typ == WaveTrack::locationCutLine) { dc.SetPen(*wxRED_PEN); } else { dc.SetPen(*wxBLACK_PEN); } - AColor::Line(dc, (int) (r.x + x), r.y, (int) (r.x + x), r.y + r.height); + AColor::Line(dc, (int) (rect.x + xx), rect.y, (int) (rect.x + xx), rect.y + rect.height); dc.SetPen(*wxGREY_PEN); - AColor::Line(dc, (int) (r.x + x + 1), r.y, (int) (r.x + x + 1), r.y + r.height); + AColor::Line(dc, (int) (rect.x + xx + 1), rect.y, (int) (rect.x + xx + 1), rect.y + rect.height); } } if (drawSliders) { - DrawTimeSlider(dc, r, true); // directed right - DrawTimeSlider(dc, r, false); // directed left + DrawTimeSlider(dc, rect, true); // directed right + DrawTimeSlider(dc, rect, false); // directed left } } @@ -1486,19 +1472,21 @@ struct ClipParameters { // Do a bunch of calculations common to waveform and spectrum drawing. ClipParameters - (bool spectrum, const WaveTrack *track, const WaveClip *clip, const wxRect &r, - const SelectedRegion &selectedRegion, const ViewInfo &viewInfo) + (bool spectrum, const WaveTrack *track, const WaveClip *clip, const wxRect &rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo) { - selectedRegion; - tOffset = clip->GetOffset(); rate = clip->GetRate(); - h = viewInfo.h; //The horizontal position in seconds - pps = viewInfo.zoom; //points-per-second--the zoom level + h = zoomInfo.PositionToTime(0, 0 + , true + ); + h1 = zoomInfo.PositionToTime(rect.width, 0 + , true + ); - double sel0 = viewInfo.selectedRegion.t0(); //left selection bound - double sel1 = viewInfo.selectedRegion.t1(); //right selection bound + double sel0 = selectedRegion.t0(); //left selection bound + double sel1 = selectedRegion.t1(); //right selection bound //If the track isn't selected, make the selection empty if (!track->GetSelected() && @@ -1508,18 +1496,17 @@ struct ClipParameters const double trackLen = clip->GetEndTime() - clip->GetStartTime(); - tstep = 1.0 / pps; // Seconds per point tpre = h - tOffset; // offset corrected time of // left edge of display - tpost = tpre + (r.width * tstep); // offset corrected time of + tpost = h1 - tOffset; // offset corrected time of // right edge of display const double sps = 1. / rate; //seconds-per-sample // Determine whether we should show individual samples // or draw circular points as well - showIndividualSamples = (pps / rate > 0.5); //zoomed in a lot - showPoints = (pps / rate > 3.0); //zoomed in even more + averagePixelsPerSample = rect.width / (rate * (h1 - h)); + showIndividualSamples = averagePixelsPerSample > 0.5; // Calculate actual selection bounds so that t0 > 0 and t1 < the // end of the track @@ -1528,7 +1515,7 @@ struct ClipParameters if (showIndividualSamples) { // adjustment so that the last circular point doesn't appear // to be hanging off the end - t1 += 2. / pps; + t1 += 2. / (averagePixelsPerSample * rate); } // Make sure t1 (the right bound) is greater than 0 @@ -1556,50 +1543,76 @@ struct ClipParameters ssel1 = (sampleCount)(0.5 + trackLen * rate); } - // The variable "mid" will be the rectangle containing the + // The variable "hiddenMid" will be the rectangle containing the // actual waveform, as opposed to any blank area before - // or after the track. - mid = r; + // or after the track, as it would appear without the fisheye. + hiddenMid = rect; // If the left edge of the track is to the right of the left // edge of the display, then there's some blank area to the - // left of the track. Reduce the "mid" - // rect by size of the blank area. + // left of the track. Reduce the "hiddenMid" + hiddenLeftOffset = 0; if (tpre < 0) { - // Fill in the area to the left of the track - double delta = r.width; - if (t0 < tpost) { - delta = (int)((t0 - tpre) * pps); - } - - // Offset the rectangle containing the waveform by the width - // of the area we just erased. - mid.x += (int)delta; - mid.width -= (int)delta; + hiddenLeftOffset = std::min(rect.width, int( + zoomInfo.TimeToPosition(tOffset, 0 + , true + ) + )); + hiddenMid.x += hiddenLeftOffset; + hiddenMid.width -= hiddenLeftOffset; } // If the right edge of the track is to the left of the the right // edge of the display, then there's some blank area to the right - // of the track. Reduce the "mid" rect by the + // of the track. Reduce the "hiddenMid" rect by the // size of the blank area. if (tpost > t1) { - wxRect post = r; - if (t1 > tpre) { - post.x += (int)((t1 - tpre) * pps); - } - post.width = r.width - (post.x - r.x); - // Reduce the rectangle containing the waveform by the width - // of the area we just erased. - mid.width -= post.width; + const int hiddenRightOffset = std::min(rect.width, int( + zoomInfo.TimeToPosition(tOffset + t1, 0 + , true + ) + )); + hiddenMid.width = std::max(0, hiddenRightOffset - hiddenLeftOffset); + } + + // The variable "mid" will be the rectangle containing the + // actual waveform, as distorted by the fisheye, + // as opposed to any blank area before or after the track. + mid = rect; + + // If the left edge of the track is to the right of the left + // edge of the display, then there's some blank area to the + // left of the track. Reduce the "hiddenMid" + leftOffset = 0; + if (tpre < 0) { + leftOffset = std::min(rect.width, int( + zoomInfo.TimeToPosition(tOffset, 0 + , false + ) + )); + mid.x += leftOffset; + mid.width -= leftOffset; + } + + // If the right edge of the track is to the left of the the right + // edge of the display, then there's some blank area to the right + // of the track. Reduce the "hiddenMid" rect by the + // size of the blank area. + if (tpost > t1) { + const int distortedRightOffset = std::min(rect.width, int( + zoomInfo.TimeToPosition(tOffset + t1, 0 + , false + ) + )); + mid.width = std::max(0, distortedRightOffset - leftOffset); } } double tOffset; double rate; double h; // absolute time of left edge of display - double tstep; double tpre; // offset corrected time of left edge of display - // double h1; + double h1; double tpost; // offset corrected time of right edge of display // Calculate actual selection bounds so that t0 > 0 and t1 < the @@ -1607,23 +1620,69 @@ struct ClipParameters double t0; double t1; - double pps; - bool showIndividualSamples, showPoints; + double averagePixelsPerSample; + bool showIndividualSamples; sampleCount ssel0; sampleCount ssel1; + wxRect hiddenMid; + int hiddenLeftOffset; + wxRect mid; + int leftOffset; }; } +namespace { +struct WavePortion { + wxRect rect; + const double averageZoom; + const bool inFisheye; + WavePortion(int x, int y, int w, int h, double zoom, bool i) + : rect(x, y, w, h), averageZoom(zoom), inFisheye(i) + {} +}; + +void FindWavePortions + (std::vector &portions, const wxRect &rect, const ZoomInfo &zoomInfo, + const ClipParameters ¶ms) +{ + // If there is no fisheye, then only one rectangle has nonzero width. + // If there is a fisheye, make rectangles for before and after + // (except when they are squeezed to zero width), and at least one for inside + // the fisheye. + + ZoomInfo::Intervals intervals; + zoomInfo.FindIntervals(params.rate, intervals, rect.x); + ZoomInfo::Intervals::const_iterator it = intervals.begin(), end = intervals.end(), prev; + wxASSERT(it != end && it->position == rect.x); + const int rightmost = rect.x + rect.width; + for (int left = rect.x; left < rightmost;) { + while (it != end && it->position <= left) + prev = it++; + const int right = std::max(left, int( + it != end ? it->position : rightmost + )); + const int width = right - left; + if (width > 0) + portions.push_back( + WavePortion(left, rect.y, width, rect.height, + prev->averageZoom, prev->inFisheye) + ); + left = right; + } +} +} + void TrackArtist::DrawClipWaveform(WaveTrack *track, WaveClip *clip, wxDC & dc, - const wxRect & r, - const ViewInfo *viewInfo, + const wxRect & rect, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo, bool drawEnvelope, - bool drawSamples, + bool bigPoints, bool dB, bool muted) { @@ -1631,92 +1690,176 @@ void TrackArtist::DrawClipWaveform(WaveTrack *track, Profiler profiler; #endif - const ClipParameters params(false, track, clip, r, viewInfo->selectedRegion, *viewInfo); - const wxRect &mid = params.mid; - // The "mid" rect contains the part of the display actually - // containing the waveform. If it's empty, we're done. - if (mid.width <= 0) { + const ClipParameters params(false, track, clip, rect, selectedRegion, zoomInfo); + const wxRect &hiddenMid = params.hiddenMid; + // The "hiddenMid" rect contains the part of the display actually + // containing the waveform, as it appears without the fisheye. If it's empty, we're done. + if (hiddenMid.width <= 0) { return; } const double &t0 = params.t0; - const double &pps = params.pps; const double &tOffset = params.tOffset; - const double &tstep = params.tstep; - const double &ssel0 = params.ssel0; - const double &ssel1 = params.ssel1; - const bool &showIndividualSamples = params.showIndividualSamples; - const bool &showPoints = params.showPoints; const double &h = params.h; const double &tpre = params.tpre; const double &tpost = params.tpost; const double &t1 = params.t1; + const double &averagePixelsPerSample = params.averagePixelsPerSample; + const double &rate = params.rate; + double leftOffset = params.leftOffset; + const wxRect &mid = params.mid; - // Calculate sample-based offset-corrected selection dc.SetPen(*wxTRANSPARENT_PEN); // If we get to this point, the clip is actually visible on the // screen, so remember the display rectangle. - clip->SetDisplayRect(mid); + clip->SetDisplayRect(hiddenMid); // The bounds (controlled by vertical zooming; -1.0...1.0 // by default) float zoomMin, zoomMax; track->GetDisplayBounds(&zoomMin, &zoomMax); - WaveDisplay display(mid.width); - bool isLoadingOD = false;//true if loading on demand block in sequence. - - // The WaveClip class handles the details of computing the shape - // of the waveform. The only way GetWaveDisplay will fail is if - // there's a serious error, like some of the waveform data can't - // be loaded. So if the function returns false, we can just exit. - if (!clip->GetWaveDisplay(display, - t0, pps, isLoadingOD)) { - return; - } - - // Get the values of the envelope corresponding to each pixel - // in the display, and use these to compute the height of the - // track at each pixel - - double *envValues = new double[mid.width]; - clip->GetEnvelope()->GetValues(envValues, mid.width, t0 + tOffset, tstep); + std::vector vEnv(mid.width); + double *const env = &vEnv[0]; + clip->GetEnvelope()->GetValues(env, mid.width, leftOffset, zoomInfo); // Draw the background of the track, outlining the shape of // the envelope and using a colored pen for the selected // part of the waveform - DrawWaveformBackground(dc, mid, envValues, zoomMin, zoomMax, dB, - &display.where[0], ssel0, ssel1, drawEnvelope, - !track->GetSelected()); + DrawWaveformBackground(dc, leftOffset, mid, + env, + zoomMin, zoomMax, dB, + selectedRegion, zoomInfo, drawEnvelope, + !track->GetSelected()); - if (!showIndividualSamples) { -#ifdef EXPERIMENTAL_OUTPUT_DISPLAY - DrawMinMaxRMS(dc, mid, envValues, zoomMin, zoomMax, dB, - min, max, rms, bl, isLoadingOD, muted, track->GetChannelGain(track->GetChannel())); -#else - DrawMinMaxRMS(dc, mid, envValues, zoomMin, zoomMax, dB, - display, isLoadingOD, muted); -#endif + WaveDisplay display(hiddenMid.width); + bool isLoadingOD = false;//true if loading on demand block in sequence. + + const double pps = + averagePixelsPerSample * rate; + if (!params.showIndividualSamples) { + // The WaveClip class handles the details of computing the shape + // of the waveform. The only way GetWaveDisplay will fail is if + // there's a serious error, like some of the waveform data can't + // be loaded. So if the function returns false, we can just exit. + + // Note that we compute the full width display even if there is a + // fisheye hiding part of it, because of the caching. If the + // fisheye moves over the background, there is then less to do when + // redrawing. + + if (!clip->GetWaveDisplay(display, + t0, pps, isLoadingOD)) + return; } - else { - DrawIndividualSamples(dc, mid, zoomMin, zoomMax, dB, - clip, t0, pps, h, - drawSamples, showPoints, muted); + + // For each portion separately, we will decide to draw + // it as min/max/rms or as individual samples. + std::vector portions; + FindWavePortions(portions, rect, zoomInfo, params); + const unsigned nPortions = portions.size(); + + // Require at least 1/2 pixel per sample for drawing individual samples. + const double threshold1 = 0.5 * rate; + // Require at least 3 pixels per sample for drawing the draggable points. + const double threshold2 = 3 * rate; + for (unsigned ii = 0; ii < nPortions; ++ii) { + WavePortion &portion = portions[ii]; + const bool showIndividualSamples = portion.averageZoom > threshold1; + const bool showPoints = portion.averageZoom > threshold2; + wxRect& rect = portion.rect; + rect.Intersect(mid); + wxASSERT(rect.width >= 0); + + float *useMin = 0, *useMax = 0, *useRms = 0; + int *useBl = 0; + WaveDisplay fisheyeDisplay(rect.width); + int skipped = 0, skippedLeft = 0, skippedRight = 0; + if (portion.inFisheye) { + if (!showIndividualSamples) { + fisheyeDisplay.Allocate(); + const sampleCount numSamples = clip->GetNumSamples(); + // Get wave display data for different magnification + int jj = 0; + for (; jj < rect.width; ++jj) { + const double time = + zoomInfo.PositionToTime(jj, -leftOffset) - tOffset; + const sampleCount sample = (sampleCount)floor(time * rate + 0.5); + if (sample < 0) { + ++rect.x; + ++skippedLeft; + continue; + } + if (sample >= numSamples) + break; + fisheyeDisplay.where[jj - skippedLeft] = sample; + } + + skippedRight = rect.width - jj; + skipped = skippedRight + skippedLeft; + rect.width -= skipped; + + // where needs a sentinel + if (jj > 0) + fisheyeDisplay.where[jj - skippedLeft] = + 1 + fisheyeDisplay.where[jj - skippedLeft - 1]; + fisheyeDisplay.width -= skipped; + // Get a wave display for the fisheye, uncached. + if (rect.width > 0) + if (!clip->GetWaveDisplay( + fisheyeDisplay, t0, -1.0, // ignored + isLoadingOD)) + continue; // serious error. just don't draw?? + useMin = fisheyeDisplay.min; + useMax = fisheyeDisplay.max; + useRms = fisheyeDisplay.rms; + useBl = fisheyeDisplay.bl; + } + } + else { + const int pos = leftOffset - params.hiddenLeftOffset; + useMin = display.min + pos; + useMax = display.max + pos; + useRms = display.rms + pos; + useBl = display.bl + pos; + } + + leftOffset += skippedLeft; + + if (rect.width > 0) { + if (!showIndividualSamples) { + std::vector vEnv2(rect.width); + double *const env2 = &vEnv2[0]; + clip->GetEnvelope()->GetValues(env2, rect.width, leftOffset, zoomInfo); + DrawMinMaxRMS(dc, rect, env2, + zoomMin, zoomMax, dB, + useMin, useMax, useRms, useBl, + isLoadingOD, muted +#ifdef EXPERIMENTAL_OUTPUT_DISPLAY + , track->GetChannelGain(track->GetChannel()) +#endif + ); + } + else + DrawIndividualSamples(dc, leftOffset, rect, zoomMin, zoomMax, dB, + clip, zoomInfo, + bigPoints, showPoints, muted); + } + + leftOffset += rect.width + skippedRight; } if (drawEnvelope) { - DrawEnvelope(dc, mid, envValues, zoomMin, zoomMax, dB); - clip->GetEnvelope()->DrawPoints(dc, r, h, pps, dB, zoomMin, zoomMax); + DrawEnvelope(dc, mid, env, zoomMin, zoomMax, dB); + clip->GetEnvelope()->DrawPoints(dc, rect, zoomInfo, dB, zoomMin, zoomMax); } - delete[] envValues; - // Draw arrows on the left side if the track extends to the left of the // beginning of time. :) if (h == 0.0 && tOffset < 0.0) { - DrawNegativeOffsetTrackArrows(dc, r); + DrawNegativeOffsetTrackArrows(dc, rect); } // Draw clip edges @@ -1724,18 +1867,18 @@ void TrackArtist::DrawClipWaveform(WaveTrack *track, if (tpre < 0) { AColor::Line(dc, mid.x - 1, mid.y, - mid.x - 1, mid.y + r.height); + mid.x - 1, mid.y + rect.height); } if (tpost > t1) { AColor::Line(dc, mid.x + mid.width, mid.y, - mid.x + mid.width, mid.y + r.height); + mid.x + mid.width, mid.y + rect.height); } } void TrackArtist::DrawTimeSlider(wxDC & dc, - const wxRect & r, + const wxRect & rect, bool rightwards) { const int border = 3; // 3 pixels all round. @@ -1746,10 +1889,10 @@ void TrackArtist::DrawTimeSlider(wxDC & dc, const int xFlat = 3; //Enough space to draw in? - if (r.height <= ((taper+border + barSpacing) * 2)) { + if (rect.height <= ((taper+border + barSpacing) * 2)) { return; } - if (r.width <= (width * 2 + border * 3)) { + if (rect.width <= (width * 2 + border * 3)) { return; } @@ -1757,10 +1900,10 @@ void TrackArtist::DrawTimeSlider(wxDC & dc, int leftTaper = rightwards ? 0 : 6; int rightTaper = rightwards ? 6 : 0; - int xLeft = rightwards ? (r.x + border - 2) - : (r.x + r.width + 1 - (border + width)); - int yTop = r.y + border; - int yBot = r.y + r.height - border - 1; + int xLeft = rightwards ? (rect.x + border - 2) + : (rect.x + rect.width + 1 - (border + width)); + int yTop = rect.y + border; + int yBot = rect.y + rect.height - border - 1; AColor::Light(&dc, false); AColor::Line(dc, xLeft, yBot - leftTaper, xLeft, yTop + leftTaper); @@ -1775,34 +1918,34 @@ void TrackArtist::DrawTimeSlider(wxDC & dc, int firstBar = yTop + taper + taper / 2; int nBars = (yBot - yTop - taper * 3) / barSpacing + 1; xLeft += (width - barWidth + 1) / 2; - int y; + int yy; int i; AColor::Light(&dc, false); for (i = 0;i < nBars; i++) { - y = firstBar + barSpacing * i; - AColor::Line(dc, xLeft, y, xLeft + barWidth, y); + yy = firstBar + barSpacing * i; + AColor::Line(dc, xLeft, yy, xLeft + barWidth, yy); } AColor::Dark(&dc, false); for(i = 0;i < nBars; i++){ - y = firstBar + barSpacing * i + 1; - AColor::Line(dc, xLeft, y, xLeft + barWidth, y); + yy = firstBar + barSpacing * i + 1; + AColor::Line(dc, xLeft, yy, xLeft + barWidth, yy); } } void TrackArtist::DrawSpectrum(WaveTrack *track, wxDC & dc, - const wxRect & r, - const ViewInfo *viewInfo) + const wxRect & rect, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo) { - DrawBackgroundWithSelection(&dc, r, track, blankSelectedBrush, blankBrush, - viewInfo->selectedRegion.t0(), viewInfo->selectedRegion.t1(), - viewInfo->h, viewInfo->zoom); + DrawBackgroundWithSelection(&dc, rect, track, blankSelectedBrush, blankBrush, + selectedRegion, zoomInfo); WaveTrackCache cache(track); for (WaveClipList::compatibility_iterator it = track->GetClipIterator(); it; it = it->GetNext()) { - DrawClipSpectrum(cache, it->GetData(), dc, r, viewInfo); + DrawClipSpectrum(cache, it->GetData(), dc, rect, selectedRegion, zoomInfo); } } @@ -1873,46 +2016,52 @@ AColor::ColorGradientChoice ChooseColorSet( float bin0, float bin1, float selBin } -void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, +void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache, WaveClip *clip, wxDC & dc, - const wxRect & r, - const ViewInfo *viewInfo) + const wxRect & rect, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo) { #ifdef PROFILE_WAVEFORM Profiler profiler; #endif - const WaveTrack *const track = cache.GetTrack(); + const WaveTrack *const track = waveTrackCache.GetTrack(); const int display = track->GetDisplay(); const bool autocorrelation = (WaveTrack::PitchDisplay == display); const bool logF = (WaveTrack::SpectrumLogDisplay == display || WaveTrack::SpectralSelectionLogDisplay == display); - - enum { MONOCHROME_LINE = 230, COLORED_LINE = 0 }; enum { DASH_LENGTH = 10 /* pixels */ }; - const ClipParameters params(true, track, clip, r, viewInfo->selectedRegion, *viewInfo); - const wxRect &mid = params.mid; - // The "mid" rect contains the part of the display actually - // containing the waveform. If it's empty, we're done. - if (mid.width <= 0) { + const ClipParameters params(true, track, clip, rect, selectedRegion, zoomInfo); + const wxRect &hiddenMid = params.hiddenMid; + // The "hiddenMid" rect contains the part of the display actually + // containing the waveform, as it appears without the fisheye. If it's empty, we're done. + if (hiddenMid.width <= 0) { return; } const double &t0 = params.t0; - const double &pps = params.pps; - const double &tstep = params.tstep; + const double &tOffset = params.tOffset; const double &ssel0 = params.ssel0; const double &ssel1 = params.ssel1; + const double &averagePixelsPerSample = params.averagePixelsPerSample; const double &rate = params.rate; + const double &hiddenLeftOffset = params.hiddenLeftOffset; + const double &leftOffset = params.leftOffset; + const wxRect &mid = params.mid; + + // If we get to this point, the clip is actually visible on the + // screen, so remember the display rectangle. + clip->SetDisplayRect(hiddenMid); double freqLo = SelectedRegion::UndefinedFrequency; double freqHi = SelectedRegion::UndefinedFrequency; #ifdef EXPERIMENTAL_SPECTRAL_EDITING if (!autocorrelation) { - freqLo = viewInfo->selectedRegion.f0(); - freqHi = viewInfo->selectedRegion.f1(); + freqLo = selectedRegion.f0(); + freqHi = selectedRegion.f1(); } #endif @@ -1936,19 +2085,23 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, // and then paint this directly to our offscreen // bitmap. Note that this could be optimized even // more, but for now this is not bad. -dmazzoni - wxImage *image = new wxImage((int) mid.width, (int) mid.height); - if (!image)return; + wxImage *image = new wxImage((int)mid.width, (int)mid.height); + if (!image) + return; unsigned char *data = image->GetData(); const int half = GetSpectrumWindowSize(!autocorrelation) / 2; const double binUnit = rate / (2 * half); const float *freq = 0; const sampleCount *where = 0; + bool updated; + { + const double pps = averagePixelsPerSample * rate; + updated = clip->GetSpectrogram(waveTrackCache, freq, where, hiddenMid.width, + t0, pps, autocorrelation); + } - bool updated = clip->GetSpectrogram(cache, freq, where, mid.width, - t0, pps, autocorrelation); - - int ifreq = lrint(rate/2); + int ifreq = lrint(rate / 2); int maxFreq; if (!logF) @@ -1989,21 +2142,22 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, bool *yGrid; yGrid = new bool[mid.height]; - for (int y = 0; y < mid.height; y++) { - float n = (float(y) / mid.height*scale2 - lmin2) * 12; - float n2 = (float(y + 1) / mid.height*scale2 - lmin2) * 12; + for (int yy = 0; yy < mid.height; ++yy) { + float n = (float(yy) / mid.height*scale2 - lmin2) * 12; + float n2 = (float(yy + 1) / mid.height*scale2 - lmin2) * 12; float f = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n / 12.0f + lmin2); float f2 = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n2 / 12.0f + lmin2); n = logf(f / 440) / log2 * 12; n2 = logf(f2 / 440) / log2 * 12; if (floor(n) < floor(n2)) - yGrid[y] = true; + yGrid[yy] = true; else - yGrid[y] = false; + yGrid[yy] = false; } #endif //EXPERIMENTAL_FFT_Y_GRID - if (!updated && clip->mSpecPxCache->valid && (clip->mSpecPxCache->len == mid.height * mid.width) + if (!updated && clip->mSpecPxCache->valid && + (clip->mSpecPxCache->len == hiddenMid.height * hiddenMid.width) && gain == clip->mSpecPxCache->gain && range == clip->mSpecPxCache->range && minFreq == clip->mSpecPxCache->minFreq @@ -2018,11 +2172,13 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, && findNotesQuantize==findNotesQuantizeOld #endif ) { - // cache is up to date + // Wave clip's spectrum cache is up to date, + // and so is the spectrum pixel cache } else { + // Update the spectrum pixel cache delete clip->mSpecPxCache; - clip->mSpecPxCache = new SpecPxCache(mid.width * mid.height); + clip->mSpecPxCache = new SpecPxCache(hiddenMid.width * hiddenMid.height); clip->mSpecPxCache->valid = true; clip->mSpecPxCache->gain = gain; clip->mSpecPxCache->range = range; @@ -2055,15 +2211,15 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, int *indexes = new int[maxTableSize]; #endif //EXPERIMENTAL_FIND_NOTES - for (int xx = 0; xx < mid.width; ++xx) + for (int xx = 0; xx < hiddenMid.width; ++xx) { if (!logF) { - for (int yy = 0; yy < mid.height; ++yy) { + for (int yy = 0; yy < hiddenMid.height; ++yy) { float bin0 = float(yy) * binPerPx + minBin; float bin1 = float(yy + 1) * binPerPx + minBin; const float value = findValue (freq + half * xx, bin0, bin1, half, autocorrelation, gain, range); - clip->mSpecPxCache->values[xx * mid.height + yy] = value; + clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value; } } else { @@ -2110,7 +2266,7 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, } // The f2pix helper macro converts a frequency into a pixel coordinate. -#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*mid.height +#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++) { @@ -2133,8 +2289,8 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, double yy2_base = exp(lmin) / binUnit; float yy2 = yy2_base; - double exp_scale_per_height = exp(scale / mid.height); - for (int yy = 0; yy < mid.height; ++yy) { + 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) @@ -2178,13 +2334,12 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, value = findValue (freq + half * xx, bin0, bin1, half, autocorrelation, gain, range); } - clip->mSpecPxCache->values[xx * mid.height + yy] = value; + clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value; yy2 = yy2_base; - } // each y + } // each yy } // is logF } // each xx - } // updating cache float selBinLo = freqLo / binUnit; @@ -2192,24 +2347,56 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, float selBinCenter = ((freqLo < 0 || freqHi < 0) ? -1 : sqrt(freqLo * freqHi)) / binUnit; - sampleCount w1 = sampleCount(0.5 + rate * - t0 + sampleCount w1(0.5 + rate * + (zoomInfo.PositionToTime(0, -leftOffset) - tOffset) ); - for (int xx = 0; xx < mid.width; ++xx) + const bool hidden = (ZoomInfo::HIDDEN == zoomInfo.GetFisheyeState()); + const int begin = hidden + ? 0 + : std::max(0, int(zoomInfo.GetFisheyeLeftBoundary(-leftOffset))); + const int end = hidden + ? 0 + : std::min(mid.width, int(zoomInfo.GetFisheyeRightBoundary(-leftOffset))); + const int numPixels = std::max(0, end - begin); + const int zeroPaddingFactor = autocorrelation ? 1 : settings.zeroPaddingFactor; + SpecCache specCache + (numPixels, autocorrelation, -1, + t0, settings.windowType, + settings.windowSize, zeroPaddingFactor, settings.frequencyGain); + if (numPixels > 0) { + for (int ii = begin; ii < end; ++ii) { + const double time = zoomInfo.PositionToTime(ii, -leftOffset) - tOffset; + specCache.where[ii - begin] = sampleCount(0.5 + rate * time); + } + specCache.Populate + (settings, waveTrackCache, + 0, 0, numPixels, + clip->GetNumSamples(), + tOffset, rate, + autocorrelation); + } + + int correctedX = leftOffset - hiddenLeftOffset; + int fisheyeColumn = 0; + for (int xx = 0; xx < mid.width; ++xx, ++correctedX) { + const bool inFisheye = zoomInfo.InFisheye(xx, -leftOffset); + float *const uncached = + inFisheye ? &specCache.freq[(fisheyeColumn++) * half] : 0; + sampleCount w0 = w1; w1 = sampleCount(0.5 + rate * - (t0 + (xx+1) * tstep) + (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 < mid.height; ++yy) { - float bin0 = float (yy) * binPerPx + minBin; - float bin1 = float (yy + 1) * binPerPx + minBin; + for (int yy = 0; yy < hiddenMid.height; ++yy) { + float bin0 = float(yy) * binPerPx + minBin; + float bin1 = float(yy + 1) * binPerPx + minBin; // For spectral selection, determine what colour // set to use. We use a darker selection if @@ -2221,13 +2408,15 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, if (ssel0 <= w0 && w1 < ssel1) { bool isSpectral = ((track->GetDisplay() == WaveTrack::SpectralSelectionDisplay) || - (track->GetDisplay() == WaveTrack::SpectralSelectionLogDisplay)); - selected = ChooseColorSet( bin0, bin1, selBinLo, selBinCenter, selBinHi, xx/DASH_LENGTH, isSpectral ); + (track->GetDisplay() == WaveTrack::SpectralSelectionLogDisplay)); + selected = ChooseColorSet(bin0, bin1, selBinLo, selBinCenter, selBinHi, + (xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral); } unsigned char rv, gv, bv; - const float value = - clip->mSpecPxCache->values[xx * mid.height + yy]; + 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; @@ -2239,8 +2428,8 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, { double yy2_base=exp(lmin)/binUnit; float yy2 = yy2_base; - double exp_scale_per_height = exp(scale/mid.height); - for (int yy = 0; yy < mid.height; ++yy) { + 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) @@ -2254,20 +2443,21 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, yy3=0; float bin1 = float(yy3); - AColor::ColorGradientChoice selected = - AColor::ColorGradientUnselected; + 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) { bool isSpectral = ((track->GetDisplay() == WaveTrack::SpectralSelectionDisplay) || - (track->GetDisplay() == WaveTrack::SpectralSelectionLogDisplay)); - selected = ChooseColorSet( bin0, bin1, selBinLo, selBinCenter, selBinHi, xx/DASH_LENGTH, isSpectral ); + (track->GetDisplay() == WaveTrack::SpectralSelectionLogDisplay)); + selected = ChooseColorSet( + bin0, bin1, selBinLo, selBinCenter, selBinHi, + (xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral); } - const float value = clip->mSpecPxCache->values[xx * mid.height + yy]; - yy2 = yy2_base; - unsigned char rv, gv, bv; + const float value = uncached + ? findValue(uncached, bin0, bin1, half, autocorrelation, gain, range) + : clip->mSpecPxCache->values[correctedX * hiddenMid.height + yy]; GetColorGradient(value, selected, isGrayscale, &rv, &gv, &bv); #ifdef EXPERIMENTAL_FFT_Y_GRID @@ -2282,13 +2472,11 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, data[px++] = rv; data[px++] = gv; data[px] = bv; - } // each y - } // logF - } // each xx - // If we get to this point, the clip is actually visible on the - // screen, so remember the display rectangle. - clip->SetDisplayRect(mid); + yy2 = yy2_base; + } + } + } wxBitmap converted = wxBitmap(*image); @@ -2466,8 +2654,8 @@ const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def return def; } -#define TIME_TO_X(t) (r.x + (int) (((t) - h) * pps)) -#define X_TO_TIME(xx) (((xx) - r.x) / pps + h) +#define TIME_TO_X(t) (zoomInfo.TimeToPosition((t), rect.x)) +#define X_TO_TIME(xx) (zoomInfo.PositionToTime((xx), rect.x)) // CLIP(x) changes x to lie between +/- CLIP_MAX due to graphics display problems // with very large coordinate values (this happens when you zoom in very far) @@ -2477,14 +2665,14 @@ const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def // particular, line plots will be correct at any zoom (limited by floating point // precision). #define CLIP_MAX 16000 -#define CLIP(x) { long c = (x); if (c < -CLIP_MAX) c = -CLIP_MAX; \ - if (c > CLIP_MAX) c = CLIP_MAX; (x) = c; } +#define CLIP(xx) { long c = (xx); if (c < -CLIP_MAX) c = -CLIP_MAX; \ + if (c > CLIP_MAX) c = CLIP_MAX; (xx) = c; } #define RED(i) ( unsigned char )( (((i) >> 16) & 0xff) ) #define GREEN(i) ( unsigned char )( (((i) >> 8) & 0xff) ) #define BLUE(i) ( unsigned char )( ((i) & 0xff) ) -//#define PITCH_TO_Y(p) (r.y + r.height - int(pitchht * ((p) + 0.5 - pitch0) + 0.5)) +//#define PITCH_TO_Y(p) (rect.y + rect.height - int(pitchht * ((p) + 0.5 - pitch0) + 0.5)) /* int PitchToY(double p, int bottom) @@ -2500,14 +2688,14 @@ int PitchToY(double p, int bottom) /* DrawNoteBackground is called by DrawNoteTrack twice: once to draw the unselected background, and once to draw the selected background. The selected background is the same except for the horizontal range - and the colors. The background rectangle region is given by r; the + and the colors. The background rectangle region is given by rect; the selected region is given by sel. The first time this is called, - sel is equal to r, and the entire region is drawn with unselected + sel is equal to rect, and the entire region is drawn with unselected background colors. */ void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, - const wxRect &r, const wxRect &sel, - const ViewInfo *viewInfo, + const wxRect &rect, const wxRect &sel, + const ZoomInfo &zoomInfo, const wxBrush &wb, const wxPen &wp, const wxBrush &bb, const wxPen &bp, const wxPen &mp) @@ -2515,8 +2703,6 @@ void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, dc.SetBrush(wb); dc.SetPen(wp); dc.DrawRectangle(sel); // fill rectangle with white keys background - double h = viewInfo->h; - double pps = viewInfo->zoom; int left = TIME_TO_X(track->GetOffset()); if (left < sel.x) left = sel.x; // clip on left @@ -2534,16 +2720,16 @@ void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, // eOffset is for the line between E and F; there's another line // between B and C, hence the offset of 2 for two line thicknesses int eOffset = track->GetPitchHeight() * 5 + 2; - while (obottom > r.y + track->GetNoteMargin() + 3) { + while (obottom > rect.y + track->GetNoteMargin() + 3) { // draw a black line separating octaves if this octave botton is visible - if (obottom < r.y + r.height - track->GetNoteMargin()) { + if (obottom < rect.y + rect.height - track->GetNoteMargin()) { dc.SetPen(*wxBLACK_PEN); // obottom - 1 because obottom is at the bottom of the line AColor::Line(dc, left, obottom - 1, right, obottom - 1); } dc.SetPen(bp); // draw a black-key stripe colored line separating E and F if visible - if (obottom - eOffset > r.y && obottom - eOffset < r.y + r.height) { + if (obottom - eOffset > rect.y && obottom - eOffset < rect.y + rect.height) { AColor::Line(dc, left, obottom - eOffset, right, obottom - eOffset); } @@ -2555,7 +2741,7 @@ void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, br.height = track->GetPitchHeight(); for (int black = 0; black < 5; black++) { br.y = obottom - track->GetBlackPos(black); - if (br.y > r.y && br.y + br.height < r.y + r.height) { + if (br.y > rect.y && br.y + br.height < rect.y + rect.height) { dc.DrawRectangle(br); // draw each black key background stripe } } @@ -2588,9 +2774,9 @@ void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, // map beat to time double t = seq->get_time_map()->beat_to_time(next_bar_beat); // map time to position - int x = TIME_TO_X(t + track->GetOffset()); - if (x > right) break; - AColor::Line(dc, x, sel.y, x, sel.y + sel.height); + int xx = TIME_TO_X(t + track->GetOffset()); + if (xx > right) break; + AColor::Line(dc, xx, sel.y, xx, sel.y + sel.height); next_bar_beat += beats_per_measure; } } @@ -2603,17 +2789,17 @@ window and draw out-of-bounds notes here instead. */ void TrackArtist::DrawNoteTrack(NoteTrack *track, wxDC & dc, - const wxRect & r, - const ViewInfo *viewInfo, + const wxRect & rect, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo, bool muted) { SonifyBeginNoteBackground(); - double h = viewInfo->h; - double pps = viewInfo->zoom; - double sel0 = viewInfo->selectedRegion.t0(); - double sel1 = viewInfo->selectedRegion.t1(); + double sel0 = selectedRegion.t0(); + double sel1 = selectedRegion.t1(); - double h1 = X_TO_TIME(r.x + r.width); + const double h = X_TO_TIME(rect.x); + const double h1 = X_TO_TIME(rect.x + rect.width); Alg_seq_ptr seq = track->mSeq; if (!seq) { @@ -2636,14 +2822,14 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, // reserve 1/2 note height at top and bottom of track for // out-of-bounds notes - int numPitches = (r.height) / track->GetPitchHeight(); + int numPitches = (rect.height) / track->GetPitchHeight(); if (numPitches < 0) numPitches = 0; // cannot be negative // bottom is the hypothetical location of the bottom of pitch 0 relative to - // the top of the clipping region r: r.height - PITCH_HEIGHT/2 is where the + // the top of the clipping region rect: rect.height - PITCH_HEIGHT/2 is where the // bottomNote is displayed, and to that // we add the height of bottomNote from the position of pitch 0 - track->PrepareIPitchToY(r); + track->PrepareIPitchToY(rect); // Background comes in 6 colors: // 214, 214,214 -- unselected white keys @@ -2659,17 +2845,17 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, wxPen barLinePen; barLinePen.SetColour(170, 170, 170); - DrawNoteBackground(track, dc, r, r, viewInfo, blankBrush, blankPen, + DrawNoteBackground(track, dc, rect, rect, zoomInfo, blankBrush, blankPen, blackStripeBrush, blackStripePen, barLinePen); - dc.SetClippingRegion(r); + dc.SetClippingRegion(rect); // Draw the selection background // First, the white keys, as a single rectangle // In other words fill the selection area with selectedWhiteKeyPen wxRect selBG; - selBG.y = r.y; - selBG.height = r.height; + selBG.y = rect.y; + selBG.height = rect.height; selBG.x = TIME_TO_X(sel0); selBG.width = TIME_TO_X(sel1) - TIME_TO_X(sel0); @@ -2687,7 +2873,7 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, wxPen selectedBarLinePen; selectedBarLinePen.SetColour(131, 131, 150); - DrawNoteBackground(track, dc, r, selBG, viewInfo, + DrawNoteBackground(track, dc, rect, selBG, zoomInfo, selectedWhiteKeyBrush, selectedWhiteKeyPen, selectedBlackKeyBrush, selectedBlackKeyPen, selectedBarLinePen); @@ -2733,49 +2919,49 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, Alg_note_ptr note = (Alg_note_ptr) evt; // if the note's channel is visible if (visibleChannels & (1 << (evt->chan & 15))) { - double x = note->time + track->GetOffset(); - double x1 = x + note->dur; - if (x < h1 && x1 > h) { // omit if outside box + double xx = note->time + track->GetOffset(); + double x1 = xx + note->dur; + if (xx < h1 && x1 > h) { // omit if outside box const char *shape = NULL; if (note->loud > 0.0 || 0 == (shape = IsShape(note))) { wxRect nr; // "note rectangle" nr.y = track->PitchToY(note->pitch); nr.height = track->GetPitchHeight(); - nr.x = r.x + (int) ((x - h) * pps); - nr.width = (int) ((note->dur * pps) + 0.5); + nr.x = TIME_TO_X(xx); + nr.width = TIME_TO_X(x1) - nr.x; - if (nr.x + nr.width >= r.x && nr.x < r.x + r.width) { - if (nr.x < r.x) { - nr.width -= (r.x - nr.x); - nr.x = r.x; + if (nr.x + nr.width >= rect.x && nr.x < rect.x + rect.width) { + if (nr.x < rect.x) { + nr.width -= (rect.x - nr.x); + nr.x = rect.x; } - if (nr.x + nr.width > r.x + r.width) // clip on right - nr.width = r.x + r.width - nr.x; + if (nr.x + nr.width > rect.x + rect.width) // clip on right + nr.width = rect.x + rect.width - nr.x; - if (nr.y + nr.height < r.y + marg + 3) { + if (nr.y + nr.height < rect.y + marg + 3) { // too high for window - nr.y = r.y; + nr.y = rect.y; nr.height = marg; dc.SetBrush(*wxBLACK_BRUSH); dc.SetPen(*wxBLACK_PEN); dc.DrawRectangle(nr); - } else if (nr.y >= r.y + r.height - marg - 1) { + } else if (nr.y >= rect.y + rect.height - marg - 1) { // too low for window - nr.y = r.y + r.height - marg; + nr.y = rect.y + rect.height - marg; nr.height = marg; dc.SetBrush(*wxBLACK_BRUSH); dc.SetPen(*wxBLACK_PEN); dc.DrawRectangle(nr); } else { - if (nr.y + nr.height > r.y + r.height - marg) - nr.height = r.y + r.height - nr.y; - if (nr.y < r.y + marg) { - int offset = r.y + marg - nr.y; + if (nr.y + nr.height > rect.y + rect.height - marg) + nr.height = rect.y + rect.height - nr.y; + if (nr.y < rect.y + marg) { + int offset = rect.y + marg - nr.y; nr.height -= offset; nr.y += offset; } - // nr.y += r.y; + // nr.y += rect.y; if (muted) AColor::LightMIDIChannel(&dc, note->chan + 1); else @@ -2799,7 +2985,7 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, // add 0.5 to pitch because pitches are plotted with // height = PITCH_HEIGHT; thus, the center is raised // by PITCH_HEIGHT * 0.5 - int y = track->PitchToY(note->pitch); + int yy = track->PitchToY(note->pitch); long linecolor = LookupIntAttribute(note, linecolori, -1); long linethick = LookupIntAttribute(note, linethicki, 1); long fillcolor = -1; @@ -2829,44 +3015,44 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, if (shape == line) { // extreme zooms caues problems under windows, so we have to do some // clipping before calling display routine - if (x < h) { // clip line on left - y = int((y + (y1 - y) * (h - x) / (x1 - x)) + 0.5); - x = h; + if (xx < h) { // clip line on left + yy = int((yy + (y1 - yy) * (h - xx) / (x1 - xx)) + 0.5); + xx = h; } if (x1 > h1) { // clip line on right - y1 = int((y + (y1 - y) * (h1 - x) / (x1 - x)) + 0.5); + y1 = int((yy + (y1 - yy) * (h1 - xx) / (x1 - xx)) + 0.5); x1 = h1; } - AColor::Line(dc, TIME_TO_X(x), y, TIME_TO_X(x1), y1); + AColor::Line(dc, TIME_TO_X(xx), yy, TIME_TO_X(x1), y1); } else if (shape == rectangle) { - if (x < h) { // clip on left, leave 10 pixels to spare - x = h - (linethick + 10) / pps; + if (xx < h) { // clip on left, leave 10 pixels to spare + xx = X_TO_TIME(rect.x - (linethick + 10)); } if (x1 > h1) { // clip on right, leave 10 pixels to spare - x1 = h1 + (linethick + 10) / pps; + xx = X_TO_TIME(rect.x + rect.width + linethick + 10); } - dc.DrawRectangle(TIME_TO_X(x), y, int((x1 - x) * pps + 0.5), y1 - y + 1); + dc.DrawRectangle(TIME_TO_X(xx), yy, TIME_TO_X(x1) - TIME_TO_X(xx), y1 - yy + 1); } else if (shape == triangle) { wxPoint points[3]; - points[0].x = TIME_TO_X(x); + points[0].x = TIME_TO_X(xx); CLIP(points[0].x); - points[0].y = y; + points[0].y = yy; points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, note->pitch)); CLIP(points[1].x); points[1].y = y1; - points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x)); + points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx)); CLIP(points[2].x); points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch)); dc.DrawPolygon(3, points); } else if (shape == polygon) { wxPoint points[20]; // upper bound of 20 sides - points[0].x = TIME_TO_X(x); + points[0].x = TIME_TO_X(xx); CLIP(points[0].x); - points[0].y = y; - points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, x)); + points[0].y = yy; + points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, xx)); CLIP(points[1].x); points[1].y = y1; - points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x)); + points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx)); CLIP(points[2].x); points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch)); int n = 3; @@ -2887,11 +3073,11 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, } dc.DrawPolygon(n, points); } else if (shape == oval) { - int ix = TIME_TO_X(x); + int ix = TIME_TO_X(xx); CLIP(ix); - int ix1 = int((x1 - x) * pps + 0.5); + int ix1 = TIME_TO_X(x1) - TIME_TO_X(xx); if (ix1 > CLIP_MAX * 2) ix1 = CLIP_MAX * 2; // CLIP a width - dc.DrawEllipse(ix, y, ix1, y1 - y + 1); + dc.DrawEllipse(ix, yy, ix1, y1 - yy + 1); } else if (shape == text) { if (linecolor != -1) dc.SetTextForeground(wxColour(RED(linecolor), @@ -2948,10 +3134,10 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, GREEN(fillcolor), BLUE(fillcolor)), 1, wxSOLID)); - dc.DrawRectangle(TIME_TO_X(x) + hoffset, y + voffset, + dc.DrawRectangle(TIME_TO_X(xx) + hoffset, yy + voffset, textWidth, textHeight); } - dc.DrawText(LAT1CTOWX(s), TIME_TO_X(x) + hoffset, y + voffset); + dc.DrawText(LAT1CTOWX(s), TIME_TO_X(xx) + hoffset, yy + voffset); } } } @@ -2961,12 +3147,12 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, iterator.end(); // draw black line between top/bottom margins and the track dc.SetPen(*wxBLACK_PEN); - AColor::Line(dc, r.x, r.y + marg, r.x + r.width, r.y + marg); - AColor::Line(dc, r.x, r.y + r.height - marg - 1, // subtract 1 to get - r.x + r.width, r.y + r.height - marg - 1); // top of line + AColor::Line(dc, rect.x, rect.y + marg, rect.x + rect.width, rect.y + marg); + AColor::Line(dc, rect.x, rect.y + rect.height - marg - 1, // subtract 1 to get + rect.x + rect.width, rect.y + rect.height - marg - 1); // top of line if (h == 0.0 && track->GetOffset() < 0.0) { - DrawNegativeOffsetTrackArrows(dc, r); + DrawNegativeOffsetTrackArrows(dc, rect); } dc.DestroyClippingRegion(); @@ -2977,25 +3163,26 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, void TrackArtist::DrawLabelTrack(LabelTrack *track, wxDC & dc, - const wxRect & r, - const ViewInfo *viewInfo) + const wxRect & rect, + const SelectedRegion &selectedRegion, + const ZoomInfo &zoomInfo) { - double sel0 = viewInfo->selectedRegion.t0(); - double sel1 = viewInfo->selectedRegion.t1(); + double sel0 = selectedRegion.t0(); + double sel1 = selectedRegion.t1(); if (!track->GetSelected() && !track->IsSyncLockSelected()) sel0 = sel1 = 0.0; - track->Draw(dc, r, viewInfo->h, viewInfo->zoom, sel0, sel1); + track->Draw(dc, rect, SelectedRegion(sel0, sel1), zoomInfo); } void TrackArtist::DrawTimeTrack(TimeTrack *track, wxDC & dc, - const wxRect & r, - const ViewInfo *viewInfo) + const wxRect & rect, + const ZoomInfo &zoomInfo) { - track->Draw(dc, r, viewInfo->h, viewInfo->zoom); - wxRect envRect = r; + track->Draw(dc, rect, zoomInfo); + wxRect envRect = rect; envRect.height -= 2; double lower = track->GetRangeLower(), upper = track->GetRangeUpper(); if(track->GetDisplayLog()) { @@ -3004,7 +3191,7 @@ void TrackArtist::DrawTimeTrack(TimeTrack *track, lower = 20.0 * log10(std::max(1.0e-7, lower)) / dBRange + 1.0; upper = 20.0 * log10(std::max(1.0e-7, upper)) / dBRange + 1.0; } - track->GetEnvelope()->DrawPoints(dc, envRect, viewInfo->h, viewInfo->zoom, + track->GetEnvelope()->DrawPoints(dc, envRect, zoomInfo, track->GetDisplayLog(), lower, upper); } @@ -3089,7 +3276,7 @@ void TrackArtist::SetSpectrumLogMaxFreq(int freq) // 5x5 box. // // There may be a better way to do this, or a more appealing pattern. -void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r) +void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect rect) { wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockSelTile)); @@ -3098,10 +3285,10 @@ void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r) int gridH = syncLockBitmap.GetHeight() - 8; // Horizontal position within the grid, modulo its period - int blockX = (r.x / gridW) % 5; + int blockX = (rect.x / gridW) % 5; // Amount to offset drawing of first column - int xOffset = r.x % gridW; + int xOffset = rect.x % gridW; if (xOffset < 0) xOffset += gridW; // Check if we're missing an extra column to the left (this can happen @@ -3115,21 +3302,21 @@ void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r) // Make sure blockX is non-negative if (blockX < 0) blockX += 5; - int x = 0; - while (x < r.width) { + int xx = 0; + while (xx < rect.width) { int width = syncLockBitmap.GetWidth() - xOffset; - if (x + width > r.width) - width = r.width - x; + if (xx + width > rect.width) + width = rect.width - xx; // // Draw each row in this column // // Vertical position in the grid, modulo its period - int blockY = (r.y / gridH) % 5; + int blockY = (rect.y / gridH) % 5; // Amount to offset drawing of first row - int yOffset = r.y % gridH; + int yOffset = rect.y % gridH; if (yOffset < 0) yOffset += gridH; // Check if we're missing an extra row on top (this can happen because @@ -3143,12 +3330,12 @@ void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r) // Make sure blockY is non-negative if (blockY < 0) blockY += 5; - int y = 0; - while (y < r.height) + int yy = 0; + while (yy < rect.height) { int height = syncLockBitmap.GetHeight() - yOffset; - if (y + height > r.height) - height = r.height - y; + if (yy + height > rect.height) + height = rect.height - yy; // AWD: draw blocks according to our pattern if ((blockX == 0 && blockY == 0) || (blockX == 2 && blockY == 1) || @@ -3160,10 +3347,10 @@ void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r) if (width != syncLockBitmap.GetWidth() || height != syncLockBitmap.GetHeight()) { wxBitmap subSyncLockBitmap = syncLockBitmap.GetSubBitmap(wxRect(xOffset, yOffset, width, height)); - dc->DrawBitmap(subSyncLockBitmap, r.x + x, r.y + y, true); + dc->DrawBitmap(subSyncLockBitmap, rect.x + xx, rect.y + yy, true); } else { - dc->DrawBitmap(syncLockBitmap, r.x + x, r.y + y, true); + dc->DrawBitmap(syncLockBitmap, rect.x + xx, rect.y + yy, true); } } @@ -3175,7 +3362,7 @@ void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r) } else { // Move on in y, no more offset rows - y += gridH - yOffset; + yy += gridH - yOffset; yOffset = 0; } blockY = (blockY + 1) % 5; @@ -3189,33 +3376,36 @@ void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r) } else { // Move on in x, no more offset rows - x += gridW - xOffset; + xx += gridW - xOffset; xOffset = 0; } blockX = (blockX + 1) % 5; } } -void TrackArtist::DrawBackgroundWithSelection(wxDC *dc, const wxRect &r, - Track *track, wxBrush &selBrush, wxBrush &unselBrush, - double sel0, double sel1, double h, double pps) +void TrackArtist::DrawBackgroundWithSelection(wxDC *dc, const wxRect &rect, + Track *track, wxBrush &selBrush, wxBrush &unselBrush, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo) { //MM: Draw background. We should optimize that a bit more. //AWD: "+ 1.5" and "+ 2.5" throughout match code in //AdornedRulerPanel::DoDrawSelection() and make selection line up with ruler. //I don't know if/why this is correct. + const double sel0 = selectedRegion.t0(); + const double sel1 = selectedRegion.t1(); + dc->SetPen(*wxTRANSPARENT_PEN); if (track->GetSelected() || track->IsSyncLockSelected()) { // Rectangles before, within, after the selction - wxRect before = r; - wxRect within = r; - wxRect after = r; + wxRect before = rect; + wxRect within = rect; + wxRect after = rect; - before.width = int ((sel0 - h) * pps + 2.5); - if (before.GetRight() > r.GetRight()) { - before.width = r.width; + before.width = int(zoomInfo.TimeToPosition(sel0) + 2); + if (before.GetRight() > rect.GetRight()) { + before.width = rect.width; } if (before.width > 0) { @@ -3224,10 +3414,10 @@ void TrackArtist::DrawBackgroundWithSelection(wxDC *dc, const wxRect &r, within.x = before.GetRight(); } - within.width = r.x + int ((sel1 - h) * pps + 2.5) - within.x; + within.width = rect.x + int(zoomInfo.TimeToPosition(sel1) + 2) - within.x; - if (within.GetRight() > r.GetRight()) { - within.width = r.GetRight() - within.x; + if (within.GetRight() > rect.GetRight()) { + within.width = rect.GetRight() - within.x; } if (within.width > 0) { @@ -3249,7 +3439,7 @@ void TrackArtist::DrawBackgroundWithSelection(wxDC *dc, const wxRect &r, after.x = within.x; } - after.width = r.GetRight() - after.x; + after.width = rect.GetRight() - after.x; if (after.width > 0) { dc->SetBrush(unselBrush); dc->DrawRectangle(after); @@ -3259,7 +3449,7 @@ void TrackArtist::DrawBackgroundWithSelection(wxDC *dc, const wxRect &r, { // Track not selected; just draw background dc->SetBrush(unselBrush); - dc->DrawRectangle(r); + dc->DrawRectangle(rect); } } diff --git a/src/TrackArtist.h b/src/TrackArtist.h index 1fa32dd8e..5c96ca38a 100644 --- a/src/TrackArtist.h +++ b/src/TrackArtist.h @@ -21,7 +21,7 @@ #include #include #include "Experimental.h" -#include "Sequence.h" +#include "audacity/Types.h" class wxDC; class wxRect; @@ -37,7 +37,8 @@ class LabelTrack; class TimeTrack; class TrackList; class Ruler; -struct ViewInfo; +class SelectedRegion; +class ZoomInfo; #ifndef uchar typedef unsigned char uchar; @@ -52,17 +53,19 @@ class AUDACITY_DLL_API TrackArtist { void SetColours(); void DrawTracks(TrackList *tracks, Track *start, wxDC & dc, wxRegion & reg, - wxRect & r, wxRect & clip, ViewInfo *viewInfo, - bool drawEnvelope, bool drawSamples, bool drawSliders); + wxRect & rect, wxRect & clip, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo, + bool drawEnvelope, bool bigPoints, bool drawSliders); void DrawTrack(const Track *t, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, - bool drawEnvelope, bool drawSamples, bool drawSliders, + wxDC & dc, const wxRect & rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo, + bool drawEnvelope, bool bigPoints, bool drawSliders, bool hasSolo); - void DrawVRuler(Track *t, wxDC *dc, wxRect & r); + void DrawVRuler(Track *t, wxDC *dc, wxRect & rect); - void UpdateVRuler(Track *t, wxRect & r); + void UpdateVRuler(Track *t, wxRect & rect); void SetInset(int left, int top, int right, int bottom); @@ -90,12 +93,12 @@ class AUDACITY_DLL_API TrackArtist { } // Helper: draws the "sync-locked" watermark tiled to a rectangle - static void DrawSyncLockTiles(wxDC *dc, wxRect r); + static void DrawSyncLockTiles(wxDC *dc, wxRect rect); // Helper: draws background with selection rect - static void DrawBackgroundWithSelection(wxDC *dc, const wxRect &r, + static void DrawBackgroundWithSelection(wxDC *dc, const wxRect &rect, Track *track, wxBrush &selBrush, wxBrush &unselBrush, - double sel0, double sel1, double h, double pps); + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo); private: @@ -104,67 +107,74 @@ class AUDACITY_DLL_API TrackArtist { // void DrawWaveform(WaveTrack *track, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, - bool drawEnvelope, bool drawSamples, bool drawSliders, + wxDC & dc, const wxRect & rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo, + bool drawEnvelope, bool bigPoints, bool drawSliders, bool dB, bool muted); void DrawSpectrum(WaveTrack *track, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo); + wxDC & dc, const wxRect & rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo); #ifdef USE_MIDI - int GetBottom(NoteTrack *t, const wxRect &r); + int GetBottom(NoteTrack *t, const wxRect &rect); void DrawNoteBackground(NoteTrack *track, wxDC &dc, - const wxRect &r, const wxRect &sel, - const ViewInfo *viewInfo, + const wxRect &rect, const wxRect &sel, + const ZoomInfo &zoomInfo, const wxBrush &wb, const wxPen &wp, const wxBrush &bb, const wxPen &bp, const wxPen &mp); void DrawNoteTrack(NoteTrack *track, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, + wxDC & dc, const wxRect & rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo, bool muted); #endif // USE_MIDI void DrawLabelTrack(LabelTrack *track, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo); + wxDC & dc, const wxRect & rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo); void DrawTimeTrack(TimeTrack *track, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo); + wxDC & dc, const wxRect & rect, const ZoomInfo &zoomInfo); - void DrawTimeSlider(wxDC & dc, const wxRect & r, + void DrawTimeSlider(wxDC & dc, const wxRect & rect, bool rightwards); void DrawClipWaveform(WaveTrack *track, WaveClip *clip, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, - bool drawEnvelope, bool drawSamples, + wxDC & dc, const wxRect & rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo, + bool drawEnvelope, bool bigPoints, bool dB, bool muted); void DrawClipSpectrum(WaveTrackCache &cache, WaveClip *clip, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo); + wxDC & dc, const wxRect & rect, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo); // Waveform utility functions - void DrawWaveformBackground(wxDC & dc, const wxRect &r, const double env[], + void DrawWaveformBackground(wxDC & dc, int leftOffset, const wxRect &rect, + const double env[], float zoomMin, float zoomMax, bool dB, - const sampleCount where[], - sampleCount ssel0, sampleCount ssel1, + const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo, bool drawEnvelope, bool bIsSyncLockSelected); - void DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[], + void DrawMinMaxRMS(wxDC &dc, const wxRect & rect, const double env[], float zoomMin, float zoomMax, bool dB, - const WaveDisplay &display, bool /* showProgress */, bool muted + const float *min, const float *max, const float *rms, const int *bl, + bool /* showProgress */, bool muted #ifdef EXPERIMENTAL_OUTPUT_DISPLAY , const float gain #endif ); - void DrawIndividualSamples(wxDC & dc, const wxRect & r, + void DrawIndividualSamples(wxDC & dc, int leftOffset, const wxRect & rect, float zoomMin, float zoomMax, bool dB, WaveClip *clip, - double t0, double pps, double h, - bool drawSamples, bool showPoints, bool muted); + const ZoomInfo &zoomInfo, + bool bigPoints, bool showPoints, bool muted); - void DrawNegativeOffsetTrackArrows(wxDC & dc, const wxRect & r); + void DrawNegativeOffsetTrackArrows(wxDC & dc, const wxRect & rect); - void DrawEnvelope(wxDC & dc, const wxRect & r, const double env[], + void DrawEnvelope(wxDC & dc, const wxRect & rect, const double env[], float zoomMin, float zoomMax, bool dB); - void DrawEnvLine(wxDC & dc, const wxRect & r, int x, int y, int cy, bool top); + void DrawEnvLine(wxDC & dc, const wxRect & rect, int x0, int y0, int cy, bool top); // Preference values float mdBrange; // "/GUI/EnvdBRange" @@ -217,7 +227,7 @@ extern int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip); extern float FromDB(float value, double dBRange); -extern float ValueOfPixel(int y, int height, bool offset, +extern float ValueOfPixel(int yy, int height, bool offset, bool dB, double dBRange, float zoomMin, float zoomMax); #endif // define __AUDACITY_TRACKARTIST__ diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 4b790b268..6ef30d837 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -848,21 +848,21 @@ void TrackPanel::UpdatePrefs() #endif mdBr = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE); gPrefs->Read(wxT("/GUI/AutoScroll"), &mViewInfo->bUpdateTrackIndicator, - true); + true); gPrefs->Read(wxT("/GUI/AdjustSelectionEdges"), &mAdjustSelectionEdges, - true); + true); gPrefs->Read(wxT("/GUI/CircularTrackNavigation"), &mCircularTrackNavigation, - false); + false); gPrefs->Read(wxT("/GUI/Solo"), &mSoloPref, wxT("Standard") ); gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &mSeekShort, - 1.0); + 1.0); gPrefs->Read(wxT("/AudioIO/SeekLongPeriod"), &mSeekLong, - 15.0); + 15.0); #ifdef EXPERIMENTAL_OUTPUT_DISPLAY bool temp = WaveTrack::mMonoAsVirtualStereo; gPrefs->Read(wxT("/GUI/MonoAsVirtualStereo"), &WaveTrack::mMonoAsVirtualStereo, - false); + false); if(WaveTrack::mMonoAsVirtualStereo != temp) UpdateVirtualStereoOrder(); @@ -1252,7 +1252,7 @@ void TrackPanel::DoDrawIndicator mViewInfo->h + mViewInfo->screen ); if( onScreen ) { - x = GetLeftOffset() + int ( ( mLastIndicator - mViewInfo->h) * mViewInfo->zoom ); + x = mViewInfo->TimeToPosition(mLastIndicator, GetLeftOffset()); // LL: Keep from trying to blit outsize of the source DC. This results in a crash on // OSX due to allocating memory using negative sizes and can be caused by resizing @@ -1321,7 +1321,7 @@ void TrackPanel::DoDrawIndicator AColor::IndicatorColor( &dc, !rec); // Calculate the horizontal position of the indicator - x = GetLeftOffset() + int ( ( pos - mViewInfo->h ) * mViewInfo->zoom ); + x = mViewInfo->TimeToPosition(pos, GetLeftOffset()); mRuler->DrawIndicator( pos, rec ); @@ -1380,7 +1380,7 @@ void TrackPanel::DoDrawCursor(wxDC & dc) mViewInfo->h + mViewInfo->screen ); if( onScreen ) { - x = GetLeftOffset() + int ( ( mLastCursor - mViewInfo->h) * mViewInfo->zoom ); + x = mViewInfo->TimeToPosition(mLastCursor, GetLeftOffset()); dc.Blit( x, 0, 1, mBacking->GetHeight(), &mBackingDC, x, 0 ); } @@ -1401,8 +1401,7 @@ void TrackPanel::DoDrawCursor(wxDC & dc) AColor::CursorColor( &dc ); - x = GetLeftOffset() + - int ( ( mLastCursor - mViewInfo->h ) * mViewInfo->zoom ); + x = mViewInfo->TimeToPosition(mLastCursor, GetLeftOffset()); // Draw cursor in all selected tracks VisibleTrackIterator iter( GetProject() ); @@ -1547,9 +1546,9 @@ void TrackPanel::OnPaint(wxPaintEvent & /* event */) DoDrawCursor(cdc); #if DEBUG_DRAW_TIMING - sw.Pause(); - wxLogDebug(wxT("Total: %ld milliseconds"), sw.Time()); - wxPrintf(wxT("Total: %ld milliseconds\n"), sw.Time()); + sw.Pause(); + wxLogDebug(wxT("Total: %ld milliseconds"), sw.Time()); + wxPrintf(wxT("Total: %ld milliseconds\n"), sw.Time()); #endif } @@ -1905,13 +1904,13 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t, // (Don't assume it's the default!) wxString keyStr (GetProject()->GetCommandManager()->GetKeyFromName(wxT("Preferences"))); - // Must compose a string that survives the function call, hence static. if (keyStr.IsEmpty()) // No keyboard preference defined for opening Preferences dialog /* i18n-hint: These are the names of a menu and a command in that menu */ keyStr = _("Edit, Preferences..."); else keyStr = KeyStringDisplay(keyStr); + // Must compose a string that survives the function call, hence static. static wxString result; /* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */ result = wxString::Format( @@ -1933,11 +1932,13 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t, MaySetOnDemandTip( t, ppTip ); return; } - wxInt64 leftSel = TimeToPosition(mViewInfo->selectedRegion.t0(), r.x); - wxInt64 rightSel = TimeToPosition(mViewInfo->selectedRegion.t1(), r.x); - // Something is wrong if right edge comes before left edge - wxASSERT(!(rightSel < leftSel)); + { + wxInt64 leftSel = mViewInfo->TimeToPosition(mViewInfo->selectedRegion.t0(), r.x); + wxInt64 rightSel = mViewInfo->TimeToPosition(mViewInfo->selectedRegion.t1(), r.x); + // Something is wrong if right edge comes before left edge + wxASSERT(!(rightSel < leftSel)); + } const bool bShiftDown = event.ShiftDown(); @@ -2124,7 +2125,7 @@ void TrackPanel::HandleCursor(wxMouseEvent & event) // practice (on a P500). int tool = DetermineToolToUse( ttb, event ); - tip = ttb->GetMessageForTool( tool ); + tip = ttb->GetMessageForTool(tool); // We don't include the select tool in // SetCursorAndTipByTool() because it's more complex than @@ -2261,7 +2262,7 @@ void TrackPanel::StartOrJumpPlayback(wxMouseEvent &event) { AudacityProject *p = GetActiveProject(); if (p) { - double clicktime = PositionToTime(event.m_x, GetLeftOffset()); + double clicktime = mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); const double t1 = mViewInfo->selectedRegion.t1(); // Play to end of selection, or if that is not right of the pick, end of track double endtime = clicktime < t1 ? t1 : mViewInfo->total; @@ -2427,9 +2428,14 @@ bool TrackPanel::MaybeStartScrubbing(wxMouseEvent &event) abs(mScrubStartPosition - position) >= SCRUBBING_PIXEL_TOLERANCE) { ControlToolBar * ctb = p->GetControlToolBar(); double maxTime = p->GetTracks()->GetEndTime(); - double time0 = std::min(maxTime, PositionToTime(mScrubStartPosition, GetLeftOffset())); - double time1 = std::min(maxTime, PositionToTime(position, GetLeftOffset())); - if (time1 != time0) { + double time0 = std::min(maxTime, + mViewInfo->PositionToTime(mScrubStartPosition, GetLeftOffset()) + ); + double time1 = std::min(maxTime, + mViewInfo->PositionToTime(position, GetLeftOffset()) + ); + if (time1 != time0) + { if (busy) ctb->StopPlaying(); @@ -2483,7 +2489,7 @@ bool TrackPanel::ContinueScrubbing(wxCoord position, bool hasFocus, bool seek) if (!hasFocus) return gAudioIO->EnqueueScrubBySignedSpeed(0, mMaxScrubSpeed, false); - const double time = PositionToTime(position, GetLeftOffset()); + const double time = mViewInfo->PositionToTime(position, GetLeftOffset()); if (seek) // Cause OnTimer() to suppress the speed display @@ -2564,7 +2570,7 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, delete mSnapManager; mSnapManager = new SnapManager(mTracks, NULL, - mViewInfo->zoom, + *mViewInfo, 4); // pixel tolerance mSnapLeft = -1; @@ -2638,9 +2644,9 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, else if(event.CmdDown() #ifdef USE_MIDI - && !stretch + && !stretch #endif - ) { + ) { #ifdef EXPERIMENTAL_SCRUBBING_BASIC if ( @@ -2744,7 +2750,7 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, { LabelTrack *lt = (LabelTrack *) pTrack; if (lt->HandleMouse(event, r,//mCapturedRect, - mViewInfo->h, mViewInfo->zoom, + *mViewInfo, &mViewInfo->selectedRegion)) { MakeParentPushState(_("Modified Label"), _("Label Edit"), @@ -2781,7 +2787,7 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, if (startNewSelection) { // mouse is not at an edge, but after // quantization, we could be indicating the selection edge mSelStartValid = true; - mSelStart = std::max(0.0, PositionToTime(event.m_x, r.x)); + mSelStart = std::max(0.0, mViewInfo->PositionToTime(event.m_x, r.x)); mStretchStart = nt->NearestBeatTime(mSelStart, ¢erBeat); if (within(qBeat0, centerBeat, 0.1)) { mListener->TP_DisplayStatusMessage( @@ -2872,10 +2878,7 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, void TrackPanel::StartSelection(int mouseXCoordinate, int trackLeftEdge) { mSelStartValid = true; - mSelStart = - std::max(0.0, - mViewInfo->h + ((mouseXCoordinate - trackLeftEdge) - / mViewInfo->zoom)); + mSelStart = std::max(0.0, mViewInfo->PositionToTime(mouseXCoordinate, trackLeftEdge)); double s = mSelStart; @@ -2886,7 +2889,7 @@ void TrackPanel::StartSelection(int mouseXCoordinate, int trackLeftEdge) if (mSnapManager->Snap(mCapturedTrack, mSelStart, false, &s, &snappedPoint, &snappedTime)) { if (snappedPoint) - mSnapLeft = TimeToPosition(s, trackLeftEdge); + mSnapLeft = mViewInfo->TimeToPosition(s, trackLeftEdge); } } @@ -2905,7 +2908,7 @@ void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge, // Must be dragging frequency bounds only. return; - double selend = std::max(0.0, PositionToTime(mouseXCoordinate, trackLeftEdge)); + double selend = std::max(0.0, mViewInfo->PositionToTime(mouseXCoordinate, trackLeftEdge)); clip_bottom(selend, 0.0); double origSel0, origSel1; @@ -2933,12 +2936,12 @@ void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge, if (mSnapManager->Snap(mCapturedTrack, sel0, false, &sel0, &snappedPoint, &snappedTime)) { if (snappedPoint) - mSnapLeft = TimeToPosition(sel0, trackLeftEdge); + mSnapLeft = mViewInfo->TimeToPosition(sel0, trackLeftEdge); } if (mSnapManager->Snap(mCapturedTrack, sel1, true, &sel1, &snappedPoint, &snappedTime)) { if (snappedPoint) - mSnapRight = TimeToPosition(sel1, trackLeftEdge); + mSnapRight = mViewInfo->TimeToPosition(sel1, trackLeftEdge); } // Check if selection endpoints are too close together to snap (unless @@ -3342,7 +3345,7 @@ void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge, } NoteTrack *pNt = (NoteTrack *) pTrack; - double moveto = std::max(0.0, PositionToTime(mouseXCoordinate, trackLeftEdge)); + double moveto = std::max(0.0, mViewInfo->PositionToTime(mouseXCoordinate, trackLeftEdge)); // check to make sure tempo is not higher than 20 beats per second // (In principle, tempo can be higher, but not infinity.) @@ -3462,7 +3465,7 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack) // Might be dragging frequency bounds only, test if (mSelStartValid) { - wxInt64 SelStart=TimeToPosition( mSelStart, r.x); //cvt time to pixels. + wxInt64 SelStart = mViewInfo->TimeToPosition(mSelStart, r.x); //cvt time to pixels. // Abandon this drag if selecting < 5 pixels. if (wxLongLong(SelStart-x).Abs() < minimumSizedSelection #ifdef USE_MIDI // limiting selection size is good, and not starting @@ -3519,26 +3522,6 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack) UpdateSelectionDisplay(); } -/// Converts a position (mouse X coordinate) to -/// project time, in seconds. Needs the left edge of -/// the track as an additional parameter. -double TrackPanel::PositionToTime(wxInt64 mouseXCoordinate, - wxInt64 trackLeftEdge) const -{ - return mViewInfo->h + ((mouseXCoordinate - trackLeftEdge) - / mViewInfo->zoom); -} - - -/// STM: Converts a project time to screen x position. -wxInt64 TrackPanel::TimeToPosition(double projectTime, - wxInt64 trackLeftEdge) const -{ - return static_cast < - wxInt64 >(mViewInfo->zoom * (projectTime - mViewInfo->h) + - trackLeftEdge); -} - #ifdef EXPERIMENTAL_SPECTRAL_EDITING // Seems 4 is too small to work at the top. Why? enum { FREQ_SNAP_DISTANCE = 10 }; @@ -3635,7 +3618,9 @@ TrackPanel::SelectionBoundary TrackPanel::ChooseTimeBoundary { const double t0 = mViewInfo->selectedRegion.t0(); const double t1 = mViewInfo->selectedRegion.t1(); - wxInt64 pixelDist = mViewInfo->zoom * fabs(selend - t0); + const wxInt64 posS = mViewInfo->TimeToPosition(selend); + const wxInt64 pos0 = mViewInfo->TimeToPosition(t0); + wxInt64 pixelDist = abs(posS - pos0); bool chooseLeft = true; if (mViewInfo->selectedRegion.isPoint()) @@ -3643,7 +3628,8 @@ TrackPanel::SelectionBoundary TrackPanel::ChooseTimeBoundary // and right distances are the same chooseLeft = (selend < t0); else { - const wxInt64 rightDist = mViewInfo->zoom * fabs(selend - t1); + const wxInt64 pos1 = mViewInfo->TimeToPosition(t1); + const wxInt64 rightDist = abs(posS - pos1); if (rightDist < pixelDist) chooseLeft = false, pixelDist = rightDist; } @@ -3676,7 +3662,7 @@ bool mayDragWidth, bool onlyWithinSnapDistance, // within the time boundaries. // May choose no boundary if onlyWithinSnapDistance is true. // Otherwise choose the eligible boundary nearest the mouse click. - const double selend = PositionToTime(event.m_x, rect.x); + const double selend = mViewInfo->PositionToTime(event.m_x, rect.x); wxInt64 pixelDist = 0; SelectionBoundary boundary = ChooseTimeBoundary(selend, onlyWithinSnapDistance, @@ -3829,7 +3815,7 @@ void TrackPanel::ForwardEventToTimeTrackEnvelope(wxMouseEvent & event) bool needUpdate = pspeedenvelope->MouseEvent( event, envRect, - mViewInfo->h, mViewInfo->zoom, + *mViewInfo, ptimetrack->GetDisplayLog(), lower, upper); if (needUpdate) { RefreshTrack(mCapturedTrack); @@ -3867,7 +3853,7 @@ void TrackPanel::ForwardEventToWaveTrackEnvelope(wxMouseEvent & event) pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax); needUpdate = penvelope->MouseEvent( event, envRect, - mViewInfo->h, mViewInfo->zoom, + *mViewInfo, dB, zoomMin, zoomMax); // If this track is linked to another track, make the identical @@ -3885,8 +3871,8 @@ void TrackPanel::ForwardEventToWaveTrackEnvelope(wxMouseEvent & event) float zoomMin, zoomMax; pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax); updateNeeded = e2->MouseEvent(event, envRect, - mViewInfo->h, mViewInfo->zoom, dB, - zoomMin, zoomMax); + *mViewInfo, dB, + zoomMin, zoomMax); needUpdate |= updateNeeded; } if(!e2 || !updateNeeded) // no envelope found at this x point, or found but not updated @@ -3899,7 +3885,7 @@ void TrackPanel::ForwardEventToWaveTrackEnvelope(wxMouseEvent & event) float zoomMin, zoomMax; pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax); needUpdate |= e2->MouseEvent(event, envRect, - mViewInfo->h, mViewInfo->zoom, dB, + *mViewInfo, dB, zoomMin, zoomMax); } } @@ -4037,7 +4023,7 @@ void TrackPanel::StartSlide(wxMouseEvent & event) mCapturedClipArray.Clear(); double clickTime = - PositionToTime(event.m_x, GetLeftOffset()); + mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); bool clickedInSelection = (vt->GetSelected() && clickTime > mViewInfo->selectedRegion.t0() && @@ -4065,8 +4051,7 @@ void TrackPanel::StartSlide(wxMouseEvent & event) // WaveClip::GetClipAtX doesn't work unless the clip is on the screen and can return bad info otherwise // instead calculate the time manually double rate = ((WaveTrack*)partner)->GetRate(); - double pps = mViewInfo->zoom; - double tt = (event.m_x - GetLeftOffset()) / pps + mViewInfo->h; + const double tt = mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); sampleCount s0 = (sampleCount)(tt * rate + 0.5); if (s0 >= 0) { @@ -4129,13 +4114,13 @@ void TrackPanel::StartSlide(wxMouseEvent & event) mMouseClickY = event.m_y; mSelStartValid = true; - mSelStart = mViewInfo->h + ((event.m_x - r.x) / mViewInfo->zoom); + mSelStart = mViewInfo->PositionToTime(event.m_x, r.x); if (mSnapManager) delete mSnapManager; mSnapManager = new SnapManager(mTracks, &mCapturedClipArray, - mViewInfo->zoom, + *mViewInfo, 4, // pixel tolerance true); // don't snap to time mSnapLeft = -1; @@ -4267,7 +4252,9 @@ void TrackPanel::DoSlide(wxMouseEvent & event) } mHSlideAmount = 0.0; - double desiredSlideAmount = (event.m_x - mMouseClickX) / mViewInfo->zoom; + double desiredSlideAmount = + mViewInfo->PositionToTime(event.m_x) - + mViewInfo->PositionToTime(mMouseClickX); #ifdef USE_MIDI if (mouseTrack->GetKind() == Track::Wave) { WaveTrack *mtw = (WaveTrack *) mouseTrack; @@ -4317,12 +4304,12 @@ void TrackPanel::DoSlide(wxMouseEvent & event) if (newClipLeft != clipLeft) { double difference = (newClipLeft - clipLeft); desiredSlideAmount += difference; - mSnapLeft = TimeToPosition(newClipLeft, GetLeftOffset()); + mSnapLeft = mViewInfo->TimeToPosition(newClipLeft, GetLeftOffset()); } else if (newClipRight != clipRight) { double difference = (newClipRight - clipRight); desiredSlideAmount += difference; - mSnapRight = TimeToPosition(newClipRight, GetLeftOffset()); + mSnapRight = mViewInfo->TimeToPosition(newClipRight, GetLeftOffset()); } } @@ -4565,18 +4552,14 @@ bool TrackPanel::IsMouseCaptured() /// a drag zoom. void TrackPanel::DragZoom(wxMouseEvent & event, int trackLeftEdge) { - double left = PositionToTime(mZoomStart, trackLeftEdge); - double right = PositionToTime(mZoomEnd, trackLeftEdge); + double left = mViewInfo->PositionToTime(mZoomStart, trackLeftEdge); + double right = mViewInfo->PositionToTime(mZoomEnd, trackLeftEdge); + double multiplier = mViewInfo->screen / (right - left); if (event.ShiftDown()) - mViewInfo->zoom /= mViewInfo->screen / (right - left); - else - mViewInfo->zoom *= mViewInfo->screen / (right - left); + multiplier = 1.0 / multiplier; - if (mViewInfo->zoom > gMaxZoom) - mViewInfo->zoom = gMaxZoom; - if (mViewInfo->zoom <= gMinZoom) - mViewInfo->zoom = gMinZoom; + mViewInfo->ZoomBy(multiplier); mViewInfo->h = left; } @@ -4586,17 +4569,17 @@ void TrackPanel::DragZoom(wxMouseEvent & event, int trackLeftEdge) /// \todo MAGIC NUMBER: We've got several in this method. void TrackPanel::DoZoomInOut(wxMouseEvent & event, int trackLeftEdge) { - double center_h = PositionToTime(event.m_x, trackLeftEdge); + double center_h = mViewInfo->PositionToTime(event.m_x, trackLeftEdge); - if (event.RightUp() || event.RightDClick() || event.ShiftDown()) - mViewInfo->zoom = wxMax(mViewInfo->zoom / 2.0, gMinZoom); - else - mViewInfo->zoom = wxMin(mViewInfo->zoom * 2.0, gMaxZoom); + const double multiplier = + (event.RightUp() || event.RightDClick() || event.ShiftDown()) + ? 0.5 : 2.0; + mViewInfo->ZoomBy(multiplier); if (event.MiddleUp() || event.MiddleDClick()) - mViewInfo->zoom = 44100.0 / 512.0; // AS: Reset zoom. + mViewInfo->SetZoom(ZoomInfo::GetDefaultZoom()); // AS: Reset zoom. - double new_center_h = PositionToTime(event.m_x, trackLeftEdge); + double new_center_h = mViewInfo->PositionToTime(event.m_x, trackLeftEdge); mViewInfo->h += (center_h - new_center_h); } @@ -4944,10 +4927,31 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event ) MakeParentModifyState(true); } +namespace { +// Is the sample horizontally nearest to the cursor sufficiently separated from +// its neighbors that the pencil tool should be allowed to drag it? +bool SampleResolutionTest(const ViewInfo &viewInfo, const WaveTrack *wt, double time, double rate) +{ + // Require more than 3 pixels per sample + // Round to an exact sample time + const double adjustedTime = wt->LongSamplesToTime(wt->TimeToLongSamples(time)); + const wxInt64 xx = std::max(wxInt64(0), viewInfo.TimeToPosition(adjustedTime)); + ZoomInfo::Intervals intervals; + viewInfo.FindIntervals(rate, intervals); + ZoomInfo::Intervals::const_iterator it = intervals.begin(), end = intervals.end(), prev; + wxASSERT(it != end && it->position == 0); + do + prev = it++; + while (it != end && it->position <= xx); + const double threshold = 3 * rate; // three times as many pixels per second, as samples + return prev->averageZoom > threshold; +} +} + /// Determines if we can edit samples in a wave track. /// Also pops up warning messages in certain cases where we can't. /// @return true if we can edit the samples, false otherwise. -bool TrackPanel::IsSampleEditingPossible( wxMouseEvent & WXUNUSED(event), Track * t ) +bool TrackPanel::IsSampleEditingPossible( wxMouseEvent &event, Track * t ) { //Exit if we don't have a track if(!t) @@ -4976,10 +4980,15 @@ bool TrackPanel::IsSampleEditingPossible( wxMouseEvent & WXUNUSED(event), Track } #endif - //Get rate in order to calculate the critical zoom threshold - //Find out the zoom level - const double rate = wt->GetRate(); - const bool showPoints = (mViewInfo->zoom / rate > 3.0); + bool showPoints; + { + wxRect r; + FindTrack(event.m_x, event.m_y, false, false, &r); + WaveTrack *const wt = static_cast(t); + const double rate = wt->GetRate(); + const double time = mViewInfo->PositionToTime(event.m_x, r.x); + showPoints = SampleResolutionTest(*mViewInfo, wt, time, rate); + } //If we aren't zoomed in far enough, show a message dialog. if(!showPoints) @@ -5054,7 +5063,7 @@ void TrackPanel::HandleSampleEditingClick( wxMouseEvent & event ) //If we are still around, we are drawing in earnest. Set some member data structures up: //First, calculate the starting sample. To get this, we need the time - double t0 = PositionToTime(event.m_x, GetLeftOffset()); + double t0 = mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); //convert t0 to samples mDrawingStartSample = mDrawingTrack->TimeToLongSamples(t0); @@ -5193,7 +5202,7 @@ void TrackPanel::HandleSampleEditingDrag( wxMouseEvent & event ) //Otherwise, adjust the sample you are dragging over right now. //convert this to samples - const double t = PositionToTime(event.m_x, GetLeftOffset()); + const double t = mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); s0 = mDrawingTrack->TimeToLongSamples(t); } @@ -5301,7 +5310,7 @@ void TrackPanel::UpdateViewIfNoTracks() { // BG: There are no more tracks on screen //BG: Set zoom to normal - mViewInfo->zoom = 44100.0 / 512.0; + mViewInfo->SetZoom(ZoomInfo::GetDefaultZoom()); //STM: Set selection to 0,0 //PRL: and default the rest of the selection information @@ -6271,22 +6280,22 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event) { // MM: Scroll left/right when used with Shift key down mListener->TP_ScrollWindow( - mViewInfo->h + - 50.0 * -steps / mViewInfo->zoom); + mViewInfo->OffsetTimeByPixels( + mViewInfo->PositionToTime(0), 50.0 * -steps)); } else if (event.CmdDown()) { #if 0 - // JKC: Alternative scroll wheel zooming code - // using AudacityProject zooming, which is smarter, - // it keeps selections on screen and centred if it can, - // also this ensures mousewheel and zoom buttons give same result. - double ZoomFactor = pow(2.0, steps); - AudacityProject *p = GetProject(); - if( steps > 0 ) - p->ZoomInByFactor( ZoomFactor ); - else - p->ZoomOutByFactor( ZoomFactor ); + // JKC: Alternative scroll wheel zooming code + // using AudacityProject zooming, which is smarter, + // it keeps selections on screen and centred if it can, + // also this ensures mousewheel and zoom buttons give same result. + double ZoomFactor = pow(2.0, steps); + AudacityProject *p = GetProject(); + if( steps > 0 ) + p->ZoomInByFactor( ZoomFactor ); + else + p->ZoomOutByFactor( ZoomFactor ); #endif // MM: Zoom in/out when used with Control key down // We're converting pixel positions to times, @@ -6300,13 +6309,13 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event) if (mSmoothScrollingScrub) { // Expand or contract about the center, ignoring mouse position center_h = mViewInfo->h + mViewInfo->screen / 2.0; - xx = TimeToPosition(center_h, trackLeftEdge); + xx = mViewInfo->TimeToPosition(center_h, trackLeftEdge); } else #endif { xx = event.m_x; - center_h = PositionToTime(xx, trackLeftEdge); + center_h = mViewInfo->PositionToTime(xx, trackLeftEdge); } // Time corresponding to last (most far right) audio. double audioEndTime = mTracks->GetEndTime(); @@ -6314,18 +6323,17 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event) // When zooming in in empty space, it's easy to 'lose' the waveform. // This prevents it. // IF zooming in - if( steps > 0) + if (steps > 0) { // IF mouse is to right of audio - if( center_h > audioEndTime ) + if (center_h > audioEndTime) // Zooming brings far right of audio to mouse. center_h = audioEndTime; } - // Constrain maximum as well as minimum zoom. - mViewInfo->zoom = wxMax( gMinZoom, wxMin(mViewInfo->zoom * pow(2.0, steps), gMaxZoom)); + mViewInfo->ZoomBy(pow(2.0, steps)); - double new_center_h = PositionToTime(xx, trackLeftEdge); + double new_center_h = mViewInfo->PositionToTime(xx, trackLeftEdge); mViewInfo->h += (center_h - new_center_h); MakeParentRedrawScrollbars(); @@ -6659,7 +6667,7 @@ bool TrackPanel::HandleTrackLocationMouseEvent(WaveTrack * track, wxRect &r, wxM { WaveTrack::Location loc = track->GetCachedLocation(i); - double x = (loc.pos - mViewInfo->h) * mViewInfo->zoom; + const double x = mViewInfo->TimeToPosition(loc.pos); if (x >= 0 && x < r.width) { wxRect locRect; @@ -6717,7 +6725,7 @@ bool TrackPanel::HandleLabelTrackMouseEvent(LabelTrack * lTrack, wxRect &r, wxMo } if (lTrack->HandleMouse(event, mCapturedRect, - mViewInfo->h, mViewInfo->zoom, &mViewInfo->selectedRegion)) { + *mViewInfo, &mViewInfo->selectedRegion)) { MakeParentPushState(_("Modified Label"), _("Label Edit"), @@ -6876,27 +6884,29 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event) if( pTtb == NULL ) return; - int toolToUse = DetermineToolToUse(pTtb, event); + { + int toolToUse = DetermineToolToUse(pTtb, event); - switch (toolToUse) { - case selectTool: - HandleSelect(event); - break; - case envelopeTool: - if (!unsafe) - HandleEnvelope(event); - break; - case slideTool: - if (!unsafe) - HandleSlide(event); - break; - case zoomTool: - HandleZoom(event); - break; - case drawTool: - if (!unsafe) - HandleSampleEditing(event); - break; + switch (toolToUse) { + case selectTool: + HandleSelect(event); + break; + case envelopeTool: + if (!unsafe) + HandleEnvelope(event); + break; + case slideTool: + if (!unsafe) + HandleSlide(event); + break; + case zoomTool: + HandleZoom(event); + break; + case drawTool: + if (!unsafe) + HandleSampleEditing(event); + break; + } } if ((event.Moving() || event.LeftUp()) && @@ -6947,7 +6957,8 @@ int TrackPanel::DetermineToolToUse( ToolsToolBar * pTtb, wxMouseEvent & event) if (event.ButtonIsDown(wxMOUSE_BTN_RIGHT) || event.RightUp()){ currentTool = zoomTool; - } else if( trackKind == Track::Time ){ + } + else if (trackKind == Track::Time){ currentTool = envelopeTool; } else if( trackKind == Track::Label ){ currentTool = selectTool; @@ -6990,8 +7001,8 @@ bool TrackPanel::HitTestStretch(Track *track, wxRect &r, wxMouseEvent & event) int center = r.y + r.height / 2; int distance = abs(event.m_y - center); const int yTolerance = 10; - wxInt64 leftSel = TimeToPosition(mViewInfo->selectedRegion.t0(), r.x); - wxInt64 rightSel = TimeToPosition(mViewInfo->selectedRegion.t1(), r.x); + wxInt64 leftSel = mViewInfo->TimeToPosition(mViewInfo->selectedRegion.t0(), r.x); + wxInt64 rightSel = mViewInfo->TimeToPosition(mViewInfo->selectedRegion.t1(), r.x); // Something is wrong if right edge comes before left edge wxASSERT(!(rightSel < leftSel)); return (leftSel <= event.m_x && event.m_x <= rightSel && @@ -7023,8 +7034,8 @@ bool TrackPanel::HitTestEnvelope(Track *track, wxRect &r, wxMouseEvent & event) return false; // No envelope, not a hit, so return. // Get envelope point, range 0.0 to 1.0 - double envValue = envelope->GetValueAtX( - event.m_x, r, mViewInfo->h, mViewInfo->zoom ); + // Convert x to time. + const double envValue = envelope->GetValue(mViewInfo->PositionToTime(event.m_x, r.x)); float zoomMin, zoomMax; wavetrack->GetDisplayBounds(&zoomMin, &zoomMax); @@ -7080,22 +7091,18 @@ bool TrackPanel::HitTestSamples(Track *track, wxRect &r, wxMouseEvent & event) //Get rate in order to calculate the critical zoom threshold double rate = wavetrack->GetRate(); - //Find out the zoom level - bool showPoints = (mViewInfo->zoom / rate > 3.0); - if( !showPoints ) - return false; - int displayType = wavetrack->GetDisplay(); bool dB = (WaveTrack::WaveformDBDisplay == displayType); if (!(WaveTrack::WaveformDisplay == displayType || dB)) return false; // Not a wave, so return. - float oneSample; - double pps = mViewInfo->zoom; - double tt = (event.m_x - r.x) / pps + mViewInfo->h; - sampleCount s0 = (sampleCount)(tt * rate + 0.5); + const double tt = mViewInfo->PositionToTime(event.m_x, r.x); + if (!SampleResolutionTest(*mViewInfo, wavetrack, tt, rate)) + return false; // Just get one sample. + float oneSample; + sampleCount s0 = (sampleCount)(tt * rate + 0.5); wavetrack->Get((samplePtr)&oneSample, floatSample, s0, 1); // Get y distance of envelope point from center line (in pixels). @@ -7144,8 +7151,7 @@ bool TrackPanel::HitTestSlide(Track * WXUNUSED(track), wxRect &r, wxMouseEvent & double TrackPanel::GetMostRecentXPos() { - return mViewInfo->h + - (mMouseMostRecentX - GetLabelWidth()) / mViewInfo->zoom; + return mViewInfo->PositionToTime(mMouseMostRecentX, GetLabelWidth()); } void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking) @@ -7224,13 +7230,14 @@ void TrackPanel::DrawTracks(wxDC * dc) ToolsToolBar *pTtb = mListener->TP_GetToolsToolBar(); bool bMultiToolDown = pTtb->IsDown(multiTool); bool envelopeFlag = pTtb->IsDown(envelopeTool) || bMultiToolDown; - bool samplesFlag = pTtb->IsDown(drawTool) || bMultiToolDown; + bool bigPointsFlag = pTtb->IsDown(drawTool) || bMultiToolDown; bool sliderFlag = bMultiToolDown; // The track artist actually draws the stuff inside each track mTrackArtist->DrawTracks(mTracks, GetProject()->GetFirstVisible(), - *dc, region, tracksRect, clip, mViewInfo, - envelopeFlag, samplesFlag, sliderFlag); + *dc, region, tracksRect, clip, + mViewInfo->selectedRegion, *mViewInfo, + envelopeFlag, bigPointsFlag, sliderFlag); DrawEverythingElse(dc, region, clip); } @@ -7409,8 +7416,8 @@ void TrackPanel::DrawScrubSpeed(wxDC &dc) #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL mSmoothScrollingScrub ? seeking - ? FindSeekSpeed(PositionToTime(xx, GetLeftOffset())) - : FindScrubSpeed(PositionToTime(xx, GetLeftOffset())) + ? FindSeekSpeed(mViewInfo->PositionToTime(xx, GetLeftOffset())) + : FindScrubSpeed(mViewInfo->PositionToTime(xx, GetLeftOffset())) : #endif mMaxScrubSpeed; @@ -8007,20 +8014,24 @@ void TrackPanel::OnToggle() // Make sure selection edge is in view void TrackPanel::ScrollIntoView(double pos) { + const int screenWidth = rint(mViewInfo->GetScreenWidth()); + int w, h; GetTracksUsableArea( &w, &h ); + // Or should we just set w = screenWidth ? - if( ( pos < mViewInfo->h ) || - ( pos > mViewInfo->h + ( ( w - 1 ) / mViewInfo->zoom ) ) ) + int pixel = mViewInfo->TimeToPosition(pos); + if (pixel < 0 || pixel >= screenWidth) { - mListener->TP_ScrollWindow( pos - ( ( w / 2 ) / mViewInfo->zoom ) ); + mListener->TP_ScrollWindow + (mViewInfo->OffsetTimeByPixels(pos, -(w / 2))); Refresh(false); } } void TrackPanel::ScrollIntoView(int x) { - ScrollIntoView(PositionToTime(x, GetLeftOffset())); + ScrollIntoView(mViewInfo->PositionToTime(x, GetLeftOffset())); } void TrackPanel::OnCursorLeft( bool shift, bool ctrl, bool keyup ) @@ -8030,11 +8041,12 @@ void TrackPanel::OnCursorLeft( bool shift, bool ctrl, bool keyup ) // and does not vary if the key is held // Else: jump depends on the zoom and gets bigger if the key is held int snapToTime = GetActiveProject()->GetSnapTo(); - double quietSeekStepPositive = 1.0 / mViewInfo->zoom; + double quietSeekStepPositive = 1.0; // pixels double audioSeekStepPositive = shift ? mSeekLong : mSeekShort; SeekLeftOrRight (true, shift, ctrl, keyup, snapToTime, true, false, - quietSeekStepPositive, audioSeekStepPositive); + quietSeekStepPositive, true, + audioSeekStepPositive, false); } void TrackPanel::OnCursorRight(bool shift, bool ctrl, bool keyup) @@ -8044,18 +8056,20 @@ void TrackPanel::OnCursorRight(bool shift, bool ctrl, bool keyup) // and does not vary if the key is held // Else: jump depends on the zoom and gets bigger if the key is held int snapToTime = GetActiveProject()->GetSnapTo(); - double quietSeekStepPositive = 1.0 / mViewInfo->zoom; + double quietSeekStepPositive = 1.0; // pixels double audioSeekStepPositive = shift ? mSeekLong : mSeekShort; SeekLeftOrRight (false, shift, ctrl, keyup, snapToTime, true, false, - quietSeekStepPositive, audioSeekStepPositive); + quietSeekStepPositive, true, + audioSeekStepPositive, false); } // Handle small cursor and play head movements void TrackPanel::SeekLeftOrRight (bool leftward, bool shift, bool ctrl, bool keyup, int snapToTime, bool mayAccelerateQuiet, bool mayAccelerateAudio, - double quietSeekStepPositive, double audioSeekStepPositive) + double quietSeekStepPositive, bool quietStepIsPixels, + double audioSeekStepPositive, bool audioStepIsPixels) { if (keyup) { @@ -8087,26 +8101,34 @@ void TrackPanel::SeekLeftOrRight { mLastSelectionAdjustment = curtime; - // Contract selection + // Contract selection // Reduce and constrain (counter-intuitive) if (leftward) { + const double t1 = mViewInfo->selectedRegion.t1(); mViewInfo->selectedRegion.setT1( std::max(mViewInfo->selectedRegion.t0(), - snapToTime - ? GridMove(mViewInfo->selectedRegion.t1(), multiplier) - : mViewInfo->selectedRegion.t1() + - multiplier * quietSeekStepPositive)); + snapToTime + ? GridMove(t1, multiplier) + : quietStepIsPixels + ? mViewInfo->OffsetTimeByPixels( + t1, int(multiplier * quietSeekStepPositive)) + : t1 + multiplier * quietSeekStepPositive + )); // Make sure it's visible. ScrollIntoView(mViewInfo->selectedRegion.t1()); } else { + const double t0 = mViewInfo->selectedRegion.t0(); mViewInfo->selectedRegion.setT0( std::min(mViewInfo->selectedRegion.t1(), snapToTime - ? GridMove(mViewInfo->selectedRegion.t0(), multiplier) - : mViewInfo->selectedRegion.t0() + - multiplier * quietSeekStepPositive)); + ? GridMove(t0, multiplier) + : quietStepIsPixels + ? mViewInfo->OffsetTimeByPixels( + t0, int(multiplier * quietSeekStepPositive)) + : t0 + multiplier * quietSeekStepPositive + )); // Make sure new position is in view. ScrollIntoView(mViewInfo->selectedRegion.t0()); @@ -8130,7 +8152,16 @@ void TrackPanel::SeekLeftOrRight multiplier = -multiplier; // If playing, reposition - gAudioIO->SeekStream(multiplier * audioSeekStepPositive); + double seconds; + if (audioStepIsPixels) { + const double streamTime = gAudioIO->GetStreamTime(); + const double newTime = + mViewInfo->OffsetTimeByPixels(streamTime, int(audioSeekStepPositive)); + seconds = newTime - streamTime; + } + else + seconds = multiplier * audioSeekStepPositive; + gAudioIO->SeekStream(seconds); return; } else if (shift) @@ -8140,24 +8171,33 @@ void TrackPanel::SeekLeftOrRight // Extend selection // Expand and constrain if (leftward) { + const double t0 = mViewInfo->selectedRegion.t0(); mViewInfo->selectedRegion.setT0( std::max(0.0, snapToTime - ? GridMove(mViewInfo->selectedRegion.t0(), multiplier) - : mViewInfo->selectedRegion.t0() + - multiplier * quietSeekStepPositive)); + ? GridMove(t0, multiplier) + : quietStepIsPixels + ? mViewInfo->OffsetTimeByPixels( + t0, int(multiplier * quietSeekStepPositive)) + : t0 + multiplier * quietSeekStepPositive + )); // Make sure it's visible. ScrollIntoView(mViewInfo->selectedRegion.t0()); } else { double end = mTracks->GetEndTime(); + + const double t1 = mViewInfo->selectedRegion.t1(); mViewInfo->selectedRegion.setT1( std::min(end, snapToTime - ? GridMove(mViewInfo->selectedRegion.t1(), multiplier) - : mViewInfo->selectedRegion.t1() + - multiplier * quietSeekStepPositive)); + ? GridMove(t1, multiplier) + : quietStepIsPixels + ? mViewInfo->OffsetTimeByPixels( + t1, int(multiplier * quietSeekStepPositive)) + : t1 + multiplier * quietSeekStepPositive + )); // Make sure new position is in view. ScrollIntoView(mViewInfo->selectedRegion.t1()); @@ -8174,15 +8214,18 @@ void TrackPanel::SeekLeftOrRight { // Move and constrain double end = mTracks->GetEndTime(); + const double t0 = mViewInfo->selectedRegion.t0(); mViewInfo->selectedRegion.setT0( std::max(0.0, std::min(end, snapToTime - ? GridMove(mViewInfo->selectedRegion.t0(), multiplier) - : mViewInfo->selectedRegion.t0() + multiplier * quietSeekStepPositive - ) - ), - false); + ? GridMove(t0, multiplier) + : quietStepIsPixels + ? mViewInfo->OffsetTimeByPixels( + t0, int(multiplier * quietSeekStepPositive)) + : t0 + multiplier * quietSeekStepPositive)), + false // do not swap selection boundaries + ); mViewInfo->selectedRegion.collapseToT0(); // Move the visual cursor @@ -8218,12 +8261,12 @@ double TrackPanel::GridMove(double t, int minPix) double result; minPix >= 0 ? ttc.Increment() : ttc.Decrement(); result = ttc.GetValue(); - if (fabs(result - t) * mViewInfo->zoom >= fabs((double)minPix)) { - return result; - } + if (abs(mViewInfo->TimeToPosition(result) - mViewInfo->TimeToPosition(t)) + >= abs(minPix)) + return result; // Otherwise, move minPix pixels, then snap to the time. - result = t + minPix / mViewInfo->zoom; + result = mViewInfo->OffsetTimeByPixels(t, minPix); ttc.SetValue(result); result = ttc.GetValue(); return result; @@ -8238,10 +8281,10 @@ void TrackPanel::OnBoundaryMove(bool left, bool boundaryContract) // If the last adjustment was very recent, we are // holding the key down and should move faster. wxLongLong curtime = ::wxGetLocalTimeMillis(); - int multiplier = 1; + int pixels = 1; if( curtime - mLastSelectionAdjustment < 50 ) { - multiplier = 4; + pixels = 4; } mLastSelectionAdjustment = curtime; @@ -8268,10 +8311,13 @@ void TrackPanel::OnBoundaryMove(bool left, bool boundaryContract) { if (left) { // Reduce and constrain left boundary (counter-intuitive) + // Move the left boundary by at most the desired number of pixels, + // but not past the right mViewInfo->selectedRegion.setT0( std::min(mViewInfo->selectedRegion.t1(), - mViewInfo->selectedRegion.t0() + - multiplier / mViewInfo->zoom)); + mViewInfo->OffsetTimeByPixels( + mViewInfo->selectedRegion.t0(), + pixels))); // Make sure it's visible ScrollIntoView( mViewInfo->selectedRegion.t0() ); @@ -8279,10 +8325,14 @@ void TrackPanel::OnBoundaryMove(bool left, bool boundaryContract) else { // Reduce and constrain right boundary (counter-intuitive) + // Move the left boundary by at most the desired number of pixels, + // but not past the left mViewInfo->selectedRegion.setT1( std::max(mViewInfo->selectedRegion.t0(), - mViewInfo->selectedRegion.t1() - - multiplier / mViewInfo->zoom)); + mViewInfo->OffsetTimeByPixels( + mViewInfo->selectedRegion.t1(), + -pixels))); + // Make sure it's visible ScrollIntoView( mViewInfo->selectedRegion.t1() ); } @@ -8295,8 +8345,10 @@ void TrackPanel::OnBoundaryMove(bool left, bool boundaryContract) // Expand and constrain left boundary mViewInfo->selectedRegion.setT0( std::max(0.0, - mViewInfo->selectedRegion.t0() - - multiplier / mViewInfo->zoom)); + mViewInfo->OffsetTimeByPixels( + mViewInfo->selectedRegion.t0(), + -pixels))); + // Make sure it's visible ScrollIntoView( mViewInfo->selectedRegion.t0() ); } @@ -8306,10 +8358,14 @@ void TrackPanel::OnBoundaryMove(bool left, bool boundaryContract) double end = mTracks->GetEndTime(); mViewInfo->selectedRegion.setT1( std::min(end, - mViewInfo->selectedRegion.t1() + - multiplier/mViewInfo->zoom)); - } + mViewInfo->OffsetTimeByPixels( + mViewInfo->selectedRegion.t1(), + pixels))); + + // Make sure it's visible + ScrollIntoView(mViewInfo->selectedRegion.t1()); } + } Refresh( false ); MakeParentModifyState(false); } @@ -8324,20 +8380,24 @@ void TrackPanel::OnCursorMove(bool forward, bool jump, bool longjump ) // PRL: nobody calls this yet with !jump double positiveSeekStep; + bool byPixels; if (jump) { if (!longjump) { positiveSeekStep = mSeekShort; } else { positiveSeekStep = mSeekLong; } + byPixels = false; } else { - positiveSeekStep = 1.0 / mViewInfo->zoom; + positiveSeekStep = 1.0; + byPixels = true; } bool mayAccelerate = !jump; SeekLeftOrRight (!forward, false, false, false, 0, mayAccelerate, mayAccelerate, - positiveSeekStep, positiveSeekStep); + positiveSeekStep, byPixels, + positiveSeekStep, byPixels); MakeParentModifyState(false); } @@ -8620,7 +8680,7 @@ void TrackPanel::OnTrackClose() if( mTracks->IsEmpty() ) { //BG: Set zoom to normal - mViewInfo->zoom = 44100.0 / 512.0; + mViewInfo->SetZoom(ZoomInfo::GetDefaultZoom()); //STM: Set selection to 0,0 //PRL: and default the rest of the selection information diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 80800b03c..6dddc2ce0 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -45,7 +45,7 @@ class AudacityProject; class TrackPanelAx; -struct ViewInfo; +class ViewInfo; WX_DEFINE_ARRAY(LWSlider *, LWSliderArray); @@ -331,8 +331,9 @@ protected: // Handle small cursor and play head movements void SeekLeftOrRight (bool left, bool shift, bool ctrl, bool keyup, - int snapToTime, bool mayAccelerateQuiet, bool mayAccelerateAudio, - double quietSeekStepPositive, double audioSeekStepPositive); + int snapToTime, bool mayAccelerateQuiet, bool mayAccelerateAudio, + double quietSeekStepPositive, bool quietStepIsPixels, + double audioSeekStepPositive, bool audioStepIsPixels); #ifdef EXPERIMENTAL_SPECTRAL_EDITING public: @@ -500,10 +501,10 @@ protected: // JKC Nov-2011: These four functions only used from within a dll such as mod-track-panel // They work around some messy problems with constructors. public: - TrackList * GetTracks(){ return mTracks;}; - ViewInfo * GetViewInfo(){ return mViewInfo;}; - TrackPanelListener * GetListener(){ return mListener;}; - AdornedRulerPanel * GetRuler(){ return mRuler;}; + TrackList * GetTracks(){ return mTracks;} + ViewInfo * GetViewInfo(){ return mViewInfo;} + TrackPanelListener * GetListener(){ return mListener;} + AdornedRulerPanel * GetRuler(){ return mRuler;} // JKC and here is a factory function which just does 'new' in standard Audacity. static TrackPanel *(*FactoryFunction)(wxWindow * parent, wxWindowID id, @@ -538,7 +539,7 @@ protected: void UpdateVirtualStereoOrder(); #endif // Accessors... - virtual bool HasSoloButton(){ return mSoloPref!=wxT("None");}; + virtual bool HasSoloButton(){ return mSoloPref!=wxT("None");} //JKC: These two belong in the label track. int mLabelTrackStartXPos; @@ -691,14 +692,7 @@ protected: void HandleCenterFrequencyClick (bool shiftDown, Track *pTrack, double value); -#endif - double PositionToTime(wxInt64 mouseXCoordinate, - wxInt64 trackLeftEdge) const; - wxInt64 TimeToPosition(double time, - wxInt64 trackLeftEdge) const; - -#ifdef EXPERIMENTAL_SPECTRAL_EDITING double PositionToFrequency(bool maySnap, wxInt64 mouseYCoordinate, wxInt64 trackTopEdge, diff --git a/src/ViewInfo.cpp b/src/ViewInfo.cpp index e69de29bb..f0f439a42 100644 --- a/src/ViewInfo.cpp +++ b/src/ViewInfo.cpp @@ -0,0 +1,148 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +ViewInfo.cpp + +Paul Licameli + +**********************************************************************/ + +#include "ViewInfo.h" + +#include + +#include "Internat.h" +#include "xml/XMLWriter.h" + +namespace { +static const double gMaxZoom = 6000000; +static const double gMinZoom = 0.001; +} + +ZoomInfo::ZoomInfo(double start, double screenDuration, double pixelsPerSecond) + : vpos(0) + , h(start) + , screen(screenDuration) + , zoom(pixelsPerSecond) +{ +} + +ZoomInfo::~ZoomInfo() +{ +} + +/// Converts a position (mouse X coordinate) to +/// project time, in seconds. Needs the left edge of +/// the track as an additional parameter. +double ZoomInfo::PositionToTime(wxInt64 position, + wxInt64 origin + , bool // ignoreFisheye +) const +{ + return h + (position - origin) / zoom; +} + + +/// STM: Converts a project time to screen x position. +wxInt64 ZoomInfo::TimeToPosition(double projectTime, + wxInt64 origin + , bool // ignoreFisheye +) const +{ + return floor(0.5 + + zoom * (projectTime - h) + origin + ); +} + +bool ZoomInfo::ZoomInAvailable() const +{ + return zoom < gMaxZoom; +} + +bool ZoomInfo::ZoomOutAvailable() const +{ + return zoom > gMinZoom; +} + +void ZoomInfo::SetZoom(double pixelsPerSecond) +{ + zoom = std::max(gMinZoom, std::min(gMaxZoom, pixelsPerSecond)); +} + +void ZoomInfo::ZoomBy(double multiplier) +{ + SetZoom(zoom * multiplier); +} + +void ZoomInfo::FindIntervals + (double /*rate*/, Intervals &results, wxInt64 origin) const +{ + results.clear(); + results.reserve(2); + + const wxInt64 rightmost(origin + (0.5 + screen * zoom)); + wxASSERT(origin <= rightmost); + { + results.push_back(Interval(origin, zoom, false)); + } + + if (origin < rightmost) + results.push_back(Interval(rightmost, 0, false)); + wxASSERT(!results.empty() && results[0].position == origin); +} + +ViewInfo::ViewInfo(double start, double screenDuration, double pixelsPerSecond) + : ZoomInfo(start, screenDuration, pixelsPerSecond) + , selectedRegion() + , track(NULL) + , total(screen) + , sbarH(0) + , sbarScreen(1) + , sbarTotal(1) + , sbarScale(1.0) + , scrollStep(16) + , bUpdateTrackIndicator(true) +{ +} + +void ViewInfo::SetBeforeScreenWidth(wxInt64 width, double lowerBoundTime) +{ + h = + std::max(lowerBoundTime, + std::min(total - screen, + width / zoom)); +} + +void ViewInfo::WriteXMLAttributes(XMLWriter &xmlFile) +{ + selectedRegion.WriteXMLAttributes(xmlFile, wxT("sel0"), wxT("sel1")); + xmlFile.WriteAttr(wxT("vpos"), vpos); + xmlFile.WriteAttr(wxT("h"), h, 10); + xmlFile.WriteAttr(wxT("zoom"), zoom, 10); +} + +bool ViewInfo::ReadXMLAttribute(const wxChar *attr, const wxChar *value) +{ + if (selectedRegion.HandleXMLAttribute(attr, value, wxT("sel0"), wxT("sel1"))) + return true; + + if (!wxStrcmp(attr, wxT("vpos"))) { + long longVpos; + wxString(value).ToLong(&longVpos); + vpos = int(longVpos); + return true; + } + + if (!wxStrcmp(attr, wxT("h"))) { + Internat::CompatibleToDouble(value, &h); + return true; + } + + if (!wxStrcmp(attr, wxT("zoom"))) { + Internat::CompatibleToDouble(value, &zoom); + return true; + } + + return false; +} diff --git a/src/ViewInfo.h b/src/ViewInfo.h index d27965aa3..03040a64f 100644 --- a/src/ViewInfo.h +++ b/src/ViewInfo.h @@ -11,14 +11,137 @@ #ifndef __AUDACITY_VIEWINFO__ #define __AUDACITY_VIEWINFO__ +#include #include "SelectedRegion.h" -const double gMaxZoom = 6000000, - gMinZoom = 0.001; class Track; -struct ViewInfo { + +// The subset of ViewInfo information (other than selection) +// that is sufficient for purposes of TrackArtist, +// and for computing conversions between track times and pixel positions. +class AUDACITY_DLL_API ZoomInfo +{ +public: + ZoomInfo(double start, double duration, double pixelsPerSecond); + ~ZoomInfo(); + + int vpos; // vertical scroll pos + + double h; // h pos in secs + + double screen; // screen width in secs + +protected: + double zoom; // pixels per second + +public: + + // do NOT use this once to convert a pixel width to a duration! + // Instead, call twice to convert start and end times, + // and take the difference. + // origin specifies the pixel corresponding to time h + double PositionToTime(wxInt64 position, + wxInt64 origin = 0 + , bool ignoreFisheye = false + ) const; + + // do NOT use this once to convert a duration to a pixel width! + // Instead, call twice to convert start and end positions, + // and take the difference. + // origin specifies the pixel corresponding to time h + wxInt64 TimeToPosition(double time, + wxInt64 origin = 0 + , bool ignoreFisheye = false + ) const; + + double OffsetTimeByPixels(double time, wxInt64 offset) const + { + return PositionToTime(offset + TimeToPosition(time)); + } + + bool ZoomInAvailable() const; + bool ZoomOutAvailable() const; + + // Return pixels, but maybe not a whole number + double GetScreenWidth() const + { return screen * zoom; } + + void SetScreenWidth(wxInt64 width) + { screen = width / zoom; } + + static double GetDefaultZoom() + { return 44100.0 / 512.0; } + + // There is NO GetZoom()! + // Use TimeToPosition and PositionToTime and OffsetTimeByPixels! + + // Limits zoom to certain bounds + void SetZoom(double pixelsPerSecond); + + // Limits zoom to certain bounds + // multipliers above 1.0 zoom in, below out + void ZoomBy(double multiplier); + + struct Interval { + const wxInt64 position; const double averageZoom; const bool inFisheye; + Interval(wxInt64 p, double z, bool i) + : position(p), averageZoom(z), inFisheye(i) {} + }; + typedef std::vector Intervals; + + // Find an increasing sequence of pixel positions. Each is the start + // of an interval, or is the end position. + // Each of the disjoint intervals should be drawn + // separately. + // It is guaranteed that there is at least one entry and the position of the + // first entry equals origin. + // @param origin specifies the pixel position corresponding to time ViewInfo::h. + void FindIntervals + (double rate, Intervals &results, wxInt64 origin = 0) const; + + enum FisheyeState { + HIDDEN, + PINNED, + + NUM_STATES, + }; + FisheyeState GetFisheyeState() const + { return HIDDEN; } // stub + + // Return true if the mouse position is anywhere in the fisheye + // origin specifies the pixel corresponding to time h + bool InFisheye(wxInt64 position, wxInt64 origin = 0) const + {origin; return false;} // stub + + // These accessors ignore the fisheye hiding state. + // Inclusive: + wxInt64 GetFisheyeLeftBoundary(wxInt64 origin = 0) const + {origin; return 0;} // stub + // Exclusive: + wxInt64 GetFisheyeRightBoundary(wxInt64 origin = 0) const + {origin; return 0;} // stub + +}; + +class AUDACITY_DLL_API ViewInfo + : public ZoomInfo +{ +public: + ViewInfo(double start, double screenDuration, double pixelsPerSecond); + + double GetBeforeScreenWidth() const + { + return h * zoom; + } + void SetBeforeScreenWidth(wxInt64 width, double lowerBoundTime = 0.0); + + double GetTotalWidth() const + { return total * zoom; } + + bool ZoomedAll() const + { return screen >= total; } // Current selection @@ -27,13 +150,8 @@ struct ViewInfo { // Scroll info Track *track; // first visible track - int vpos; // vertical scroll pos - double h; // h pos in secs - double screen; // screen width in secs double total; // total width in secs - double zoom; // pixels per second - // Current horizontal scroll bar positions, in pixels wxInt64 sbarH; wxInt64 sbarScreen; @@ -52,6 +170,9 @@ struct ViewInfo { // drawing the waveform. Maybe this should be put somewhere else? bool bUpdateTrackIndicator; + + void WriteXMLAttributes(XMLWriter &xmlFile); + bool ReadXMLAttribute(const wxChar *attr, const wxChar *value); }; #endif diff --git a/src/WaveClip.cpp b/src/WaveClip.cpp index fd66e7c4c..762683782 100644 --- a/src/WaveClip.cpp +++ b/src/WaveClip.cpp @@ -17,12 +17,6 @@ \class WaveCache \brief Cache used with WaveClip to cache wave information (for drawing). -*//****************************************************************//** - -\class SpecCache -\brief Cache used with WaveClip to cache spectrum information (for -drawing). Cache's the Spectrogram frequency samples. - *//*******************************************************************/ #include "WaveClip.h" @@ -61,8 +55,8 @@ public: { } - WaveCache(int len_, double pixelsPerSecond, double rate_, double t0) - : dirty(-1) + WaveCache(int len_, double pixelsPerSecond, double rate_, double t0, int dirty_) + : dirty(dirty_) , len(len_) , start(t0) , pps(pixelsPerSecond) @@ -263,93 +257,10 @@ protected: }; -class SpecCache { -public: - - // Make invalid cache - SpecCache() - : len(-1) - , ac(false) - , pps(-1.0) - , start(-1.0) - , windowType(-1) - , windowSize(-1) - , zeroPaddingFactor(-1) - , frequencyGain(-1) - - , freq(NULL) - , where(NULL) - - , dirty(-1) - { - } - - // Make valid cache, to be filled in - SpecCache(int cacheLen, bool autocorrelation, - double pps_, double start_, int windowType_, int windowSize_, - int zeroPaddingFactor_, int frequencyGain_ - ) - : len(cacheLen) - , ac(autocorrelation) - , pps(pps_) - , start(start_) - , windowType(windowType_) - , windowSize(windowSize_) - , zeroPaddingFactor(zeroPaddingFactor_) - , frequencyGain(frequencyGain_) - - // len columns, and so many rows, column-major. - // Don't take column literally -- this isn't pixel data yet, it's the - // raw data to be mapped onto the display. - , freq(len * ((windowSize * zeroPaddingFactor) / 2)) - - // Sample counts corresponding to the columns, and to one past the end. - , where(len + 1) - - , dirty(-1) - { - where[0] = 0; - } - - ~SpecCache() - { - } - - bool Matches(int dirty_, bool autocorrelation, double pixelsPerSecond, - const SpectrogramSettings &settings, double rate) const; - - void CalculateOneSpectrum - (const SpectrogramSettings &settings, - WaveTrackCache &waveTrackCache, - int xx, sampleCount numSamples, - double offset, double rate, - bool autocorrelation, const std::vector &gainFactors, - float *scratch); - - void Populate - (const SpectrogramSettings &settings, WaveTrackCache &waveTrackCache, - int copyBegin, int copyEnd, int numPixels, - sampleCount numSamples, - double offset, double rate, - bool autocorrelation); - - const int len; // counts pixels, not samples - const bool ac; - const double pps; - const double start; - const int windowType; - const int windowSize; - const int zeroPaddingFactor; - const int frequencyGain; - std::vector freq; - std::vector where; - - int dirty; -}; - #ifdef EXPERIMENTAL_USE_REALFFTF #include "FFT.h" -static void ComputeSpectrumUsingRealFFTf(float *buffer, HFFT hFFT, const float *window, int len, float *out) +static void ComputeSpectrumUsingRealFFTf + (float *buffer, HFFT hFFT, const float *window, int len, float *out) { int i; if(len > hFFT->Points*2) @@ -568,11 +479,11 @@ fillWhere(std::vector &where, int len, double bias, double correcti double t0, double rate, double samplesPerPixel) { // Be careful to make the first value non-negative - correction += 0.5 + bias; - where[0] = sampleCount(std::max(0.0, floor(correction + t0 * rate))); + const double w0 = 0.5 + correction + bias + t0 * rate; + where[0] = sampleCount(std::max(0.0, floor(w0))); for (sampleCount x = 1; x < len + 1; x++) where[x] = sampleCount( - floor(correction + t0 * rate + double(x) * samplesPerPixel) + floor(w0 + double(x) * samplesPerPixel) ); } @@ -586,110 +497,137 @@ fillWhere(std::vector &where, int len, double bias, double correcti bool WaveClip::GetWaveDisplay(WaveDisplay &display, double t0, double pixelsPerSecond, bool &isLoadingOD) { - int numPixels = display.width; + const bool allocated = (display.where != 0); - ODLocker locker(mWaveCacheMutex); + const int numPixels = display.width; - const double tstep = 1.0 / pixelsPerSecond; - const double samplesPerPixel = mRate * tstep; + int p0 = 0; // least column requiring computation + int p1 = numPixels; // greatest column requiring computation, plus one - // Make a tolerant comparison of the pps values in this wise: - // accumulated difference of times over the number of pixels is less than - // a sample period. - const bool ppsMatch = mWaveCache && - (fabs(tstep - 1.0 / mWaveCache->pps) * numPixels < (1.0 / mRate)); + float *min; + float *max; + float *rms; + int *bl; + std::vector *pWhere; - const bool match = - mWaveCache && - ppsMatch && - mWaveCache->len > 0 && - mWaveCache->dirty == mDirty; - - if (match && - mWaveCache->start == t0 && - mWaveCache->len >= numPixels) { - mWaveCache->LoadInvalidRegions(mSequence, true); - mWaveCache->ClearInvalidRegions(); - - // Satisfy the request completely from the cache - display.min = &mWaveCache->min[0]; - display.max = &mWaveCache->max[0]; - display.rms = &mWaveCache->rms[0]; - display.bl = &mWaveCache->bl[0]; - display.where = &mWaveCache->where[0]; - isLoadingOD = mWaveCache->numODPixels > 0; - return true; + if (allocated) { + // assume ownWhere is filled. + min = &display.min[0]; + max = &display.max[0]; + rms = &display.rms[0]; + bl = &display.bl[0]; + pWhere = &display.ownWhere; } + else { + // Lock the list of invalid regions + ODLocker locker(mWaveCacheMutex); - std::auto_ptr oldCache(mWaveCache); - mWaveCache = 0; + const double tstep = 1.0 / pixelsPerSecond; + const double samplesPerPixel = mRate * tstep; - int oldX0 = 0; - double correction = 0.0; + // Make a tolerant comparison of the pps values in this wise: + // accumulated difference of times over the number of pixels is less than + // a sample period. + const bool ppsMatch = mWaveCache && + (fabs(tstep - 1.0 / mWaveCache->pps) * numPixels < (1.0 / mRate)); - int copyBegin = 0, copyEnd = 0; - if (match) { - findCorrection(oldCache->where, oldCache->len, numPixels, - t0, mRate, samplesPerPixel, - oldX0, correction); - // Remember our first pixel maps to oldX0 in the old cache, - // possibly out of bounds. - // For what range of pixels can data be copied? - copyBegin = std::min(numPixels, std::max(0, -oldX0)); - copyEnd = std::min(numPixels, - copyBegin + oldCache->len - std::max(0, oldX0) - ); - } + const bool match = + mWaveCache && + ppsMatch && + mWaveCache->len > 0 && + mWaveCache->dirty == mDirty; - if (!(copyEnd > copyBegin)) - oldCache.reset(0); + if (match && + mWaveCache->start == t0 && + mWaveCache->len >= numPixels) { + mWaveCache->LoadInvalidRegions(mSequence, true); + mWaveCache->ClearInvalidRegions(); - mWaveCache = new WaveCache(numPixels, pixelsPerSecond, mRate, t0); - float *const min = &mWaveCache->min[0]; - float *const max = &mWaveCache->max[0]; - float *const rms = &mWaveCache->rms[0]; - int *const bl = &mWaveCache->bl[0]; - std::vector &where = mWaveCache->where; + // Satisfy the request completely from the cache + display.min = &mWaveCache->min[0]; + display.max = &mWaveCache->max[0]; + display.rms = &mWaveCache->rms[0]; + display.bl = &mWaveCache->bl[0]; + display.where = &mWaveCache->where[0]; + isLoadingOD = mWaveCache->numODPixels > 0; + return true; + } - fillWhere(where, numPixels, 0.0, correction, - t0, mRate, samplesPerPixel); + std::auto_ptr oldCache(mWaveCache); + mWaveCache = 0; - // The range of pixels we must fetch from the Sequence: - const int p0 = (copyBegin > 0) ? 0 : copyEnd; - int p1 = (copyEnd >= numPixels) ? copyBegin : numPixels; + int oldX0 = 0; + double correction = 0.0; + int copyBegin = 0, copyEnd = 0; + if (match) { + findCorrection(oldCache->where, oldCache->len, numPixels, + t0, mRate, samplesPerPixel, + oldX0, correction); + // Remember our first pixel maps to oldX0 in the old cache, + // possibly out of bounds. + // For what range of pixels can data be copied? + copyBegin = std::min(numPixels, std::max(0, -oldX0)); + copyEnd = std::min(numPixels, + copyBegin + oldCache->len - std::max(0, oldX0) + ); + } + if (!(copyEnd > copyBegin)) + oldCache.reset(0); - // Optimization: if the old cache is good and overlaps - // with the current one, re-use as much of the cache as - // possible + mWaveCache = new WaveCache(numPixels, pixelsPerSecond, mRate, t0, mDirty); + min = &mWaveCache->min[0]; + max = &mWaveCache->max[0]; + rms = &mWaveCache->rms[0]; + bl = &mWaveCache->bl[0]; + pWhere = &mWaveCache->where; - if (oldCache.get()) { + fillWhere(*pWhere, numPixels, 0.0, correction, + t0, mRate, samplesPerPixel); - //TODO: only load inval regions if - //necessary. (usually is the case, so no rush.) - //also, we should be updating the NEW cache, but here we are patching the old one up. - oldCache->LoadInvalidRegions(mSequence, false); - oldCache->ClearInvalidRegions(); + // The range of pixels we must fetch from the Sequence: + p0 = (copyBegin > 0) ? 0 : copyEnd; + p1 = (copyEnd >= numPixels) ? copyBegin : numPixels; - // Copy what we can from the old cache. - const int length = copyEnd - copyBegin; - const size_t sizeFloats = length * sizeof(float); - const int srcIdx = copyBegin + oldX0; - memcpy(&min[copyBegin], &oldCache->min[srcIdx], sizeFloats); - memcpy(&max[copyBegin], &oldCache->max[srcIdx], sizeFloats); - memcpy(&rms[copyBegin], &oldCache->rms[srcIdx], sizeFloats); - memcpy(&bl[copyBegin], &oldCache->bl[srcIdx], length * sizeof(int)); + // Optimization: if the old cache is good and overlaps + // with the current one, re-use as much of the cache as + // possible + + if (oldCache.get()) { + + //TODO: only load inval regions if + //necessary. (usually is the case, so no rush.) + //also, we should be updating the NEW cache, but here we are patching the old one up. + oldCache->LoadInvalidRegions(mSequence, false); + oldCache->ClearInvalidRegions(); + + // Copy what we can from the old cache. + const int length = copyEnd - copyBegin; + const size_t sizeFloats = length * sizeof(float); + const int srcIdx = copyBegin + oldX0; + memcpy(&min[copyBegin], &oldCache->min[srcIdx], sizeFloats); + memcpy(&max[copyBegin], &oldCache->max[srcIdx], sizeFloats); + memcpy(&rms[copyBegin], &oldCache->rms[srcIdx], sizeFloats); + memcpy(&bl[copyBegin], &oldCache->bl[srcIdx], length * sizeof(int)); + } } if (p1 > p0) { + // Cache was not used or did not satisfy the whole request + std::vector &where = *pWhere; /* handle values in the append buffer */ int numSamples = mSequence->GetNumSamples(); int a; - for(a = p0; a < p1; ++a) - if (where[a+1] > numSamples) - break; + // Not all of the required columns might be in the sequence. + // Some might be in the append buffer. + for (a = p0; a < p1; ++a) { + if (where[a + 1] > numSamples) + break; + } + + // Handle the columns that land in the append buffer. //compute the values that are outside the overlap from scratch. if (a < p1) { int i; @@ -700,7 +638,7 @@ bool WaveClip::GetWaveDisplay(WaveDisplay &display, double t0, sampleCount left; left = where[i] - numSamples; sampleCount right; - right = where[i+1] - numSamples; + right = where[i + 1] - numSamples; //wxCriticalSectionLocker locker(mAppendCriticalSection); @@ -753,6 +691,8 @@ bool WaveClip::GetWaveDisplay(WaveDisplay &display, double t0, p1 = a; } + // Done with append buffer, now fetch the rest of the cache miss + // from the sequence if (p1 > p0) { if (!mSequence->GetWaveDisplay(&min[p0], &max[p0], @@ -767,15 +707,23 @@ bool WaveClip::GetWaveDisplay(WaveDisplay &display, double t0, } } - mWaveCache->dirty = mDirty; + //find the number of OD pixels - the only way to do this is by recounting + if (!allocated) { + // Now report the results + display.min = min; + display.max = max; + display.rms = rms; + display.bl = bl; + display.where = &(*pWhere)[0]; + isLoadingOD = mWaveCache->numODPixels > 0; + } + else { + using namespace std; + isLoadingOD = + count_if(display.ownBl.begin(), display.ownBl.end(), + bind2nd(less(), 0)) > 0; + } - // Now report the results - display.min = min; - display.max = max; - display.rms = rms; - display.bl = bl; - display.where = &where[0]; - isLoadingOD = mWaveCache->numODPixels > 0; return true; } diff --git a/src/WaveClip.h b/src/WaveClip.h index 42e435e30..07bd5230b 100644 --- a/src/WaveClip.h +++ b/src/WaveClip.h @@ -32,9 +32,93 @@ #include class Envelope; +class SpectrogramSettings; class WaveCache; class WaveTrackCache; -class SpecCache; + +class SpecCache { +public: + + // Make invalid cache + SpecCache() + : len(-1) + , ac(false) + , pps(-1.0) + , start(-1.0) + , windowType(-1) + , windowSize(-1) + , zeroPaddingFactor(-1) + , frequencyGain(-1) + + , freq(NULL) + , where(NULL) + + , dirty(-1) + { + } + + // Make valid cache, to be filled in + SpecCache(int cacheLen, bool autocorrelation, + double pps_, double start_, int windowType_, int windowSize_, + int zeroPaddingFactor_, int frequencyGain_ + ) + : len(cacheLen) + , ac(autocorrelation) + , pps(pps_) + , start(start_) + , windowType(windowType_) + , windowSize(windowSize_) + , zeroPaddingFactor(zeroPaddingFactor_) + , frequencyGain(frequencyGain_) + + // len columns, and so many rows, column-major. + // Don't take column literally -- this isn't pixel data yet, it's the + // raw data to be mapped onto the display. + , freq(len * ((windowSize * zeroPaddingFactor) / 2)) + + // Sample counts corresponding to the columns, and to one past the end. + , where(len + 1) + + , dirty(-1) + { + where[0] = 0; + } + + ~SpecCache() + { + } + + bool Matches(int dirty_, bool autocorrelation, double pixelsPerSecond, + const SpectrogramSettings &settings, double rate) const; + + void CalculateOneSpectrum + (const SpectrogramSettings &settings, + WaveTrackCache &waveTrackCache, + int xx, sampleCount numSamples, + double offset, double rate, + bool autocorrelation, const std::vector &gainFactors, + float *scratch); + + void Populate + (const SpectrogramSettings &settings, WaveTrackCache &waveTrackCache, + int copyBegin, int copyEnd, int numPixels, + sampleCount numSamples, + double offset, double rate, + bool autocorrelation); + + const int len; // counts pixels, not samples + const bool ac; + const double pps; + const double start; + const int windowType; + const int windowSize; + const int zeroPaddingFactor; + const int frequencyGain; + std::vector freq; + std::vector where; + + int dirty; +}; class SpecPxCache { public: @@ -67,6 +151,8 @@ class WaveClip; WX_DECLARE_USER_EXPORTED_LIST(WaveClip, WaveClipList, AUDACITY_DLL_API); WX_DEFINE_USER_EXPORTED_ARRAY_PTR(WaveClip*, WaveClipArray, class AUDACITY_DLL_API); +// A bundle of arrays needed for drawing waveforms. The object may or may not +// own the storage for those arrays. If it does, it destroys them. class WaveDisplay { public: @@ -75,11 +161,38 @@ public: float *min, *max, *rms; int* bl; + std::vector ownWhere; + std::vector ownMin, ownMax, ownRms; + std::vector ownBl; + +public: WaveDisplay(int w) : width(w), where(0), min(0), max(0), rms(0), bl(0) { } + // Create "own" arrays. + void Allocate() + { + ownWhere.resize(width + 1); + ownMin.resize(width); + ownMax.resize(width); + ownRms.resize(width); + ownBl.resize(width); + + where = &ownWhere[0]; + if (width > 0) { + min = &ownMin[0]; + max = &ownMax[0]; + rms = &ownRms[0]; + bl = &ownBl[0]; + } + else { + min = max = rms = 0; + bl = 0; + } + } + ~WaveDisplay() { } diff --git a/src/effects/Equalization.cpp b/src/effects/Equalization.cpp index 97ac092e1..ac81f46ea 100644 --- a/src/effects/Equalization.cpp +++ b/src/effects/Equalization.cpp @@ -2804,7 +2804,8 @@ void EqualizationPanel::OnPaint(wxPaintEvent & WXUNUSED(event)) memDC.SetPen(*wxBLACK_PEN); if( mEffect->mDraw->GetValue() ) - mEffect->mEnvelope->DrawPoints(memDC, mEnvRect, 0.0, mEnvRect.width-1, false, mEffect->mdBMin, mEffect->mdBMax); + mEffect->mEnvelope->DrawPoints(memDC, mEnvRect, ZoomInfo(0.0, 1.0, mEnvRect.width-1), false, + mEffect->mdBMin, mEffect->mdBMax); dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE); @@ -2822,7 +2823,7 @@ void EqualizationPanel::OnMouseEvent(wxMouseEvent & event) CaptureMouse(); } - if (mEffect->mEnvelope->MouseEvent(event, mEnvRect, 0.0, mEnvRect.width, false, + if (mEffect->mEnvelope->MouseEvent(event, mEnvRect, ZoomInfo(0.0, 1.0, mEnvRect.width), false, mEffect->mdBMin, mEffect->mdBMax)) { mEffect->EnvelopeUpdated(); diff --git a/src/import/ImportLOF.cpp b/src/import/ImportLOF.cpp index 9bd2d999f..fb1d0ad1c 100644 --- a/src/import/ImportLOF.cpp +++ b/src/import/ImportLOF.cpp @@ -70,6 +70,7 @@ *//*******************************************************************/ #include "../Audacity.h" +#include "ImportLOF.h" #include #include @@ -78,7 +79,6 @@ #include #include -#include "ImportLOF.h" #ifdef USE_MIDI #include "ImportMIDI.h" #endif // USE_MIDI @@ -485,8 +485,7 @@ void LOFImportFileHandle::doDuration() if (callDurationFactor) { double longestDuration = mProject->GetTracks()->GetEndTime(); - double realZoomValue = ((longestDuration/durationFactor)*(mProject->GetZoom())); - mProject->Zoom(realZoomValue); + mProject->ZoomBy(longestDuration / durationFactor); callDurationFactor = false; } } diff --git a/src/toolbars/EditToolBar.cpp b/src/toolbars/EditToolBar.cpp index b314388d9..16237ed01 100644 --- a/src/toolbars/EditToolBar.cpp +++ b/src/toolbars/EditToolBar.cpp @@ -32,6 +32,7 @@ #include "../Audacity.h" +#include "EditToolBar.h" // For compilers that support precompilation, includes "wx/wx.h". #include @@ -44,8 +45,6 @@ #include #endif -#include "EditToolBar.h" - #include "../AllThemeResources.h" #include "../AudioIO.h" #include "../ImageManipulation.h" @@ -308,8 +307,8 @@ void EditToolBar::EnableDisableButtons() bool tracks = (!p->GetTracks()->IsEmpty()); - mButtons[ETBZoomInID]->SetEnabled(tracks && (p->GetZoom() < gMaxZoom)); - mButtons[ETBZoomOutID]->SetEnabled(tracks && (p->GetZoom() > gMinZoom) ); + mButtons[ETBZoomInID]->SetEnabled(tracks && (p->ZoomInAvailable())); + mButtons[ETBZoomOutID]->SetEnabled(tracks && (p->ZoomOutAvailable()) ); #if 0 // Disabled for version 1.2.0 since it doesn't work quite right... mButtons[ETBZoomToggleID]->SetEnabled(tracks); diff --git a/src/widgets/AttachableScrollBar.cpp b/src/widgets/AttachableScrollBar.cpp index 241201254..5ffb2fefd 100644 --- a/src/widgets/AttachableScrollBar.cpp +++ b/src/widgets/AttachableScrollBar.cpp @@ -28,9 +28,9 @@ internally, not ints, allowing for (external) control of zooming. *//*******************************************************************/ #include "../Audacity.h" +#include "AttachableScrollBar.h" #include -#include "AttachableScrollBar.h" #include "../ViewInfo.h" @@ -58,9 +58,9 @@ void AttachableScrollBar::SetScrollBarFromViewInfo() { ViewInfo & mViewInfo = *mpViewInfo; - mViewInfo.sbarTotal = (int) (mViewInfo.total * mViewInfo.zoom); - mViewInfo.sbarScreen = (int) (mViewInfo.screen * mViewInfo.zoom); - mViewInfo.sbarH = (int) (mViewInfo.h * mViewInfo.zoom); + mViewInfo.sbarTotal = (int) (mViewInfo.GetTotalWidth()); + mViewInfo.sbarScreen = (int) (mViewInfo.GetScreenWidth()); + mViewInfo.sbarH = (int) (mViewInfo.GetBeforeScreenWidth()); SetScrollbar(mViewInfo.sbarH, mViewInfo.sbarScreen, mViewInfo.sbarTotal, mViewInfo.sbarScreen, TRUE); @@ -75,14 +75,8 @@ void AttachableScrollBar::SetViewInfoFromScrollBar() mViewInfo.sbarH = GetThumbPosition(); - if (mViewInfo.sbarH != hlast) { - mViewInfo.h = mViewInfo.sbarH / mViewInfo.zoom; - - if (mViewInfo.h > mViewInfo.total - mViewInfo.screen) - mViewInfo.h = mViewInfo.total - mViewInfo.screen; - if (mViewInfo.h < 0.0) - mViewInfo.h = 0.0; - } + if (mViewInfo.sbarH != hlast) + mViewInfo.SetBeforeScreenWidth(mViewInfo.sbarH); } // Used to associated a ViewInfo structure with a scrollbar. diff --git a/src/widgets/AttachableScrollBar.h b/src/widgets/AttachableScrollBar.h index a05f180bd..eb20ea575 100644 --- a/src/widgets/AttachableScrollBar.h +++ b/src/widgets/AttachableScrollBar.h @@ -19,7 +19,7 @@ #include -struct ViewInfo; +class ViewInfo; class AUDACITY_DLL_API AttachableScrollBar : public wxScrollBar diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index 81f0308c6..994e15fe8 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -79,7 +79,8 @@ array of Ruler::Label. #include "../Prefs.h" #include "../Snap.h" -#define max(a,b) ( (aGetZoomInfo(); + // This gets called when something has been changed // (i.e. we've been invalidated). Recompute all // tick positions and font size. @@ -1063,7 +1083,11 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo } else if(mLog==false) { - double UPP = (mMax-mMin)/mLength; // Units per pixel + // Use the "hidden" min and max to determine the tick size. + // That may make a difference with fisheye. + // Otherwise you may see the tick size for the whole ruler change + // when the fisheye approaches start or end. + double UPP = (mHiddenMax-mHiddenMin)/mLength; // Units per pixel FindLinearTickSizes(UPP); // Left and Right Edges @@ -1074,59 +1098,62 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo // Zero (if it's in the middle somewhere) if (mMin * mMax < 0.0) { - int mid = (int)(mLength*(mMin/(mMin-mMax)) + 0.5); - Tick(mid, 0.0, true, false); + int mid; + if (zoomInfo != NULL) + mid = int(zoomInfo->TimeToPosition(0.0, mLeftOffset)); + else + mid = (int)(mLength*(mMin / (mMin - mMax)) + 0.5); + const int iMaxPos = (mOrientation == wxHORIZONTAL) ? mRight : mBottom - 5; + if (mid >= 0 && mid < iMaxPos) + Tick(mid, 0.0, true, false); } double sg = UPP > 0.0? 1.0: -1.0; - // Major ticks - double d, warpedD; - d = mMin - UPP/2; - if(timetrack) - warpedD = timetrack->ComputeWarpedLength(0.0, d); - else - warpedD = d; - // using ints for majorint doesn't work, as - // majorint will overflow and be negative at high zoom. - double majorInt = floor(sg * warpedD / mMajor); - i = -1; - while(i <= mLength) { - i++; - if(timetrack) - warpedD += timetrack->ComputeWarpedLength(d, d + UPP); - else - warpedD += UPP; - d += UPP; + // Major and minor ticks + for (int jj = 0; jj < 2; ++jj) { + const double denom = jj == 0 ? mMajor : mMinor; + i = -1; j = 0; + double d, warpedD, nextD; - if (floor(sg * warpedD / mMajor) > majorInt) { - majorInt = floor(sg * warpedD / mMajor); - Tick(i, sg * majorInt * mMajor, true, false); + double prevTime = 0.0, time = 0.0; + if (zoomInfo != NULL) { + j = zoomInfo->TimeToPosition(mMin); + prevTime = zoomInfo->PositionToTime(--j); + time = zoomInfo->PositionToTime(++j); + d = (prevTime + time) / 2.0; } - } - - // Minor ticks - d = mMin - UPP/2; - if(timetrack) - warpedD = timetrack->ComputeWarpedLength(0.0, d); - else - warpedD = d; - // using ints for majorint doesn't work, as - // majorint will overflow and be negative at high zoom. - // MB: I assume the same applies to minorInt - double minorInt = floor(sg * warpedD / mMinor); - i = -1; - while(i <= mLength) { - i++; - if(timetrack) - warpedD += timetrack->ComputeWarpedLength(d, d + UPP); else - warpedD += UPP; - d += UPP; + d = mMin - UPP / 2; + if (timetrack) + warpedD = timetrack->ComputeWarpedLength(0.0, d); + else + warpedD = d; + // using ints doesn't work, as + // this will overflow and be negative at high zoom. + double step = floor(sg * warpedD / denom); + while (i <= mLength) { + i++; + if (zoomInfo) + { + prevTime = time; + time = zoomInfo->PositionToTime(++j); + nextD = (prevTime + time) / 2.0; + // wxASSERT(time >= prevTime); + } + else + nextD = d + UPP; + if (timetrack) + warpedD += timetrack->ComputeWarpedLength(d, nextD); + else + warpedD = nextD; + d = nextD; - if (floor(sg * warpedD / mMinor) > minorInt) { - minorInt = floor(sg * warpedD / mMinor); - Tick(i, sg * minorInt * mMinor, false, true); + if (floor(sg * warpedD / denom) > step) { + step = floor(sg * warpedD / denom); + bool major = jj == 0; + Tick(i, sg * step * denom, major, !major); + } } } @@ -1135,11 +1162,10 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo Tick(0, mMin, true, false); Tick(mLength, mMax, true, false); } - } else { // log case - mDigits=2; //TODO: implement dynamic digit computation + mDigits=2; //TODO: implement dynamic digit computation double loLog = log10(mMin); double hiLog = log10(mMax); double scale = mLength/(hiLog - loLog); @@ -1538,6 +1564,12 @@ void Ruler::Label::Draw(wxDC&dc, bool twoTone) const } } +void Ruler::SetUseZoomInfo(int leftOffset) +{ + mLeftOffset = leftOffset; + mUseZoomInfo = true; +} + // // RulerPanel // @@ -1641,8 +1673,8 @@ AdornedRulerPanel::AdornedRulerPanel(wxWindow* parent, SetName(GetLabel()); mLeftOffset = 0; - mCurPos = -1; - mIndPos = -1; + mCurTime = -1; + mIndTime = -1; mIndType = -1; mQuickPlayInd = false; mPlayRegionStart = -1; @@ -1665,6 +1697,7 @@ AdornedRulerPanel::AdornedRulerPanel(wxWindow* parent, mInner.width -= 2; // -2 for left and right bevels mInner.height -= 3; // -3 for top and bottom bevels and bottom line + ruler.SetUseZoomInfo(mLeftOffset); ruler.SetBounds( mInner.GetLeft(), mInner.GetTop(), mInner.GetRight(), @@ -1719,6 +1752,11 @@ void AdornedRulerPanel::UpdatePrefs() RegenerateTooltips(); } +void AdornedRulerPanel::InvalidateRuler() +{ + ruler.Invalidate(); +} + void AdornedRulerPanel::RegenerateTooltips() { #if wxUSE_TOOLTIPS @@ -1822,19 +1860,18 @@ void AdornedRulerPanel::OnSize(wxSizeEvent & WXUNUSED(evt)) Refresh( false ); } -double AdornedRulerPanel::Pos2Time(int p) +double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) { - return (p-mLeftOffset) / mViewInfo->zoom + mViewInfo->h; + return mViewInfo->PositionToTime(p, mLeftOffset + , ignoreFisheye + ); } -int AdornedRulerPanel::Time2Pos(double t) +int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) { - return mLeftOffset + Seconds2Pixels(t-mViewInfo->h); -} - -int AdornedRulerPanel::Seconds2Pixels(double t) -{ - return (int)(t * mViewInfo->zoom + 0.5); + return mViewInfo->TimeToPosition(t, mLeftOffset + , ignoreFisheye + ); } @@ -1924,7 +1961,6 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt) if (!mQuickPlayEnabled) return; - HandleSnapping(); if (evt.LeftDown()) @@ -1945,7 +1981,10 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt) mMouseEventState = mesSelectingPlayRegionClick; // otherwise check which marker is nearer else { - if (fabs(mQuickPlayPos - mOldPlayRegionStart) < fabs(mQuickPlayPos - mOldPlayRegionEnd)) + // Don't compare times, compare positions. + //if (fabs(mQuickPlayPos - mPlayRegionStart) < fabs(mQuickPlayPos - mPlayRegionEnd)) + if (abs(Time2Pos(mQuickPlayPos) - Time2Pos(mPlayRegionStart)) < + abs(Time2Pos(mQuickPlayPos) - Time2Pos(mPlayRegionEnd))) mMouseEventState = mesDraggingPlayRegionStart; else mMouseEventState = mesDraggingPlayRegionEnd; @@ -2225,7 +2264,7 @@ void AdornedRulerPanel::HandleSnapping() delete mSnapManager; } mSnapManager = new SnapManager(mProject->GetTracks(), NULL, - mViewInfo->zoom, + *mViewInfo, QUICK_PLAY_SNAP_PIXEL); bool snappedPoint, snappedTime; mIsSnapped = (mSnapManager->Snap(NULL, mQuickPlayPos, false, @@ -2341,11 +2380,13 @@ void AdornedRulerPanel::DoDrawBorder(wxDC * dc) void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ ) { - double min = mViewInfo->h - mLeftOffset / mViewInfo->zoom; - double max = min + mInner.width / mViewInfo->zoom; + const double min = Pos2Time(0); + const double hiddenMin = Pos2Time(0, true); + const double max = Pos2Time(mInner.width); + const double hiddenMax = Pos2Time(mInner.width, true); ruler.SetTickColour( theTheme.Colour( clrTrackPanelText ) ); - ruler.SetRange( min, max ); + ruler.SetRange( min, max, hiddenMin, hiddenMax ); ruler.Draw( *dc ); } @@ -2357,20 +2398,8 @@ void AdornedRulerPanel::DrawSelection() void AdornedRulerPanel::DoDrawSelection(wxDC * dc) { // Draw selection - double zoom = mViewInfo->zoom; - double sel0 = - mViewInfo->selectedRegion.t0() - mViewInfo->h + mLeftOffset / zoom; - double sel1 = - mViewInfo->selectedRegion.t1() - mViewInfo->h + mLeftOffset / zoom; - - if( sel0 < 0.0 ) - sel0 = 0.0; - - if( sel1 > ( mInner.width / zoom ) ) - sel1 = mInner.width / zoom; - - int p0 = int ( sel0 * zoom + 1.5 ); - int p1 = int ( sel1 * zoom + 2.5 ); + const int p0 = 1 + max(0, Time2Pos(mViewInfo->selectedRegion.t0())); + const int p1 = 2 + min(mInner.width, Time2Pos(mViewInfo->selectedRegion.t1())); dc->SetBrush( wxBrush( theTheme.Colour( clrRulerBackground )) ); dc->SetPen( wxPen( theTheme.Colour( clrRulerBackground )) ); @@ -2383,16 +2412,22 @@ void AdornedRulerPanel::DoDrawSelection(wxDC * dc) dc->DrawRectangle( r ); } -void AdornedRulerPanel::DrawCursor(double pos) +void AdornedRulerPanel::SetLeftOffset(int offset) { - mCurPos = pos; + mLeftOffset = offset; + ruler.SetUseZoomInfo(offset); +} + +void AdornedRulerPanel::DrawCursor(double time) +{ + mCurTime = time; Refresh(false); } void AdornedRulerPanel::DoDrawCursor(wxDC * dc) { - int x = mLeftOffset + int ( ( mCurPos - mViewInfo->h ) * mViewInfo->zoom ); + const int x = Time2Pos(mCurTime); // Draw cursor in ruler dc->DrawLine( x, 1, x, mInner.height ); @@ -2409,11 +2444,11 @@ void AdornedRulerPanel::ClearIndicator() Refresh(false); } -void AdornedRulerPanel::DrawIndicator( double pos, bool rec ) +void AdornedRulerPanel::DrawIndicator( double time, bool rec ) { - mIndPos = pos; + mIndTime = time; - if( mIndPos < 0 ) + if (mIndTime < 0) { ClearIndicator(); return; @@ -2432,7 +2467,7 @@ void AdornedRulerPanel::DoDrawIndicator(wxDC * dc) } int indsize = 6; - int x = mLeftOffset + int ( ( mIndPos - mViewInfo->h ) * mViewInfo->zoom ); + const int x = Time2Pos(mIndTime); wxPoint tri[ 3 ]; tri[ 0 ].x = x - indsize; @@ -2459,7 +2494,7 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc, bool clear) } int indsize = 4; - int x = mLeftOffset + int((mQuickPlayPos - mViewInfo->h) * mViewInfo->zoom); + int x = Time2Pos(mQuickPlayPos); wxPoint tri[3]; tri[0].x = -indsize; diff --git a/src/widgets/Ruler.h b/src/widgets/Ruler.h index d1133f22d..935659e81 100644 --- a/src/widgets/Ruler.h +++ b/src/widgets/Ruler.h @@ -19,7 +19,7 @@ #include "../Envelope.h" #include "../Experimental.h" -struct ViewInfo; +class ViewInfo; class AudacityProject; class TimeTrack; class SnapManager; @@ -56,6 +56,13 @@ class AUDACITY_DLL_API Ruler { // (at the center of the pixel, in both cases) void SetRange(double min, double max); + // An overload needed for the special case of fisheye + // min is the value at (x, y) + // max is the value at (x+width, y+height) + // hiddenMin, hiddenMax are the values that would be shown without the fisheye. + // (at the center of the pixel, in both cases) + void SetRange(double min, double max, double hiddenMin, double hiddenMax); + // // Optional Ruler Parameters // @@ -114,6 +121,8 @@ class AUDACITY_DLL_API Ruler { void SetCustomMajorLabels(wxArrayString *label, int numLabel, int start, int step); void SetCustomMinorLabels(wxArrayString *label, int numLabel, int start, int step); + void SetUseZoomInfo(int leftOffset); + // // Drawing // @@ -130,8 +139,10 @@ class AUDACITY_DLL_API Ruler { void SetTickColour( const wxColour & colour) { mTickColour = colour; mPen.SetColour( colour );} - private: + // Force regeneration of labels at next draw time void Invalidate(); + + private: void Update(); void Update(TimeTrack* timetrack); void FindTickSizes(); @@ -163,6 +174,7 @@ private: bool mUserFonts; double mMin, mMax; + double mHiddenMin, mHiddenMax; double mMajor; double mMinor; @@ -213,6 +225,8 @@ private: int mGridLineLength; // end wxString mUnits; bool mTwoTone; + bool mUseZoomInfo; + int mLeftOffset; }; class AUDACITY_DLL_API RulerPanel : public wxPanel { @@ -262,10 +276,10 @@ public: public: static int GetRulerHeight() { return 28; } - void SetLeftOffset(int offset){ mLeftOffset = offset; } + void SetLeftOffset(int offset); - void DrawCursor(double pos); - void DrawIndicator(double pos, bool rec); + void DrawCursor(double time); + void DrawIndicator(double time, bool rec); void DrawSelection(); void ClearIndicator(); @@ -273,9 +287,11 @@ public: void ClearPlayRegion(); void GetPlayRegion(double* playRegionStart, double* playRegionEnd); - void SetProject(AudacityProject* project) {mProject = project;}; + void SetProject(AudacityProject* project) {mProject = project;} void GetMaxSize(wxCoord *width, wxCoord *height); + void InvalidateRuler(); + void UpdatePrefs(); void RegenerateTooltips(); @@ -297,9 +313,8 @@ private: void DrawQuickPlayIndicator(wxDC * dc, bool clear /*delete old only*/); void DoDrawPlayRegion(wxDC * dc); - double Pos2Time(int p); - int Time2Pos(double t); - int Seconds2Pixels(double t); + double Pos2Time(int p, bool ignoreFisheye = false); + int Time2Pos(double t, bool ignoreFisheye = false); bool IsWithinMarker(int mousePosX, double markerTime); @@ -314,10 +329,11 @@ private: int mLeftOffset; // Number of pixels before we hit the 'zero position'. - double mCurPos; + double mCurTime; - int mIndType; // -1 = No indicator, 0 = Play, 1 = Record - double mIndPos; + + int mIndType; // -1 = No indicator, 0 = Play, 1 = Record + double mIndTime; bool mQuickPlayInd; double mQuickPlayPos; SnapManager *mSnapManager;