diff --git a/src/AudioVisualizer.cc b/src/AudioVisualizer.cc
index 8ab587011ca885103f6b8e31b8b778b726fc950c..00c50febae5057b5a24b7251f593ba1f48d4735a 100644
--- a/src/AudioVisualizer.cc
+++ b/src/AudioVisualizer.cc
@@ -1,4 +1,5 @@
 #include "AudioVisualizer.h"
+#include "AudioUtils.h"
 
 #include <QLabel>
 #include <QScrollArea>
@@ -21,3 +22,40 @@ AudioVisualizer::printSpectrum(QImage pixmap) noexcept
     layout->addWidget(timer);
     setLayout(layout);
 }
+
+AudioVisualizer *
+AudioVisualizer::fromFile(const QString &filename)
+{
+    if (filename.isEmpty())
+        return nullptr;
+
+    const int sample_rate = 44100;
+    double *data          = nullptr;
+    int size              = 0;
+    int rc                = decodeAudioFile(filename.toStdString().c_str(), sample_rate, &data, &size);
+    auto data_deleter     = [](double *ptr) -> void {
+        if (ptr != nullptr)
+            av_free(ptr);
+    };
+    std::unique_ptr<double, decltype(data_deleter)> data_holder(data, data_deleter);
+
+    if (rc != 0) {
+        printf("ERROR\n");
+        return nullptr;
+    }
+
+    auto raw_image_deleter  = [](RawImageData *ptr) -> void { RawImageDataFree(ptr); };
+    RawImageData *raw_image = RawImageDataNewFromData(data, size);
+    std::unique_ptr<RawImageData, decltype(raw_image_deleter)> raw_image_holder(raw_image, raw_image_deleter);
+
+    auto pixs_deleter = [](void *ptr) -> void { free(ptr); };
+    const int width   = RawImageDataGetWidth(raw_image);
+    const int height  = RawImageDataGetHeight(raw_image);
+    uchar *pixs       = RawImageDataDuplicatePixels(raw_image);
+    QImage img(pixs, width, height / 2, width, QImage::Format_Grayscale8, pixs_deleter, pixs);
+    img = img.mirrored(false, true);
+
+    AudioVisualizer *audioVisualizer = new AudioVisualizer;
+    audioVisualizer->printSpectrum(img);
+    return audioVisualizer;
+}
diff --git a/src/AudioVisualizer.h b/src/AudioVisualizer.h
index 8a6f8b84718acfddfb6e59349322559f4314364d..2e47eb5c72137510773c2368cb27a63d2ee4f40d 100644
--- a/src/AudioVisualizer.h
+++ b/src/AudioVisualizer.h
@@ -7,6 +7,7 @@
 
 #include "TimingView.hpp"
 #include <QWidget>
+#include <QString>
 
 class AudioVisualizer final : public QWidget {
     Q_OBJECT
@@ -15,6 +16,8 @@ public:
     AudioVisualizer(QWidget *parent = nullptr) noexcept;
     ~AudioVisualizer() noexcept = default;
 
+    static AudioVisualizer *fromFile(const QString &);
+
 public slots:
     void printSpectrum(QImage) noexcept;
 };
diff --git a/src/MainWindow.cc b/src/MainWindow.cc
index 7ad9d4b5abbbcd4d647b38dc23e8e20b3eefba34..be251dae4779283ddb289ae03629be08d5a33851 100644
--- a/src/MainWindow.cc
+++ b/src/MainWindow.cc
@@ -1,7 +1,6 @@
 #define VIVY_MAINWINDOW_CC
 
 #include "MainWindow.h"
-#include "AudioUtils.h"
 #include "DialogHelp.h"
 #include "VivyDocumentView.h"
 
@@ -46,38 +45,10 @@ void
 MainWindow::openAudioFile() noexcept
 {
     QString filename = QFileDialog::getOpenFileName(this, "Select a file");
-    if (filename.isEmpty())
+    AudioVisualizer *audioVisualizer = AudioVisualizer::fromFile(filename);
+    if (audioVisualizer == nullptr)
         return;
 
-    const int sample_rate = 44100;
-    double *data          = nullptr;
-    int size              = 0;
-    int rc                = decodeAudioFile(filename.toStdString().c_str(), sample_rate, &data, &size);
-    auto data_deleter     = [](double *ptr) -> void {
-        if (ptr != nullptr)
-            av_free(ptr);
-    };
-    std::unique_ptr<double, decltype(data_deleter)> data_holder(data, data_deleter);
-
-    if (rc != 0) {
-        printf("ERROR\n");
-        return;
-    }
-
-    auto raw_image_deleter  = [](RawImageData *ptr) -> void { RawImageDataFree(ptr); };
-    RawImageData *raw_image = RawImageDataNewFromData(data, size);
-    std::unique_ptr<RawImageData, decltype(raw_image_deleter)> raw_image_holder(raw_image, raw_image_deleter);
-
-    auto pixs_deleter = [](void *ptr) -> void { free(ptr); };
-    const int width   = RawImageDataGetWidth(raw_image);
-    const int height  = RawImageDataGetHeight(raw_image);
-    uchar *pixs       = RawImageDataDuplicatePixels(raw_image);
-    QImage img(pixs, width, height / 2, width, QImage::Format_Grayscale8, pixs_deleter, pixs);
-    img = img.mirrored(false, true);
-
-    AudioVisualizer *audioVisualizer = new AudioVisualizer;
-    audioVisualizer->printSpectrum(img);
-
     QVBoxLayout *layout = new QVBoxLayout;
     layout->addWidget(audioVisualizer);