diff --git a/CMakeLists.txt b/CMakeLists.txt
index 75ad7037bace36ea74fa722eab71a4b32237dd82..bc73970b37abf6bfa8ceca76ddf5616be52aa301 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.5)
 
-project(Vivy VERSION 0.1)
+project(Vivy VERSION 0.1 LANGUAGES CXX)
 
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
 cmake_policy(SET CMP0100 NEW) # Let cmake use moc and uic for .hh files
@@ -28,16 +28,16 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
 # Grab all files
 file(GLOB Vivy_SRC
     "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc"
-    "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c"
     "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/*.cc"
     "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/*.cc"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/*.cc"
     "${CMAKE_CURRENT_SOURCE_DIR}/src/UI/*.cc"
 )
 file(GLOB Vivy_INC
-    "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
     "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hh"
     "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/*.hh"
     "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/*.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/*.hh"
     "${CMAKE_CURRENT_SOURCE_DIR}/src/UI/*.hh"
 )
 set(PROJECT_SOURCES
@@ -70,20 +70,21 @@ target_link_libraries(Vivy PRIVATE ${AVFORMAT_LIBRARY})
 target_include_directories(Vivy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc)
 target_precompile_headers(Vivy PRIVATE
     # ASS headers
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Ass.hh>"
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Line.hh>"
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Syl.hh>"
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Style.hh>"
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/AssFactory.hh>"
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Char.hh>"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Ass.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Line.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Syl.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Style.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/AssFactory.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Char.hh"
 
-    # Utils
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Utils.hh>"
+    # Libs
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/Utils.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/Audio.hh"
 
     # Document headers
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocument.hh>"
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocumentStore.hh>"
-    "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Document/CRTPSubDocument.hh>"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocument.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocumentStore.hh"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/CRTPSubDocument.hh"
 )
 
 # More options and warnings
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..17c97c662b07573e06dce2ff27cce04694fc2c5e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,56 @@
+# Vivy developer help
+
+Some rules and help.
+
+### Using the namespace
+
+All Vivy's code must live inside the `Vivy` namespace. There are some
+rules to keep in mind:
+
+- You can use `using namespace Vivy;` inside source files, but
+  ***NEVER*** inside headers files
+- The utility garbage must live inside its own sub-namespace
+  `Vivy::Utils`. You ***can't*** `using namespace Vivy::Utils` apart
+  from inside one of the utils source files because it will ruin the
+  purpose of using a different namespace for that
+- You can also ***NEVER*** `using namespace std;`
+
+### Code dependencies inside the project
+
+The dependencies between the src's sub-folders are as follows:
+
+| folder       | the folders dependencies |
+| :----------- | :----------------------- |
+| src          | *everything*             |
+| src/Lib      | *nothing*                |
+| src/Ass      | src/Lib                  |
+| src/Document | src/Ass, src/Lib         |
+| src/UI       | src/Document, src/Lib    |
+
+The C++ source files are `.cc` files and their corresponding header
+files are `.hh` files.
+
+### C++ Standard
+
+Here we use C++20, or at least we try. Even if the stl has some good
+points, the use of Qt is favored over the stl as it will result in a
+more coherent code base and less conversions between the types used in
+the Vivy's lib and the types used in Vivy's UI.
+
+#### Used or will be used features
+
+Some of the new features will be used:
+
+- concepts (of course!)
+- coroutines
+- likely and unlikely attributes
+
+#### Unused features
+
+Some of the feature may not be
+used or not already be in use for multiple reasons:
+
+- the new C++20 modules feature is not used because I don't know how it
+will play with `moc`
+
+
diff --git a/README.md b/README.md
index 672a97fd76cd475bda8e4facd265889899476bd0..e4a74754eb1a08f03bdb876755146a853b5e4f78 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,25 @@ To do a debug build, use the debug switch with cmake:
 
 ### Dependencies
 
+This project depends on cmake as a build system. For dependencies on
+Windows and MacOS, the easier thing to do is grab qtcreator and find a
+way to add the av libraries to it.
+
+#### Ubuntu/debian
+
 Vivy depends on Qt5, libav and more. On ubuntu install the following
 packages: `qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools
 libavutil-dev libavcodec-dev libavformat-dev`.
 
+#### Arch/Manjaro
+
+TODO!
+
+#### FreeBSD and other toasters
+
+You will need cmake, qt and av dev packages. Set your favorite C++
+compiler with `-DCMAKE_CXX_COMPILER` and you're good to go.
+
+---
+
 ## TODO!
diff --git a/src/Ass/AssFactory.hh b/src/Ass/AssFactory.hh
index 48f849b01cc6ef60afbaa5b4d18627b59d24f439..e5343ec97deae93cbd864fa1844ca2fb72512b4a 100644
--- a/src/Ass/AssFactory.hh
+++ b/src/Ass/AssFactory.hh
@@ -1,7 +1,7 @@
 #ifndef VIVY_ASS_FACTORY_H
 #define VIVY_ASS_FACTORY_H
 
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 #include "Style.hh"
 #include "Line.hh"
 
diff --git a/src/AudioUtils.c b/src/AudioUtils.c
deleted file mode 100644
index 92b6303676e7b8e73bb72185ce0c5bf7787210f7..0000000000000000000000000000000000000000
--- a/src/AudioUtils.c
+++ /dev/null
@@ -1,236 +0,0 @@
-#include "AudioUtils.h"
-
-#include <assert.h>
-#include <libavutil/opt.h>
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libswresample/swresample.h>
-#include <libavcodec/avfft.h>
-
-#define MAXPIXVALUE 7 /* Some magix AV magic stuff */
-
-int
-decodeAudioFile(const char *path, const int sample_rate, double **const data, size_t *const size)
-{
-    AVPacket packet;
-    AVFormatContext *format      = avformat_alloc_context();
-    struct SwrContext *swr       = swr_alloc();
-    AVFrame *frame               = av_frame_alloc();
-    int ret_code                 = -1;
-    ssize_t stream_index         = -1;
-    AVStream *stream             = NULL;
-    AVCodecContext *codec_ctx    = NULL;
-    AVCodecParameters *codec_par = NULL;
-    AVCodec *codec               = NULL;
-
-    if (!frame) {
-        fprintf(stderr, "Error allocating the frame\n");
-        goto exit_error;
-    }
-
-    /* Get format from audio file */
-    if (avformat_open_input(&format, path, NULL, NULL) != 0) {
-        fprintf(stderr, "Could not open file '%s'\n", path);
-        goto exit_error;
-    }
-    if (avformat_find_stream_info(format, NULL) < 0) {
-        fprintf(stderr, "Could not retrieve stream info from file '%s'\n", path);
-        return -1;
-    }
-
-    /* Find the index of the first audio stream */
-    for (unsigned int i = 0; i < format->nb_streams; i++) {
-        codec = avcodec_find_encoder(format->streams[i]->codecpar->codec_id);
-        if (codec->type == AVMEDIA_TYPE_AUDIO) {
-            stream_index = i;
-            break;
-        }
-    }
-    if (stream_index == -1) {
-        fprintf(stderr, "Could not retrieve audio stream from file '%s'\n", path);
-        goto exit_error;
-    }
-    stream = format->streams[stream_index];
-
-    /* Find & open codec */
-    codec_ctx = stream->codec;
-    codec_par = stream->codecpar;
-    codec     = avcodec_find_decoder(codec_par->codec_id);
-
-    if (codec == NULL) {
-        fprintf(stderr, "Failed to find decoder for stream #%zu in file '%s'\n", stream_index,
-                path);
-        goto exit_error;
-    }
-
-    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
-        fprintf(stderr, "Failed to open decoder for stream #%zu in file '%s'\n", stream_index,
-                path);
-        goto exit_error;
-    }
-
-    /* Prepare resampler */
-    av_opt_set_int(swr, "in_channel_count", codec_ctx->channels, 0);
-    av_opt_set_int(swr, "out_channel_count", 1, 0);
-    av_opt_set_int(swr, "in_channel_layout", (int64_t)codec_ctx->channel_layout, 0);
-    av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_MONO, 0);
-    av_opt_set_int(swr, "in_sample_rate", codec_ctx->sample_rate, 0);
-    av_opt_set_int(swr, "out_sample_rate", sample_rate, 0);
-    av_opt_set_sample_fmt(swr, "in_sample_fmt", codec_ctx->sample_fmt, 0);
-    av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0);
-    swr_init(swr);
-    if (!swr_is_initialized(swr)) {
-        fprintf(stderr, "Resampler has not been properly initialized\n");
-        goto exit_error;
-    }
-
-    /* Prepare to read data */
-    av_init_packet(&packet);
-
-    /* Iterate through frames */
-    *data = NULL;
-    *size = 0;
-    while (av_read_frame(format, &packet) >= 0) {
-        /* Decode one frame */
-        int gotFrame;
-        if (avcodec_decode_audio4(codec_ctx, frame, &gotFrame, &packet) < 0) {
-            break;
-        }
-        if (!gotFrame) {
-            continue;
-        }
-
-        /* Resample frames */
-        double *buffer;
-        av_samples_alloc((uint8_t **)&buffer, NULL, 1, frame->nb_samples, AV_SAMPLE_FMT_DBL, 0);
-        const int frame_count_int = swr_convert(swr, (uint8_t **)&buffer, frame->nb_samples,
-                                                (const uint8_t **)frame->data, frame->nb_samples);
-        assert(frame_count_int >= 0);
-        const size_t frame_count = (size_t)frame_count_int;
-
-        /* Append resampled frames to data */
-        *data = (double *)realloc(*data, (*size + (size_t)frame->nb_samples) * sizeof(double));
-        memcpy(*data + *size, buffer, frame_count * sizeof(double));
-        av_free(buffer);
-        *size += frame_count;
-    }
-
-    /* Clean up */
-    ret_code = 0;
-exit_error:
-    if (frame != NULL)
-        av_frame_free(&frame);
-    if (swr != NULL)
-        swr_free(&swr);
-    if (codec_ctx != NULL)
-        avcodec_close(codec_ctx);
-    if (format != NULL)
-        avformat_free_context(format);
-
-    return ret_code;
-}
-
-struct ___RawImageData {
-    const int width;           /* The width of the image  */
-    const int height;          /* The height of the image */
-    const unsigned char *pixs; /* The pixels of the image */
-
-    /* Private stuff */
-    FFTSample *chunk_data;
-    RDFTContext *ctx;
-};
-
-void
-RawImageDataFree(RawImageData *const ptr)
-{
-    free((void *)ptr->pixs);
-    av_rdft_end(ptr->ctx);
-    av_free(ptr->chunk_data);
-    free(ptr);
-}
-
-int
-RawImageDataGetWidth(RawImageData *const ptr)
-{
-    return ptr->width;
-}
-
-int
-RawImageDataGetHeight(RawImageData *const ptr)
-{
-    return ptr->height;
-}
-
-unsigned char *
-RawImageDataDuplicatePixels(RawImageData *const ptr)
-{
-    const size_t size  = sizeof(unsigned char) * (size_t)ptr->width * (size_t)ptr->height / 2;
-    unsigned char *ret = (unsigned char *)malloc(size);
-    assert(ret);
-    memcpy(ret, ptr->pixs, size);
-    return ret;
-}
-
-#define CAP_VALUE(_value, _lower, _upper)                                                          \
-    {                                                                                              \
-        if (_value > _upper) {                                                                     \
-            _value = _upper;                                                                       \
-        } else if (_value < _lower) {                                                              \
-            _value = _lower;                                                                       \
-        }                                                                                          \
-    }
-
-RawImageData *
-RawImageDataNewFromData(const double *const data, const size_t size)
-{
-    const unsigned int chunk_size = 512;
-    const unsigned int overlap    = 128;
-    const unsigned int decal      = chunk_size - overlap;
-    const size_t width            = (size - chunk_size) / decal;
-    const size_t height           = chunk_size;
-    unsigned char *pixs   = (unsigned char *)malloc(sizeof(unsigned char) * width * height / 2);
-    FFTSample *chunk_data = (FFTSample *)av_malloc_array(2 * chunk_size, sizeof(FFTSample));
-    RDFTContext *ctx      = av_rdft_init((int)log2(chunk_size), DFT_R2C);
-
-    if (!(pixs && chunk_data && ctx)) {
-        if (pixs)
-            free(pixs);
-        if (chunk_data)
-            av_free(chunk_data);
-        if (ctx)
-            av_rdft_end(ctx);
-        return NULL;
-    }
-
-    for (size_t x = 0, i = 0; i < size - chunk_size; i += decal, ++x) {
-#pragma omp parallel for
-        for (size_t j = 0; j < chunk_size; j++) {
-            const double curr_dat        = data[i + j];
-            const double window_modifier = 0.5 * (1 - cos(2 * M_PI * ((int)j) / (chunk_size - 1)));
-            float value                  = (float)(window_modifier * curr_dat);
-            CAP_VALUE(value, -1.0f, 1.0f);
-            chunk_data[j] = value;
-        }
-
-        av_rdft_calc(ctx, chunk_data);
-
-#pragma omp parallel for
-        for (size_t j = 0; j < chunk_size / 2; j++) {
-            const float im      = chunk_data[j * 2];
-            const float re      = chunk_data[j * 2 + 1];
-            const float mag     = sqrtf(im * im + re * re);
-            pixs[j * width + x] = (unsigned char)(mag)*MAXPIXVALUE;
-        }
-    }
-
-    RawImageData *ret     = (RawImageData *)malloc(sizeof(RawImageData));
-    RawImageData ret_data = {
-        .width      = (int)width,
-        .height     = (int)height,
-        .pixs       = pixs,
-        .chunk_data = chunk_data,
-        .ctx        = ctx,
-    };
-    memcpy(ret, &ret_data, sizeof(RawImageData));
-    return ret;
-}
diff --git a/src/AudioUtils.h b/src/AudioUtils.h
deleted file mode 100644
index 41f36a3cf0fc2c6dcb5fd76bd14be8fd8a88fca0..0000000000000000000000000000000000000000
--- a/src/AudioUtils.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef VIVY_AUDIOUTILS_H
-#define VIVY_AUDIOUTILS_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <libavcodec/avcodec.h>
-
-int decodeAudioFile(const char *path, const int sample_rate, double **const data,
-                    size_t *const size);
-
-typedef struct ___RawImageData RawImageData;
-RawImageData *RawImageDataNewFromData(const double *const data, const size_t size);
-void RawImageDataFree(RawImageData *const);
-int RawImageDataGetWidth(RawImageData *const);
-int RawImageDataGetHeight(RawImageData *const);
-unsigned char *RawImageDataDuplicatePixels(RawImageData *const);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // VIVY_AUDIOUTILS_H
diff --git a/src/Document/CRTPSubDocument.cc b/src/Document/CRTPSubDocument.cc
new file mode 100644
index 0000000000000000000000000000000000000000..85983b5e5389a04d152ba1fe50efd10d09e387a7
--- /dev/null
+++ b/src/Document/CRTPSubDocument.cc
@@ -0,0 +1,73 @@
+#include "CRTPSubDocument.hh"
+
+using namespace Vivy;
+
+// AudioSubDocument implementation
+
+// Get the default stream index or -1 if not possible
+int
+AudioSubDocument::getDefaultStreamIndex() const noexcept
+{
+    if (auto ptr = getDefaultStream()) {
+        return ptr->getStreamIndex();
+    } else {
+        return -1;
+    }
+}
+
+// Get a pointer to the default stream, nullptr if not possible
+AudioContext::StreamPtr
+AudioSubDocument::getDefaultStream() const noexcept
+{
+    if (auto ptr = contextPtr->getDefaultStream().lock()) {
+        return ptr;
+    } else {
+        return nullptr;
+    }
+}
+
+// Get the stream count, may be 0
+int
+AudioSubDocument::getStreamCount() const noexcept
+{
+    return contextPtr->getStreamCount();
+}
+
+// Get the stream asked for, nullptr if no stream or if the index is invalid
+AudioContext::StreamPtr
+AudioSubDocument::getStream(int index) const noexcept
+{
+    if (auto ptr = contextPtr->getStream(index).lock()) {
+        return ptr;
+    } else {
+        return nullptr;
+    }
+}
+
+// Init an audio sub-document from a file
+void
+AudioSubDocument::initFromPath(const QString &path)
+{
+    if (contextPtr)
+        qDebug() << "Replacing the audio contetx by a new one for file" << path;
+    contextPtr.reset(new AudioContext(path)); // May throw
+
+    qDebug() << "Audio OK for" << path;
+}
+
+// VideoSubDocument implementation
+
+// Init a video sub-document from a file
+void
+VideoSubDocument::initFromPath(const QString &)
+{
+}
+
+// AssSubDocument implementation
+void
+AssSubDocument::initFromPath(const QString &path)
+{
+    Ass::AssFactory factory(path);
+    factory.getStyles(styles);
+    factory.getLines(lines);
+}
diff --git a/src/Document/CRTPSubDocument.hh b/src/Document/CRTPSubDocument.hh
index 7d1dcebd18c52306bf41cf8f0cb4a4cd97fb1875..29b1c620eceb0e0c442b238a2d43ac37dd67cd13 100644
--- a/src/Document/CRTPSubDocument.hh
+++ b/src/Document/CRTPSubDocument.hh
@@ -5,31 +5,15 @@
 #error "This is a C++ header"
 #endif
 
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
+#include "../Lib/Audio.hh"
 #include "../Ass/Ass.hh"
 #include <QString>
 #include <memory>
 
