diff --git a/au4/src/au3wrap/iau3wavepainter.h b/au4/src/au3wrap/iau3wavepainter.h
index b7b7117b5105b48b18fb1ddcb235071b7b47bdc4..019ebfebf1e0173969ad1b80254308b394f6569c 100644
--- a/au4/src/au3wrap/iau3wavepainter.h
+++ b/au4/src/au3wrap/iau3wavepainter.h
@@ -22,8 +22,16 @@ public:
         QColor highlight;
     };
 
+    struct Geometry {
+        double clipHeight = 0.0;    // clip view height
+        double clipWidth = 0.0;     // clip view width
+        double relClipLeft = 0.0;   // relatively to frameLeft
+        double frameLeft = 0.0;     // track line shift
+        double frameWidth = 0.0;    // track line visible width
+    };
+
     struct Params {
-        QRect viewRect;
+        Geometry geometry;
         double zoom = 0.0;
         Style style;
     };
diff --git a/au4/src/au3wrap/internal/au3wavepainter.cpp b/au4/src/au3wrap/internal/au3wavepainter.cpp
index 234e397656246895b5c65e26c96794ceccb47231..a6578574b434e9139da332c0873dfcc8ac9828ec 100644
--- a/au4/src/au3wrap/internal/au3wavepainter.cpp
+++ b/au4/src/au3wrap/internal/au3wavepainter.cpp
@@ -8,15 +8,11 @@
 #include <wx/utils.h>
 
 #include "ClipInterface.h"
-#include "CodeConversions.h"
-#include "SelectedRegion.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
 #include "ZoomInfo.h"
 #include "Envelope.h"
 #include "FrameStatistics.h"
-#include "SyncLock.h"
-#include "ViewInfo.h"
 #include "WaveformScale.h"
 #include "graphics/Color.h"
 
@@ -25,11 +21,7 @@
 
 #include "domaccessor.h"
 
-constexpr int kClipDetailedViewMinimumWidth{ 3 };
-
-constexpr int FrameRadius { 4 };
-constexpr int HeaderHeight { 0 };
-constexpr int HeaderVMargin { 2 };
+constexpr double CLIPVIEW_WIDTH_MIN = 4; // px
 
 using Style = au::au3::Au3WavePainter::Style;
 
@@ -120,19 +112,25 @@ public:
                 clip, dataCache,
                 [] { return std::make_unique<WaveBitmapCacheElementQt>(); });
 
-            mChannelCaches.push_back(
-                { std::move(dataCache), std::move(bitmapCache) });
+            mChannelCaches.push_back({ std::move(dataCache), std::move(bitmapCache) });
         }
 
         return *this;
     }
 
