diff --git a/.clang-format b/.clang-format
index 0ec9b67a19cd62a48a6ae3bcd78657c8503b0e9b..339654d33e3f5531d2c7c3739d630a05ed236cbd 100644
--- a/.clang-format
+++ b/.clang-format
@@ -2,7 +2,7 @@ Language: Cpp
 Standard: c++20
 BasedOnStyle: Google
 IndentWidth: 4
-ColumnLimit: 0
+ColumnLimit: 120
 BreakBeforeBraces: Custom
 BraceWrapping:
   AfterCaseLabel: true
@@ -22,6 +22,7 @@ BraceWrapping:
   SplitEmptyFunction: true
   SplitEmptyRecord: true
   SplitEmptyNamespace: true
+AllowShortLambdasOnASingleLine: false
 SpacesBeforeTrailingComments: 1
 AccessModifierOffset: -2
 AlignAfterOpenBracket: BlockIndent
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e52a581cf070dc12f209d7af59b18adf2204cd04..f51fd282a1e80c458fb9a0f45ccc0e6bc98c86d7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,6 +5,7 @@ variables:
 
 stages:
   - prepare
+  - lint
   - build
   - pre-release
   - release
@@ -22,6 +23,16 @@ AppImage build environment image:
   rules:
     - if: '$CI_PIPELINE_SOURCE == "web"'
 
+Lint:
+  stage: lint
+  image: $CI_REGISTRY_IMAGE/builder:latest
+  script:
+    - make lint
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+    - if: '$CI_PIPELINE_SOURCE == "web"'
+
 Build poppi AppImage:
   stage: build
   image: $CI_REGISTRY_IMAGE/builder:latest
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index 4cf8b03f9c8d1a10e191f550adeb80e9016f7250..7e6371383227617ecfae2b237f34946c9a5b3a49 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -2,9 +2,7 @@
 	"configurations": [
 		{
 			"name": "Linux",
-			"compilerPath": "/usr/bin/g++",
-			"cppStandard": "c++20",
-			"cStandard": "c17",
+			"cppStandard": "gnu++20",
 			"configurationProvider": "ms-vscode.makefile-tools"
 		}
 	],
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 3c5541d4abd20577a522356a018592d750dad4f6..f25a4348b62096aeadf4fcaf2c4397edc43704ed 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,7 +10,7 @@
 			"request": "launch",
 			"program": "${workspaceFolder}/poppi-debug",
 			"preLaunchTask": "build",
-			"args": ["localhost", "4000"],
+			"args": ["localhost"],
 			"stopAtEntry": false,
 			"cwd": "${workspaceFolder}",
 			"environment": [],
diff --git a/CPPLINT.cfg b/CPPLINT.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..cc5062ef4eb4c1d5006a526b46a449cb672673f3
--- /dev/null
+++ b/CPPLINT.cfg
@@ -0,0 +1,5 @@
+linelength = 120
+filter = -whitespace/braces,-whitespace/parens
+filter = -build/include_subdir,-build/c++11,-build/c++17,-build/namespaces,-runtime/string
+filter = -whitespace/comments,-whitespace/indent,-whitespace/newline
+filter = -legal/copyright,-readability/todo
diff --git a/Makefile b/Makefile
index 204564d84024be982808a6f8a8a2352f8e94ea02..95dbc00d1cec828c277498bc4e57d9ab29d2d1df 100644
--- a/Makefile
+++ b/Makefile
@@ -37,7 +37,7 @@ DEBUG_CPPFLAGS = -MMD -MP -MF $(@:$(DEBUG_OBJDIR)/%.o=$(DEBUG_DEPDIR)/%.d)
 DEBUG_CXXFLAGS := $(CXXFLAGS) -g -DDEBUG
 CXXFLAGS := $(CXXFLAGS) $(CXXFLAGS_RELEASE)
 
-.PHONY: release debug clean install appimage
+.PHONY: release debug clean install appimage lint
 
 release: $(TARGET)
 
@@ -50,6 +50,9 @@ install: release
 appimage: release
 	./package-appimage.sh $(APPIMAGE_TARGET) $(APPIMAGE_DIR)
 
+lint:
+	ci/lint.sh
+
 $(TARGET): $(RELEASE_OBJS)
 	$(CXX) -o $@ $^ $(CXXFLAGS) $(LINKFLAGS)
 
diff --git a/build-environment/Dockerfile b/build-environment/Dockerfile
index e7fdf453c672b7e87820c84fccc90345f8f17054..7f95741da31f60aba7c66ab8e9df6c6f8dfebb0a 100644
--- a/build-environment/Dockerfile
+++ b/build-environment/Dockerfile
@@ -1,6 +1,7 @@
 FROM ubuntu:18.04
 
 WORKDIR /app
+ENV DEBIAN_FRONTEND=noninteractive
 RUN apt-get update && apt-get upgrade -y
 
 RUN apt-get install -y \
@@ -23,6 +24,19 @@ ENV LDAI_RUNTIME_FILE="/opt/linuxdeploy/type2-runtime"
 ENV PATH="$PATH:/opt/linuxdeploy/"
 ENV CXX="g++-13"
 
+COPY install-llvm.sh install-llvm.sh
+RUN ./install-llvm.sh
+RUN rm install-llvm.sh
+
+RUN apt-get install -y python3.8 python3-pip
+RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1
+
+
+RUN pip3 install cpplint
+RUN apt-get install -y clang-format-18 clang-tidy-18
+RUN update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-18 1
+RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-18 1
+
 COPY dependencies.sh dependencies.sh
 RUN ./dependencies.sh
 RUN rm dependencies.sh
\ No newline at end of file
diff --git a/build-environment/install-llvm.sh b/build-environment/install-llvm.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ef88196f34170fae33968b1a723fa1014c95fd66
--- /dev/null
+++ b/build-environment/install-llvm.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -e
+
+wget https://apt.llvm.org/llvm.sh
+chmod +x llvm.sh
+./llvm.sh 18
+rm llvm.sh
\ No newline at end of file
diff --git a/buildinfo.json b/buildinfo.json
index 133d3d454725973388cc3d100b4e0f1fa88d2e41..e9525546f2c2fc701abd008774c54ef3d8b08dde 100644
--- a/buildinfo.json
+++ b/buildinfo.json
@@ -1,4 +1,4 @@
 {
-	"version": "0.3.1",
+	"version": "0.4.0",
 	"minServerVersion": "0.1.0"
 }