-/* Types for the different documents */
-
 namespace Vivy
 {
-enum class AudioDocumentType : quint64 {
-    MP3 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP3),
-    OGG = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::OGG),
-    M4A = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::M4A),
-};
-
-enum class VideoDocumentType : quint64 {
-    MKV = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MKV),
-    MP4 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP4),
-};
-
-enum class AssDocumentType : quint64 {
-    ASS = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::ASS),
-};
-
-/* The Big CRTP class for all common things to all the subdocuments */
+// The Big CRTP class for all common things to all the subdocuments
 template <class CRTPSubDocumentType, class Document> class CRTPSubDocument {
 public:
     using Type = CRTPSubDocumentType;
@@ -53,7 +37,14 @@ public:
         qDebug() << "Init document from file " << path;
         auto ret      = std::unique_ptr<Document>(new Document());
         ret->filePath = path;
-        ret->initFromPath(path);
+
+        try {
+            ret->initFromPath(path); // May throw
+        } catch (const std::runtime_error &e) {
+            qDebug() << "Failed to init document from file" << path;
+            ret.reset();
+        }
+
         return ret;
     }
 
@@ -68,43 +59,42 @@ public:
     }
 };
 
-/* Audio document */
+// Audio document
 class AudioSubDocument final : public CRTPSubDocument<AudioDocumentType, AudioSubDocument> {
 private:
     const QStringList &suffixList = Vivy::Utils::audioFileSuffix;
 
-    inline void initFromPath([[maybe_unused]] const QString &path) noexcept
-    {
-        qDebug() << "Audio OK for" << path;
-    }
+    void initFromPath(const QString &);
 
     explicit AudioSubDocument() = default;
     friend CRTPSubDocument<AudioDocumentType, AudioSubDocument>;
+
+private:
+    std::unique_ptr<AudioContext> contextPtr;
+
+public:
+    int getDefaultStreamIndex() const noexcept;
+    AudioContext::StreamPtr getDefaultStream() const noexcept;
+    int getStreamCount() const noexcept;
+    AudioContext::StreamPtr getStream(int index) const noexcept;
 };
 
