diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingAxis.cc b/src/UI/DocumentViews/AudioVisualizer/TimingAxis.cc
index 248dd011eddb570a8e93cbe6a4c04fea4d093c65..668c618299670cb94bc097c918381ffec4e97598 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingAxis.cc
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingAxis.cc
@@ -59,6 +59,7 @@ TimingAxis::refreshTicks()
         minorTicks = 0;
         majorTicks = availableTicks[minorTicksIndex];
     }
+    TimingUtils::setTicks(minorTicks, majorTicks);
 }
 
 void
@@ -83,7 +84,7 @@ TimingAxis::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWi
      */
     for (int i = 0; i < audioLength; i += majorTicks) {
         int pos = TimingUtils::posFromMs(i, width, audioLength);
-        painter->drawText(QPoint(pos, yText), msToString(i));
+        painter->drawText(QPoint(pos, yText), TimingUtils::printMajorTicks(i));
         painter->drawLine(pos, 0, pos, -majorTicksHeight);
     }
     if (minorTicks > 0) {
@@ -94,16 +95,3 @@ TimingAxis::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWi
         }
     }
 }
-
-QString
-TimingAxis::msToString(int i) const noexcept
-{
-    QString ret(
-        QStringLiteral("%1").arg(i % 60000 / 1000, i >= 60000 ? 2 : 1, 10, QLatin1Char('0')) +
-        QString(".") + QString::number(i % 1000 / majorTicks));
-    if (i >= 60000)
-        ret.prepend(QString::number(i / 60000) + QString(":"));
-    if (i >= 360000)
-        ret.prepend(QString::number(i / 360000) + QString(":"));
-    return ret;
-}
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingAxis.hh b/src/UI/DocumentViews/AudioVisualizer/TimingAxis.hh
index 1f7196e48a3a4d248bab70bc90fe568957355bb7..243468ac98537f0777c7db5c5c3e83e4dcdb17d7 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingAxis.hh
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingAxis.hh
@@ -50,8 +50,6 @@ private:
     int timeDigitsHeight{ 20 };
     int timeDigitsMargin{ 3 };
 
-    QString msToString(int i) const noexcept;
-
 protected:
 public slots:
     void refreshTicks();
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingCursor.cc b/src/UI/DocumentViews/AudioVisualizer/TimingCursor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..349a1f1e852cb8d2754ac14730763a1994e8a5b9
--- /dev/null
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingCursor.cc
@@ -0,0 +1,41 @@
+#include "TimingCursor.hh"
+
+#include <QPainter>
+#include <QGraphicsScene>
+
+#include "TimingUtils.hh"
+
+using namespace Vivy;
+
+TimingCursor::TimingCursor(int height_, QGraphicsItem *parent)
+    : QGraphicsItem(parent)
+    , height(height_)
+{
+    textRect = QRectF(0, 10, maxWidth, height - 20); // TODO : remove 10/20 magic numbers
+}
+
+QRectF
+TimingCursor::boundingRect() const
+{
+    return QRectF(-maxWidth, 0, maxWidth * 2, height);
+}
+
+void
+TimingCursor::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+    painter->drawLine(0, 0, 0, height);
+
+    QRectF textRectangle = textRect;
+    QPointF sceneLeft    = mapFromScene(QPointF(0, 0));
+    QPointF sceneRight   = mapFromScene(QPointF(scene()->sceneRect().right(), 0));
+    TimingUtils::adjustFlip(&textRectangle, sceneLeft.rx(), sceneRight.rx());
+
+    //painter->drawRect(textRectangle);
+    painter->drawText(textRectangle, Qt::AlignHCenter | Qt::AlignTop, cursorTime);
+}
+
+void
+TimingCursor::setTime(QString str) noexcept
+{
+    cursorTime = str;
+}
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingCursor.hh b/src/UI/DocumentViews/AudioVisualizer/TimingCursor.hh
new file mode 100644
index 0000000000000000000000000000000000000000..f9026bed4bbb4fb89c8c1eca5dc26f96dc1464df
--- /dev/null
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingCursor.hh
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <QGraphicsItem>
+
+namespace Vivy
+{
+class TimingCursor final : public QGraphicsItem {
+public:
+    explicit TimingCursor(int, QGraphicsItem *parent = nullptr);
+
+    QRectF boundingRect() const override;
+    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+
+private:
+    int height;
+    QString cursorTime;
+    // FIXME
+    // Forced to define a "max-width" for the text,
+    // as it doesn't seem possible to get the width of the text.
+    // We should try drawing it as a QGraphicsTextItem and see if
+    // boundingRect()->width() is appropriate
+    qreal maxWidth{ 50 }; // TODO: proper variable
+    QRectF textRect;
+
+public:
+    void setTime(QString str) noexcept;
+};
+
+}
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingScene.cc b/src/UI/DocumentViews/AudioVisualizer/TimingScene.cc
index a99fd4b02fef2cd6938828e5147966249b98f488..383c4aab6fec7776ee9b7b2ae828296fb94221c5 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingScene.cc
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingScene.cc
@@ -11,6 +11,8 @@
 #include <QScrollArea>
 #include <QScrollBar>
 #include <QVBoxLayout>