+    struct Geometry
+    {
+        double top = 0.0;
+        double left = 0.0;
+        double height = 0.0;
+        double width = 0.0;    // not used for draw, just info
+    };
+
     void Draw(int channelIndex,
               QPainter& painter, const
               WavePaintParameters& params,
-              const ZoomInfo& zoomInfo,
-              const QRect& targetRect,
-              int leftOffset,
+              const Geometry& geometry,
+              double zoom,
               double from,
               double to)
     {
@@ -144,26 +142,24 @@ public:
         auto& bitmapCache = mChannelCaches[channelIndex].BitmapCache;
         bitmapCache->SetPaintParameters(params);
 
-        auto range = bitmapCache->PerformLookup(zoomInfo, from, to);
+        const ZoomInfo zoomInfo(0.0, zoom);
 
-        auto left = targetRect.x() + leftOffset;
-        auto height = targetRect.height();
+        auto range = bitmapCache->PerformLookup(zoomInfo, from, to);
 
-        const auto top = targetRect.y();
+        int left = geometry.left;
+        int height = geometry.height;
 
         for (auto it = range.begin(); it != range.end(); ++it) {
             const auto elementLeftOffset = it.GetLeftOffset();
             const auto elementRightOffset = it.GetRightOffset();
 
-            const auto width = WaveBitmapCache::CacheElementWidth
-                               - elementLeftOffset - elementRightOffset;
+            const auto width = WaveBitmapCache::CacheElementWidth - elementLeftOffset - elementRightOffset;
 
-            const auto drawableWidth = std::min<int32_t>(
-                width, it->Width() - elementLeftOffset);
+            const auto drawableWidth = std::min<int32_t>(width, it->Width() - elementLeftOffset);
 
             const auto image = static_cast<const WaveBitmapCacheElementQt&>(*it).GetImage();
             painter.drawImage(
-                QRectF(left, targetRect.y(), drawableWidth, height),
+                QRectF(left, geometry.top, drawableWidth, height),
                 image,
                 QRectF(
                     elementLeftOffset,
@@ -271,7 +267,7 @@ double CalculateAdjustmentForZoomLevel(double avgPixPerSecond, bool showSamples)
         // adjustment so that the last circular point doesn't appear
         // to be hanging off the end
         return pixelsOffset
-               / avgPixPerSecond; // pixels / ( pixels / second ) = seconds
+               / avgPixPerSecond;   // pixels / ( pixels / second ) = seconds
     }
     return .0;
 }
@@ -365,170 +361,57 @@ int GetWaveYPos(float value, float min, float max,
     return (int)(value * (height - 1) + 0.5);
 }
 
+struct WaveGeometry
+{
+    double waveTop = 0.0;       // wave channel view top
+    double waveHeight = 0.0;    // wave channel view height
+    double clipWidth = 0.0;     // clip view width
+    double relClipLeft = 0.0;   // relatively to frameLeft
+    double frameLeft = 0.0;     // track line shift
+    double frameWidth = 0.0;    // track line visible width
+};
+
 struct ClipParameters
 {
     // Do a bunch of calculations common to waveform and spectrum drawing.
-    ClipParameters(const ClipTimes& clip, const QRect& rect, const ZoomInfo& zoomInfo);
+    ClipParameters(const WaveGeometry& geometry, double zoom);
 
-    const double trackRectT0; // absolute time of left edge of track
+    const WaveGeometry geometry;
+
+    WaveformPainter::Geometry drawGeometry;
 
     // Lower and upper visible time boundaries (relative to clip). If completely
     // off-screen, `t0 == t1`.
-    double t0;
-    double t1;
-
-    const double averagePixelsPerSecond;
-    const bool showIndividualSamples;
-
-    QRect hiddenMid;
-    int hiddenLeftOffset;
-
-    QRect mid;
-    int leftOffset;
-
-    // returns a clip rectangle restricted by viewRect,
-    // and with clipOffsetX - clip horizontal origin offset within view rect
-    static QRect GetClipRect(
-        const ClipTimes& clip, const ZoomInfo& zoomInfo, const QRect& viewRect, bool* outShowSamples = nullptr);
+    double t0 = 0.0;
+    double t1 = 0.0;
 };
 
-ClipParameters::ClipParameters(
-    const ClipTimes& clip, const QRect& rect, const ZoomInfo& zoomInfo)
-    : trackRectT0{zoomInfo.PositionToTime(0, 0, true)}
-    , averagePixelsPerSecond{GetPixelsPerSecond(rect, zoomInfo)}
-    , showIndividualSamples{ShowIndividualSamples(
-                                clip.GetRate(), clip.GetStretchRatio(), averagePixelsPerSecond)}
+ClipParameters::ClipParameters(const WaveGeometry& geomet, double zoom)
+    : geometry(geomet)
 {
-    const auto trackRectT1 = zoomInfo.PositionToTime(rect.width(), 0, true);
-    const auto playStartTime = clip.GetPlayStartTime();
-
-    const double clipLength = clip.GetPlayEndTime() - clip.GetPlayStartTime();
-
-    // Hidden duration because too far left.
-    const auto tpre = trackRectT0 - playStartTime;
-    const auto tpost = trackRectT1 - playStartTime;
-
-    const auto blank = GetBlankSpaceBeforePlayEndTime(clip);
-
-    // Calculate actual selection bounds so that t0 > 0 and t1 < the
-    // end of the track
-    t0 = std::max(tpre, .0);
-    t1 = std::min(tpost, clipLength - blank)
-         + CalculateAdjustmentForZoomLevel(
-        averagePixelsPerSecond, showIndividualSamples);
-
-    // 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;
-    }
-
-    // The variable "hiddenMid" will be the rectangle containing the
-    // actual waveform, as opposed to any blank area before
-    // 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 unused area to the
-    // left of the track.  Reduce the "hiddenMid"
-    hiddenLeftOffset = 0;
-    if (tpre < 0) {
-        // Fix Bug #1296 caused by premature conversion to (int).
-        wxInt64 time64 = zoomInfo.TimeToPosition(playStartTime, 0, true);
-        if (time64 < 0) {
-            time64 = 0;
-        }
-        hiddenLeftOffset = (time64 < rect.width()) ? (int)time64 : rect.width();
-
-        hiddenMid.setLeft(hiddenMid.left() + hiddenLeftOffset);
-    }
-
-    // If the right edge of the track is to the left of the right
-    // edge of the display, then there's some unused area to the right
-    // of the track.  Reduce the "hiddenMid" rect by the
-    // size of the blank area.
-    if (tpost > t1) {
-        wxInt64 time64 = zoomInfo.TimeToPosition(playStartTime + t1, 0, true);
-        if (time64 < 0) {
-            time64 = 0;
-        }
-        const int hiddenRightOffset = (time64 < rect.width()) ? (int)time64 : rect.width();
-
-        hiddenMid.setWidth(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 unused area to the
-    // left of the track.  Reduce the "mid"
-    leftOffset = 0;
-    if (tpre < 0) {
-        wxInt64 time64 = 0;//zoomInfo.TimeToPosition(playStartTime, 0, false);
-        if (time64 < 0) {
-            time64 = 0;
-        }
-        leftOffset = (time64 < rect.width()) ? (int)time64 : rect.width();
-
-        mid.setLeft(mid.left() + leftOffset);
-    }
-
-    // If the right edge of the track is to the left of the right
-    // edge of the display, then there's some unused area to the right
-    // of the track.  Reduce the "mid" rect by the
-    // size of the blank area.
-    if (tpost > t1) {
-        wxInt64 time64 = zoomInfo.TimeToPosition(playStartTime + t1, 0, false);
-        if (time64 < 0) {
-            time64 = 0;
-        }
-        const int distortedRightOffset = (time64 < rect.width()) ? (int)time64 : rect.width();
-
-        mid.setWidth(std::max(0, distortedRightOffset - leftOffset));
-    }
-}
-
-QRect ClipParameters::GetClipRect(const ClipTimes& clip,
-                                  const ZoomInfo& zoomInfo, const QRect& viewRect, bool* outShowSamples)
-{
-    const auto pixelsPerSecond = GetPixelsPerSecond(viewRect, zoomInfo);
-    const auto showIndividualSamples = ShowIndividualSamples(
-        clip.GetRate(), clip.GetStretchRatio(), pixelsPerSecond);
-    const auto clipEndingAdjustment
-        =CalculateAdjustmentForZoomLevel(pixelsPerSecond, showIndividualSamples);
-    if (outShowSamples != nullptr) {
-        *outShowSamples = showIndividualSamples;
-    }
-    constexpr auto edgeLeft
-        =static_cast<ZoomInfo::int64>(std::numeric_limits<int>::min());
-    constexpr auto edgeRight
-        =static_cast<ZoomInfo::int64>(std::numeric_limits<int>::max());
-    const auto left = std::clamp(
-        zoomInfo.TimeToPosition(clip.GetPlayStartTime(), viewRect.x(), true),
-        edgeLeft, edgeRight);
-    const auto right = std::clamp(
-        zoomInfo.TimeToPosition(
-            clip.GetPlayEndTime() - GetBlankSpaceBeforePlayEndTime(clip)
-            + clipEndingAdjustment,
-            viewRect.x(), true),
-        edgeLeft, edgeRight);
-    if (right >= left) {
-        // after clamping we can expect that left and right
-        // are small enough to be put into int
-        return {
-            static_cast<int>(left),
-            viewRect.y(),
-            std::max(1, static_cast<int>(right - left)),
-            viewRect.height()
-        };
-    }
-    return {};
+    double drawWidth = std::min(geometry.relClipLeft + geometry.clipWidth, geometry.frameWidth) - geometry.relClipLeft;
+    double drawLeft = 0.0;
+    if (geometry.relClipLeft < 0) {
+        drawLeft = -geometry.relClipLeft;
+    }
+
+    drawGeometry.top = geometry.waveTop;
+    drawGeometry.left = drawLeft;
+    drawGeometry.height = geometry.waveHeight;
+    drawGeometry.width = drawWidth;
+
+    t0 = drawLeft / zoom;
+    t1 = drawWidth / zoom;
+
+    LOGDA() << " relClipLeft: " << geometry.relClipLeft
+            << " clipWidth: " << geometry.clipWidth
+            << " frameLeft: " << geometry.frameLeft
+            << " frameWidth: " << geometry.frameWidth
+            << " draw width: " << drawWidth
+            << " draw left: " << drawLeft
+            << " t0: " << t0
+            << " t1: " << t1
+    ;
 }
 
 void DrawIndividualSamples(int channelIndex, QPainter& painter, const QRect& rect,
@@ -538,10 +421,8 @@ void DrawIndividualSamples(int channelIndex, QPainter& painter, const QRect& rec
                            int leftOffset,
                            float zoomMin, float zoomMax,
                            bool dB, float dBRange,
-                           bool showPoints, bool muted, bool highlight)
+                           bool showPoints, bool highlight)
 {
-    UNUSED(muted);
-
     const double toffset = clip.GetPlayStartTime();
     double rate = clip.GetRate();
     const double t0 = std::max(0.0, zoomInfo.PositionToTime(0, -leftOffset) - toffset);
@@ -657,167 +538,22 @@ void DrawIndividualSamples(int channelIndex, QPainter& painter, const QRect& rec
     }
 }
 
-void DrawWaveformBackground(QPainter& painter, const QRect& rect,
-                            const Style& style,
-                            const ZoomInfo& zoomInfo,
-                            const double* env, int leftOffset,
-                            float zoomMin, float zoomMax,
-                            double t0, double t1,
-                            bool dB, float dBRange,
-                            int zeroLevelYCoordinate,
-                            bool bIsSyncLockSelected,
-                            bool highlightEnvelope)
-{
-    // Visually (one vertical slice of the waveform background, on its side;
-    // the "*" is the actual waveform background we're drawing
-    //
-    //1.0                              0.0                             -1.0
-    // |--------------------------------|--------------------------------|
-    //      ***************                           ***************
-    //      |             |                           |             |
-    //    maxtop        maxbot                      mintop        minbot
-
-    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 xx, lx = 0;
-    int l, w;
-
-    painter.setPen(Qt::NoPen);
-    painter.setBrush(style.blankBrush);
-
-    painter.drawRect(rect);
-
-    // Bug 2389 - always draw at least one pixel of selection.
-    int selectedX = zoomInfo.TimeToPosition(t0, -leftOffset);
-
-    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[xx], zoomMin, zoomMax,
-                             h, dB, true, dBRange, true);
-        maxbot = GetWaveYPos(env[xx], zoomMin, zoomMax,
-                             h, dB, false, dBRange, true);
-
-        mintop = GetWaveYPos(-env[xx], zoomMin, zoomMax,
-                             h, dB, false, dBRange, true);
-        minbot = GetWaveYPos(-env[xx], zoomMin, zoomMax,
-                             h, dB, true, dBRange, true);
-
-        // Make sure it's odd so that a that max and min mirror each other
-        mintop +=1;
-        minbot +=1;
-
-        //TODO: uncomment and fix
-        //const auto drawEnvelope = artist->drawEnvelope;
-        const auto drawEnvelope = false;
-        if (!drawEnvelope || maxbot > mintop) {
-            maxbot = halfHeight;
-            mintop = halfHeight;
-        }
-
-        sel = (t0 <= time && nextTime < t1);
-        sel = sel || (xx == selectedX);
-        // We don't draw selection color for sync-lock selected tracks.
-        sel = sel && !bIsSyncLockSelected;
-
-        if (lmaxtop == maxtop
-            && lmintop == mintop
-            && lmaxbot == maxbot
-            && lminbot == minbot
-            && lsel == sel) {
-            continue;
-        }
-
-        painter.setBrush(style.blankBrush);
-
-        l = rect.x() + lx;
-        w = xx - lx;
-        if (lmaxbot < lmintop - 1) {
-            painter.drawRect(l, rect.y() + lmaxtop, w, lmaxbot - lmaxtop);
-            painter.drawRect(l, rect.y() + lmintop, w, lminbot - lmintop);
-        } else {
-            painter.drawRect(l, rect.y() + lmaxtop, w, lminbot - lmaxtop);
-        }
-
-        if (highlightEnvelope && lmaxbot < lmintop - 1) {
-            painter.setBrush(style.highlight);
-            painter.drawRect(l, rect.y() + lmaxbot, w, lmintop - lmaxbot);
-        }
-
-        lmaxtop = maxtop;
-        lmintop = mintop;
-        lmaxbot = maxbot;
-        lminbot = minbot;
-        lsel = sel;
-        lx = xx;
-    }
-
-    painter.setBrush(style.blankBrush);
-    l = rect.x() + lx;
-    w = xx - lx;
-    if (lmaxbot < lmintop - 1) {
-        painter.drawRect(l, rect.y() + lmaxtop, w, lmaxbot - lmaxtop);
-        painter.drawRect(l, rect.y() + lmintop, w, lminbot - lmintop);
-    } else {
-        painter.drawRect(l, rect.y() + lmaxtop, w, lminbot - lmaxtop);
-    }
-    if (highlightEnvelope && lmaxbot < lmintop - 1) {
-        painter.setBrush(style.highlight);
-        painter.drawRect(l, rect.y() + lmaxbot, w, lmintop - lmaxbot);
-    }
-
-    //TODO: uncomment and fix
-    // If sync-lock selected, draw in linked graphics.
-    /*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))));
-      TrackArt::DrawSyncLockTiles( context,
-         { rect.x + begin, rect.y, end - 1 - begin, rect.height } );
-   }*/
-
-    //OK, the display bounds are between min and max, which
-    //is spread across rect.height.  Draw the line at the proper place.
-    if (zeroLevelYCoordinate >= rect.top()
-        && zeroLevelYCoordinate <= rect.bottom()) {
-        painter.setPen(Qt::black);
-        painter.drawLine(rect.x(), zeroLevelYCoordinate,
-                         rect.x() + rect.width() - 1, zeroLevelYCoordinate);
-    }
-}
-
 void DrawMinMaxRMS(int channelIndex, QPainter& painter,
-                   const QRect& rect,
+                   const ClipParameters& params,
+                   double zoom,
                    const Style& style,
-                   const ZoomInfo& zoomInfo,
                    const WaveClip& clip,
-                   double t0, double t1, int leftOffset,
                    double zoomMin, double zoomMax,
-                   bool dB, double dbRange,
-                   bool muted)
+                   bool dB, double dbRange)
 {
-    UNUSED(muted);
-
-    const ZoomInfo localZoomInfo(0.0, zoomInfo.GetZoom());
-
     auto& waveformPainter = WaveformPainter::Get(clip);
 
-    const auto trimLeft = clip.GetTrimLeft();
-
     WavePaintParameters paintParameters;
 
     paintParameters
     .SetDisplayParameters(
         //TODO: uncomment and fix
-        rect.height(), zoomMin, zoomMax, false /*artist->mShowClipping*/)
+        params.drawGeometry.height, zoomMin, zoomMax, false /*artist->mShowClipping*/)
     .SetDBParameters(dbRange, dB)
     .SetBlankColor(ColorFromQColor(style.blankBrush))
     .SetSampleColors(
@@ -834,37 +570,35 @@ void DrawMinMaxRMS(int channelIndex, QPainter& painter,
         ColorFromQColor(style.clippedPen))
     .SetEnvelope(clip.GetEnvelope());
 
-    //TODO: uncomment and fix
-    //clipPainter.SetSelection(
-    //   zoomInfo, artist->pSelectedRegion->t0() - sequenceStartTime,
-    //   artist->pSelectedRegion->t1() - sequenceStartTime);
+    const auto trimLeft = clip.GetTrimLeft();
 
-    waveformPainter.Draw(channelIndex, painter, paintParameters, localZoomInfo, rect, leftOffset, t0 + trimLeft, t1 + trimLeft);
+    waveformPainter.Draw(channelIndex, painter, paintParameters, params.drawGeometry, zoom, params.t0 + trimLeft, params.t1 + trimLeft);
 }
 