-/* Video document */
+// Video document
 class VideoSubDocument final : public CRTPSubDocument<VideoDocumentType, VideoSubDocument> {
 private:
     const QStringList &suffixList = Vivy::Utils::videoFileSuffix;
 
-    inline void initFromPath([[maybe_unused]] const QString &path) noexcept
-    {
-    }
+    void initFromPath(const QString &);
 
     explicit VideoSubDocument() noexcept = default;
     friend CRTPSubDocument<VideoDocumentType, VideoSubDocument>;
 };
 
-/* ASS document */
+// ASS document
 class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocument> {
     const QStringList &suffixList = Vivy::Utils::assFileSuffix;
 
-    inline void initFromPath([[maybe_unused]] const QString &path) noexcept
-    {
-        Ass::AssFactory factory(path);
-        factory.getStyles(styles);
-        factory.getLines(lines);
-    }
+    void initFromPath(const QString &);
 
     explicit AssSubDocument() noexcept = default;
     friend CRTPSubDocument<AssDocumentType, AssSubDocument>;
diff --git a/src/Document/VivyDocument.cc b/src/Document/VivyDocument.cc
index c2f04cabf8c1986d1300c0c8768fa8ea34767638..53dd53d970cfc5c0a9deb365aa9e8380753d957b 100644
--- a/src/Document/VivyDocument.cc
+++ b/src/Document/VivyDocument.cc
@@ -1,5 +1,5 @@
 #include "VivyDocument.hh"
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 
 #include <QFileInfo>
 #include <QString>
@@ -33,8 +33,9 @@ VivyDocument::loadSubDocument(const QString &name) noexcept
             return true;
 
         case Capabilities::VideoAble:
-            qDebug() << "Auto-detect video document for" << name;
+            qDebug() << "Auto-detect video (and try audio) document for" << name;
             setVideoSubDocument(file.absoluteFilePath());
+            setAudioSubDocument(file.absoluteFilePath());
             return true;
 
         case Capabilities::AssAble:
@@ -192,11 +193,14 @@ VivyDocument::setAudioSubDocument(const QString filename) noexcept
 
     if (auto doc = audioDocument.get()) {
         // Here we may want to do some confirmation / cleanup
+        audioDocument.reset();
     }
 
     audioDocument = AudioSubDocument::fromFile(filename);
-    documentType |= Capabilities::AudioAble;
-    documentOptions &= ~Options::UntouchedByDefault;
+    if (audioDocument) {
+        documentType |= Capabilities::AudioAble;
+        documentOptions &= ~Options::UntouchedByDefault;
+    }
 }
 
 void