diff --git a/ci/lint.sh b/ci/lint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..41c498e76e55569e58fcf40c1c991a18d794bb18
--- /dev/null
+++ b/ci/lint.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+set -euo pipefail
+
+if ! command -v cpplint &>/dev/null; then
+    echo "Error: cpplint is not installed or not in PATH." >&2
+    exit 1
+fi
+
+if ! command -v clang-format &>/dev/null; then
+    echo "Error: clang-format is not installed or not in PATH." >&2
+    exit 1
+fi
+
+cpplint_failed=0
+clang_format_failed=0
+
+echo "Running cpplint..."
+cpplint --recursive . || cpplint_failed=$?
+
+clang_format_files=("*.cpp" "*.hpp")
+
+echo "Checking clang-format..."
+for file_type in "${clang_format_files[@]}"; do
+    find . -name "$file_type" -exec clang-format --dry-run -Werror {} + || clang_format_failed=$?
+done
+
+if [ $cpplint_failed -ne 0 ] || [ $clang_format_failed -ne 0 ]; then
+    echo "One or more checks failed."
+    exit 1
+fi
+
+echo "Lint and format check passed successfully!"
+exit 0
diff --git a/src/audioBackends/AudioBackend.cpp b/src/audioBackends/AudioBackend.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9c4ed609ff501e670e829c0a494e3fd43be106b9
--- /dev/null
+++ b/src/audioBackends/AudioBackend.cpp
@@ -0,0 +1,40 @@
+#include "AudioBackend.hpp"
+
+#include <iostream>
+#include <memory>
+#include <utility>
+
+#include "PulseAudioBackend.hpp"
+
+namespace {
+std::unique_ptr<AudioBackend> backend = nullptr;
+
+audioRequestFunction providedAudioRequestCallback;
+
+} // namespace
+
+void audio::setBackend(std::unique_ptr<AudioBackend> newBackend) { backend = std::move(newBackend); }
+
+void audio::start()
+{
+    auto audioBackend = std::make_unique<PulseAudioBackend>();
+    audioBackend->connect();
+
+    setBackend(std::move(audioBackend));
+}
+
+void audio::setCallback(audioRequestFunction callback) { providedAudioRequestCallback = callback; }
+
+audioRequestFunction audio::getCallback() { return providedAudioRequestCallback; }
+
+int64_t audio::timestampToFrameIndex(std::chrono::time_point<server_clock> timestamp)
+{
+    __uint128_t epoch = std::chrono::duration_cast<std::chrono::microseconds>(timestamp.time_since_epoch()).count();
+    return epoch * PLAYBACK_SAMPLERATE / 1000000;
+}
+
+std::chrono::time_point<server_clock> audio::frameIndexToTimestamp(int64_t frameIndex)
+{
+    int64_t epoch = static_cast<__uint128_t>(frameIndex) * 1000000 / PLAYBACK_SAMPLERATE;
+    return std::chrono::time_point<server_clock>(std::chrono::microseconds(epoch));
+}
diff --git a/src/audioBackends/AudioBackend.hpp b/src/audioBackends/AudioBackend.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f063a117f3ba1ad8f5bddadbcd965d9a3c53dc0b
--- /dev/null
+++ b/src/audioBackends/AudioBackend.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <chrono>
+#include <functional>
+#include <mutex>
+
+#include "../serverClock.hpp"
+
+#define PLAYBACK_SAMPLERATE 48000
+#define PLAYBACK_SAMPLE_BYTESIZE sizeof(float)
+
+#pragma pack(push, 1)
+struct AudioFrame
+{
+    float left;
+    float right;
+
+    AudioFrame operator+(const AudioFrame& other) const { return {left + other.left, right + other.right}; }
+
+    AudioFrame& operator+=(const AudioFrame& other)
+    {
+        left += other.left;
+        right += other.right;
+        return *this;
+    }
+};
+#pragma pack(pop)
+
+using audioRequestFunction = std::function<size_t(AudioFrame* audioData, size_t sampleCount, int64_t sampleIndex)>;
+
+class AudioBackend
+{
+  protected:
+    enum ConnectionState
+    {
+        DISCONNECTED,
+        DISCONNECTING,
+        CONNECTED,
+        CONNECTING,
+    };
+
+    ConnectionState connectionState;
+
+    std::mutex stateOperationMutex;
+
+  public:
+    virtual ~AudioBackend() {}
+    virtual void connect() = 0;
+};
+
+namespace audio {
+void setBackend(std::unique_ptr<AudioBackend> backend);
+
+void setCallback(audioRequestFunction callback);
+audioRequestFunction getCallback();
+
+void start();
+
+int64_t timestampToFrameIndex(std::chrono::time_point<server_clock> timestamp);
+std::chrono::time_point<server_clock> frameIndexToTimestamp(int64_t frameIndex);
+
+} // namespace audio
diff --git a/src/audioBackends/PulseAudioBackend.cpp b/src/audioBackends/PulseAudioBackend.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..98c42a1a9b61b3fe06969e60c14b4b7eeb70492d
--- /dev/null
+++ b/src/audioBackends/PulseAudioBackend.cpp
@@ -0,0 +1,539 @@
+#include "PulseAudioBackend.hpp"
+
+#include <algorithm>
+#include <chrono>
+#include <cmath>
+#include <iostream>
+#include <memory>
+#include <span>
+#include <string>
+#include <thread>
+#include <vector>
+
+namespace {
+
+struct averageAndJitter
+{
+    server_clock::time_point average;
+    std::chrono::microseconds jitter;
+};
+
+int64_t absoluteFrameIndex = 0;
+int64_t hardwareFrameIndex = 0;
+double playbackSpeed = 1.0;
+
+std::chrono::time_point<server_clock> hardwareIndexZeroTime = std::chrono::time_point<server_clock>::min();
+
+double calculateDriftExpo(
+    double distanceAtZero, double distanceAtEdge, double distanceAtEnd, double edgePosition, double endPosition
+)
+{
+    return std::log((distanceAtZero - distanceAtEdge) / (distanceAtZero - 2 * distanceAtEnd + distanceAtEdge)) /
+           std::log(edgePosition / endPosition);
+}
+
+double calculateDriftDistance(
+    double value, double expo, double distanceAtZero, double distanceAtEnd, double endPosition
+)
+{
+    double ratio = std::pow(std::abs(value) / endPosition, expo);
+    return ((1 - ratio) / (1 + ratio)) * (distanceAtZero - distanceAtEnd) + distanceAtEnd;
+}
+
+averageAndJitter averageAndJitterWithDiscard(
+    const std::span<const server_clock::time_point> &values, int outlierDiscardCount
+)
+{
+    if (values.size() <= 2 * outlierDiscardCount)
+    {
+        return {server_clock::time_point::min(), std::chrono::microseconds(0)};
+    }
+
+    std::vector<server_clock::time_point> sortedValues(values.begin(), values.end());
+    std::sort(sortedValues.begin(), sortedValues.end());
+
+    auto minThreshold = sortedValues[outlierDiscardCount];
+    auto maxThreshold = sortedValues[sortedValues.size() - outlierDiscardCount - 1];
+
+    std::vector<server_clock::time_point> filteredValues;
+    for (auto v : values)
+    {
+        if (v >= minThreshold && v <= maxThreshold)
+        {
+            filteredValues.push_back(v);
+        }
+    }
+
+    if (filteredValues.empty())
+    {
+        return {server_clock::time_point::min(), std::chrono::microseconds(0)};
+    }
+
+    std::chrono::microseconds sum = std::chrono::microseconds(0);
+    for (auto v : filteredValues)
+    {
+        sum += v.time_since_epoch();
+    }
+    // Casting to ssize_t as signed microsecond divided by unsigned size seems to underflow is sum is negative.
+    server_clock::time_point avg = server_clock::time_point(sum / static_cast<ssize_t>(filteredValues.size()));
+
+    std::chrono::microseconds jitter = std::chrono::microseconds(0);
+    if (filteredValues.size() > 1)
+    {
+        std::chrono::microseconds jitterSum = std::chrono::microseconds(0);
+        for (size_t i = 1; i < filteredValues.size(); ++i)
+        {
+            jitterSum += std::chrono::microseconds(std::abs((filteredValues[i] - filteredValues[i - 1]).count()));
+        }
+        jitter = jitterSum / (filteredValues.size() - 1);
+    }
+
+    return {avg, jitter};
+}
+
+} // namespace
+
+void PulseAudioBackend::timingInfoCB()
+{
+    if (!server_clock::getIsSynced())
+    {
+        return;
+    }
+
+    // TODO: detect if suddenly the difference changes a lot to notice that the difference isn't valid. If the
+    // difference is calculated with a different system time than the timing info is stored with, the difference will be
+    // invalid.
+    std::chrono::time_point<std::chrono::system_clock> currentRealTime = std::chrono::system_clock::now();
+    std::chrono::time_point<server_clock> currentServerTime = server_clock::now();
+
+    std::chrono::microseconds timeDifference = std::chrono::duration_cast<std::chrono::microseconds>(
+        currentServerTime.time_since_epoch() - currentRealTime.time_since_epoch()
+    );
+
+    const pa_timing_info *timingInfo = pa_stream_get_timing_info(playstream);
+    // Explataions on some timing info fields
+    // timingInfo->playing: 1 if hardware is playing audio, 0 if not. Essentially whether read_index is moving or not.
+    // timingInfo->read_index: The index of the next sample in the pulse buffer that the hardware will read.
+    // timingInfo->write_index: The index where the next sample should be written to the pulse buffer.
+    // timingInfo->sink_usec: Additional the time in microseconds the audio server takes to process the audio. Caused
+    //                        mainly by e.g. easyeffects plugins that cause delay.
+    // timingInfo->timestamp: The SYSTEM (bruh) time when the timing info was updated. System time converted to non
+    //                        changing server time as I don't want to handle with changing system time.
+    // timingInfo->*_index_corrupt: Supposedly means that the index is not reliable. I don't know what causes this
+    //                              as I have never seen it, but I'll just check for it and early return if it is.
+    // timingInfo->transport_usec: Time that it supposedly takes to transfer audio from pulse to hardware. I have seen
+    //                             this value mostly in the range of 0-1000 microseconds so just not bother for now.
+
+    // A bug in pipewire? pulse? easyeffects? causes sinc_usec not to update without reconnecting to the easyeffect sink
+    // if a effect delay changes. I suspect whatever asks the sink for the delay is not reasking it
+    // every time. It probably just assumes that since the sink existed before, it still has the same delay.
+    // Weirdly when I change the sink from pavucontrol, the delay is updated to the correct value.
+    // This same effect can also be observed in google chrome.
+
+    if (timingInfo == nullptr) return;
+    if (!timingInfo->playing) return;
+
+    // Calculating the timestamp in steady time when the hardware index was 0
+
+    std::chrono::time_point<std::chrono::system_clock> timingInfoSystemTimestamp =
+        std::chrono::time_point<std::chrono::system_clock>(
+            std::chrono::seconds(timingInfo->timestamp.tv_sec) +
+            std::chrono::microseconds(timingInfo->timestamp.tv_usec)
+        );
+
+    std::chrono::time_point<server_clock> timingInfoTimestamp =
+        std::chrono::time_point<server_clock>(std::chrono::duration_cast<std::chrono::microseconds>(
+            timingInfoSystemTimestamp.time_since_epoch() + timeDifference
+        ));
+
+    hardwareIndexZeroTime = std::chrono::time_point<server_clock>(
+        timingInfoTimestamp - std::chrono::microseconds(pa_bytes_to_usec(timingInfo->read_index, &playbackSpec)) +
+        std::chrono::microseconds(timingInfo->sink_usec)
+    );
+    hardwareZeroTimes[currentHardwareZeroTimeIndex] = hardwareIndexZeroTime;
+    currentHardwareZeroTimeIndex++;
+    if (currentHardwareZeroTimeIndex >= hardwareZeroTimes.size())
+    {
+        currentHardwareZeroTimeIndex = 0;
+        hardwareZeroTimesValid = true;
+    }
+    if (!hardwareZeroTimesValid)
+    {
+        return;
+    }
+
+    // positive drift means we are playing audio early and negative means we are playing audio late
+    // This function also discards the highest and lowest values to best effort get rid of random spikes
+    averageAndJitter zeroTimesAvgAndJitter = averageAndJitterWithDiscard(hardwareZeroTimes, 5);
+
+    // Have a minimum value for jitter to make drift calculation make sense
+    std::chrono::microseconds jitterEdgeValue = std::max(zeroTimesAvgAndJitter.jitter, std::chrono::microseconds(10));
+
+    // ---- Calculating drift ----
+
+    auto currentShouldWriteServerTimestamp =
+        hardwareIndexZeroTime +
+        std::chrono::microseconds(pa_bytes_to_usec(hardwareFrameIndex * PLAYBACK_SAMPLE_BYTESIZE * 2, &playbackSpec));
+
+    auto averageShouldWriteServerTimestamp =
+        zeroTimesAvgAndJitter.average +
+        std::chrono::microseconds(pa_bytes_to_usec(hardwareFrameIndex * PLAYBACK_SAMPLE_BYTESIZE * 2, &playbackSpec));
+
+    auto goingToWriteServerTimestamp = audio::frameIndexToTimestamp(absoluteFrameIndex);
+
+    std::chrono::microseconds currentDrift = std::chrono::duration_cast<std::chrono::microseconds>(
+        goingToWriteServerTimestamp - currentShouldWriteServerTimestamp
+    );
+
+    std::chrono::microseconds averageDrift = std::chrono::duration_cast<std::chrono::microseconds>(
+        goingToWriteServerTimestamp - averageShouldWriteServerTimestamp
+    );
+
+    // ---- Calculating playback speed ---- //
+
+    // Figured out via basic trial and error
+    std::chrono::seconds maxDriftValue(5);
+    std::chrono::minutes noDriftDistance(10);
+    std::chrono::milliseconds maxDriftDistance(500);
+    std::chrono::seconds jitterEdgeDistance(2);
+
+    double maxDriftValueMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(maxDriftValue).count();
+    double jitterEdgeValueMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(jitterEdgeValue).count();
+    double noDriftMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(noDriftDistance).count();
+    double maxDriftMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(maxDriftDistance).count();
+    double jitterEdgeMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(jitterEdgeDistance).count();
+
+    // This is calculated directly from the current drift and not average to react immediately to sudden wrong values,
+    // e.g. at the start of the program.
+    if (std::abs(currentDrift.count()) > maxDriftValueMicroseconds)
+    {
+        // This could be way smaller, but speeding up and slowing down audio sounds more fun. Even extra code
+        // had to be made to allow larger playback speed than miniscule normal clock drift enduced speed changes.
+        absoluteFrameIndex = audio::timestampToFrameIndex(currentShouldWriteServerTimestamp);
+        playbackSpeed = 1.0;
+        if (firstTimesync)
+        {
+            std::cout << "Pulse audio backend synced and ready for playback" << std::endl;
+            firstTimesync = false;
+        }
+        else
+            std::cerr << "Warning: drift over 5 second. Audio will jump to correct time" << std::endl;
+        return;
+    }
+
+    // The idea is to calculate a length of time in how long until synced. E.g, if drift is less than jitter, assume we
+    // are pretty synced and set speed to a value where in e.g 5 minutes we would be synced. Then again if drift is more
+    // than jitter, assume we are not synced and set speed to sync in 5 seconds.
+
+    double expo = calculateDriftExpo(
+        noDriftMicroseconds, jitterEdgeMicroseconds, maxDriftMicroseconds, jitterEdgeValueMicroseconds,
+        maxDriftValueMicroseconds
+    );
+    double distanceWithAveragedDrift = calculateDriftDistance(
+        std::abs(averageDrift.count()), expo, noDriftMicroseconds, maxDriftMicroseconds, maxDriftValueMicroseconds
+    );
+
+    std::chrono::microseconds distanceWithAveragedDriftMicroseconds(static_cast<int64_t>(distanceWithAveragedDrift));
+
+    playbackSpeed = 1.0 - static_cast<double>(averageDrift.count()) /
+                              static_cast<double>(distanceWithAveragedDriftMicroseconds.count());
+
+    if (playbackSpeed < 0.1)
+    {
+        playbackSpeed = 0.1;
+    }
+    else if (playbackSpeed > 4)
+    {
+        playbackSpeed = 4;
+    }
+}
+
+void PulseAudioBackend::audioRequestCallback(size_t length)
+{
+    AudioFrame *audioData;
+    size_t allocatedBufferLength = length;
+    pa_stream_begin_write(playstream, reinterpret_cast<void **>(&audioData), &allocatedBufferLength);
+
+    size_t allocatedHardwareBufferFrameCount = allocatedBufferLength / PLAYBACK_SAMPLE_BYTESIZE / 2;
+
+    std::fill(audioData, audioData + allocatedHardwareBufferFrameCount, AudioFrame{0.0f, 0.0f});
+
+    size_t usedHardwareBufferFrameCount = allocatedHardwareBufferFrameCount;
+    auto providedAudioRequestCallback = audio::getCallback();
+    if (providedAudioRequestCallback && server_clock::getIsSynced() && hardwareZeroTimesValid)
+    {
+        // TODO implement actual proper way to handle this and think it out. This kind of works but
+        // seems to do something weird
+        const pa_timing_info *timingInfo = pa_stream_get_timing_info(playstream);
+        if (timingInfo != nullptr && timingInfo->playing)
+        {
+            // Just before everything else, jump to start writing in position after read_index if for some reason the
+            // write_index is significantly behind the read_index. Pulse seems to ask in a loop for more data to scrub
+            // through all that would be played otherwise. E.g. after sleeping the read_index may be significantly ahead
+            // or just with SIGSTOP and SIGCONT testing.
+            if (timingInfo->read_index > timingInfo->write_index + pa_usec_to_bytes(1000000, &playbackSpec))
+            {
+                std::cout << "Warning: write_index is over a second behind read_index (e.g. from sleeping). Jumping to "
+                             "read_index + 100ms"
+                          << std::endl;
+                hardwareFrameIndex =
+                    (timingInfo->read_index + pa_usec_to_bytes(100000, &playbackSpec)) / PLAYBACK_SAMPLE_BYTESIZE / 2;
+            }
+        }
+
+        // If playing in e.g. 2x speed, pulseaudio requests a buffer of size x but we need 2x audio data to fill that.
+        // So we request 2x audio data and remove a small amount to make absolutely sure we don't ask for more data than
+        // can fit in the pulse requested buffer after resampling.
+        size_t absoluteAudioDataBufferSize = std::max<ssize_t>(
+            0,
+            static_cast<ssize_t>(allocatedHardwareBufferFrameCount) * playbackSpeed - 10 * PLAYBACK_SAMPLE_BYTESIZE * 2
+        );
+        auto absoluteAudioData = std::make_unique<AudioFrame[]>(absoluteAudioDataBufferSize);
+        std::fill(
+            absoluteAudioData.get(), absoluteAudioData.get() + absoluteAudioDataBufferSize, AudioFrame{0.0f, 0.0f}
+        );
+
+        // TODO: change things to take unique pointer references
+        size_t receivedAbsoluteFrameCount =
+            providedAudioRequestCallback(absoluteAudioData.get(), absoluteAudioDataBufferSize, absoluteFrameIndex);
+        absoluteFrameIndex += receivedAbsoluteFrameCount;
+
+        SRC_DATA resamplerData;
+        resamplerData.data_in = reinterpret_cast<float *>(absoluteAudioData.get());
+        resamplerData.data_out = reinterpret_cast<float *>(audioData);
+        resamplerData.input_frames = receivedAbsoluteFrameCount;
+        resamplerData.output_frames = allocatedHardwareBufferFrameCount;
+        resamplerData.src_ratio = 1.0 / playbackSpeed;
+        resamplerData.end_of_input = 0; // We are always playing audio. For now stopping is not considered
+
+        int processError = src_process(resamplerState, &resamplerData);
+        if (processError != 0)
+        {
+            std::cerr << "error resampling data: " << src_strerror(processError) << std::endl;
+        }
+        if (resamplerData.input_frames_used != receivedAbsoluteFrameCount)
+        {
+            std::cerr << "Warning: not all absolute audio data was used. Audio frames will be skipped" << std::endl;
+            std::cout << "input_frames: " << resamplerData.input_frames << std::endl;
+            std::cout << "output_frames: " << resamplerData.output_frames << std::endl;
+            std::cout << "input_frames_used: " << resamplerData.input_frames_used << std::endl;
+            std::cout << "output_frames_gen: " << resamplerData.output_frames_gen << std::endl;
+            std::cout << "ratio: " << resamplerData.src_ratio << std::endl;
+            std::cout << "receivedAbsoluteFrameCount: " << receivedAbsoluteFrameCount << std::endl;
+        }
+
+        usedHardwareBufferFrameCount = resamplerData.output_frames_gen;
+    }
+
+    pa_stream_write(
+        playstream, audioData, usedHardwareBufferFrameCount * PLAYBACK_SAMPLE_BYTESIZE * 2, nullptr,
+        hardwareFrameIndex * PLAYBACK_SAMPLE_BYTESIZE * 2, PA_SEEK_ABSOLUTE
+    );
+    hardwareFrameIndex += usedHardwareBufferFrameCount;
+}
+
+void PulseAudioBackend::stateCallback()
+{
+    pa_context_state_t state;
+    state = pa_context_get_state(pulseContext);
+    switch (state)
+    {
+        case PA_CONTEXT_UNCONNECTED:
+            pulseConnectionState = UNCONNECTED;
+            break;
+        case PA_CONTEXT_CONNECTING:
+            pulseConnectionState = CONNECTING;
+            break;
+        case PA_CONTEXT_AUTHORIZING:
+            pulseConnectionState = AUTHORIZING;
+            break;
+        case PA_CONTEXT_SETTING_NAME:
+            pulseConnectionState = SETTING_NAME;
+            break;
+        case PA_CONTEXT_FAILED:
+            pulseConnectionState = FAILED;
+            break;
+        case PA_CONTEXT_TERMINATED:
+            pulseConnectionState = TERMINATED;
+            break;
+        case PA_CONTEXT_READY:
+            pulseConnectionState = READY;
+            break;
+    }
+}
+
+void PulseAudioBackend::underflowCallback() { std::cerr << "underflow" << std::endl; }
+
+void PulseAudioBackend::audioPlaybackThreadFunction()
+{
+    std::cout << "start audio mainloop" << std::endl;
+
+    while (true)
+    {
+        pa_mainloop_iterate(mainloop, 1, NULL);
+
+        auto now = std::chrono::steady_clock::now();
+        if (now >= nextTimingUpdate && readyForNextTimingUpdate)
+        {
+            nextTimingUpdate = now + std::chrono::milliseconds(50);
+            readyForNextTimingUpdate = false;
+
+            pa_stream_update_timing_info(
+                playstream,
+                [](pa_stream *s, int success, void *userdata) {
+                    auto pulseBackendClassInstance = static_cast<PulseAudioBackend *>(userdata);
+                    pulseBackendClassInstance->timingInfoCB();
+                    pulseBackendClassInstance->readyForNextTimingUpdate = true;
+                },
+                this
+            );
+        }
+    }
+}
+
+void PulseAudioBackend::connect()
+{
+    std::lock_guard lock(stateOperationMutex);
+
+    if (connectionState != ConnectionState::DISCONNECTED)
+    {
+        throw std::runtime_error("Already connected");
+    }
+
+    connectionState = ConnectionState::CONNECTING;
+
+    mainloop = pa_mainloop_new();
+    mainloopAPI = pa_mainloop_get_api(mainloop);
+
+    // TODO use proplist to describe the application
+    // https://freedesktop.org/software/pulseaudio/doxygen/proplist_8h.html#details
+    pulseContext = pa_context_new(mainloopAPI, "Poppi 2");
+
+    pa_context_set_state_callback(
+        pulseContext,
+        [](pa_context *c, void *classReference) {
+            static_cast<PulseAudioBackend *>(classReference)->stateCallback();
+        },
+        this
+    );
+
+    // connect to the pulse server
+    pa_context_connect(pulseContext, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
+    // TODO add timeout
+    while (true)
+    {
+        int retval = pa_mainloop_iterate(mainloop, 1, nullptr);
+
+        if (retval < 0)
+        {
+            std::cerr << "failed iterating mainloop" << std::endl;
+            pa_context_unref(pulseContext);
+            pa_mainloop_free(mainloop);
+            return;
+        }
+
+        if (pulseConnectionState == READY)
+        {
+            break;
+        }
+
+        if (pulseConnectionState == FAILED || pulseConnectionState == TERMINATED)
+        {
+            std::cerr << "failed connecting to pulse server" << std::endl;
+            pa_context_unref(pulseContext);
+            pa_mainloop_free(mainloop);
+            return;
+        }
+    }
+
+    playstream = pa_stream_new(pulseContext, "Playback", &playbackSpec, nullptr);
+    if (playstream == nullptr)
+    {
+        std::cerr << "failed creating a new stream" << std::endl;
+        pa_context_unref(pulseContext);
+        pa_mainloop_free(mainloop);
+        return;
+    }
+
+    pa_stream_set_write_callback(
+        playstream,
+        [](pa_stream *stream, size_t length, void *classReference) {
+            static_cast<PulseAudioBackend *>(classReference)->audioRequestCallback(length);
+        },
+        this
+    );
+    pa_stream_set_underflow_callback(
+        playstream,
+        [](pa_stream *stream, void *classReference) {
+            static_cast<PulseAudioBackend *>(classReference)->underflowCallback();
+        },
+        this
+    );
+
+    bufferAttributes.maxlength = pa_usec_to_bytes(playbackBufferLength.count(), &playbackSpec);
+    // Setting prebuf to 0 disables automatic starting and stopping of playback when the buffer
+    // is empty or full enough. There is some bug that makes pulse not ask more audio data when
+    // it is stopped due to buffer running out of data. And not playing because it doesn't
+    // have enough data. This is a workaround for that. Also most likely we want to just play
+    // audio all the time and rather have crackling if audio runs out.
+    bufferAttributes.prebuf = 0;
+    bufferAttributes.tlength = -1;
+    bufferAttributes.minreq = -1;
+
+    if (pa_stream_connect_playback(playstream, nullptr, &bufferAttributes, PA_STREAM_NOFLAGS, nullptr, nullptr) < 0)
+    {
+        std::cerr << "failed connecting for playback" << std::endl;
+        pa_stream_unref(playstream);
+        pa_context_unref(pulseContext);
+        pa_mainloop_free(mainloop);
+        return;
+    }
+
+    const pa_buffer_attr *actualBufferAttributes = nullptr;
+    // TODO add timeout
+    while (actualBufferAttributes == nullptr)
+    {
+        pa_mainloop_iterate(mainloop, 1, NULL);
+        actualBufferAttributes = pa_stream_get_buffer_attr(playstream);
+    }
+
+    // bufferAttributes = *actualBufferAttributes;
+    playbackBufferLength = std::chrono::microseconds(pa_bytes_to_usec(bufferAttributes.maxlength, &playbackSpec));
+
+    // pulseSystemInitialized = true;
+    audioPlaybackThread = std::thread(&PulseAudioBackend::audioPlaybackThreadFunction, this);
+
+    connectionState = ConnectionState::CONNECTED;
+}
+
+PulseAudioBackend::PulseAudioBackend()
+{
+    connectionState = ConnectionState::DISCONNECTED;
+
+    int resamplerError;
+    resamplerState = src_new(SRC_SINC_BEST_QUALITY, 2, &resamplerError);
+    if (resamplerState == nullptr)
+    {
+        throw std::runtime_error("failed to create resampler: " + std::string(src_strerror(resamplerError)));
+    }
+
+    // timing updates run about every 50ms so 200 updates is about 10 seconds
+    for (size_t i = 0; i < hardwareZeroTimes.size(); i++)
+    {
+        hardwareZeroTimes[i] = std::chrono::time_point<server_clock>(std::chrono::microseconds(0));
+    }
+}
+
+PulseAudioBackend::~PulseAudioBackend()
+{
+    if (connectionState == ConnectionState::CONNECTED)
+    {
+        pa_stream_disconnect(playstream);
+        pa_stream_unref(playstream);
+        pa_context_disconnect(pulseContext);
+        pa_context_unref(pulseContext);
+        pa_mainloop_free(mainloop);
+    }
+
+    src_delete(resamplerState);
+}
diff --git a/src/audioBackends/PulseAudioBackend.hpp b/src/audioBackends/PulseAudioBackend.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0aab125a83fa1b8694d050724b6276bc6ab95d04
--- /dev/null
+++ b/src/audioBackends/PulseAudioBackend.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <pulse/pulseaudio.h>
+#include <samplerate.h>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <thread>
+
+#include "AudioBackend.hpp"
+
+class PulseAudioBackend : public AudioBackend
+{
+    enum PulseConnectionState
+    {
+        UNCONNECTED,
+        CONNECTING,
+        AUTHORIZING,
+        SETTING_NAME,
+        FAILED,
+        TERMINATED,
+        READY
+    };
+
+    SRC_STATE *resamplerState;
+
+    PulseConnectionState pulseConnectionState = UNCONNECTED;
+
+    pa_mainloop *mainloop = nullptr;
+    pa_mainloop_api *mainloopAPI = nullptr;
+    pa_context *pulseContext = nullptr;
+    pa_stream *playstream = nullptr;
+    pa_sample_spec playbackSpec = {
+        .format = PA_SAMPLE_FLOAT32,
+        .rate = PLAYBACK_SAMPLERATE,
+        .channels = 2,
+    };
+    pa_buffer_attr bufferAttributes;
+    std::chrono::microseconds playbackBufferLength = std::chrono::microseconds(100000);
+
+    std::chrono::time_point<std::chrono::steady_clock> nextTimingUpdate = std::chrono::steady_clock::now();
+    bool readyForNextTimingUpdate = true;
+
+    std::thread audioPlaybackThread;
+
+    bool hardwareZeroTimesValid = false;
+    bool firstTimesync = true;
+    size_t currentHardwareZeroTimeIndex = 0;
+    std::array<server_clock::time_point, 20> hardwareZeroTimes;
+
+    void audioRequestCallback(size_t length);
+
+    void audioPlaybackThreadFunction();
+    void stateCallback();
+    void timingInfoCB();
+
+    void underflowCallback();
+
+  public:
+    void connect();
+    PulseAudioBackend();
+    ~PulseAudioBackend();
+};
diff --git a/src/audioProviders/CPPLINT.cfg b/src/audioProviders/CPPLINT.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..3b2d985a816bdfd90bacb0ad9b1bce55c78109fb
--- /dev/null
+++ b/src/audioProviders/CPPLINT.cfg
@@ -0,0 +1 @@
+exclude_files = flac_compatability.hpp
diff --git a/src/audioProviders/audioProvider.hpp b/src/audioProviders/audioProvider.hpp
index 27fc9ce4fb232374fb3181b7f110d7ba7e93b944..35155ba3baf4ff951efddcd65839642cfd716bcb 100644
--- a/src/audioProviders/audioProvider.hpp
+++ b/src/audioProviders/audioProvider.hpp
@@ -3,9 +3,14 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <chrono>
+
+#include "../audioBackends/AudioBackend.hpp"
+#include "../serverClock.hpp"
+
 class AudioProvider
 {
-public:
-    virtual void getData(int16_t *audioData, size_t sampleCount, size_t sampleIndex, int64_t samplePosition) {}
-    virtual ~AudioProvider() {}
-};
\ No newline at end of file
+  public:
+    virtual void getData(AudioFrame *audioData, size_t frameCount, int64_t frameIndex) = 0;
+    virtual ~AudioProvider() = default;
+};
diff --git a/src/audioProviders/flacDecoder.cpp b/src/audioProviders/flacDecoder.cpp
index 3965a6b585839975ab2a930f53e497cfdf106368..392f5f56931e83b2330665eefa7c04a255d4ad43 100644
--- a/src/audioProviders/flacDecoder.cpp
+++ b/src/audioProviders/flacDecoder.cpp
@@ -3,10 +3,9 @@
 #include <math.h>
 #include <string.h>
 