-static bool ClipDetailsVisible(const ClipTimes& clip, const ZoomInfo& zoomInfo, const QRect& viewRect)
+static bool showIndividualSamples(const WaveClip& clip, bool zoom)
 {
-    //Do not fold clips to line at sample zoom level, as
-    //it may become impossible to 'unfold' it when clip is trimmed
-    //to a single sample
-    bool showSamples{ false };
-    auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, viewRect, &showSamples);
-    return showSamples || clipRect.width() >= kClipDetailedViewMinimumWidth;
+    const double sampleRate = clip.GetRate();
+    const double stretchRatio = clip.GetStretchRatio();
+
+    // Require at least 1/2 pixel per sample for drawing individual samples.
+    const double threshold1 = 0.5 * sampleRate / stretchRatio;
+
+    bool showIndividualSamples = zoom > threshold1;
+    return showIndividualSamples;
 }
 
-void DrawWaveform(int channelIndex,
-                  QPainter& painter,
-                  WaveTrack& track,
-                  const WaveClip& clip,
-                  const ZoomInfo& zoomInfo,
-                  const SelectedRegion& selectedRegion,
-                  const QRect& rect,
-                  const Style& style,
-                  bool dB, bool muted, bool selected)
+static void DrawWaveform(int channelIndex,
+                         QPainter& painter,
+                         WaveTrack& track,
+                         const WaveClip& clip,
+                         const WaveGeometry& geometry,
+                         double zoom,
+                         const Style& style,
+                         bool dB)
 {
     //If clip is "too small" draw a placeholder instead of
     //attempting to fit the contents into a few pixels
-    if (!ClipDetailsVisible(clip, zoomInfo, rect)) {
+    if (geometry.clipWidth < CLIPVIEW_WIDTH_MIN) {
         //TODO: uncomment and fix me
         /*
       auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, rect);
@@ -873,29 +607,11 @@ void DrawWaveform(int channelIndex,
         return;
     }
 
-    bool highlightEnvelope = false;
-#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
-    auto target = dynamic_cast<EnvelopeHandle*>(context.target.get());
-    highlightEnvelope = target && target->GetEnvelope() == clip.GetEnvelope();
-#endif
-
-    const ClipParameters params(clip, rect, zoomInfo);
-    const auto& 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) {
+    const ClipParameters params(geometry, zoom);
+    if (params.drawGeometry.width < 0.1) {
         return;
     }
 
-    const double t0 = params.t0;
-    const double sampleRate = clip.GetRate();
-    const double playStartTime = clip.GetPlayStartTime();
-    const double trackRectT0 = params.trackRectT0;
-    const double stretchRatio = clip.GetStretchRatio();
-    const double t1 = params.t1;
-    const int leftOffset = params.leftOffset;
-    const QRect mid = params.mid;
-
     auto& settings = WaveformSettings::Get(track);
     const float dBRange = settings.dBRange;
 
@@ -907,99 +623,28 @@ void DrawWaveform(int channelIndex,
     auto& cache = WaveformScale::Get(track);
     cache.GetDisplayBounds(zoomMin, zoomMax);
 
-    std::vector<double> vEnv(mid.width());
-    double* const env = &vEnv[0];
-    GetEnvelopeValues(
-        clip.GetEnvelope(),
-        playStartTime,
-
-        // PRL: change back to make envelope evaluate only at sample times
-        // and then interpolate the display
-        0,         // 1.0 / sampleRate,
-
-        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
-    {
-        double tt0, tt1;
-        if (SyncLock::IsSelectedOrSyncLockSelected(track)) {
-            tt0 = track.LongSamplesToTime(track.TimeToLongSamples(selectedRegion.t0())),
-            tt1 = track.LongSamplesToTime(track.TimeToLongSamples(selectedRegion.t1()));
-        } else {
-            tt0 = tt1 = 0.0;
-        }
-
-        DrawWaveformBackground(
-            painter, mid,
-            style,
-            zoomInfo,
-            env, leftOffset,
-            zoomMin, zoomMax,
-            tt0, tt1,
-            dB, dBRange,
-            cache.ZeroLevelYCoordinate({ mid.x(), mid.y(), mid.width(), mid.height() }),
-            !track.GetSelected(), highlightEnvelope);
-    }
-
-    //const double pps =
-    //   averagePixelsPerSample * rate;
-
-    // Require at least 1/2 pixel per sample for drawing individual samples.
-    const double threshold1 = 0.5 * sampleRate / stretchRatio;
-    // Require at least 3 pixels per sample for drawing the draggable points.
-    const double threshold2 = 3 * sampleRate / stretchRatio;
-
-    const bool showIndividualSamples = zoomInfo.GetZoom() > threshold1;
-    const bool showPoints = zoomInfo.GetZoom() > threshold2;
-
-    if (!showIndividualSamples) {
-        DrawMinMaxRMS(channelIndex,
-                      painter, rect,
+    if (!showIndividualSamples(clip, zoom)) {
+        DrawMinMaxRMS(channelIndex, painter,
+                      params, zoom,
                       style,
-                      zoomInfo,
                       clip,
-                      t0, t1, leftOffset,
                       zoomMin, zoomMax,
-                      dB, dBRange, muted);
+                      dB, dBRange);
     } else {
-        bool highlight = false;
-#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
-        auto target = dynamic_cast<SampleHandle*>(context.target.get());
-        highlight = target && target->GetTrack().get() == track;
-#endif
-        DrawIndividualSamples(
-            channelIndex,
-            painter, rect,
-            style,
-            zoomInfo,
-            clip, leftOffset,
-            zoomMin, zoomMax,
-            dB, dBRange,
-            showPoints, muted, highlight);
+        // Require at least 3 pixels per sample for drawing the draggable points.
+        // const double threshold2 = 3 * sampleRate / stretchRatio;
+        // const bool showPoints =zoom > threshold2;
+        // bool highlight = false;
+        // DrawIndividualSamples(
+        //     channelIndex,
+        //     painter, rect,
+        //     style,
+        //     zoomInfo,
+        //     clip, leftOffset,
+        //     zoomMin, zoomMax,
+        //     dB, dBRange,
+        //     showPoints, highlight);
     }
-
-    //TODO: uncomment and fix me
-    /*const auto drawEnvelope = artist->drawEnvelope;
-   if (drawEnvelope) {
-      DrawEnvelope(
-         context, mid, env, zoomMin, zoomMax, dB, dBRange, highlightEnvelope );
-      EnvelopeEditor::DrawPoints( *clip->GetEnvelope(),
-          context, mid, dB, dBRange, zoomMin, zoomMax, true, rect.x - mid.x );
-   }*/
-
-    // Draw arrows on the left side if the track extends to the left of the
-    // beginning of time.  :)
-    //TODO: uncomment and fix me
-    /*if (trackRectT0 == 0.0 && playStartTime < 0.0) {
-      TrackArt::DrawNegativeOffsetTrackArrows( context, rect );
-   }
-   {
-      auto clipRect = ClipParameters::GetClipRect(*clip, zoomInfo, rect);
-      TrackArt::DrawClipEdges(context.painter, clipRect, selected);
-   }*/
 }
 
 using namespace au::au3;
@@ -1012,6 +657,12 @@ AudacityProject& Au3WavePainter::projectRef() const
 
 void Au3WavePainter::paint(QPainter& painter, const processing::ClipKey& clipKey, const Params& params)
 {
+    //! NOTE Please don't remove, need for debug
+    // if (!(clipKey.trackId == 2 && clipKey.index == 0)) {
+    //     return;
+    // }
+    // LOGD() << "trackId: " << clipKey.trackId << ", clip: " << clipKey.index;
+
     WaveTrack* track = DomAccessor::findWaveTrack(projectRef(), TrackId(clipKey.trackId));
     IF_ASSERT_FAILED(track) {
         return;
@@ -1027,30 +678,26 @@ void Au3WavePainter::paint(QPainter& painter, const processing::ClipKey& clipKey
 
 void Au3WavePainter::doPaint(QPainter& painter, const WaveTrack* _track, const WaveClip* clip, const Params& params)
 {
-    // debug
-    // const auto& vr = params.viewRect;
-    // LOGDA() << "viewRect: w: " << vr.width() << ", h: " << vr.height() << ", x: " << vr.x() << ", y: " << vr.y();
-
     auto sw = FrameStatistics::CreateStopwatch(FrameStatistics::SectionID::WaveformView);
 
     WaveTrack* track = const_cast<WaveTrack*>(_track);
 
-    bool highlightEnvelope = false;
-#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
-    auto target = dynamic_cast<EnvelopeHandle*>(context.target.get());
-    highlightEnvelope = target && target->GetEnvelope() == clip->GetEnvelope();
-#endif
-
     const bool dB = !WaveformSettings::Get(*track).isLinear();
 
-    auto top = params.viewRect.top();
-    const auto channelHeight = (params.viewRect.height()) / static_cast<int>(clip->NChannels());
+    const auto channelHeight = (params.geometry.clipHeight) / static_cast<int>(clip->NChannels());
+
+    const Geometry& g = params.geometry;
+
+    WaveGeometry cg;
+    cg.waveHeight = channelHeight;
+    cg.clipWidth = g.clipWidth;
+    cg.relClipLeft = g.relClipLeft;
+    cg.frameLeft = g.frameLeft;
+    cg.frameWidth = g.frameWidth;
 
-    ZoomInfo zoomInfo{ clip->GetPlayStartTime(), params.zoom };
-    SelectedRegion selectedRegion{};
+    cg.waveTop = 0.0;
     for (unsigned i = 0; i < clip->NChannels(); ++i) {
-        QRect rect = QRect(params.viewRect.left(), top, params.viewRect.width(), channelHeight);
-        DrawWaveform(i, painter, *track, *clip, zoomInfo, selectedRegion, rect, params.style, dB, track->GetMute(), false);
-        top += channelHeight;
+        DrawWaveform(i, painter, *track, *clip, cg, params.zoom, params.style, dB);
+        cg.waveTop += channelHeight;
     }
 }
diff --git a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/ClipItem.qml b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/ClipItem.qml
index eaed6cad032da6e4679807cdeeeee3e4c0a2ffeb..94bd4f8db4e456bb0ce8f49ca7d2708b1d151951 100644
--- a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/ClipItem.qml
+++ b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/ClipItem.qml
@@ -55,5 +55,7 @@ Rectangle {
         anchors.right: parent.right
         anchors.bottom: parent.bottom
         anchors.margins: 1
+
+        clipLeft: root.x
     }
 }
diff --git a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml
index a041976f24fd596ee85f8fb78c1b48c78014614c..b7d0d0561d7d9704126b82b1f25fde77862bbce6 100644
--- a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml
+++ b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml
@@ -13,6 +13,18 @@ Rectangle {
     height: 76
     color: ui.theme.backgroundPrimaryColor
 
+    //! NOTE This element must be the same width as the track wave visible area.
+    //! If this is different, than appropriate changes must be made.
+    onWidthChanged: {
+        timelineContext.onResizeFrameWidth(root.width)
+    }
+
+    Component.onCompleted: {
+        timelineContext.init(root.width)
+    }
+
+    //! ~~~ TimelineContext ~~~
+    //! NOTE See comment in TimelineContext (.h)
     function onWheel(y) {
         timelineContext.onWheel(y)
     }
@@ -28,11 +40,15 @@ Rectangle {
     TimelineContext {
         id: timelineContext
     }
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     StyledTextLabel {
         anchors.fill: parent
         anchors.leftMargin: 16
-        text: "zoom: " + timelineContext.zoom + ", offset: " + timelineContext.offset
+        text: "zoom: " + timelineContext.zoom
+              + ", frame start time: " + timelineContext.frameStartTime
+              + ", end time: " + timelineContext.frameEndTime
+              + ", root.width: " + root.width
     }
 
     SeparatorLine { anchors.bottom: parent.bottom }
diff --git a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/TracksClipsView.qml b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/TracksClipsView.qml
index 5fc57a83fac2522551e30269bac4357210dec479..9928bc4fc791137bf23373bd36190c07e9a8d2cf 100644
--- a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/TracksClipsView.qml
+++ b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/TracksClipsView.qml
@@ -4,12 +4,14 @@ import Muse.UiComponents
 
 import Audacity.ProjectScene
 
-Item {
+Rectangle {
 
     id: root
 
     clip: true
 
+    color: ui.theme.backgroundPrimaryColor
+
     TracksListClipsModel {
         id: tracksModel
     }
diff --git a/au4/src/projectscene/view/clipsview/clipslistmodel.cpp b/au4/src/projectscene/view/clipsview/clipslistmodel.cpp
index 8b2d3512c1fe6a7071e490b9cc80ca42262342e2..4856435d2001f78934ca98612782645d0f78ca6a 100644
--- a/au4/src/projectscene/view/clipsview/clipslistmodel.cpp
+++ b/au4/src/projectscene/view/clipsview/clipslistmodel.cpp
@@ -25,7 +25,7 @@ void ClipsListModel::load()
 
     if (m_context) {
         connect(m_context, &TimelineContext::zoomChanged, this, &ClipsListModel::onTimelineContextValuesChanged);
-        connect(m_context, &TimelineContext::offsetChanged, this, &ClipsListModel::onTimelineContextValuesChanged);
+        connect(m_context, &TimelineContext::frameTimeChanged, this, &ClipsListModel::onTimelineContextValuesChanged);
     }
 
     beginResetModel();
@@ -91,7 +91,7 @@ QVariant ClipsListModel::data(const QModelIndex& index, int role) const
 
 bool ClipsListModel::setData(const QModelIndex& index, const QVariant& value, int role)
 {
-    LOGD() << "" << index.row() << ", value: " << value << ", role: " << role;
+    //LOGD() << "" << index.row() << ", value: " << value << ", role: " << role;
     switch (role) {
     case ClipLeftRole: {
         return changeClipStartTime(index, value);
@@ -104,6 +104,10 @@ bool ClipsListModel::setData(const QModelIndex& index, const QVariant& value, in
 
 void ClipsListModel::onTimelineContextValuesChanged()
 {
+    // LOGDA() << "zoom: " << m_context->zoom()
+    //         << " frameStartTime: " << m_context->frameStartTime()
+    //         << " frameEndTime: " << m_context->frameEndTime();
+
     for (size_t i = 0; i < m_clipList.size(); ++i) {
         QModelIndex idx = this->index(int(i));
         emit dataChanged(idx, idx, { ClipWidthRole, ClipLeftRole });
diff --git a/au4/src/projectscene/view/clipsview/timelinecontext.cpp b/au4/src/projectscene/view/clipsview/timelinecontext.cpp
index 28e6e0bb9aa1f99c00a9ba7bafdf584726da6b78..c3e4676f1551190639ddb58d92eaa9d4c9980c23 100644
--- a/au4/src/projectscene/view/clipsview/timelinecontext.cpp
+++ b/au4/src/projectscene/view/clipsview/timelinecontext.cpp
@@ -8,40 +8,64 @@
 
 static constexpr double ZOOM_MIN = 0.1;
 
-TimelineContext::TimelineContext(QQuickItem* parent)
-    : QQuickItem(parent)
+TimelineContext::TimelineContext(QObject* parent)
+    : QObject(parent)
 {
 }
 
+void TimelineContext::init(double frameWidth)
+{
+    m_zoom = 2.0;//{ 44100.0 / 512.0 };
+    emit zoomChanged();
+
+    m_frameStartTime = 0.0;
+    emit frameStartTimeChanged();
+    m_frameEndTime = positionToTime(frameWidth);
+    emit frameEndTimeChanged();
+    emit frameTimeChanged();
+}
+
 void TimelineContext::onWheel(double y)
 {
     Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
     if (modifiers.testFlag(Qt::ControlModifier)) {
         changeZoom(y < 0 ? -1 : 1);
     } else if (modifiers.testFlag(Qt::ShiftModifier)) {
-        changeOffset(y < 0 ? -1 : 1);
+        shiftFrameTime(y < 0 ? -1 : 1);
     }
 }
 
 void TimelineContext::changeZoom(int direction)
 {
-    double step = std::max(zoom() / 2.0, ZOOM_MIN);
+    double step = (muse::is_equal(m_zoom, 1.0) && direction < 0) ? 0.1 : 1.0;
 
-    double _zoom = zoom() + (step * direction);
-    _zoom = std::floor(_zoom * 10.0) / 10.0;
-    if (_zoom > 4) {
-        _zoom = std::floor(_zoom);
-    }
+    double zoom = m_zoom + (step * direction);
+    zoom = std::max(zoom, ZOOM_MIN);
 
-    _zoom = std::max(_zoom, ZOOM_MIN);
+    setZoom(zoom);
+}
 
-    setZoom(_zoom);
+void TimelineContext::onResizeFrameWidth(double frameWidth)
+{
+    m_frameWidth = frameWidth;
+    updateFrameTime();
 }
 
-void TimelineContext::changeOffset(int direction)
+void TimelineContext::shiftFrameTime(int direction)
 {
-    double step = 10;
-    setOffset(offset() + (step * direction));
+    double step = 10.0;
+    double shift = step * direction;
+
+    setFrameStartTime(m_frameStartTime + shift);
+    setFrameEndTime(m_frameEndTime + shift);
+
+    emit frameTimeChanged();
+}
+
+void TimelineContext::updateFrameTime()
+{
+    setFrameEndTime(positionToTime(m_frameWidth));
+    emit frameTimeChanged();
 }
 
 void TimelineContext::onSelection(double x1, double x2)
@@ -65,35 +89,16 @@ void TimelineContext::onSelectionTime(double startTime, double endTime)
     setSelectionActive(!muse::is_zero(startTime) && !muse::is_zero(endTime));
 }
 
-qint64 TimelineContext::timeToPosition(double time) const
+double TimelineContext::timeToPosition(double time) const
 {
-    double t = 0.5 + m_zoom * (time - m_offset);
-    if (t < INT64_MIN) {
-        return INT64_MIN;
-    }
-    if (t > INT64_MAX) {
-        return INT64_MAX;
-    }
-    t = floor(t);
-    return static_cast<qint64>(t);
+    double p = 0.5 + m_zoom * (time - m_frameStartTime);
+    p = std::floor(p);
+    return p;
 }
 
-double TimelineContext::positionToTime(qint64 position) const
+double TimelineContext::positionToTime(double position) const
 {
-    return m_offset + position / m_zoom;
-}
-
-double TimelineContext::offset() const
-{
-    return m_offset;
-}
-
-void TimelineContext::setOffset(double newOffset)
-{
-    if (m_offset != newOffset) {
-        m_offset = newOffset;
-        emit offsetChanged();
-    }
+    return m_frameStartTime + position / m_zoom;
 }
 
 double TimelineContext::zoom() const
@@ -106,6 +111,7 @@ void TimelineContext::setZoom(double zoom)
     if (m_zoom != zoom) {
         m_zoom = zoom;
         emit zoomChanged();
+        updateFrameTime();
     }
 }
 
@@ -148,3 +154,31 @@ void TimelineContext::setSelectionActive(bool newSelectionActive)
     m_selectionActive = newSelectionActive;
     emit selectionActiveChanged();
 }
+
+double TimelineContext::frameStartTime() const
+{
+    return m_frameStartTime;
+}
+
+void TimelineContext::setFrameStartTime(double newFrameStartTime)
+{
+    if (qFuzzyCompare(m_frameStartTime, newFrameStartTime)) {
+        return;
+    }
+    m_frameStartTime = newFrameStartTime;
+    emit frameStartTimeChanged();
+}
+
+double TimelineContext::frameEndTime() const
+{
+    return m_frameEndTime;
+}
+
+void TimelineContext::setFrameEndTime(double newFrameEndTime)
+{
+    if (qFuzzyCompare(m_frameEndTime, newFrameEndTime)) {
+        return;
+    }
+    m_frameEndTime = newFrameEndTime;
+    emit frameEndTimeChanged();
+}
diff --git a/au4/src/projectscene/view/clipsview/timelinecontext.h b/au4/src/projectscene/view/clipsview/timelinecontext.h
index 0cbc407c021f71d016e84a322724bb87eddf1086..9524fab9aca70684bf8575230e6f9114ab718cae 100644
--- a/au4/src/projectscene/view/clipsview/timelinecontext.h
+++ b/au4/src/projectscene/view/clipsview/timelinecontext.h
@@ -1,12 +1,23 @@
 #pragma once
 
-#include <QQuickItem>
+#include <QObject>
 
-class TimelineContext : public QQuickItem
+//! NOTE This class does two things:
+//! 1. This is a context that is passed to other classes
+//! 2. This is a controller that interprets mouse and view resize events into context values
+//!
+//! If this class becomes more complex,
+//! or we notice that its "controller" methods are being called in unexpected places,
+//! then we should split it into two separate classes.
+
+class TimelineContext : public QObject
 {
     Q_OBJECT
 
-    Q_PROPERTY(double offset READ offset WRITE setOffset NOTIFY offsetChanged FINAL)
+    //  0 sec     visible frame          end
+    //          | ~~~~~ ~~~~ ~~~|
+    Q_PROPERTY(double frameStartTime READ frameStartTime NOTIFY frameStartTimeChanged FINAL)
+    Q_PROPERTY(double frameEndTime READ frameEndTime NOTIFY frameEndTimeChanged FINAL)
     Q_PROPERTY(double zoom READ zoom WRITE setZoom NOTIFY zoomChanged FINAL)
 
     Q_PROPERTY(double selectionStartTime READ selectionStartTime NOTIFY selectionStartTimeChanged FINAL)
@@ -15,17 +26,11 @@ class TimelineContext : public QQuickItem
 
 public:
 
-    TimelineContext(QQuickItem* parent = nullptr);
-
-    Q_INVOKABLE void onWheel(double y);
-    Q_INVOKABLE void onSelection(double x1, double x2);
-    Q_INVOKABLE void resetSelection();
+    TimelineContext(QObject* parent = nullptr);
 
-    Q_INVOKABLE qint64 timeToPosition(double time) const;
-    Q_INVOKABLE double positionToTime(qint64 position) const;
+    double frameStartTime() const;
+    double frameEndTime() const;
 
-    double offset() const;
-    void setOffset(double newOffset);
     double zoom() const;
     void setZoom(double zoom);
 
@@ -35,9 +40,23 @@ public:
     void setSelectionEndTime(double time);
     bool selectionActive() const;
 
+    Q_INVOKABLE void init(double frameWidth);
+
+    Q_INVOKABLE void onResizeFrameWidth(double frameWidth);
+    Q_INVOKABLE void onWheel(double y);
+
+    Q_INVOKABLE void onSelection(double x1, double x2);
+    Q_INVOKABLE void resetSelection();
+
+    Q_INVOKABLE double timeToPosition(double time) const;
+    Q_INVOKABLE double positionToTime(double position) const;
+
 signals:
 
-    void offsetChanged();
+    void frameStartTimeChanged();
+    void frameEndTimeChanged();
+    void frameTimeChanged(); // any or both together
+
     void zoomChanged();
 
     void selectionStartTimeChanged();
@@ -46,15 +65,22 @@ signals:
 
 private:
 
+    void shiftFrameTime(int direction);
+    void setFrameStartTime(double newFrameStartTime);
+    void setFrameEndTime(double newFrameEndTime);
+    void updateFrameTime();
+
     void changeZoom(int direction);
-    void changeOffset(int direction);
 
     void onSelectionTime(double t1, double t2);
     void updateSelectionActive();
     void setSelectionActive(bool newSelectionActive);
 
-    double m_offset = 0.0;
-    double m_zoom = 2.0;//{ 44100.0 / 512.0 };
+    double m_frameWidth = 0.0;
+    double m_frameStartTime = 0.0;
+    double m_frameEndTime = 0.0;
+
+    double m_zoom = 1.0; // see init
 
     double m_selecitonStartTime = 0.0;
     double m_selectionEndTime = 0.0;
diff --git a/au4/src/projectscene/view/clipsview/waveview.cpp b/au4/src/projectscene/view/clipsview/waveview.cpp
index 899b1f19c006aada4adee7f4f63cded9bfb9cb9b..19e6130920d3e8221e070cb839003aca4590cade 100644
--- a/au4/src/projectscene/view/clipsview/waveview.cpp
+++ b/au4/src/projectscene/view/clipsview/waveview.cpp
@@ -29,7 +29,12 @@ void WaveView::setClipKey(const ClipKey& newClipKey)
 void WaveView::paint(QPainter* painter)
 {
     au3::IAu3WavePainter::Params params;
-    params.viewRect = QRect(0, 0, width(), height());
+    params.geometry.clipHeight = height();
+    params.geometry.clipWidth = width();
+    params.geometry.relClipLeft = m_clipLeft;
+    params.geometry.frameLeft = m_context->frameStartTime() * m_context->zoom();
+    params.geometry.frameWidth = (m_context->frameEndTime() - m_context->frameStartTime()) * m_context->zoom();
+
     params.zoom = m_context->zoom();
 
     const WaveStyle& style = configuration()->waveStyle();
@@ -60,7 +65,36 @@ void WaveView::setTimelineContext(TimelineContext* newContext)
         return;
     }
 
-    //! TODO Subscribe on context props changes
+    if (m_context) {
+        disconnect(m_context, nullptr, this, nullptr);
+    }
+
     m_context = newContext;
+
+    if (m_context) {
+        connect(m_context, &TimelineContext::frameTimeChanged, this, &WaveView::onFrameTimeChanged);
+    }
+
     emit timelineContextChanged();
 }
+
+void WaveView::onFrameTimeChanged()
+{
+    update();
+}
+
+double WaveView::clipLeft() const
+{
+    return m_clipLeft;
+}
+
+void WaveView::setClipLeft(double newClipLeft)
+{
+    if (qFuzzyCompare(m_clipLeft, newClipLeft)) {
+        return;
+    }
+    m_clipLeft = newClipLeft;
+    emit clipLeftChanged();
+
+    update();
+}
diff --git a/au4/src/projectscene/view/clipsview/waveview.h b/au4/src/projectscene/view/clipsview/waveview.h
index 149b9bcf8651a0cb416b63556b39696f9db567a5..f65913a1404fb294057b17d6b1fc4de30b824c8a 100644
--- a/au4/src/projectscene/view/clipsview/waveview.h
+++ b/au4/src/projectscene/view/clipsview/waveview.h
@@ -17,6 +17,11 @@ class WaveView : public QQuickPaintedItem
     Q_PROPERTY(TimelineContext * context READ timelineContext WRITE setTimelineContext NOTIFY timelineContextChanged FINAL)
     Q_PROPERTY(ClipKey clipKey READ clipKey WRITE setClipKey NOTIFY clipKeyChanged FINAL)
 
+    //! NOTE In a static position, the slip start time corresponds,
+    //! but it can change while dragging the clip, with the not changing start time
+    //! (until the end of dragging)
+    Q_PROPERTY(double clipLeft READ clipLeft WRITE setClipLeft NOTIFY clipLeftChanged FINAL)
+
     muse::Inject<au3::IAu3WavePainter> wavePainter;
     muse::Inject<IProjectSceneConfiguration> configuration;
 
@@ -28,17 +33,22 @@ public:
     void setClipKey(const ClipKey& newClipKey);
     TimelineContext* timelineContext() const;
     void setTimelineContext(TimelineContext* newContext);
+    double clipLeft() const;
+    void setClipLeft(double newClipLeft);
 
     void paint(QPainter* painter) override;
 
 signals:
     void clipKeyChanged();
     void timelineContextChanged();
+    void clipLeftChanged();
 
 private:
-    void UpdateItemsCache(TimelineContext& trackPanelView);
+
+    void onFrameTimeChanged();
 
     ClipKey m_clipKey;
     TimelineContext* m_context = nullptr;
+    double m_clipLeft = 0;
 };
 }