@@ -208,11 +212,14 @@ VivyDocument::setVideoSubDocument(const QString filename) noexcept
 
     if (auto doc = videoDocument.get()) {
         // Here we may want to do some confirmation / cleanup
+        videoDocument.reset();
     }
 
     videoDocument = VideoSubDocument::fromFile(filename);
-    documentType |= Capabilities::VideoAble;
-    documentOptions &= ~Options::UntouchedByDefault;
+    if (videoDocument) {
+        documentType |= Capabilities::VideoAble;
+        documentOptions &= ~Options::UntouchedByDefault;
+    }
 }
 
 void
@@ -224,9 +231,12 @@ VivyDocument::setAssSubDocument(const QString filename) noexcept
 
     if (auto doc = assDocument.get()) {
         // Here we may want to do some confirmation / cleanup
+        assDocument.reset();
     }
 
     assDocument = AssSubDocument::fromFile(filename);
-    documentType |= Capabilities::AssAble;
-    documentOptions &= ~Options::UntouchedByDefault;
+    if (assDocument) {
+        documentType |= Capabilities::AssAble;
+        documentOptions &= ~Options::UntouchedByDefault;
+    }
 }
diff --git a/src/Document/VivyDocument.hh b/src/Document/VivyDocument.hh
index 16bef9dc8ed6eefea979c33d96a364209a9b8638..4d7fa9e4031e5eb3375068aae948eca0557e95e6 100644
--- a/src/Document/VivyDocument.hh
+++ b/src/Document/VivyDocument.hh
@@ -5,7 +5,7 @@
 #error "This is a C++ header"
 #endif
 
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 #include "CRTPSubDocument.hh"
 
 #include <memory>
diff --git a/src/Document/VivyDocumentStore.hh b/src/Document/VivyDocumentStore.hh
index f25fd3f7d374840596fa81ee0df07a38ddc4db66..047a49ab29172b076b88a43af606f681fde0d5fa 100644
--- a/src/Document/VivyDocumentStore.hh
+++ b/src/Document/VivyDocumentStore.hh
@@ -2,7 +2,7 @@
 #define VIVY_DOCUMENTSTORE_H
 
 #include "VivyDocument.hh"
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 
 #include <QMap>
 #include <QString>