+#include <algorithm>
 #include <iostream>
 
-#define PLAYBACK_SAMPLERATE 48000
-
 #define RAW_DATA_MILLISECONDS 5000
 
 bool FlacDecoder::getMoreData()
@@ -21,10 +20,16 @@ bool FlacDecoder::getMoreData()
     {
         int CopyAmountReduced = std::min(rawDataAllocatedLength - rawDataWritePosition, copyAmount);
 
-        memcpy(rawData + rawDataWritePosition, overflowSamples + overflowSamplesReadPosition, CopyAmountReduced * sizeof(float));
+        memcpy(
+            rawData + rawDataWritePosition, overflowSamples + overflowSamplesReadPosition,
+            CopyAmountReduced * sizeof(float)
+        );
         if (CopyAmountReduced != copyAmount)
         {
-            memcpy(rawData, overflowSamples + overflowSamplesReadPosition + CopyAmountReduced, (copyAmount - CopyAmountReduced) * sizeof(float));
+            memcpy(
+                rawData, overflowSamples + overflowSamplesReadPosition + CopyAmountReduced,
+                (copyAmount - CopyAmountReduced) * sizeof(float)
+            );
         }
 
         unreadSamplesLeft -= copyAmount;
@@ -32,7 +37,8 @@ bool FlacDecoder::getMoreData()
         overflowSamplesLength -= copyAmount;
         rawDataLength += copyAmount;
         rawDataWritePosition += copyAmount;
-        if (rawDataWritePosition >= rawDataAllocatedLength) // here it is actually needed to have >= because the whole overflow sample length may be added to it
+        if (rawDataWritePosition >= rawDataAllocatedLength) // here it is actually needed to have >= because the whole
+                                                            // overflow sample length may be added to it
         {
             rawDataWritePosition -= rawDataAllocatedLength;
         }
@@ -56,7 +62,9 @@ bool FlacDecoder::getMoreData()
     return true;
 }
 
-FLAC__StreamDecoderWriteStatus FlacDecoder::write_callback(const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[])
+FLAC__StreamDecoderWriteStatus FlacDecoder::write_callback(
+    const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[]
+)
 {
     for (uint32_t i = 0; i < frame->header.blocksize; i++)
     {
@@ -67,13 +75,13 @@ FLAC__StreamDecoderWriteStatus FlacDecoder::write_callback(const ::FLAC__Frame *
             {
                 if (channels == 1)
                 {
-                    overflowSamples[overflowSamplesLength] = (float)buffer[0][i] / (1 << bps);
-                    overflowSamples[overflowSamplesLength + 1] = (float)buffer[0][i] / (1 << bps);
+                    overflowSamples[overflowSamplesLength] = static_cast<float>(buffer[0][i]) / (1 << bps);
+                    overflowSamples[overflowSamplesLength + 1] = static_cast<float>(buffer[0][i]) / (1 << bps);
                 }
                 else
                 {
-                    overflowSamples[overflowSamplesLength] = (float)buffer[0][i] / (1 << bps);
-                    overflowSamples[overflowSamplesLength + 1] = (float)buffer[1][i] / (1 << bps);
+                    overflowSamples[overflowSamplesLength] = static_cast<float>(buffer[0][i]) / (1 << bps);
+                    overflowSamples[overflowSamplesLength + 1] = static_cast<float>(buffer[1][i]) / (1 << bps);
                 }
                 overflowSamplesLength += 2;
             }
@@ -81,13 +89,13 @@ FLAC__StreamDecoderWriteStatus FlacDecoder::write_callback(const ::FLAC__Frame *
         }
         if (channels == 1)
         {
-            rawData[rawDataWritePosition] = (float)buffer[0][i] / (1 << bps);
-            rawData[rawDataWritePosition + 1] = (float)buffer[0][i] / (1 << bps);
+            rawData[rawDataWritePosition] = static_cast<float>(buffer[0][i]) / (1 << bps);
+            rawData[rawDataWritePosition + 1] = static_cast<float>(buffer[0][i]) / (1 << bps);
         }
         else // always use max first 2 channels
         {
-            rawData[rawDataWritePosition] = (float)buffer[0][i] / (1 << bps);
-            rawData[rawDataWritePosition + 1] = (float)buffer[1][i] / (1 << bps);
+            rawData[rawDataWritePosition] = static_cast<float>(buffer[0][i]) / (1 << bps);
+            rawData[rawDataWritePosition + 1] = static_cast<float>(buffer[1][i]) / (1 << bps);
         }
 
         rawDataLength += 2;
@@ -102,7 +110,8 @@ FLAC__StreamDecoderWriteStatus FlacDecoder::write_callback(const ::FLAC__Frame *
     return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
 }
 
-// we don't need to check for the type of the callback since by default this callback is only called with type streaminfo
+// we don't need to check for the type of the callback since by default this callback is only called with type
+// streaminfo
 void FlacDecoder::metadata_callback(const ::FLAC__StreamMetadata *metadata)
 {
     bps = metadata->data.stream_info.bits_per_sample;
@@ -111,7 +120,9 @@ void FlacDecoder::metadata_callback(const ::FLAC__StreamMetadata *metadata)
     totalSamples = metadata->data.stream_info.total_samples;
     unreadSamplesLeft = totalSamples * 2;
 
-    std::cout << "bps: " << bps << "\nsamplerate: " << sampleRate << "\nchannels: " << channels << "\ntotalSamples: " << totalSamples << "\nmax blocksize: " << metadata->data.stream_info.max_blocksize << std::endl;
+    std::cout << "bps: " << bps << "\nsamplerate: " << sampleRate << "\nchannels: " << channels
+              << "\ntotalSamples: " << totalSamples << "\nmax blocksize: " << metadata->data.stream_info.max_blocksize
+              << std::endl;
 
     overflowSamplesAllocatedLength = metadata->data.stream_info.max_blocksize * 2; // 2 for channel count
     overflowSamples = new float[overflowSamplesAllocatedLength];
@@ -126,12 +137,10 @@ void FlacDecoder::error_callback(::FLAC__StreamDecoderErrorStatus status)
     std::cerr << "Got error callback" << FLAC__StreamDecoderErrorStatusString[status] << std::endl;
 }
 
-FlacDecoder::FlacDecoder()
-{
-}
+FlacDecoder::FlacDecoder() {}
 
 FlacDecoder::~FlacDecoder()
 {
     delete overflowSamples;
     delete rawData;
-}
\ No newline at end of file
+}
diff --git a/src/audioProviders/flacDecoder.hpp b/src/audioProviders/flacDecoder.hpp
index ceaab4b34de51f2eb3b62b04120b10d117be5a85..0f1dbf98d32b526576d06a1324fe8285dbb87e9b 100644
--- a/src/audioProviders/flacDecoder.hpp
+++ b/src/audioProviders/flacDecoder.hpp
@@ -6,7 +6,7 @@
 
 class FlacDecoder : public FLAC::Decoder::File
 {
-public:
+  public:
     FlacDecoder();
     ~FlacDecoder();
     bool getMoreData();
@@ -24,14 +24,16 @@ public:
     int rawDataWritePosition = 0;
     int rawDataReadPosition = 0;
 
-protected:
-    virtual ::FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[]);
+  protected:
+    virtual ::FLAC__StreamDecoderWriteStatus write_callback(
+        const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[]
+    );
     virtual void metadata_callback(const ::FLAC__StreamMetadata *metadata);
     virtual void error_callback(::FLAC__StreamDecoderErrorStatus status);
 
-private:
+  private:
     float *overflowSamples = nullptr;
     int overflowSamplesAllocatedLength = 0;
     int overflowSamplesLength = 0;
     int overflowSamplesReadPosition = 0;
-};
\ No newline at end of file
+};
diff --git a/src/audioProviders/flacProvider.cpp b/src/audioProviders/flacProvider.cpp
index 48085d0cefe91979bcc6d58cd9cfb61ae9147f5e..eede9197c206a6922c0c2a6fa40851bdfca69620 100644
--- a/src/audioProviders/flacProvider.cpp
+++ b/src/audioProviders/flacProvider.cpp
@@ -6,6 +6,7 @@
 #include <functional>
 #include <iostream>
 #include <mutex>
+#include <string>
 #include <thread>
 
 #include "../timing.hpp"