+#include <QGraphicsItem>
+#include <QGraphicsScene>
 
 #include "../../../Lib/Utils.hh"
 #include "../../../Lib/Ass/Ass.hh"
@@ -50,7 +52,7 @@ TimingScene::rebuildScene()
     }
 
     // We put a common origin for the items at the bottom of the axis
-    int timingAxisHeight = 30;
+    int timingAxisHeight = 30; // TODO: remove magic number
 
     QPixmap pixmap(QPixmap::fromImage(img));
     backgroundImg = addPixmap(pixmap);
@@ -63,6 +65,10 @@ TimingScene::rebuildScene()
     // Freeze the scene boundaries
     setSceneRect(sceneRect());
 
+    cursor = new TimingCursor(pixmap.height());
+    addItem(cursor);
+    cursor->setPos(0, timingAxisHeight);
+
     if (auto assDocument = currentVivyDocument->getAssSubDocument()) {
         QVector<Ass::LinePtr> lines = assDocument->getLines();
         for (int i = 0; i < lines.size(); ++i) {
@@ -90,6 +96,16 @@ TimingScene::mousePressEvent(QGraphicsSceneMouseEvent *event) noexcept
     QGraphicsScene::mousePressEvent(event);
 }
 
+void
+TimingScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) noexcept
+{
+    int x = int(mouseEvent->scenePos().rx());
+    cursor->setPos(x, 30);
+    cursor->setTime(TimingUtils::printCursor(
+        TimingUtils::msFromPos(x, int(audioStream->getLength()), width())));
+    QGraphicsScene::mouseMoveEvent(mouseEvent);
+}
+
 void
 TimingScene::handleMousePressEventLine(QGraphicsSceneMouseEvent *event, Ass::LinePtr p) noexcept
 {
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingScene.hh b/src/UI/DocumentViews/AudioVisualizer/TimingScene.hh
index 87779e0cf6f3ab9cf8f500284451f5d8fc649411..832923b11650f383b0ab23eea9acef7e22324a65 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingScene.hh
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingScene.hh
@@ -5,6 +5,7 @@
 #include "../../../Lib/Ass/Ass.hh"
 #include "TimingBar.hh"
 #include "TimingAxis.hh"
+#include "TimingCursor.hh"
 
 namespace Vivy
 {
@@ -25,6 +26,7 @@ public:
 private:
     QGraphicsPixmapItem *backgroundImg{ nullptr };
     QImage img;
+    TimingCursor *cursor;
     AudioContext::StreamPtr audioStream;
     Ass::LineWeakPtr currentLine{};
     TimingMode timingMode{ TimingMode::Line };
@@ -33,6 +35,7 @@ private:
 public:
     QGraphicsPixmapItem *bg() noexcept;
     void mousePressEvent(QGraphicsSceneMouseEvent *event) noexcept override;
+    void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) noexcept override;
 
     TimingAxis *getAxis();
 
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingUtils.cc b/src/UI/DocumentViews/AudioVisualizer/TimingUtils.cc
index d9a398393d60b6b20c107df67cde980e7b5f1500..2e847ba2f1378aad0612fe4b411b60b523ef463e 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingUtils.cc
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingUtils.cc
@@ -1,3 +1,56 @@
 #include "TimingUtils.hh"
 
 using namespace Vivy;
+
+int TimingUtils::majorTicks{ 0 };
+int TimingUtils::minorTicks{ 0 };
+
+void
+TimingUtils::adjustFlip(QRectF *rect, qreal left, qreal right) noexcept
+{
+    if (Q_UNLIKELY(right - rect->right() < 0)) {
+        qreal w = rect->width();
+        rect->adjust(-w, 0, -w, 0);
+    }
+}
+
+void
+TimingUtils::adjustTranslate(QRectF *rect, qreal left, qreal right) noexcept
+{
+    qreal d;
+    qreal hw = rect->width() / 2;
+    if (Q_UNLIKELY((d = hw + left) > 0)) {
+        rect->adjust(d, 0, d, 0);
+    }
+    if (Q_UNLIKELY((d = right - hw) < 0)) {
+        rect->adjust(d, 0, d, 0);
+    }
+}
+
+QString
+TimingUtils::printMajorTicks(int t) noexcept
+{
+    // TODO : adapt to different ticks (not everytime with "." first)
+    QString ret(
+        QStringLiteral("%1").arg(t % 60000 / 1000, t >= 60000 ? 2 : 1, 10, QLatin1Char('0')) +
+        QString(".") + QString::number(t % 1000 / majorTicks));
+    if (t >= 60000)
+        ret.prepend(QString::number(t / 60000) + QString(":"));
+    if (t >= 360000)
+        ret.prepend(QString::number(t / 360000) + QString(":"));
+    return ret;
+}
+
+QString
+TimingUtils::printCursor(int t) noexcept
+{
+    // TODO : adapt to different ticks (not everytime with "." first)
+    QString ret(
+        QStringLiteral("%1").arg(t % 60000 / 1000, t >= 60000 ? 2 : 1, 10, QLatin1Char('0')) +
+        QString(".") + QString::number(t % 1000 / minorTicks));
+    if (t >= 60000)
+        ret.prepend(QString::number(t / 60000) + QString(":"));
+    if (t >= 360000)
+        ret.prepend(QString::number(t / 360000) + QString(":"));
+    return ret;
+}
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingUtils.hh b/src/UI/DocumentViews/AudioVisualizer/TimingUtils.hh
index d286004d49db7e983c59b4b8c8c41e5f550bdb2f..2f709ac59a42138a7e9cf6f6d186390f2bd0d9e6 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingUtils.hh
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingUtils.hh
@@ -4,9 +4,16 @@
 #error "This is a C++ header"
 #endif
 
+#include <QRectF>
+#include <QString>
+
 namespace Vivy
 {
 class TimingUtils {
+private:
+    static int majorTicks;
+    static int minorTicks;
+
 public:
     static inline int posFromMs(int t, int audioWidth, int audioLength) noexcept
     {
@@ -17,5 +24,16 @@ public:
     {
         return audioWidth == 0 ? 0 : int(qint64(x) * qint64(audioLength) / audioWidth);
     }
+
+    static inline void setTicks(int t, int T) noexcept
+    {
+        minorTicks = t;
+        majorTicks = T;
+    };
+
+    static void adjustTranslate(QRectF *, qreal, qreal) noexcept;
+    static void adjustFlip(QRectF *, qreal, qreal) noexcept;
+    static QString printMajorTicks(int) noexcept;
+    static QString printCursor(int) noexcept;
 };
 }
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingView.cc b/src/UI/DocumentViews/AudioVisualizer/TimingView.cc
index 8c31a37524e9ae029a1caa40ac9d38bc5cbfb82c..49fc449af5ccf223cb11323eeec65d15c6338ec4 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingView.cc
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingView.cc
@@ -7,6 +7,7 @@ TimingView::TimingView(QGraphicsScene *scene, QImage img, AudioContext::StreamPt
     : QGraphicsView(scene, parent)
     , audioStream(stream)
 {
+    setMouseTracking(true);
     int interestingViewHeight = int(scene->height() + horizontalScrollBar()->height());
     setFixedHeight(interestingViewHeight);
     setMaximumHeight(interestingViewHeight);