diff --git a/src/Lib/Audio.cc b/src/Lib/Audio.cc
new file mode 100644
index 0000000000000000000000000000000000000000..65c1cdc856d01a6d6a8023dc87ea1925fd751c78
--- /dev/null
+++ b/src/Lib/Audio.cc
@@ -0,0 +1,343 @@
+#include "Audio.hh"
+
+#include <QJsonObject>
+#include <QJsonArray>
+
+using namespace Vivy;
+
+// AudioContext class implementation
+
+// Create an audio contecxt from a file
+AudioContext::AudioContext(const QString &path)
+    : filePath(path)
+{
+    if (!format)
+        throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
+
+    const std::string filePathStdHolder = filePath.toStdString();
+    const char *filename                = filePathStdHolder.c_str();
+    AVFormatContext *formatPtr          = format.get();
+
+    // Get the format from the audio file
+    if (avformat_open_input(&formatPtr, filename, NULL, NULL) != 0) {
+        [[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input
+        throw std::runtime_error("failed to open file");
+    }
+
+    if (avformat_find_stream_info(formatPtr, NULL) < 0) {
+        throw std::runtime_error("failed to get audio stream info");
+    }
+
+    // Populate all the stream indexes
+    for (uint i = 0; i < format->nb_streams; ++i) {
+        AVStream *itFormat        = format->streams[i];
+        AVCodecParameters *params = itFormat->codecpar;
+        AVCodec *streamCodec      = avcodec_find_decoder(params->codec_id);
+        if (streamCodec && streamCodec->type == AVMEDIA_TYPE_AUDIO) {
+            audioStreamIndexes.push_back(i);
+            audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i));
+        }
+    }
+
+    // Get the default stream
+    defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO,
+                                             -1, // Let AV find one stream
+                                             -1, // We don't want related streams
+                                             nullptr, 0);
+
+    qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration;
+}
+
+// Get the number of audio streams in the audio context
+int
+AudioContext::getStreamCount() const noexcept
+{
+    return audioStreamIndexes.size();
+}
+
+// Get a specific audio stream, try to lock the weak pointer. if the index was
+// not present the used_count will be 0 and you won't be able to lock it.
+AudioContext::StreamWeakPtr
+AudioContext::getStream(int index) const noexcept
+{
+    if (index >= audioStreamIndexes.size())
+        return StreamWeakPtr{ spareNullSreamPtr };
+
+    const uint streamIndex = audioStreamIndexes[index];
+    const auto found       = audioStreams.find(streamIndex);
+
+    if (found != audioStreams.end()) {
+        return StreamWeakPtr{ *found };
+    }
+
+    return StreamWeakPtr{ spareNullSreamPtr };
+}
+
+// Get the default stream of this audio context. If no default audio stream is
+// present you won't be able to lock the weak pointer.
+AudioContext::StreamWeakPtr
+AudioContext::getDefaultStream() const noexcept
+{
+    if (defaultStreamIndex < 0)
+        return StreamWeakPtr{ spareNullSreamPtr };
+    return getStream(defaultStreamIndex);
+}
+
+QString
+AudioContext::getElementName() const noexcept
+{
+    QFileInfo file(filePath);
+    return "AudioContext: " + file.baseName();
+}
+
+QJsonDocument
+AudioContext::getProperties() const noexcept
+{
+    QJsonDocument ret;
+    QJsonObject self;
+    QJsonArray streams;
+
+    for (const auto &audioStreamPtr : audioStreams) {
+        streams.append(audioStreamPtr->getProperties());
+    }
+
+    self.insert("File path", filePath);
+    self.insert("Streams", streams);
+
+    ret.setObject(self);
+    return ret;
+}
+
+// AudioContext::Stream class implementation
+
+// Constructor, need an AVFormat and an AVStream
+AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *format, AVStream *stream,
+                             int index)
+    : codecId(stream->codecpar->codec_id)
+    , codec(streamCodec)
+    , codecParams(stream->codecpar)
+    , audioStream(stream)
+    , streamIndexInAudioContext(index)
+    , dataFormat(format)
+{
+    if (codec == nullptr)
+        throw std::runtime_error("failed to find a decoder for stream");
+
+    codecContext.reset(avcodec_alloc_context3(codec));
+    if (!codecContext)
+        throw std::runtime_error("failed to allocate codec context");
+
+    if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0)
+        throw std::runtime_error("failed to copy parameters to codec context");
+
+    if (avcodec_open2(codecContext.get(), codec, nullptr) < 0)
+        throw std::runtime_error("failed to open audio decoder for a stream");
+
+    SwrContext *s = dataSwrContext.get();
+    av_opt_set_int(s, "in_channel_count", codecContext->channels, 0);
+    av_opt_set_int(s, "out_channel_count", 1, 0);
+    av_opt_set_int(s, "in_channel_layout", static_cast<int64_t>(codecContext->channel_layout), 0);
+    av_opt_set_int(s, "out_channel_layout", AV_CH_LAYOUT_MONO, 0);
+    av_opt_set_int(s, "in_sample_rate", codecContext->sample_rate, 0);
+    av_opt_set_int(s, "out_sample_rate", resamplerSampleRate, 0);
+    av_opt_set_sample_fmt(s, "in_sample_fmt", codecContext->sample_fmt, 0);
+    av_opt_set_sample_fmt(s, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0);
+    swr_init(s);
+    if (!swr_is_initialized(s))
+        throw std::runtime_error("failed to initialize SwrContext resampler");
+
+    qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId;
+    qDebug() << "[Stream] sample rate:" << codecParams->sample_rate;
+    qDebug() << "[Stream] bit rate:   " << codecParams->bit_rate;
+    qDebug() << "[Stream] channels:   " << codecParams->channels;
+}
+
+AudioContext::Stream::~Stream() noexcept
+{
+    if (dataPtr)
+        free(dataPtr);
+}
+
+QJsonObject
+AudioContext::Stream::getProperties() const noexcept
+{
+    QJsonObject ret;
+    ret.insert("Codec name", codec->name);
+    ret.insert("Sample rate", codecParams->sample_rate);
+    ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate));
+    ret.insert("Channels", codecParams->channels);
+    return ret;
+}
+
+// Decode the data, don't call it if data already decoded
+void
+AudioContext::Stream::decodeData()
+{
+    if (isDecoded())
+        throw std::logic_error("audio stream is already resampled");
+    qDebug() << "Launch decoding of stream" << streamIndexInAudioContext;
+
+    AVPacket packet;
+    av_init_packet(&packet);
+
+    // Iterate through frames
+    while (av_read_frame(dataFormat, &packet) >= 0) {
+        // Only decode audio
+        if (packet.stream_index != streamIndexInAudioContext) {
+            av_packet_unref(&packet);
+            continue;
+        }
+
+        // Decode one frame
+        int response = avcodec_send_packet(codecContext.get(), &packet);
+        if (response < 0) [[unlikely]] {
+            throw std::runtime_error(
+                QStringLiteral("error n°%1 while sending a packet to the decoder")
+                    .arg(response)
+                    .toStdString());
+        }
+
+        double *buffer        = nullptr;
+        int oldFrameNbSamples = 0;
+
+        while (response >= 0) {
+            response = avcodec_receive_frame(codecContext.get(), dataFrame.get());
+            if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) [[unlikely]] {
+                break;
+            } else if (response < 0) [[unlikely]] {
+                throw std::runtime_error(
+                    QStringLiteral("error n°%1 while receiving a from from the decoder")
+                        .arg(response)
+                        .toStdString());
+            }
+
+            // Reuse buffer if possible
+            if (oldFrameNbSamples < dataFrame->nb_samples) {
+                dataDeleter(buffer);
+                oldFrameNbSamples = dataFrame->nb_samples;
+                av_samples_alloc(reinterpret_cast<uint8_t **>(&buffer), nullptr, 1,
+                                 dataFrame->nb_samples, AV_SAMPLE_FMT_DBL, 0);
+            }
+
+            // Resample frame
+            if (const int frame_count_int =
+                    swr_convert(dataSwrContext.get(), reinterpret_cast<uint8_t **>(&buffer),
+                                dataFrame->nb_samples, (const uint8_t **)dataFrame->data,
+                                dataFrame->nb_samples);
+                frame_count_int < 0) [[unlikely]] {
+                throw std::runtime_error("error on frame count, is negative but should not be");
+            }
+
+            // Append resampled frames to data
+            else [[likely]] {
+                const size_t frame_count = static_cast<size_t>(frame_count_int);
+                dataPtr                  = reinterpret_cast<double *>(
+                    realloc(dataPtr, (dataSize + (size_t)dataFrame->nb_samples) * sizeof(double)));
+                memcpy(dataPtr + dataSize, buffer, frame_count * sizeof(double));
+                dataSize += frame_count;
+            }
+        }
+
+        dataDeleter(buffer);
+        av_packet_unref(&packet);
+    }
+
+    qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext
+             << "dataPtr =" << dataPtr << "with dataSize =" << dataSize;
+}
+
+// Delete decoded data, clean up thing
+void
+AudioContext::Stream::cleanUpData() noexcept
+{
+    free(dataPtr);
+    dataPtr  = nullptr;
+    dataSize = 0;
+}
+
+// Get the number of channels in the audio context stream
+int
+AudioContext::Stream::getChannels() const noexcept
+{
+    return codecContext->channels;
+}
+
+// Get the sample rate
+int
+AudioContext::Stream::getSampleRate() const noexcept
+{
+    return codecContext->sample_rate;
+}
+
+// Get the sample rate of the decoded data (need to call decodeData for that
+// value to have a meaning).
+int
+AudioContext::Stream::getDataSampleRate() const noexcept
+{
+    return resamplerSampleRate;
+}
+
+// Get the name of the codec used to decode the stream
+QString
+AudioContext::Stream::getName() const noexcept
+{
+    return QString(codec->name);
+}
+
+// Get the codec's ID used to decode the stream
+AVCodecID
+AudioContext::Stream::getCodecId() const noexcept
+{
+    return codecId;
+}
+
+// Get the bit rate of the stream
+qint64
+AudioContext::Stream::getBitRate() const noexcept
+{
+    return codecContext->bit_rate;
+}
+
+// Get the information about the decoded state of this stream
+bool
+AudioContext::Stream::isDecoded() const noexcept
+{
+    return dataPtr != nullptr;
+}
+
+// Get the decoded data's size, will return 0 if the data is not decoded. Don't
+// rely on this behaviour to get the decoded state of the stream!
+size_t
+AudioContext::Stream::getDecodedDataSize() const noexcept
+{
+    return dataSize;
+}
+
+// Get the decoded data, safe to call even if the data is not decoded has the
+// pointer will be null. You must check it with an `if` statement.
+double *
+AudioContext::Stream::getDecodedData() const noexcept
+{
+    return dataPtr;
+}
+
+// Get the chunk size of the decoded data
+size_t
+AudioContext::Stream::getDecodedChunkSize() const noexcept
+{
+    return 512;
+}
+
+// Get the decalage of the decoded data
+size_t
+AudioContext::Stream::getDecodedDecalage() const noexcept
+{
+    constexpr size_t overlap = 128; // The overlap
+    return getDecodedChunkSize() - overlap;
+}
+
+int
+AudioContext::Stream::getStreamIndex() const noexcept
+{
+    return streamIndexInAudioContext;
+}
diff --git a/src/Lib/Audio.hh b/src/Lib/Audio.hh
new file mode 100644
index 0000000000000000000000000000000000000000..72b67d288ea24fe93cf160e9f244ee4b7f733a75
--- /dev/null
+++ b/src/Lib/Audio.hh
@@ -0,0 +1,159 @@
+#ifndef VIVY_AUDIO_H
+#define VIVY_AUDIO_H
+
+#ifndef __cplusplus
+#error "This is a C++ header"
+#endif
+
+extern "C" {
+#include <libavutil/opt.h>
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswresample/swresample.h>
+#include <libavcodec/avfft.h>
+}
+
+#include "Utils.hh"
+#include <QtGlobal>
+#include <QMap>
+#include <QVector>
+#include <QString>
+#include <memory.h>
+
+namespace Vivy
+{
+class AudioContext;
+
+// The memory representation of an audio file. It is created inplace and should
+// not be moved around. It should be used to get the properties of an audio
+// file and its streams. The user can use a stream from the file and get data
+// from it.
+class AudioContext final {
+    VIVY_UNMOVABLE_OBJECT(AudioContext)
+
+public:
+    // Hold all the data for a stream in an audio file. Should only be owned by
+    // the AudioContext class.
+    class Stream final {
+        VIVY_UNMOVABLE_OBJECT(Stream)
+
+        // All the needed deleters
+        static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void {
+            if (ptr)
+                avcodec_free_context(&ptr);
+        };
+        static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void {
+            if (ptr)
+                av_free(ptr);
+        };
+        static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void {
+            if (ptr)
+                av_frame_free(&ptr);
+        };
+
+        static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void {
+            if (swr)
+                swr_free(&swr);
+        };
+
+        // All the used types
+        using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
+        using DataPtr           = std::shared_ptr<double[]>;
+        using AVFramePtr        = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>;
+        using SwrContextPtr     = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>;
+
+    public:
+        Stream(AVCodec *, AVFormatContext *, AVStream *, int index);
+        ~Stream() noexcept;
+
+        // The non-owning view of the stream's data
+        using DataWeakPtr = std::weak_ptr<double[]>;
+
+        // Decode the stream
+        void decodeData();
+        void cleanUpData() noexcept;
+        bool isDecoded() const noexcept;
+        int getDataSampleRate() const noexcept;
+        size_t getDecodedDataSize() const noexcept;
+        size_t getDecodedChunkSize() const noexcept;
+        size_t getDecodedDecalage() const noexcept;
+        double *getDecodedData() const noexcept;
+
+        // Some getters
+        int getChannels() const noexcept;
+        int getSampleRate() const noexcept;
+        QString getName() const noexcept;
+        AVCodecID getCodecId() const noexcept;
+        qint64 getBitRate() const noexcept;
+
+        // Get the index from the audio context
+        int getStreamIndex() const noexcept;
+
+        QJsonObject getProperties() const noexcept;
+
+    private:
+        // Codec related informations
+        AVCodecID codecId{ AV_CODEC_ID_NONE };
+        AVCodec *codec{ nullptr };
+        AVCodecParameters *codecParams{ nullptr };
+        AVCodecContextPtr codecContext{ nullptr };
+
+        // Stream is held by AudioContext
+        AVStream *audioStream{ nullptr };
+
+        // Store the index of this stream
+        int streamIndexInAudioContext;
+
+        // Resampled frame data
+        AVFormatContext *dataFormat;
+        SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter };
+        AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter };
+        double *dataPtr{ nullptr };
+        size_t dataSize{ 0 };
+
+        // Constants for the resampler
+        static constexpr uint resamplerChunkSize  = 512;
+        static constexpr uint resamplerOverlap    = 128;
+        static constexpr uint resamplerDecalage   = resamplerChunkSize - resamplerOverlap;
+        static constexpr uint resamplerSampleRate = 44100;
+    };
+
+    using StreamPtr = std::shared_ptr<Stream>;
+
+public:
+    AudioContext(const QString &path);
+    ~AudioContext() noexcept = default;
+
+    // The stream non-owning view pointer
+    using StreamWeakPtr = std::weak_ptr<Stream>;
+
+    int getStreamCount() const noexcept;
+    StreamWeakPtr getStream(int) const noexcept;
+    StreamWeakPtr getDefaultStream() const noexcept;
+
+    QString getElementName() const noexcept;
+    QJsonDocument getProperties() const noexcept;
+
+private:
+    // Regarding the format
+    static inline constexpr auto avFormatContextDeleter =
+        [](AVFormatContext *ptr) noexcept -> void {
+        if (ptr)
+            avformat_free_context(ptr);
+    };
+    using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>;
+    AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter };
+
+    QString filePath;                     // Usefull information
+    QVector<uint> audioStreamIndexes{};   // Index all the audio streams of the file
+    QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file
+
+    int defaultStreamIndex{ -1 };
+
+    // Spare always null shared pointer, to be used when the audioStream[i] was
+    // not found.
+    StreamPtr spareNullSreamPtr{ nullptr };
+};
+}
+
+#endif // VIVY_AUDIO_H
diff --git a/src/Utils.cc b/src/Lib/Utils.cc
similarity index 100%
rename from src/Utils.cc
rename to src/Lib/Utils.cc
diff --git a/src/Utils.hh b/src/Lib/Utils.hh
similarity index 63%
rename from src/Utils.hh
rename to src/Lib/Utils.hh
index 97bfa1f57e5e1f86d8dafa6646808cc499aa7689..95f8e7488117d1242f52cf8f5e7b6d8bde94d147 100644
--- a/src/Utils.hh
+++ b/src/Lib/Utils.hh
@@ -6,6 +6,7 @@
 #include <QStringList>
 #include <QDebug>
 #include <QMessageLogger>