@@ -30,12 +31,26 @@ void FlacProvider::preprocessData()
 
     while (true)
     {
-        long inputFrames = std::min(flacDecoder.rawDataAllocatedLength - flacDecoder.rawDataReadPosition, flacDecoder.rawDataLength) / 2;
-        long outputFrames = std::min(preloadedDataAllocatedLength - preloadedDataWritePosition, preloadedDataAllocatedLength - preloadedDataLength) / 2;
+        size_t ringBufferReadIndex;
+        size_t ringBufferWriteIndex;
+        {
+            std::scoped_lock lock(ringBufferStartMutex, ringBufferEndMutex);
+            ringBufferReadIndex = preloadedFramesStart % preloadedAudioRingBufferSize;
+            ringBufferWriteIndex = preloadedFramesEnd % preloadedAudioRingBufferSize;
+        }
+
+        size_t ringBufferFilledLength =
+            (preloadedAudioRingBufferSize + ringBufferWriteIndex - ringBufferReadIndex) % preloadedAudioRingBufferSize;
+        size_t ringBufferFreeLength = preloadedAudioRingBufferSize - ringBufferFilledLength - 1;
+
+        size_t inputFrames =
+            std::min(flacDecoder.rawDataAllocatedLength - flacDecoder.rawDataReadPosition, flacDecoder.rawDataLength) /
+            2;
+        size_t outputFrames = std::min(ringBufferFreeLength, preloadedAudioRingBufferSize - ringBufferWriteIndex);
 
         resamplerData.end_of_input = (inputFrames == flacDecoder.rawDataLength && flacDecoder.unreadSamplesLeft == 0);
         resamplerData.data_in = flacDecoder.rawData + flacDecoder.rawDataReadPosition;
-        resamplerData.data_out = preloadedDataBuffer + preloadedDataWritePosition;
+        resamplerData.data_out = reinterpret_cast<float *>(preloadedAudioRingBuffer + ringBufferWriteIndex);
         resamplerData.input_frames = inputFrames;
         resamplerData.output_frames = outputFrames;
         resamplerData.src_ratio = resamplingRatio;
@@ -47,23 +62,23 @@ void FlacProvider::preprocessData()
         }
 
         flacDecoder.rawDataLength -= resamplerData.input_frames_used * 2;
-        preloadedDataLength += resamplerData.output_frames_gen * 2;
         flacDecoder.rawDataReadPosition += resamplerData.input_frames_used * 2;
         if (flacDecoder.rawDataReadPosition == flacDecoder.rawDataAllocatedLength)
         {
             flacDecoder.rawDataReadPosition = 0;
         }
-        preloadedDataWritePosition += resamplerData.output_frames_gen * 2;
-        if (preloadedDataWritePosition == preloadedDataAllocatedLength)
-        {
-            preloadedDataWritePosition = 0;
-        }
 
+        std::scoped_lock lock(ringBufferEndMutex);
+
+        preloadedFramesEnd += resamplerData.output_frames_gen;
         // TODO: problems may accour if data is actually left but there was no space left to process more data
+        // Fix by getting rid of mystery buffering system in flac decoder. Check max amount of data flac can generate
+        // per read and resample that directly to the ring buffer
         if (resamplerData.input_frames_used == 0)
         {
             if (flacDecoder.unreadSamplesLeft == 0)
             {
+                // I believe it is correct to also always lock this with the same end mutex
                 unconvertedAudioLeft = false;
             }
             break;
@@ -71,50 +86,101 @@ void FlacProvider::preprocessData()
     }
 }
 
-// it is assumed that sampleCount is divisible by 2 (2 channel audio)
-void FlacProvider::getData(int16_t *audioData, size_t sampleCount, size_t sampleIndex, int64_t samplePosition)
+// Frame count is the number of frames in the audioData buffer. A single frame is 2 samples (left and right channel)
+// Frame index is the index of the first frame in the audioData array since playback started.
+void FlacProvider::getData(AudioFrame *audioData, size_t frameCount, int64_t frameIndex)
 {
-    // TODO: add total samples played from provider so music playback can start from the middle of the track
-    // not so important currently
+    uint64_t firstFrameToPlayOn = audio::timestampToFrameIndex(startTime);
+
+    int64_t askedFramesStart = frameIndex - firstFrameToPlayOn;
+    int64_t askedFramesEnd = askedFramesStart + frameCount;
 
-    int64_t playbackStartTime = serverTimeToLocalTime(startTime);
-    int64_t sampleCountInMicroseconds = sampleCount * 1000000 / 2 / PLAYBACK_SAMPLERATE;
+    size_t outputIndex = 0;
 
-    // code to offset audio playback start to sync with playback start time that has been set previously
-    size_t i = 0;
-    if (samplePosition + sampleCountInMicroseconds <= playbackStartTime)
+    // Song start checks
+    if (askedFramesEnd < 0)
     {
+        // Exit early if all samples asked for are before the start time of the song
         return;
     }
-    else if (samplePosition < playbackStartTime)
+    if (askedFramesStart < 0)
     {
-        i += (samplePosition + sampleCountInMicroseconds - playbackStartTime) * PLAYBACK_SAMPLERATE * 2 / 1000000;
+        // Some samples are before the start time of the song, offset the output to skip before the start time
+        outputIndex = -askedFramesStart;
+        askedFramesStart = 0;
     }
 
-    for (; i < sampleCount; i += 2)
+    // TODO: Maybe just create a ring buffer class that handles all the ring buffer logic
     {
-        if (preloadedDataLength == 0)
+        std::scoped_lock lock(ringBufferStartMutex, ringBufferEndMutex);
+
+        // Buffer start checks
+        if (askedFramesEnd <= preloadedFramesStart)
+        {
+            // All frames asked are before the start of the preloaded data. Wait in silence
+            // TODO: Add navigation to requested position in the song instead of just waiting in silence
+            std::cerr << "asked for audio frames before preloaded data\n";
+            std::cerr << "asked: " << askedFramesStart << " -> " << askedFramesEnd << " | has: " << preloadedFramesStart
+                      << " -> " << preloadedFramesEnd << std::endl;
+            return;
+        }
+        if (askedFramesStart < preloadedFramesStart)
+        {
+            // Some of asked frames are before the start of the preloaded data. Offset the output to skip before the
+            // start of the preloaded data
+            std::cerr << "asked for audio frames before preloaded data\n";
+            std::cerr << "asked: " << askedFramesStart << " -> " << askedFramesEnd << " | has: " << preloadedFramesStart
+                      << " -> " << preloadedFramesEnd << std::endl;
+            outputIndex = preloadedFramesStart - askedFramesStart;
+            askedFramesStart = preloadedFramesStart;
+        }
+
+        // Buffer end checks
+        if (askedFramesStart >= preloadedFramesEnd)
         {
-            if (!unconvertedAudioLeft)
+            // If not, song has ended
+            if (unconvertedAudioLeft)
             {
-                // reached to the end of the audio file
-                shouldRemove = true;
+                // All frames asked are after the end of the preloaded data. Empty preloaded data and exit early
+                // TODO: Add navigation to requested position in the song instead of forcing the flac
+                // decoder to fast forward to the requested position
+                std::cerr << "asked for audio frames after preloaded data\n";
+                std::cerr << "asked: " << askedFramesStart << " -> " << askedFramesEnd
+                          << " | has: " << preloadedFramesStart << " -> " << preloadedFramesEnd << std::endl;
+                preloadedFramesStart = preloadedFramesEnd;
             }
-            else
+            return;
+        }
+        if (askedFramesEnd > preloadedFramesEnd)
+        {
+            // If not, song has ended but end of song has not been played yet
+            if (unconvertedAudioLeft)
             {
-                std::cerr << "ran out of preloaded samples before music ended" << std::endl;
+                // Some of asked frames are after the end of the preloaded data, offset the output to skip after the end
+                // of the preloaded data
+                std::cerr << "asked for audio frames after preloaded data\n";
+                std::cerr << "asked: " << askedFramesStart << " -> " << askedFramesEnd
+                          << " | has: " << preloadedFramesStart << " -> " << preloadedFramesEnd << std::endl;
             }
-            break;
+            askedFramesEnd = preloadedFramesEnd;
         }
-        audioData[i] = sat_adds16b(audioData[i], preloadedDataBuffer[preloadedDataReadPosition] * 32767);
-        audioData[i + 1] = sat_adds16b(audioData[i + 1], preloadedDataBuffer[preloadedDataReadPosition + 1] * 32767);
+    }
 
-        preloadedDataLength -= 2;
-        preloadedDataReadPosition += 2;
-        if (preloadedDataReadPosition == preloadedDataAllocatedLength)
-        {
-            preloadedDataReadPosition = 0;
-        }
+    for (int64_t askedFrame = askedFramesStart; askedFrame < askedFramesEnd; askedFrame++)
+    {
+        size_t frameIndexInRingBuffer = askedFrame % preloadedAudioRingBufferSize;
+
+        // Remember to add to audio data instead of overwriting
+        audioData[outputIndex] += preloadedAudioRingBuffer[frameIndexInRingBuffer];
+        outputIndex++;
+    }
+
+    std::lock_guard lock(ringBufferStartMutex);
+    preloadedFramesStart = askedFramesEnd;
+
+    if (askedFramesEnd >= preloadedFramesEnd && unconvertedAudioLeft == false)
+    {
+        shouldRemove = true;
     }
 }
 
@@ -123,7 +189,8 @@ bool FlacProvider::loadFile(std::string filepath)
     FLAC__StreamDecoderInitStatus init_status = flacDecoder.init(filepath);
     if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
     {
-        std::cerr << "error initializing flac decoder: " << FLAC__StreamDecoderInitStatusString[init_status] << std::endl;
+        std::cerr << "error initializing flac decoder: " << FLAC__StreamDecoderInitStatusString[init_status]
+                  << std::endl;
         return false;
     }
 
@@ -133,7 +200,7 @@ bool FlacProvider::loadFile(std::string filepath)
         std::cerr << "error processing metadata: " << FLAC__StreamDecoderStateString[state] << std::endl;
         return false;
     }
-    resamplingRatio = (double)PLAYBACK_SAMPLERATE / flacDecoder.sampleRate;
+    resamplingRatio = static_cast<double>(PLAYBACK_SAMPLERATE) / flacDecoder.sampleRate;
 
     std::cout << "samplerate: " << flacDecoder.sampleRate << std::endl;
 
@@ -152,15 +219,17 @@ bool FlacProvider::initResampler()
     return true;
 }
 
-FlacProvider::FlacProvider(std::string filepath, std::chrono::milliseconds _startTime, std::function<void()> _onEndedCallback)
+FlacProvider::FlacProvider(
+    std::string filepath, std::chrono::time_point<server_clock> _startTime, std::function<void()> _onEndedCallback
+)
     : startTime(_startTime), flacDecoder(), onEndedCallback(_onEndedCallback)
 {
-    preloadedDataAllocatedLength = 2 * PRELOADED_DATA_MILLISECONDS * PLAYBACK_SAMPLERATE / 1000;
-    preloadedDataBuffer = new float[preloadedDataAllocatedLength];
+    preloadedAudioRingBufferSize = PRELOADED_DATA_MILLISECONDS * PLAYBACK_SAMPLERATE / 1000;
+    preloadedAudioRingBuffer = new AudioFrame[preloadedAudioRingBufferSize];
 
     if (loadFile(filepath) == false)
     {
-        delete[] preloadedDataBuffer;
+        delete[] preloadedAudioRingBuffer;
         throw std::runtime_error("failed loading file: " + filepath);
     }
 
@@ -200,7 +269,7 @@ FlacProvider::~FlacProvider()
 {
     shouldExitPreprocessingThread = true;
     preprocessingThread.join();
-    delete[] preloadedDataBuffer;
+    delete[] preloadedAudioRingBuffer;
 }
 
 void FlacProvider::waitForInitialPreprosessing()
@@ -209,4 +278,4 @@ void FlacProvider::waitForInitialPreprosessing()
     preprocessingConditionVariable.wait(lock, [this]() {
         return preprocessingFinished;
     });
-}
\ No newline at end of file
+}
diff --git a/src/audioProviders/flacProvider.hpp b/src/audioProviders/flacProvider.hpp
index 85e86d08299b3126b820e7f4750fb24918d081ec..8807a48ee6f06a3e5620b80832088b4485bbdb29 100644
--- a/src/audioProviders/flacProvider.hpp
+++ b/src/audioProviders/flacProvider.hpp
@@ -9,8 +9,10 @@
 #include <condition_variable>
 #include <functional>
 #include <mutex>
+#include <string>
 #include <thread>
 
+#include "../audioBackends/AudioBackend.hpp"
 #include "audioProvider.hpp"
 #include "flacDecoder.hpp"
 
@@ -18,18 +20,23 @@
 
 class FlacProvider : public AudioProvider
 {
-public:
-    FlacProvider(std::string filepath, std::chrono::milliseconds _startTime, std::function<void()> _onEndedCallback);
+  public:
+    FlacProvider(
+        std::string filepath, std::chrono::time_point<server_clock> _startTime, std::function<void()> _onEndedCallback
+    );
     ~FlacProvider();
-    void getData(int16_t *audioData, size_t sampleCount, size_t sampleIndex, int64_t samplePosition);
+    void getData(AudioFrame *audioData, size_t frameCount, int64_t frameIndex);
+
     void waitForInitialPreprosessing();
 
-private:
+  private:
     bool loadFile(std::string filepath);
     void preprocessData();
     bool initResampler();
 
-    std::chrono::milliseconds startTime;
+    uint64_t firstSampleIndex = 0;
+
+    std::chrono::time_point<server_clock> startTime;
 
     bool unconvertedAudioLeft = true;
 
@@ -40,12 +47,14 @@ private:
 
     double resamplingRatio;
 
-    float *preloadedDataBuffer;
-    // it is assumed that all of these aare divisible by 2 (2 channel audio)               // 1 frame is 2 samples (2 channel audio)
-    int preloadedDataAllocatedLength = 0;
-    std::atomic<int> preloadedDataLength = 0; // has to be atomic as the playback thread and the preprocessing thread modifies this at the same time
-    int preloadedDataReadPosition = 0;
-    int preloadedDataWritePosition = 0;
+    AudioFrame *preloadedAudioRingBuffer;
+    int preloadedAudioRingBufferSize = 0;
+
+    // Start inclusive, end exclusive
+    std::mutex ringBufferStartMutex;
+    std::mutex ringBufferEndMutex;
+    uint64_t preloadedFramesStart = 0;
+    uint64_t preloadedFramesEnd = 0;
 
     bool shouldRemove = false;
 
@@ -56,4 +65,4 @@ private:
     std::condition_variable preprocessingConditionVariable;
     bool preprocessingFinished = false;
     std::atomic<bool> shouldExitPreprocessingThread = false;
-};
\ No newline at end of file
+};
diff --git a/src/audioProviders/flac_compatability.hpp b/src/audioProviders/flac_compatability.hpp
index 0430e845bc5dac376f26f1f52c36747612a04c03..6b54ea38d53d7604cd1abaf32d641f2422c5c155 100644
--- a/src/audioProviders/flac_compatability.hpp
+++ b/src/audioProviders/flac_compatability.hpp
@@ -99,8 +99,8 @@
 #endif
 
 #if defined _MSC_VER || defined __MINGW32__ || defined __EMX__
-#include <io.h>    /* for _setmode(), chmod() */
 #include <fcntl.h> /* for _O_BINARY */
+#include <io.h>    /* for _setmode(), chmod() */
 #else
 #include <unistd.h> /* for chown(), unlink() */
 #endif
@@ -211,4 +211,4 @@ extern "C"
 };
 #endif
 
-#endif /* FLAC__SHARE__COMPAT_H */
\ No newline at end of file
+#endif /* FLAC__SHARE__COMPAT_H */
diff --git a/src/audioSystem.cpp b/src/audioSystem.cpp
index 101462c7986dc46c2b7c61acb84ee7e1f29467c5..a13e44d3648457c7970f8a1194efc8331ab4d41e 100644
--- a/src/audioSystem.cpp
+++ b/src/audioSystem.cpp
@@ -12,6 +12,7 @@
 #include <thread>
 #include <vector>
 
+#include "audioBackends/AudioBackend.hpp"
 #include "audioProviders/flacProvider.hpp"
 #include "timing.hpp"
 
@@ -21,8 +22,6 @@
 
 // TODO: cleanup sample bytesize mess
 
-static std::thread audioPlaybackThread;
-
 static std::mutex mainThreadMutex;
 static std::condition_variable mainThreadConditionVariable;
 static bool mainThreadShouldContinue = false;