+#include <QJsonDocument>
 #include <QtGlobal>
 #include <type_traits>
 
@@ -16,6 +17,19 @@
     classname &operator=(const classname &) = delete; /* Copy assign */                            \
     classname &operator=(classname &&) = delete;      /* Move assign */
 
+namespace Vivy
+{
+// Concept for classes that can be viewd inside a property view
+template <typename T>
+concept PropertyConstViewable = requires(T element)
+{
+    // clang-format off
+    { element.getElementName() } -> std::same_as<QString>;
+    { element.getProperties() }  -> std::same_as<QJsonDocument>;
+    // clang-format on
+};
+}
+
 namespace Vivy::Utils
 {
 static const QStringList audioFileSuffix = { "wave", "wav", "ogg", "mp3", "m4a" };
@@ -54,7 +68,26 @@ to_underlying(E e) noexcept
 bool detectDocumentType(const QFileInfo &, DocumentType *);
 }
 
-/* All forward delcarations are placed here */
+namespace Vivy
+{
+// Audio document types
+enum class AudioDocumentType : quint64 {
+    MP3 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP3),
+    OGG = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::OGG),
+    M4A = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::M4A),
+};
+
+// Video document types
+enum class VideoDocumentType : quint64 {
+    MKV = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MKV),
+    MP4 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP4),
+};
+
+// Ass document types
+enum class AssDocumentType : quint64 {
+    ASS = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::ASS),
+};
+}
 
 class QMenu;
 class QAction;
diff --git a/src/UI/AudioVisualizer.cc b/src/UI/AudioVisualizer.cc
index b53c20d8c93734f23a08005c0727309527922814..a85ac12664f3ba222972bba19f0b99e4aca84f28 100644
--- a/src/UI/AudioVisualizer.cc
+++ b/src/UI/AudioVisualizer.cc
@@ -1,5 +1,5 @@
 #include "AudioVisualizer.hh"
-#include "../AudioUtils.h"
+#include "../Lib/Audio.hh"
 
 #include <QGraphicsPixmapItem>
 #include <QLabel>
@@ -10,9 +10,77 @@
 
 using namespace Vivy;
 
-AudioVisualizer::AudioVisualizer(QWidget *parent) noexcept
+#define MAXPIXVALUE 7 // Some magix AV magic stuff
+
+#define CAP_VALUE(_value, _lower, _upper)                                                          \
+    {                                                                                              \
+        if (_value > _upper) {                                                                     \
+            _value = _upper;                                                                       \
+        } else if (_value < _lower) {                                                              \
+            _value = _lower;                                                                       \
+        }                                                                                          \
+    }
+
+AudioVisualizer::AudioVisualizer(AudioContext::StreamPtr stream, QWidget *parent)
     : QWidget(parent)
 {
+    if (!stream->isDecoded()) {
+        qDebug() << "Need to decode data for stream" << stream->getStreamIndex();
+        stream->decodeData();
+    }
+
+    double *decodedData = stream->getDecodedData();
+    if (decodedData == nullptr)
+        throw std::logic_error("the passed stream is not decoded");
+
+    const size_t size     = stream->getDecodedDataSize();
+    const size_t height   = stream->getDecodedChunkSize();
+    const size_t decalage = stream->getDecodedDecalage();
+    const size_t width    = (size - height) / decalage;
+    uchar *pixels         = new uchar[static_cast<size_t>(width * height / 2)]();
+
+    FFTSamplePtr chunkData(
+        reinterpret_cast<FFTSample *>(av_malloc_array(2 * height, sizeof(FFTSample))),
+        fftSampleDeleter);
+    RDFTContextPtr ctx(av_rdft_init((static_cast<int>(log2(static_cast<int>(height)))), DFT_R2C),
+                       rdftContextDeleter);
+
+    if (!pixels) {
+        throw std::runtime_error("out of memory");
+    } else if (!(chunkData && ctx)) {
+        delete[] pixels;
+        throw std::runtime_error("out of memory");
+    }
+
+    /* Compute the image data */
+
+    for (size_t x = 0, i = 0; i < size - height; i += decalage, ++x) {
+#pragma omp parallel for
+        for (size_t j = 0; j < height; j++) {
+            const double curr_dat = decodedData[i + j];
+            const double window_modifier =
+                (1 - cos(2 * M_PI * static_cast<double>(j) / static_cast<double>(height - 1))) / 2;
+            float value = static_cast<float>(window_modifier * curr_dat);
+            CAP_VALUE(value, -1.0f, 1.0f);
+            chunkData[j] = value;
+        }
+
+        av_rdft_calc(ctx.get(), chunkData.get());
+
+#pragma omp parallel for
+        for (size_t j = 0; j < height / 2; j++) {
+            const float im     = chunkData[j * 2];
+            const float re     = chunkData[j * 2 + 1];
+            const float mag    = sqrtf(im * im + re * re);
+            const size_t index = static_cast<size_t>(j * static_cast<ulong>(width) + x);
+            pixels[index]      = (unsigned char)(mag)*MAXPIXVALUE;
+        }
+    }
+
+    QImage img = QImage(pixels, static_cast<int>(width), static_cast<int>(height / 2),
+                        static_cast<int>(width), QImage::Format_Grayscale8, pixelsDeleter, pixels)
+                     .mirrored(false, true);
+    printSpectrum(img);
 }
 
 void