@@ -30,10 +29,6 @@ static bool mainThreadShouldContinue = false;
 static std::mutex audioProviderPlaybackThreadMutex;
 // stop
 
-// TODO: make global
-#define PLAYBACK_SAMPLERATE 48000
-#define PLAYBACK_SAMPLE_BYTESIZE sizeof(int16_t)
-
 int playbackBufferSize = 80000;
 
 static std::vector<AudioProvider *> audioProviders;
@@ -71,7 +66,6 @@ void shutdownAudioSystem()
     }
     pa_mainloop_quit(pa_ml, 0);
     */
-    audioPlaybackThread.join();
 }
 
 #define PLAYBACK_TIME_POSITION_ROUND_COUNT 50
@@ -91,285 +85,16 @@ int64_t playbackStartTimeCalibrationMinMaxDifference = 0; // TODO: fix variable
 
 std::vector<int64_t> playbackStartPoints(PLAYBACK_TIME_POSITION_ROUND_COUNT);
 
-static void timingInfoCB(pa_stream *s, int success, void *userdata)
-{
-    pa_usec_t usec;
-    int neg;
-
-    const pa_timing_info *timingInfo = pa_stream_get_timing_info(playstream);
-
-    if (timingInfo == nullptr)
-        return;
-    /*
-    std::cout << "configured_sink_usec   " << timingInfo->configured_sink_usec << "\n";
-    std::cout << "configured_source_usec " << timingInfo->configured_source_usec << "\n";
-    std::cout << "playing                " << timingInfo->playing << "\n";
-    std::cout << "read_index             " << timingInfo->read_index << "\n";
-    std::cout << "read_index_corrupt     " << timingInfo->read_index_corrupt << "\n";
-    std::cout << "since_underrun         " << timingInfo->since_underrun << "\n";
-    std::cout << "sink_usec              " << timingInfo->sink_usec << "\n";
-    std::cout << "source_usec            " << timingInfo->source_usec << "\n";
-    std::cout << "synchronized_clocks    " << timingInfo->synchronized_clocks << "\n";
-    std::cout << "timestamp.tv_sec       " << timingInfo->timestamp.tv_sec << "\n";
-    std::cout << "timestamp.tv_usec      " << timingInfo->timestamp.tv_usec << "\n";
-    std::cout << "transport_usec         " << timingInfo->transport_usec << "\n";
-    std::cout << "write_index            " << timingInfo->write_index << "\n";
-    std::cout << "write_index_corrupt    " << timingInfo->write_index_corrupt << std::endl;
-    */
-
-    if (timingInfo->playing)
-    {
-        playbackStartPoints[playbackTimePositionIndex] = (int64_t)timingInfo->timestamp.tv_sec * 1000000 + timingInfo->timestamp.tv_usec - pa_bytes_to_usec(timingInfo->read_index, &playbackSpec);
-
-        playbackTimePositionIndex++;
-        if (playbackTimePositionIndex == PLAYBACK_TIME_POSITION_ROUND_COUNT)
-        {
-            /*
-            std::cout << "normal clock start time: " << playbackStartTime << std::endl;
-            std::cout << "steady clock start time: " << playbackStartTimeSteadyClock << std::endl
-                      << std::endl;
-                      */
-            playbackTimePositionIndex = 0;
-            playbackTimeCalibrated = true;
-        }
-        if (playbackTimeCalibrated)
-        {
-            int64_t playbackStartTimeRounding = 0;
-            int64_t minTime = LONG_LONG_MAX;
-            int64_t maxTime = LONG_LONG_MIN;
-            for (auto playbackStartPoint : playbackStartPoints) // TODO: maybe add checking for if local time has been adjusted by checking if values are too different
-            {
-                minTime = std::min(minTime, playbackStartPoint);
-                maxTime = std::max(maxTime, playbackStartPoint);
-                playbackStartTimeRounding += playbackStartPoint / PLAYBACK_TIME_POSITION_ROUND_COUNT;
-            }
-            playbackStartTime = playbackStartTimeRounding;
-            playbackStartTimeCalibrationMinMaxDifference = maxTime - minTime;
-
-            // FIXME: add system to check if local time has jumped
-
-            auto currentRealTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
-            auto currentSteadyTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch());
-
-            int64_t microsecondsSincePlaybackStarted = currentRealTime.count() - playbackStartTime;
-            playbackStartTimeSteadyClock = currentSteadyTime.count() - microsecondsSincePlaybackStarted;
-        }
-    }
-    /*
-    int getLatencyReturn = pa_stream_get_latency(playstream, &usec, &neg);
-
-    if (getLatencyReturn == -PA_ERR_NODATA)
-    {
-        printf("nodata\n");
-    }
-    else
-    {
-        printf("latency %lu us\n", usec);
-        std::cout << "data left: " << timingInfo->write_index - timingInfo->read_index << std::endl;
-    }
-    */
-}
-
-static size_t getAudio(int16_t *audioData, size_t sampleCount, size_t sampleIndex, int64_t samplePosition)
+static size_t getAudio(AudioFrame *audioData, size_t frameCount, int64_t frameIndex)
 {
-    std::fill(audioData, audioData + sampleCount, 0);
-
     audioProviderPlaybackThreadMutex.lock();
     for (AudioProvider *audioProvider : audioProviders)
     {
-        audioProvider->getData(audioData, sampleCount, sampleIndex, samplePosition); // TODO: better handling of saturated addition
+        audioProvider->getData(audioData, frameCount, frameIndex);
     }
     audioProviderPlaybackThreadMutex.unlock();
 
-    return sampleCount;
-}
-
-static size_t audioWriteIndex = 0;
-
-bool firstCalibrated = true;
-
-static bool pulseSystemInitialized = false;
-
-static void stream_request_cb(pa_stream *s, long unsigned int length, void *userdata)
-{
-    // std::cout << "asked for " << length / 2 << " samples of audio" << std::endl;
-    // std::cout << "playbackBuffer size: " << pa_usec_to_bytes(playbackBufferSize, &playbackSpec) / 2 << " samples" << std::endl;
-
-    pa_stream_update_timing_info(s, timingInfoCB, nullptr);
-
-    if (!playbackTimeCalibrated || firstCalibrated)
-    {
-        int16_t *audioData;
-        long unsigned int audioDataLength = length;
-
-        pa_stream_begin_write(s, (void **)&audioData, &audioDataLength);
-        // TODO: handle errors: https://freedesktop.org/software/pulseaudio/doxygen/stream_8h.html#a6cf50cfc4ea8897391941184d74d7dfa
-        std::fill(audioData, audioData + audioDataLength / PLAYBACK_SAMPLE_BYTESIZE, 0);
-        pa_stream_write(s, audioData, audioDataLength, nullptr, 0, PA_SEEK_RELATIVE);
-        // TODO: handle errors: https://freedesktop.org/software/pulseaudio/doxygen/stream_8h.html#a4fc69dec0cc202fcc174125dc88dada7
-        audioWriteIndex += audioDataLength;
-
-        if (firstCalibrated && playbackTimeCalibrated)
-        {
-            firstCalibrated = false;
-            calibratePlaybackTiming = false;
-            mainThreadShouldContinue = true;
-            mainThreadConditionVariable.notify_one();
-        }
-
-        return;
-    }
-
-    int16_t *audioData;
-    long unsigned int audioDataLength = length;
-    pa_stream_begin_write(s, (void **)&audioData, &audioDataLength);
-
-    long unsigned int writtenDataLength = getAudio(audioData, audioDataLength / PLAYBACK_SAMPLE_BYTESIZE, audioWriteIndex / PLAYBACK_SAMPLE_BYTESIZE, playbackStartTimeSteadyClock + pa_bytes_to_usec(audioWriteIndex, &playbackSpec)) * PLAYBACK_SAMPLE_BYTESIZE;
-    pa_stream_write(s, audioData, writtenDataLength, nullptr, 0, PA_SEEK_RELATIVE);
-    audioWriteIndex += writtenDataLength;
+    return frameCount;
 }
 
-static void stream_underflow_cb(pa_stream *s, void *userdata)
-{
-    std::cout << "stream underflow" << std::endl;
-}
-
-static void pa_state_cb(pa_context *c, void *userdata)
-{
-    pa_context_state_t state;
-    state = pa_context_get_state(c);
-    switch (state)
-    {
-        case PA_CONTEXT_UNCONNECTED:
-            std::cout << "pa_state_cb: PA_CONTEXT_UNCONNECTED" << std::endl;
-            break;
-        case PA_CONTEXT_CONNECTING:
-            std::cout << "pa_state_cb: PA_CONTEXT_CONNECTING" << std::endl;
-            break;
-        case PA_CONTEXT_AUTHORIZING:
-            std::cout << "pa_state_cb: PA_CONTEXT_AUTHORIZING" << std::endl;
-            break;
-        case PA_CONTEXT_SETTING_NAME:
-            std::cout << "pa_state_cb: PA_CONTEXT_SETTING_NAME" << std::endl;
-            break;
-        default:
-            std::cout << "pa_state_cb: default" << std::endl;
-            break;
-        case PA_CONTEXT_FAILED:
-            std::cout << "pa_state_cb: PA_CONTEXT_FAILED" << std::endl;
-            pa_ready = 2;
-            break;
-        case PA_CONTEXT_TERMINATED:
-            pa_ready = 2;
-            std::cout << "pa_state_cb: PA_CONTEXT_TERMINATED" << std::endl;
-            break;
-        case PA_CONTEXT_READY:
-            pa_ready = 1;
-            std::cout << "pa_state_cb: PA_CONTEXT_READY" << std::endl;
-            break;
-    }
-}
-
-static bool initPulseSystem()
-{
-    playbackSpec.format = PA_SAMPLE_S16LE;
-    playbackSpec.channels = 2;
-    playbackSpec.rate = PLAYBACK_SAMPLERATE;
-
-    pa_ml = pa_mainloop_new();
-    pa_mlapi = pa_mainloop_get_api(pa_ml);
-    pa_ctx = pa_context_new(pa_mlapi, "poppi2");
-
-    pa_context_set_state_callback(pa_ctx, pa_state_cb, NULL);
-
-    pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
-    while (pa_ready == 0)
-    {
-        pa_mainloop_iterate(pa_ml, 1, NULL);
-    }
-
-    if (pa_ready == 2)
-    {
-        std::cerr << "failed connecting to pulse server" << std::endl;
-        pa_context_unref(pa_ctx);
-        pa_mainloop_free(pa_ml);
-        return false;
-    }
-
-    playstream = pa_stream_new(pa_ctx, "Playback", &playbackSpec, NULL);
-    if (playstream == NULL)
-    {
-        std::cerr << "failed creating a new stream" << std::endl;
-        pa_context_unref(pa_ctx);
-        pa_mainloop_free(pa_ml);
-        return false;
-    }
-
-    pa_stream_set_write_callback(playstream, stream_request_cb, NULL);
-    pa_stream_set_underflow_callback(playstream, stream_underflow_cb, NULL);
-
-    bufferAttributes.maxlength = pa_usec_to_bytes(playbackBufferSize, &playbackSpec);
-    bufferAttributes.prebuf = pa_usec_to_bytes(playbackBufferSize, &playbackSpec);
-    bufferAttributes.tlength = pa_usec_to_bytes(playbackBufferSize, &playbackSpec); // because PA_STREAM_ADJUST_LATENCY is set, the playback buffer size is automatically set to the same as this
-    bufferAttributes.minreq = 0;
-
-    if (pa_stream_connect_playback(playstream, NULL, &bufferAttributes, (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY), NULL, NULL) < 0)
-    {
-        std::cerr << "failed connecting for playback" << std::endl;
-        pa_stream_unref(playstream);
-        pa_context_unref(pa_ctx);
-        pa_mainloop_free(pa_ml);
-        return false;
-    }
-
-    const pa_buffer_attr *actualBufferAttributes = nullptr;
-    while (actualBufferAttributes == nullptr)
-    {
-        pa_mainloop_iterate(pa_ml, 1, NULL);
-        actualBufferAttributes = pa_stream_get_buffer_attr(playstream);
-    }
-
-    bufferAttributes = *actualBufferAttributes;
-    playbackBufferSize = pa_bytes_to_usec(bufferAttributes.maxlength, &playbackSpec);
-
-    pulseSystemInitialized = true;
-    return true;
-}
-
-static void audioPlaybackThreadFunction()
-{
-    std::cout << "start mainloop" << std::endl;
-    while (true)
-    {
-        pa_mainloop_iterate(pa_ml, 1, NULL);
-    }
-}
-
-static void startCalibratePlaybackTiming()
-{
-    calibratePlaybackTiming = true;
-    std::unique_lock<std::mutex> lk(mainThreadMutex);
-    mainThreadConditionVariable.wait(lk, [] { return mainThreadShouldContinue; });
-    mainThreadShouldContinue = false;
-}
-
-bool startAudioSystem()
-{
-    if (initPulseSystem() == false)
-    {
-        std::cerr << "failed initializing pulse system" << std::endl;
-        return false;
-    }
-    std::cout << "Initialized pulse system" << std::endl;
-
-    audioPlaybackThread = std::thread(audioPlaybackThreadFunction);
-    // audioPreprocessorThread = std::thread(audioPreprocessorThreadFunction);
-
-    std::cout << "Created playback and preprocessing threads" << std::endl;
-
-    startCalibratePlaybackTiming();
-    std::cout << "calibration done" << std::endl;
-    std::cout << "starting time: " << playbackStartTime << " with max difference of: " << playbackStartTimeCalibrationMinMaxDifference << " microseconds" << std::endl;
-
-    return true;
-}
+void startAudioSystem() { audio::setCallback(getAudio); }
diff --git a/src/audioSystem.hpp b/src/audioSystem.hpp
index 3a17ba16fd12fd4b93ea0bfa85a967341dca9ccd..9361430a32e5aee60947c09aa826203aca16f353 100644
--- a/src/audioSystem.hpp
+++ b/src/audioSystem.hpp
@@ -2,10 +2,10 @@
 
 #include "audioProviders/audioProvider.hpp"
 
-bool startAudioSystem();
+void startAudioSystem();
 
 void shutdownAudioSystem();
 
 void addAudioProvider(AudioProvider *provider);
 
-void removeAudioProvider(AudioProvider *provider);
\ No newline at end of file
+void removeAudioProvider(AudioProvider *provider);
diff --git a/src/crashHandler.cpp b/src/crashHandler.cpp
index 1c02b5dc3df86ad8f29e62152182295cf79573bf..59c8607c2a0003fc92457954fdce9f5cc940e54f 100644
--- a/src/crashHandler.cpp
+++ b/src/crashHandler.cpp
@@ -8,6 +8,7 @@
 #include <cstring>
 #include <functional>
 #include <iostream>
+#include <string>
 #include <thread>
 
 bool isRestartableExit(int exitInfo)
@@ -49,7 +50,8 @@ bool isRestartableExit(int exitInfo)
                 basicExplanation = "Abort: Unhandled exception";
                 break;
             default:
-                basicExplanation = "Exited by signal \"" + signalName + "\" (" + std::to_string(terminationSignal) + ")";
+                basicExplanation =
+                    "Exited by signal \"" + signalName + "\" (" + std::to_string(terminationSignal) + ")";
                 break;
         }
     }
@@ -63,8 +65,7 @@ bool isRestartableExit(int exitInfo)
 
     if (isRestartable)
     {
-        std::cerr << "\n\n\n"
-                  << basicExplanation << std::endl;
+        std::cerr << "\n\n\n" << basicExplanation << std::endl;
     }
 
     return isRestartable;
@@ -77,7 +78,10 @@ bool isRestartableExit(int exitInfo)
 static int restartCount = 0;
 static std::chrono::time_point<std::chrono::steady_clock> burstFirstRestartTime = std::chrono::steady_clock::now();
 