@@ -24,43 +92,3 @@ 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;
-    size_t 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 =
-        QImage(pixs, width, height / 2, width, QImage::Format_Grayscale8, pixs_deleter, pixs)
-            .mirrored(false, true);
-
-    auto *audioVisualizer = new AudioVisualizer;
-    audioVisualizer->printSpectrum(img);
-    return audioVisualizer;
-}
diff --git a/src/UI/AudioVisualizer.hh b/src/UI/AudioVisualizer.hh
index 8a267183e353dca5756b1a215336db8c239fa6b7..3273d285bba08de8865e08be657cf62ba414a483 100644
--- a/src/UI/AudioVisualizer.hh
+++ b/src/UI/AudioVisualizer.hh
@@ -6,6 +6,7 @@
 #endif
 
 #include "TimingView.hh"
+#include "../Lib/Audio.hh"
 #include <QWidget>
 #include <QString>
 
@@ -14,12 +15,26 @@ namespace Vivy
 class AudioVisualizer final : public QWidget {
     Q_OBJECT
 
+private:
+    static constexpr inline auto fftSampleDeleter = [](FFTSample *ptr) noexcept -> void {
+        if (ptr)
+            av_free(ptr);
+    };
+    static constexpr inline auto rdftContextDeleter = [](RDFTContext *ptr) noexcept -> void {
+        if (ptr)
+            av_rdft_end(ptr);
+    };
+    static constexpr inline auto pixelsDeleter = [](void *ptr) noexcept -> void {
+        if (ptr)
+            delete[](reinterpret_cast<uchar *>(ptr));
+    };
+    using FFTSamplePtr   = std::unique_ptr<FFTSample[], decltype(fftSampleDeleter)>;
+    using RDFTContextPtr = std::unique_ptr<RDFTContext, decltype(rdftContextDeleter)>;
+
 public:
-    explicit AudioVisualizer(QWidget *parent = nullptr) noexcept;
+    explicit AudioVisualizer(AudioContext::StreamPtr, QWidget *parent = nullptr);
     ~AudioVisualizer() noexcept = default;
 
-    [[nodiscard("allocated")]] static AudioVisualizer *fromFile(const QString &);
-
 public slots:
     void printSpectrum(QImage) noexcept;
 };
diff --git a/src/UI/MainWindow.cc b/src/UI/MainWindow.cc
index 929659291b851e1147cdaa0f2a27935f85d227f7..212abaf9f3312c909a0d2f02a33de2bde7e46c2c 100644
--- a/src/UI/MainWindow.cc
+++ b/src/UI/MainWindow.cc
@@ -1,7 +1,7 @@
 #include "MainWindow.hh"
 #include "DialogHelp.hh"
 #include "VivyDocumentView.hh"
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 #include "../VivyApplication.hh"
 
 #include <functional>
diff --git a/src/UI/MainWindow.hh b/src/UI/MainWindow.hh
index 7947e04aa3f34777dee6ec1eda10bde46f0cbcd7..fc3e60ea739a7be2ca75da3e4132855c9afd72af 100644
--- a/src/UI/MainWindow.hh
+++ b/src/UI/MainWindow.hh
@@ -5,7 +5,7 @@
 #error "This is a C++ header"
 #endif
 
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 #include "AudioVisualizer.hh"
 #include "../Document/VivyDocumentStore.hh"
 #include "VivyDocumentView.hh"
diff --git a/src/UI/TimingScene.cc b/src/UI/TimingScene.cc
index 042149bd2c27427c35f64f3d82fa8e3f4046a9f7..7d2e21156582e7c6c77de94958b53785c057cb1a 100644
--- a/src/UI/TimingScene.cc
+++ b/src/UI/TimingScene.cc
@@ -1,5 +1,5 @@
 #include "TimingScene.hh"
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 
 #include <QGraphicsLineItem>
 #include <QGraphicsPixmapItem>
diff --git a/src/UI/TimingScene.hh b/src/UI/TimingScene.hh
index ed6742f95b9bbc8addb2c18016f0591c6bf0a53d..860acecbc35628983b137e517385f82da36cf1ef 100644
--- a/src/UI/TimingScene.hh
+++ b/src/UI/TimingScene.hh
@@ -1,7 +1,7 @@
 #ifndef VIVY_TIMING_SCENE_H
 #define VIVY_TIMING_SCENE_H
 
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 #include "../Ass/Ass.hh"
 #include "TimingBar.hh"
 
diff --git a/src/UI/TimingView.hh b/src/UI/TimingView.hh
index 3358226c85aa09ca5516e89c82c2e9dffaac3c44..b2f11f792576e7d66b1e2628ae2cefb12bb51e67 100644
--- a/src/UI/TimingView.hh
+++ b/src/UI/TimingView.hh
@@ -5,7 +5,7 @@
 #error "This is a C++ header"
 #endif
 
-#include "../Utils.hh"
+#include "../Lib/Utils.hh"
 #include "TimingBar.hh"
 #include "TimingScene.hh"
 
diff --git a/src/UI/VivyDocumentView.cc b/src/UI/VivyDocumentView.cc
index d9a0cc4c8e8ee992d688d4fc8dec50b198d0c294..f686215d6aa2f4e3983d4afb1ff571dce2642071 100644
--- a/src/UI/VivyDocumentView.cc
+++ b/src/UI/VivyDocumentView.cc
@@ -53,19 +53,26 @@ VivyDocumentView::loadAudioView() noexcept
         std::shared_ptr<AudioSubDocument> audioDocument = document->getAudioSubDocument();
         qDebug() << "Create an audio vizualizer for the audio sub document"
                  << audioDocument->getFilePath();
-        visualizer = AudioVisualizer::fromFile(audioDocument->getFilePath());
+
+        AudioContext::StreamPtr stream = audioDocument->getDefaultStream();
+        if (stream == nullptr) {
+            qCritical() << "Failed to get default audio stream";
+            return;
+        }
+
+        visualizer = new AudioVisualizer(stream);
         if (visualizer == nullptr) {
             qCritical() << "Failed to create visualizer for" << audioDocument->getFilePath();
             return;
         }
 
         QVBoxLayout *layout = new QVBoxLayout(this);
-        if (visualizer != nullptr) {
-            qDebug() << "Add visualizer to the view";
-            layout->addWidget(visualizer);
-        }
+        layout->addWidget(visualizer);
+
         setLayout(layout);
-    } else {
+    }
+
+    else {
         qDebug() << "The document" << document->getName() << "is not AudioAble";
     }
 }