-void runWithCrashHandler(ExecutionParameters params, std::function<int(ExecutionParameters)> mainFunction, std::function<void()> beforeRestart)
+void runWithCrashHandler(
+    ExecutionParameters params, std::function<int(ExecutionParameters)> mainFunction,
+    std::function<void()> beforeRestart
+)
 {
     bool firstRun = true;
     bool shouldRestart = true;
@@ -88,8 +92,7 @@ void runWithCrashHandler(ExecutionParameters params, std::function<int(Execution
         if (pid == 0)
         {
             // Don't print the version number on the first run as it is already printed in the parent process
-            if (!firstRun)
-                beforeRestart();
+            if (!firstRun) beforeRestart();
             firstRun = false;
 
             int exitCode = mainFunction(params);
@@ -124,16 +127,18 @@ void runWithCrashHandler(ExecutionParameters params, std::function<int(Execution
         {
             if (restartCount >= BURST_RESTART_LIMIT)
             {
-                std::cerr << "It has crashed " << restartCount << " times in " << std::chrono::duration_cast<std::chrono::seconds>(BURST_RESTART_TIME).count() << " seconds. Waiting "
-                          << std::chrono::duration_cast<std::chrono::seconds>(AFTER_BURST_FAIL_RESTART_TIME).count() << " seconds before restarting again." << std::endl;
+                std::cerr << "It has crashed " << restartCount << " times in "
+                          << std::chrono::duration_cast<std::chrono::seconds>(BURST_RESTART_TIME).count()
+                          << " seconds. Waiting "
+                          << std::chrono::duration_cast<std::chrono::seconds>(AFTER_BURST_FAIL_RESTART_TIME).count()
+                          << " seconds before restarting again." << std::endl;
                 std::this_thread::sleep_for(AFTER_BURST_FAIL_RESTART_TIME);
 
                 burstFirstRestartTime = currentTime;
             }
         }
 
-        std::cerr << "Restarting...\n\n\n"
-                  << std::endl;
+        std::cerr << "Restarting...\n\n\n" << std::endl;
         restartCount++;
     }
 }
diff --git a/src/crashHandler.hpp b/src/crashHandler.hpp
index f293d49fd47b287ab6d955271909992ff964bf69..7b434e4429f6eae0cef45ececad5dae670095c2b 100644
--- a/src/crashHandler.hpp
+++ b/src/crashHandler.hpp
@@ -4,4 +4,7 @@
 
 #include "poppi.hpp"
 
-void runWithCrashHandler(ExecutionParameters params, std::function<int(ExecutionParameters)> mainFunction, std::function<void()> beforeRestart);
+void runWithCrashHandler(
+    ExecutionParameters params, std::function<int(ExecutionParameters)> mainFunction,
+    std::function<void()> beforeRestart
+);
diff --git a/src/fileSystem.cpp b/src/fileSystem.cpp
index d5024d3ed0c5a4a8f564ee82d0eb52a51e552924..0e5290d00b1643c1efb3199df4ecf105ffd8554c 100644
--- a/src/fileSystem.cpp
+++ b/src/fileSystem.cpp
@@ -9,15 +9,9 @@
 #include <string>
 #include <vector>
 
-void removeFile(std::string name)
-{
-    std::remove(name.c_str());
-}
+void removeFile(std::string name) { std::remove(name.c_str()); }
 
-void moveFile(std::string oldName, std::string newName)
-{
-    std::rename(oldName.c_str(), newName.c_str());
-}
+void moveFile(std::string oldName, std::string newName) { std::rename(oldName.c_str(), newName.c_str()); }
 
 void appendToFile(std::string name, unsigned char *data, uint16_t length)
 {
@@ -56,7 +50,7 @@ std::string getFileHash(std::string filename)
     std::stringstream ss;
     for (int i = 0; i < md5_digest_len; i++)
     {
-        ss << std::hex << std::setw(2) << std::setfill('0') << (int)md5_digest[i];
+        ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(md5_digest[i]);
     }
     OPENSSL_free(md5_digest);
     return ss.str();
@@ -72,7 +66,4 @@ std::vector<std::string> getFiles()
     return files;
 }
 
-void createDirectories()
-{
-    std::filesystem::create_directory("music/");
-}
\ No newline at end of file
+void createDirectories() { std::filesystem::create_directory("music/"); }
diff --git a/src/fileSystem.hpp b/src/fileSystem.hpp
index 7d061d608985b03a4c01147b7962b9b0b50c4c5f..0b094a3d47875d68a578f495d096b9cc0119339a 100644
--- a/src/fileSystem.hpp
+++ b/src/fileSystem.hpp
@@ -11,4 +11,4 @@ void appendToFile(std::string name, unsigned char *data, uint16_t length);
 std::string getFileHash(std::string filename);
 std::vector<std::string> getFiles();
 
-void createDirectories();
\ No newline at end of file
+void createDirectories();
diff --git a/src/glibcPolyfills/strerrorname_np.hpp b/src/glibcPolyfills/strerrorname_np.hpp
index 07b26744afa854122c31eb11071bbe009daa0b70..7799c7d303c8dc5d15730a0522254f91317c7ef5 100644
--- a/src/glibcPolyfills/strerrorname_np.hpp
+++ b/src/glibcPolyfills/strerrorname_np.hpp
@@ -71,4 +71,4 @@ const char* strerrorname_np(int errnum)
             return "Unknown error";
     }
 }
-#endif
\ No newline at end of file
+#endif
diff --git a/src/networking/TCPClient.cpp b/src/networking/TCPClient.cpp
index 278277e5dd4923e1875e13b9cc1af25e6b92c960..660b3ce58ad9aeeb078a6791d1bb31eb1e8dfe3d 100644
--- a/src/networking/TCPClient.cpp
+++ b/src/networking/TCPClient.cpp
@@ -7,6 +7,8 @@
 #include <unistd.h>
 
 #include <cstring>
+#include <iostream>
+#include <string>
 #include <vector>
 
 #include "../glibcPolyfills/strerrorname_np.hpp"
@@ -26,16 +28,27 @@ std::string explainConnectError(int error, addrinfo* addressInfo)
             break;
         case EHOSTUNREACH:
         case EHOSTDOWN:
-            errorExplanation = "Usually means the host is down, doesn't exist or a firewall is blocking the connection. (Some router that received the original packet from its source/gateway port doesn't know how to route it forward because it doesn't exist on the network)";
+            errorExplanation =
+                "Usually means the host is down, doesn't exist or a firewall is blocking the connection. (Some router "
+                "that received the original packet from its source/gateway port doesn't know how to route it forward "
+                "because it doesn't exist on the network)";
             break;
         case ENETUNREACH:
             if (addressInfo->ai_family == AF_INET)
-                errorExplanation = "Usually means that you are not connected to the internet. (The address is in another network and this network (usually you) doesn't have a default gateway to where route packets that are not in the same network)";
+                errorExplanation =
+                    "Usually means that you are not connected to the internet. (The address is in another network and "
+                    "this network (usually you) doesn't have a default gateway to where route packets that are not in "
+                    "the same network)";
             else if (addressInfo->ai_family == AF_INET6)
-                errorExplanation = "Since IPv6 is not widely used, this is likely because you don't have an IPv6 address. (The address is in another network and this network (usually you) doesn't have a default gateway to where route packets that are not in the same network)";
+                errorExplanation =
+                    "Since IPv6 is not widely used, this is likely because you don't have an IPv6 address. (The "
+                    "address is in another network and this network (usually you) doesn't have a default gateway to "
+                    "where route packets that are not in the same network)";
             break;
         case ETIMEDOUT:
-            errorExplanation = "Usually means that the host down or a firewall is dropping all incoming connections without a response.";
+            errorExplanation =
+                "Usually means that the host down or a firewall is dropping all incoming connections without a "
+                "response.";
             break;
     }
     std::string fullErrorString = "connect error: " + errnoErrorString;
@@ -135,7 +148,8 @@ struct addrinfo* resolveAddress(std::string host, std::string port)
     {
         int errnoError = errno;
 
-        std::string connectErrorString = "Failed to resolve address \"" + host + "\"\n" + explainResolveError(errnoError, status);
+        std::string connectErrorString =
+            "Failed to resolve address \"" + host + "\"\n" + explainResolveError(errnoError, status);
 
         throw TCPClient::ConnectException(connectErrorString);
     }
@@ -160,7 +174,8 @@ void TCPClient::connect(std::string hostAddress, std::string port)
 
     addrinfo* addressInfo = resolveAddress(hostAddress, port);
 
-    // To my knowledge, this should never happen as resolveAddress should always return either a valid addrinfo struct or throw an exception.
+    // To my knowledge, this should never happen as resolveAddress should always return either a valid addrinfo struct
+    // or throw an exception.
     if (addressInfo == nullptr)
     {
         throw ConnectException("Unknown error when resolving address (addrinfo is nullptr)");
@@ -168,12 +183,14 @@ void TCPClient::connect(std::string hostAddress, std::string port)
 
     std::vector<std::pair<int, addrinfo*>> connectErrors;
 
+    addrinfo* originalAddressInfo = addressInfo;
     while (addressInfo != nullptr)
     {
         socketfd = socket(addressInfo->ai_family, addressInfo->ai_socktype, addressInfo->ai_protocol);
         if (socketfd < 0)
         {
-            std::cerr << "Error when creating socket??: \"" << strerrorname_np(errno) << "\" " << strerror(errno) << std::endl;
+            std::cerr << "Error when creating socket??: \"" << strerrorname_np(errno) << "\" " << strerror(errno)
+                      << std::endl;
             addressInfo = addressInfo->ai_next;
             continue;
         }
@@ -203,12 +220,16 @@ void TCPClient::connect(std::string hostAddress, std::string port)
         {
             addrinfo* errorAddressInfo = connectErrors[0].second;
 
-            int getNameInfoError = getnameinfo(errorAddressInfo->ai_addr, errorAddressInfo->ai_addrlen, resolvedIp, NI_MAXHOST, resolvedPort, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV);
+            int getNameInfoError = getnameinfo(
+                errorAddressInfo->ai_addr, errorAddressInfo->ai_addrlen, resolvedIp, NI_MAXHOST, resolvedPort,
+                NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV
+            );
 
             if (getNameInfoError != 0)
             {
                 std::cerr << "Failed to get info about name info: " << gai_strerror(getNameInfoError) << std::endl;
-                connectErrorString = ". Could not get address from internal address structure but was able to resolve it???\n";
+                connectErrorString =
+                    ". Could not get address from internal address structure but was able to resolve it???\n";
             }
             else
             {
@@ -222,12 +243,16 @@ void TCPClient::connect(std::string hostAddress, std::string port)
             for (auto& [connectErrno, errorAddressInfo] : connectErrors)
             {
                 connectErrorString += "\n";
-                int getNameInfoError = getnameinfo(errorAddressInfo->ai_addr, errorAddressInfo->ai_addrlen, resolvedIp, NI_MAXHOST, resolvedPort, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV);
+                int getNameInfoError = getnameinfo(
+                    errorAddressInfo->ai_addr, errorAddressInfo->ai_addrlen, resolvedIp, NI_MAXHOST, resolvedPort,
+                    NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV
+                );
 
                 if (getNameInfoError != 0)
                 {
                     std::cerr << "Failed to get info about name info: " << gai_strerror(getNameInfoError) << std::endl;
-                    connectErrorString += "    Could not get address from internal address structure but was able to resolve it???\n    ";
+                    connectErrorString +=
+                        "    Could not get address from internal address structure but was able to resolve it???\n    ";
                 }
                 else
                 {
@@ -237,16 +262,18 @@ void TCPClient::connect(std::string hostAddress, std::string port)
             }
         }
 
+        freeaddrinfo(originalAddressInfo);
         throw ConnectException(connectErrorString);
     }
 
-    freeaddrinfo(addressInfo);
+    freeaddrinfo(originalAddressInfo);
 
     int setsockoptFlag = 1;
     int setsocketoptError = setsockopt(socketfd, IPPROTO_TCP, TCP_NODELAY, &setsockoptFlag, sizeof(int));
     if (setsocketoptError < 0)
     {
-        std::cerr << "ALERT! Failed to set TCP_NODELAY: \"" << strerrorname_np(errno) << "\" " << strerror(errno) << "\n";
+        std::cerr << "ALERT! Failed to set TCP_NODELAY: \"" << strerrorname_np(errno) << "\" " << strerror(errno)
+                  << "\n";
         std::cerr << "This might cause sync issues." << std::endl;
     }
 
@@ -279,12 +306,16 @@ void TCPClient::disconnect()
 
     if (socketState == SocketState::DISCONNECTING)
     {
-        if (finishedCV.wait_for(lock, 60s, [&] { return socketState == SocketState::DISCONNECTED; }))
+        if (finishedCV.wait_for(lock, 60s, [&] {
+                return socketState == SocketState::DISCONNECTED;
+            }))
         {
             return;
         }
 
-        throw std::runtime_error("Data receiving thread did not exit in 60 seconds after disconnecting the socket via another thread.");
+        throw std::runtime_error(
+            "Data receiving thread did not exit in 60 seconds after disconnecting the socket via another thread."
+        );
     }
 
     socketState = SocketState::DISCONNECTING;
@@ -295,11 +326,15 @@ void TCPClient::disconnect()
         int errnoError = errno;
         if (errnoError != ENOTCONN)
         {
-            throw std::runtime_error("Failed to shutdown socket: " + std::string(strerrorname_np(errnoError)) + ": " + strerror(errnoError));
+            throw std::runtime_error(
+                "Failed to shutdown socket: " + std::string(strerrorname_np(errnoError)) + ": " + strerror(errnoError)
+            );
         }
     }
 
-    if (finishedCV.wait_for(lock, 10s, [&] { return socketState == SocketState::DISCONNECTED; }))
+    if (finishedCV.wait_for(lock, 10s, [&] {
+            return socketState == SocketState::DISCONNECTED;
+        }))
     {
         return;
     }
@@ -313,16 +348,22 @@ void TCPClient::disconnect()
         int errnoError = errno;
         if (errnoError != ENOTCONN)
         {
-            throw std::runtime_error("Failed to shutdown socket: " + std::string(strerrorname_np(errnoError)) + ": " + strerror(errnoError));
+            throw std::runtime_error(
+                "Failed to shutdown socket: " + std::string(strerrorname_np(errnoError)) + ": " + strerror(errnoError)
+            );
         }
     }
 
-    if (finishedCV.wait_for(lock, 20s, [&] { return socketState == SocketState::DISCONNECTED; }))
+    if (finishedCV.wait_for(lock, 20s, [&] {
+            return socketState == SocketState::DISCONNECTED;
+        }))
     {
         return;
     }
 
-    throw std::runtime_error("Data receiving thread did not exit in 20 seconds after forcefully disconnecting the socket.");
+    throw std::runtime_error(
+        "Data receiving thread did not exit in 20 seconds after forcefully disconnecting the socket."
+    );
 }
 
 void TCPClient::send(const char* data, size_t size, bool corc)
diff --git a/src/networking/TCPClient.hpp b/src/networking/TCPClient.hpp
index 306b20a72b51096161a82883cd8a789c8b1ced83..cd5bb1dc9d0f8f4a0d9c88876574a435bb364443 100644
--- a/src/networking/TCPClient.hpp
+++ b/src/networking/TCPClient.hpp
@@ -3,6 +3,7 @@
 #include <condition_variable>
 #include <iostream>
 #include <mutex>
+#include <string>
 
 class TCPClient
 {
@@ -50,24 +51,24 @@ class TCPClient
     class ConnectException : public std::runtime_error
     {
       public:
-        ConnectException(std::string message) : std::runtime_error(message) {}
+        explicit ConnectException(std::string message) : std::runtime_error(message) {}
     };
 
     class SendException : public std::runtime_error
     {
       public:
-        SendException(std::string message) : std::runtime_error(message) {}
+        explicit SendException(std::string message) : std::runtime_error(message) {}
     };
 
     class ReceiveException : public std::runtime_error
     {
       public:
-        ReceiveException(std::string message) : std::runtime_error(message) {}
+        explicit ReceiveException(std::string message) : std::runtime_error(message) {}
     };
 
     class NotConnectedException : public std::runtime_error
     {
       public:
-        NotConnectedException(std::string message) : std::runtime_error(message) {}
+        explicit NotConnectedException(std::string message) : std::runtime_error(message) {}
     };
 };
diff --git a/src/networking/messageHandler.cpp b/src/networking/messageHandler.cpp
index 88f46c80e4c75cc8429e7d175c262e3636f0da58..7352fa6478bb24ac26b5b566cedebe9f100320ee 100644
--- a/src/networking/messageHandler.cpp
+++ b/src/networking/messageHandler.cpp
@@ -10,9 +10,11 @@
 #include <stdexcept>
 #include <string>
 #include <thread>
+#include <vector>
 
 #include "../fileSystem.hpp"
 #include "../playbackSystem/playbackSystem.hpp"
+#include "../serverClock.hpp"
 #include "networking.hpp"
 #include "protocolEnums.hpp"
 
@@ -35,11 +37,14 @@ void onMessageReceived(RecieveMessageType messageType, unsigned char *message, u
             auto playbackStartTime = std::chrono::milliseconds(ntohll(*reinterpret_cast<uint64_t *>(message + 8)));
             std::string filename = std::string(reinterpret_cast<char *>(message + 16), length - 16);
 
+            std::chrono::time_point<server_clock> startTime = std::chrono::time_point<server_clock>(playbackStartTime);
+
             // Might be a problem that addNewMusic and removeMusic do block for a few moments.
-            // What makes this hard with multithreading, is that addNewMusic and removeMusic needs to be called in the same order as they are recieved.
+            // What makes this hard with multithreading, is that addNewMusic and removeMusic needs to be called in the
+            // same order as they are recieved.
             try
             {
-                addNewMusic(id, filename, playbackStartTime);
+                addNewMusic(id, filename, startTime);
             }
             catch (std::runtime_error &e)
             {
@@ -100,7 +105,10 @@ void onMessageReceived(RecieveMessageType messageType, unsigned char *message, u
                 std::cout << "got last chunk for file: " << filename << std::endl;
                 try
                 {
-                    networking::sendMessage(SendMessageType::FILE_DONE, reinterpret_cast<unsigned char *>(const_cast<char *>(filename.c_str())), filename.length());
+                    networking::sendMessage(
+                        SendMessageType::FILE_DONE,
+                        reinterpret_cast<unsigned char *>(const_cast<char *>(filename.c_str())), filename.length()
+                    );
                 }
                 catch (networking::NotConnectedException &e)
                 {
@@ -124,7 +132,10 @@ void onMessageReceived(RecieveMessageType messageType, unsigned char *message, u
                 uint32_t fileCountNetwork = htonl(fileCount);
                 try
                 {
-                    networking::sendMessage(SendMessageType::FILE_LIST_INIT, reinterpret_cast<unsigned char *>(&fileCountNetwork), sizeof(fileCount));
+                    networking::sendMessage(
+                        SendMessageType::FILE_LIST_INIT, reinterpret_cast<unsigned char *>(&fileCountNetwork),
+                        sizeof(fileCount)
+                    );
                 }
                 catch (NotConnectedException &e)
                 {
@@ -144,7 +155,9 @@ void onMessageReceived(RecieveMessageType messageType, unsigned char *message, u
 
                     try
                     {
-                        networking::sendMessage(SendMessageType::FILE_LIST_ENTRY, data, file.length() + hash.length() + 1);
+                        networking::sendMessage(
+                            SendMessageType::FILE_LIST_ENTRY, data, file.length() + hash.length() + 1
+                        );
                     }
                     catch (NotConnectedException &e)
                     {
@@ -210,4 +223,4 @@ void onMessageReceived(RecieveMessageType messageType, unsigned char *message, u
         default:
             break;
     }
-}
\ No newline at end of file
+}
diff --git a/src/networking/networking.cpp b/src/networking/networking.cpp
index c1a20c2184ce186277b38d1972a1ee2cb56441d3..6e9dfb5bb05faf9ca6d1d5a390459d137089f41b 100644
--- a/src/networking/networking.cpp
+++ b/src/networking/networking.cpp
@@ -4,13 +4,16 @@
 
 #include <atomic>
 #include <condition_variable>
+#include <cstdio>
 #include <cstring>
 #include <fstream>
 #include <iostream>
 #include <mutex>
 #include <sstream>
+#include <string>
 #include <thread>
 
+#include "../serverClock.hpp"
 #include "../timing.hpp"
 #include "TCPClient.hpp"
 #include "messageHandler.hpp"
@@ -60,27 +63,23 @@ void sendProtocolMessage(ProtocolMessageType type, unsigned char *message, uint1
 {
     {
         std::lock_guard<std::mutex> lk(protocolInitializedMutex);
-        if (!protocolInitialized)
-            throw TCPClient::NotConnectedException("not connected to server");
+        if (!protocolInitialized) throw TCPClient::NotConnectedException("not connected to server");
     }
     std::lock_guard<std::mutex> lock(sendPacketMutex);
 
     uint8_t header[HEADER_SIZE];
     header[0] = type;
-    uint16_t *header_messageLength = (uint16_t *)&header[1];
+    uint16_t *header_messageLength = reinterpret_cast<uint16_t *>(&header[1]);
     *header_messageLength = htons(length + HEADER_SIZE);
 
-    tcpClient->send((char *)&header, HEADER_SIZE);
+    tcpClient->send(reinterpret_cast<char *>(&header), HEADER_SIZE);
     if (length != 0)
     {
-        tcpClient->send((char *)message, length);
+        tcpClient->send(reinterpret_cast<char *>(message), length);
     }
 }
 
-void sendProtocolMessage(ProtocolMessageType type)
-{
-    sendProtocolMessage(type, nullptr, 0);
-}
+void sendProtocolMessage(ProtocolMessageType type) { sendProtocolMessage(type, nullptr, 0); }
 
 #define TEMP_MESSAGE_TIMEOUT std::chrono::milliseconds(10000)
 
@@ -92,15 +91,13 @@ bool syncThreadShouldContinue = false;
 class PongTimeoutException : public std::runtime_error
 {
   public:
-    PongTimeoutException(const std::string &message)
-        : std::runtime_error(message) {}
+    explicit PongTimeoutException(const std::string &message) : std::runtime_error(message) {}
 };
 
 class ClientNotConnectedException : public std::runtime_error
 {
   public:
-    ClientNotConnectedException(const std::string &message)
-        : std::runtime_error(message) {}
+    explicit ClientNotConnectedException(const std::string &message) : std::runtime_error(message) {}
 };
 
 void waitForPongMessage()
@@ -116,14 +113,12 @@ void waitForPongMessage()
     while (true)
     {
         auto syncWaitTime = latestRecievedMessageTime.load() - std::chrono::steady_clock::now() + TEMP_MESSAGE_TIMEOUT;
-        if (syncWaitTime < std::chrono::milliseconds(0))
-            break;
+        if (syncWaitTime < std::chrono::milliseconds(0)) break;
 
         cvWaitForExitedByNotify = syncThreadNotificationVariable.wait_for(lk, syncWaitTime, [&] {
             return syncThreadShouldContinue;
         });
-        if (cvWaitForExitedByNotify)
-            break;
+        if (cvWaitForExitedByNotify) break;
     }
     syncThreadShouldContinue = false;
 
@@ -169,7 +164,9 @@ void clockSyncThreadFunction()
                 clocksSynced = false;
                 pongRecieveCount = 0;
 
-                syncThreadNotificationVariable.wait(lk, [&] { return protocolInitialized; });
+                syncThreadNotificationVariable.wait(lk, [&] {
+                    return protocolInitialized;
+                });
                 std::cout << "protocol initialized" << std::endl;
             }
         }
@@ -205,14 +202,22 @@ void clockSyncThreadFunction()
         }
         pongRecieveCount++;
 
-        std::chrono::microseconds oneWayLatency = std::chrono::duration_cast<std::chrono::microseconds>(clockSyncMessageHighResRecieveTime.load() - clockSyncMessageHighResSendTime) / 2;
+        std::chrono::microseconds oneWayLatency =
+            std::chrono::duration_cast<std::chrono::microseconds>(
+                clockSyncMessageHighResRecieveTime.load() - clockSyncMessageHighResSendTime
+            ) /
+            2;
 
         // TODO: detect if diff suddenly shiftes a lot and alert the user
-        std::chrono::microseconds clockDiff = std::chrono::duration_cast<std::chrono::microseconds>(clockSyncMessageHighResRecieveTime.load().time_since_epoch()) - latestRecievedServerTime + oneWayLatency;
+        std::chrono::microseconds clockDiff = std::chrono::duration_cast<std::chrono::microseconds>(
+                                                  clockSyncMessageHighResRecieveTime.load().time_since_epoch()
+                                              ) -
+                                              latestRecievedServerTime + oneWayLatency;
 
         // Rank differences by how recent they are and how accurate the latency is.
-        // Drift per hour is just arbitrarily assumed to be around what it can be in the worst case. It should rather be slightly too high than too low.
-        // Too low drift causes it to retain old data for too long causing the application clock to drift. Too high drift causes it to discard data too quickly resulting in jittery clock
+        // Drift per hour is just arbitrarily assumed to be around what it can be in the worst case. It should rather be
+        // slightly too high than too low. Too low drift causes it to retain old data for too long causing the
+        // application clock to drift. Too high drift causes it to discard data too quickly resulting in jittery clock
         // and not as accurate clock as it prefers newer ping results that had higher latency.
         std::chrono::microseconds clockDriftPerHour = std::chrono::milliseconds(10);
 
@@ -220,8 +225,12 @@ void clockSyncThreadFunction()
         std::chrono::microseconds leastAccurateDifference = std::chrono::milliseconds(0);
         for (size_t i = 0; auto &difference : differences)
         {
-            std::chrono::microseconds timeSinceRecieve = std::chrono::duration_cast<std::chrono::microseconds>(clockSyncMessageHighResRecieveTime.load() - difference.timeRecieved);
-            std::chrono::microseconds clockDrift = std::chrono::microseconds(static_cast<int64_t>(timeSinceRecieve.count() * (static_cast<float>(clockDriftPerHour.count()) / 60 / 60 / 1000 / 1000)));
+            std::chrono::microseconds timeSinceRecieve = std::chrono::duration_cast<std::chrono::microseconds>(
+                clockSyncMessageHighResRecieveTime.load() - difference.timeRecieved
+            );
+            std::chrono::microseconds clockDrift = std::chrono::microseconds(static_cast<int64_t>(
+                timeSinceRecieve.count() * (static_cast<float>(clockDriftPerHour.count()) / 60 / 60 / 1000 / 1000)
+            ));
 
             if (difference.oneWayLatency + clockDrift > leastAccurateDifference)
             {
@@ -232,12 +241,11 @@ void clockSyncThreadFunction()
         }
 
         // If worst drift compensated latency is still better than the new one, then the new one is discarded
-        // Only if we are on exactly the PONG_COUNT_BEFORE_CONSIDERED_CONNECTED pong count, we allow the new one to be used
-        // to not make the system wait for the next better difference before reporting that it is connected
+        // Only if we are on exactly the PONG_COUNT_BEFORE_CONSIDERED_CONNECTED pong count, we allow the new one to be
+        // used to not make the system wait for the next better difference before reporting that it is connected
         if (oneWayLatency > leastAccurateDifference && pongRecieveCount != PONG_COUNT_BEFORE_CONSIDERED_CONNECTED)
         {
-            if (pongRecieveCount >= PONG_COUNT_BEFORE_CONSIDERED_CONNECTED)
-                std::this_thread::sleep_for(PING_INTERVAL);
+            if (pongRecieveCount >= PONG_COUNT_BEFORE_CONSIDERED_CONNECTED) std::this_thread::sleep_for(PING_INTERVAL);
             continue;
         }
 
@@ -246,7 +254,8 @@ void clockSyncThreadFunction()
         differences[leastAccurateIndex].oneWayLatency = oneWayLatency;
         differences[leastAccurateIndex].timeRecieved = clockSyncMessageHighResRecieveTime.load();
 
-        // If we have received a total of at least CACHED_DIFFERENCE_COUNT pongs, then we can consider the connection to be stable
+        // If we have received a total of at least CACHED_DIFFERENCE_COUNT pongs, then we can consider the connection to
+        // be stable
         if (pongRecieveCount >= PONG_COUNT_BEFORE_CONSIDERED_CONNECTED)
         {
             auto differenceAverage = std::chrono::microseconds(0);
@@ -256,7 +265,9 @@ void clockSyncThreadFunction()
             }
             differenceAverage /= CACHED_DIFFERENCE_COUNT;
 
-            setSyncDifference(std::chrono::duration_cast<std::chrono::milliseconds>(differenceAverage));
+            server_clock::setSteadyClockOffset(differenceAverage);
+
+            // setSyncDifference(std::chrono::duration_cast<std::chrono::milliseconds>(differenceAverage));
 
             if (!clocksSynced)
             {
@@ -270,7 +281,8 @@ void clockSyncThreadFunction()
     }
 }
 
-// hostname(7) says max hostname length is 253, but I'm not sure if that includes the null terminator and a few bytes more doesn't hurt
+// hostname(7) says max hostname length is 253, but I'm not sure if that includes the null terminator and a few bytes
+// more doesn't hurt
 #define HOSTNAME_BUFFER_LENGTH 255
 
 void sendInitInfo()
@@ -283,7 +295,7 @@ void sendInitInfo()
     if (gethostname(hostnameBuffer, HOSTNAME_BUFFER_LENGTH) != 0)
     {
         std::cerr << "failed to get hostname" << std::endl;
-        strcpy(hostnameBuffer, "unknown");
+        snprintf(hostnameBuffer, HOSTNAME_BUFFER_LENGTH, "unknown");
     }
 
     std::string hostname(hostnameBuffer, strnlen(hostnameBuffer, HOSTNAME_BUFFER_LENGTH));
@@ -307,13 +319,13 @@ void sendInitInfo()
         std::cout << "uuid file contents too long" << std::endl;
         header[0] = 0;
     }
-    tcpClient->send((char *)header, 1);
+    tcpClient->send(reinterpret_cast<char *>(header), 1);
     if (header[0] != 0)
     {
         tcpClient->send(uuid.c_str(), uuid.length());
     }
     header[0] = hostname.length();
-    tcpClient->send((char *)header, 1);
+    tcpClient->send(reinterpret_cast<char *>(header), 1);
     tcpClient->send(hostname.c_str(), hostname.length());
 }
 
@@ -336,7 +348,8 @@ bool disconnectFromServer()
 
 std::string previousConnectError = "";
 
-// TODO: Implement system where reconnection isn't attempted until everything dependent on the connection has been reset to waiting until connected state
+// TODO: Implement system where reconnection isn't attempted until everything dependent on the connection has been reset
+// to waiting until connected state
 void connectToServer()
 {
     std::cout << "trying to connect to server" << std::endl;
@@ -403,7 +416,8 @@ void connectionThreadFunction()
 
         if (bytesRead == 0) // disconnect
         {
-            // TODO: handle random disconnects by distinguishing between them and the ones that are caused by actual wantend disconnects
+            // TODO: handle random disconnects by distinguishing between them and the ones that are caused by actual
+            // wantend disconnects
             disconnectFromServer();
 
             std::cout << "disconnected suddenly. Waiting 1 second before reconnect attempt" << std::endl;
@@ -427,13 +441,11 @@ void connectionThreadFunction()
             // init data format: [id length: 1 byte][id: id length bytes]
             // if id length is 0, then no id is sent and current is valid
 
-            if (dataBufferLength < 1)
-                continue;
+            if (dataBufferLength < 1) continue;
 
             uint8_t uuidLength = tcpDataBuffer[0];
 
-            if (dataBufferLength < uuidLength + 1)
-                continue;
+            if (dataBufferLength < uuidLength + 1) continue;
 
             std::string uuid(reinterpret_cast<char *>(tcpDataBuffer + 1), uuidLength);
 
@@ -450,13 +462,14 @@ void connectionThreadFunction()
             }
 
             {
-                // nothing should be able to send packets before this point, but leaving the lock for the sake of consistency
+                // nothing should be able to send packets before this point, but leaving the lock for the sake of
+                // consistency
                 std::lock_guard<std::mutex> lock(sendPacketMutex);
                 uint8_t header[1];
                 header[0] = 0;
                 try
                 {
-                    tcpClient->send((char *)header, 1);
+                    tcpClient->send(reinterpret_cast<char *>(header), 1);
                 }
                 catch (TCPClient::NotConnectedException &e)
                 {
@@ -486,7 +499,8 @@ void connectionThreadFunction()
             uint16_t currentPacketLength = ntohs(reinterpret_cast<uint16_t *>(tcpDataBuffer + 1)[0]);
             if (currentPacketLength < HEADER_SIZE)
             {
-                std::cerr << "Invalid packet size. Packet size claimed smaller than header size: " << currentPacketLength << std::endl;
+                std::cerr << "Invalid packet size. Packet size claimed smaller than header size: "
+                          << currentPacketLength << std::endl;
                 std::cerr << "Disconnecting because of invalid packets" << std::endl;
 
                 disconnectFromServer();
@@ -519,7 +533,8 @@ void connectionThreadFunction()
                         std::cerr << "Pong message length is invalid: " << currentPacketLength << std::endl;
                         break;
                     }
-                    latestRecievedServerTime = std::chrono::milliseconds(ntohll(reinterpret_cast<uint64_t *>(tcpDataBuffer + HEADER_SIZE)[0]));
+                    latestRecievedServerTime =
+                        std::chrono::milliseconds(ntohll(reinterpret_cast<uint64_t *>(tcpDataBuffer + HEADER_SIZE)[0]));
 
                     clockSyncMessageHighResRecieveTime = std::chrono::steady_clock::now();
 
@@ -534,7 +549,10 @@ void connectionThreadFunction()
                 {
                     RecieveMessageType messageType = static_cast<RecieveMessageType>((tcpDataBuffer + HEADER_SIZE)[0]);
 
-                    onMessageReceived(messageType, reinterpret_cast<unsigned char *>(tcpDataBuffer) + HEADER_SIZE + 1, currentPacketLength - HEADER_SIZE - 1);
+                    onMessageReceived(
+                        messageType, reinterpret_cast<unsigned char *>(tcpDataBuffer) + HEADER_SIZE + 1,
+                        currentPacketLength - HEADER_SIZE - 1
+                    );
                     break;
                 }
                 default:
@@ -566,26 +584,22 @@ void networking::sendMessage(SendMessageType type, const unsigned char *message,
 {
     {
         std::lock_guard<std::mutex> lk(protocolInitializedMutex);
-        if (!protocolInitialized)
-            throw NotConnectedException("not connected to server");
+        if (!protocolInitialized) throw NotConnectedException("not connected to server");
     }
     std::lock_guard<std::mutex> lock(sendPacketMutex);
 
     int totalMessageLength = HEADER_SIZE + 1 + length;
     uint8_t header[HEADER_SIZE + 1];
     header[0] = ProtocolMessageType::MESSAGE;
-    uint16_t *header_messageLength = (uint16_t *)&header[1];
+    uint16_t *header_messageLength = reinterpret_cast<uint16_t *>(&header[1]);
     *header_messageLength = htons(totalMessageLength);
     header[3] = static_cast<uint8_t>(type);
 
-    tcpClient->send((char *)&header, HEADER_SIZE + 1);
+    tcpClient->send(reinterpret_cast<char *>(&header), HEADER_SIZE + 1);
     if (length != 0)
     {
-        tcpClient->send((char *)message, length);
+        tcpClient->send(reinterpret_cast<const char *>(message), length);
     }
 }
 
-void networking::sendMessage(SendMessageType type)
-{
-    sendMessage(type, nullptr, 0);
-}
+void networking::sendMessage(SendMessageType type) { sendMessage(type, nullptr, 0); }
diff --git a/src/networking/networking.hpp b/src/networking/networking.hpp
index f765d6ebae5575c327ce59fb16da74b15d2d8f97..ec4b347c8889a45fafb814614cb0c54aca7e31e8 100644
--- a/src/networking/networking.hpp
+++ b/src/networking/networking.hpp
@@ -13,16 +13,15 @@
 #define htonll(x) (x)
 #define ntohll(x) (x)
 #else
-#define htonll(x) (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
-#define ntohll(x) (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#define htonll(x) ((static_cast<uint64_t>(htonl((x) & 0xFFFFFFFF)) << 32) | htonl((x) >> 32))
+#define ntohll(x) ((static_cast<uint64_t>(ntohl((x) & 0xFFFFFFFF)) << 32) | ntohl((x) >> 32))
 #endif
 
 namespace networking {
 class NotConnectedException : public std::runtime_error
 {
   public:
-    NotConnectedException(const std::string& message)
-        : std::runtime_error(message) {}
+    explicit NotConnectedException(const std::string& message) : std::runtime_error(message) {}
 };
 
 void start(std::string host, std::string port);
diff --git a/src/playbackSystem/playbackSystem.cpp b/src/playbackSystem/playbackSystem.cpp
index fdd32b0ae70246181838085f3dfefd85615030c1..50b98e718acd1fbf272194d9b710e04370e1c818 100644
--- a/src/playbackSystem/playbackSystem.cpp
+++ b/src/playbackSystem/playbackSystem.cpp
@@ -3,6 +3,7 @@
 #include <iostream>
 #include <map>
 #include <mutex>
+#include <string>
 
 #include "../audioProviders/flacProvider.hpp"
 #include "../audioSystem.hpp"
@@ -12,7 +13,7 @@
 static std::map<uint64_t, AudioProvider *> audioProviders;
 static std::recursive_mutex audioProvidersMutex;
 
-void addNewMusic(uint64_t id, std::string filename, std::chrono::milliseconds startTime)
+void addNewMusic(uint64_t id, std::string filename, std::chrono::time_point<server_clock> startTime)
 {
     std::lock_guard<std::recursive_mutex> lock(audioProvidersMutex);
 
diff --git a/src/playbackSystem/playbackSystem.hpp b/src/playbackSystem/playbackSystem.hpp
index bcec13e78a8d24642a095aa0983d0d0bfb60f811..e82d89378fe5e805a2b2179b711b8b0d61b19399 100644
--- a/src/playbackSystem/playbackSystem.hpp
+++ b/src/playbackSystem/playbackSystem.hpp
@@ -8,11 +8,13 @@
 #include <thread>
 #include <vector>
 
-void addNewMusic(uint64_t id, std::string filename, std::chrono::milliseconds startTime);
+#include "../serverClock.hpp"
+
+void addNewMusic(uint64_t id, std::string filename, std::chrono::time_point<server_clock> startTime);
 
 void removeMusic(uint64_t id);
 
 // TODO: Probably remove the timing system all together
 void addEvent(std::function<void()> callback, std::chrono::milliseconds eventTime);
 
-void runTimingSystemLoop();
\ No newline at end of file
+void runTimingSystemLoop();
diff --git a/src/playbackSystem/playbackSystemLogic.cpp b/src/playbackSystem/playbackSystemLogic.cpp
index fa43a599831a6f3c66516fb23513046f84fda493..0cad5b9e810bd083202c0a47160b1dcfac1d05e3 100644
--- a/src/playbackSystem/playbackSystemLogic.cpp
+++ b/src/playbackSystem/playbackSystemLogic.cpp
@@ -6,6 +6,7 @@
 #include <iostream>
 #include <string>
 #include <thread>
+#include <vector>
 
 #include "../timing.hpp"
 #include "playbackSystem.hpp"
@@ -27,14 +28,8 @@ struct EventInfo
     std::function<void()> callback;
     std::chrono::milliseconds eventTime;
 
-    bool operator<(EventInfo const &other)
-    {
-        return eventTime < other.eventTime;
-    }
-    bool operator<(std::chrono::milliseconds const &other)
-    {
-        return eventTime < other;
-    }
+    bool operator<(EventInfo const &other) { return eventTime < other.eventTime; }
+    bool operator<(std::chrono::milliseconds const &other) { return eventTime < other; }
 };
 
 static std::vector<EventInfo> eventList;
@@ -63,7 +58,9 @@ void runTimingSystemLoop()
         {
             std::unique_lock<std::mutex> lk(sleepingMutex);
             threadSafetyMutex.unlock();
-            sleepingConditionVariable.wait(lk, [&] { return (playbackSystemShouldShutdown || settingTimedFunction); });
+            sleepingConditionVariable.wait(lk, [&] {
+                return (playbackSystemShouldShutdown || settingTimedFunction);
+            });
             threadSafetyMutex.lock();
 
             if (playbackSystemShouldShutdown)
@@ -75,7 +72,9 @@ void runTimingSystemLoop()
 
         settingTimedFunction = false;
 
-        auto nextEventStartTime = std::chrono::steady_clock::time_point(std::chrono::microseconds(serverTimeToLocalTime(eventList[0].eventTime)));
+        auto nextEventStartTime = std::chrono::steady_clock::time_point(
+            std::chrono::microseconds(serverTimeToLocalTime(eventList[0].eventTime))
+        );
 
         std::unique_lock<std::mutex> lk(sleepingMutex);
 
@@ -83,9 +82,10 @@ void runTimingSystemLoop()
         {
             playbackSystemThreadNext = false;
             threadSafetyMutex.unlock();
-            bool sleepWasCancelled = sleepingConditionVariable.wait_until(
-                lk, nextEventStartTime - MUTEX_WAIT_BUFFER, [&] { return (playbackSystemShouldShutdown || settingTimedFunction); }
-            );
+            bool sleepWasCancelled =
+                sleepingConditionVariable.wait_until(lk, nextEventStartTime - MUTEX_WAIT_BUFFER, [&] {
+                    return (playbackSystemShouldShutdown || settingTimedFunction);
+                });
 
             threadSafetyMutex.lock();
 
diff --git a/src/poppi.cpp b/src/poppi.cpp
index 7790eaf404bf2635a1bf78f1410cf8241df57f19..c6974b44ca65646ecdcafc7e43da8557881352bf 100644
--- a/src/poppi.cpp
+++ b/src/poppi.cpp
@@ -1,8 +1,13 @@
+#include "poppi.hpp"
+
 #include <sys/wait.h>
 
+#include <cmath>
 #include <cstring>
 #include <iostream>
+#include <string>
 
+#include "audioBackends/PulseAudioBackend.hpp"
 #include "audioProviders/flacProvider.hpp"
 #include "audioSystem.hpp"
 #include "crashHandler.hpp"
@@ -12,10 +17,7 @@
 
 static std::string defaultPort = "13173";
 
-void printVersion()
-{
-    std::cerr << "Poppi " << VERSION << std::endl;
-}
+void printVersion() { std::cerr << "Poppi " << VERSION << std::endl; }
 
 ExecutionParameters parseExecutionParameters(int argc, char **argv)
 {
@@ -80,15 +82,14 @@ int poppiMain(ExecutionParameters params)
     createDirectories();
 
     networking::start(params.serverAddress, params.serverPort);
+    audio::start();
 
-    if (startAudioSystem() == false)
+    startAudioSystem();
+    while (true)
     {
-        return 0;
+        std::this_thread::sleep_for(std::chrono::seconds(1));
     }
 
-    // TODO: make the shutdown process make sense
-    shutdownAudioSystem();
-
     return 0;
 }
 
@@ -98,4 +99,4 @@ int main(int argc, char **argv)
     ExecutionParameters params = parseExecutionParameters(argc, argv);
 
     runWithCrashHandler(params, poppiMain, printVersion);
-}
\ No newline at end of file
+}
diff --git a/src/serverClock.cpp b/src/serverClock.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6cb4d40ab8f1fca59789987cf9dd5295363205c7
--- /dev/null
+++ b/src/serverClock.cpp
@@ -0,0 +1,38 @@
+#include "serverClock.hpp"
+
+#include <iostream>
+
+std::mutex server_clock::clockOffsetMutex;
+
+std::chrono::microseconds server_clock::steadyClockOffset = std::chrono::microseconds(0);
+bool server_clock::isSynced = false;
+
+void server_clock::setSteadyClockOffset(std::chrono::microseconds _steadyClockOffset)
+{
+    std::lock_guard<std::mutex> lock(clockOffsetMutex);
+
+    steadyClockOffset = _steadyClockOffset;
+    isSynced = true;
+}
+
+server_clock::time_point server_clock::now() noexcept
+{
+    std::lock_guard<std::mutex> lock(clockOffsetMutex);
+
+    if (!isSynced)
+    {
+        std::cerr << "WARNING!! server clock now asked before was known. Returning lowest possible" << std::endl;
+        return time_point::min();
+    }
+    std::chrono::microseconds steadyClockNow =
+        std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch());
+
+    return time_point(steadyClockNow - steadyClockOffset);
+}
+
+bool server_clock::getIsSynced()
+{
+    std::lock_guard<std::mutex> lock(clockOffsetMutex);
+
+    return isSynced;
+}
diff --git a/src/serverClock.hpp b/src/serverClock.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d282c3d8a92a459d825e7a542ac5bc0512ebb96b
--- /dev/null
+++ b/src/serverClock.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <chrono>
+#include <iostream>
+#include <mutex>
+
+struct server_clock
+{
+    using rep = std::chrono::microseconds::rep;
+    using period = std::chrono::microseconds::period;
+    using duration = std::chrono::duration<rep, period>;
+    using time_point = std::chrono::time_point<server_clock, duration>;
+    static constexpr bool is_steady = false;
+
+    static void setSteadyClockOffset(std::chrono::microseconds _steadyClockOffset);
+    static bool getIsSynced();
+    static time_point now() noexcept;
+
+  private:
+    static std::mutex clockOffsetMutex;
+    static bool isSynced;
+    static std::chrono::microseconds steadyClockOffset;
+};
diff --git a/src/timing.hpp b/src/timing.hpp
index ea37d899b6ae7b4a4b0879393547d61f1dbfe3eb..c8f45d8b42c44845d86a36e7549ac6a6b48f6851 100644
--- a/src/timing.hpp
+++ b/src/timing.hpp
@@ -6,4 +6,4 @@
 
 int64_t serverTimeToLocalTime(std::chrono::milliseconds playbackTime);
 
-void setSyncDifference(std::chrono::milliseconds syncDifference);
\ No newline at end of file
+void setSyncDifference(std::chrono::milliseconds syncDifference);
diff --git a/src/utils.cpp b/src/utils.cpp
index 8bd6686b30f70f291adba7b8de9190f297851819..aa9b2c95e384452b4667cbff1be938fa88b72b74 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -10,10 +10,10 @@ int16_t sat_adds16b(int16_t x, int16_t y)
     ux = (ux >> 15) + 32767;
 
     /* Force compiler to use cmovns instruction */
-    if ((int16_t)((ux ^ uy) | ~(uy ^ res)) >= 0)
+    if (static_cast<int16_t>((ux ^ uy) | ~(uy ^ res)) >= 0)
     {
         res = ux;
     }
 
     return res;
-}
\ No newline at end of file
+}
diff --git a/src/utils.hpp b/src/utils.hpp
index f87163e943b7444d123d26b20342ac15b928b40c..c1522ecf90906f48facf50e7b5ec3bcba053e986 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -2,4 +2,4 @@
 
 #include <stdint.h>
 
-int16_t sat_adds16b(int16_t x, int16_t y);
\ No newline at end of file
+int16_t sat_adds16b(int16_t x, int16_t y);