From 22362d29400a3bc1684d227e355feb52fd28c9de Mon Sep 17 00:00:00 2001
From: sudden6 <sudden6@gmx.at>
Date: Sat, 18 May 2019 19:08:58 +0200
Subject: [PATCH] refactor(audio): Remove Audio singleton

This commit finally removes the Audio singleton and allocates the audio
backend in main.cpp.
---
 CMakeLists.txt                      |   1 +
 src/audio/audio.cpp                 | 101 +++------------------
 src/audio/audio.h                   |  51 +----------
 src/audio/backend/openal.h          |   4 +-
 src/audio/backend/openal2.cpp       |   2 +-
 src/audio/backend/openal2.h         |   2 +-
 src/audio/iaudiocontrol.h           | 131 ++++++++++++++++++++++++++++
 src/core/core.cpp                   |   1 +
 src/core/core.h                     |   1 +
 src/core/coreav.cpp                 |  49 +++++++++--
 src/core/coreav.h                   |  10 ++-
 src/core/toxcall.cpp                |  22 ++---
 src/core/toxcall.h                  |   8 +-
 src/main.cpp                        |  14 ++-
 src/nexus.cpp                       |   2 +-
 src/nexus.h                         |   5 ++
 src/persistence/profile.cpp         |   7 +-
 src/widget/form/settings/avform.cpp |  51 ++++++-----
 src/widget/form/settings/avform.h   |   8 +-
 src/widget/form/settingswidget.cpp  |   8 +-
 src/widget/form/settingswidget.h    |   3 +-
 src/widget/widget.cpp               |  75 +++++++++-------
 src/widget/widget.h                 |  13 +--
 23 files changed, 325 insertions(+), 244 deletions(-)
 create mode 100644 src/audio/iaudiocontrol.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 726f2137d..774c89cea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -243,6 +243,7 @@ set(${PROJECT_NAME}_SOURCES
   src/audio/backend/openal.cpp
   src/audio/backend/openal.h
   src/audio/iaudiosettings.h
+  src/audio/iaudiocontrol.h
   src/audio/iaudiosink.h
   src/audio/iaudiosource.h
   src/chatlog/chatlinecontent.cpp
diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp
index 5b04fce0d..954b2c1bd 100644
--- a/src/audio/audio.cpp
+++ b/src/audio/audio.cpp
@@ -17,109 +17,30 @@
     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include "audio.h"
+#include <memory>
+
+#include "src/audio/audio.h"
+#include "src/audio/iaudiosettings.h"
 #include "src/audio/backend/openal.h"
 #ifdef USE_FILTERAUDIO
 #include "src/audio/backend/openal2.h"
 #endif
-#include "src/persistence/settings.h"
-
-/**
- * @class Audio
- *
- * @var Audio::AUDIO_SAMPLE_RATE
- * @brief The next best Opus would take is 24k
- *
- * @var Audio::AUDIO_FRAME_DURATION
- * @brief In milliseconds
- *
- * @var Audio::AUDIO_FRAME_SAMPLE_COUNT
- * @brief Frame sample count
- *
- * @fn qreal Audio::outputVolume() const
- * @brief Returns the current output volume (between 0 and 1)
- *
- * @fn void Audio::setOutputVolume(qreal volume)
- * @brief Set the master output volume.
- *
- * @param[in] volume   the master volume (between 0 and 1)
- *
- * @fn qreal Audio::minInputGain() const
- * @brief The minimum gain value for an input device.
- *
- * @return minimum gain value in dB
- *
- * @fn void Audio::setMinInputGain(qreal dB)
- * @brief Set the minimum allowed gain value in dB.
- *
- * @note Default is -30dB; usually you don't need to alter this value;
- *
- * @fn qreal Audio::maxInputGain() const
- * @brief The maximum gain value for an input device.
- *
- * @return maximum gain value in dB
- *
- * @fn void Audio::setMaxInputGain(qreal dB)
- * @brief Set the maximum allowed gain value in dB.
- *
- * @note Default is 30dB; usually you don't need to alter this value.
- *
- * @fn bool Audio::isOutputReady() const
- * @brief check if the output is ready to play audio
- *
- * @return true if the output device is open, false otherwise
- *
- * @fn QStringList Audio::outDeviceNames()
- * @brief Get the names of available output devices
- *
- * @return list of output devices
- *
- * @fn QStringList Audio::inDeviceNames()
- * @brief Get the names of available input devices
- *
- * @return list of input devices
- *
- * @fn qreal Audio::inputGain() const
- * @brief get the current input gain
- *
- * @return current input gain in dB
- *
- * @fn void Audio::setInputGain(qreal dB)
- * @brief set the input gain
- *
- * @fn void Audio::getInputThreshold()
- * @brief get the current input threshold
- *
- * @return current input threshold percentage
- *
- * @fn void Audio::setInputThreshold(qreal percent)
- * @brief set the input threshold
- *
- * @param[in] percent the new input threshold percentage
- */
 
 /**
- * @brief Returns the singleton instance.
+ * @brief Select the audio backend
+ * @param settings Audio settings to use
+ * @return Audio backend selection based on settings
  */
-Audio& Audio::getInstance()
+std::unique_ptr<IAudioControl> Audio::makeAudio(IAudioSettings& settings)
 {
-    // TODO: replace backend selection by inversion of control
 #ifdef USE_FILTERAUDIO
-    static bool initialized = false;
-    static bool Backend2 = false;
-
-    if (!initialized) {
-        Backend2 = Settings::getInstance().getEnableBackend2();
-        initialized = true;
-    }
+    const bool Backend2 = settings.getEnableBackend2();
 
     if (Backend2) {
-        static OpenAL2 instance;
-        return instance;
+        return std::unique_ptr<IAudioControl>(new OpenAL2());
     } else
 #endif
     {
-        static OpenAL instance;
-        return instance;
+        return std::unique_ptr<IAudioControl>(new OpenAL());
     }
 }
diff --git a/src/audio/audio.h b/src/audio/audio.h
index 3ad575135..d0eb91c8c 100644
--- a/src/audio/audio.h
+++ b/src/audio/audio.h
@@ -21,57 +21,14 @@
 #ifndef AUDIO_H
 #define AUDIO_H
 
-#include <QObject>
 #include <memory>
 
-class IAudioSink;
-class IAudioSource;
-class Audio : public QObject
+class IAudioControl;
+class IAudioSettings;
+class Audio
 {
-    Q_OBJECT
-
 public:
-    static Audio& getInstance();
-
-    virtual qreal outputVolume() const = 0;
-    virtual void setOutputVolume(qreal volume) = 0;
-    virtual qreal maxOutputVolume() const = 0;
-    virtual qreal minOutputVolume() const = 0;
-
-    virtual qreal minInputGain() const = 0;
-    virtual void setMinInputGain(qreal dB) = 0;
-
-    virtual qreal maxInputGain() const = 0;
-    virtual void setMaxInputGain(qreal dB) = 0;
-
-    virtual qreal inputGain() const = 0;
-    virtual void setInputGain(qreal dB) = 0;
-
-    virtual qreal minInputThreshold() const = 0;
-    virtual qreal maxInputThreshold() const = 0;
-
-    virtual qreal getInputThreshold() const = 0;
-    virtual void setInputThreshold(qreal percent) = 0;
-
-    virtual void reinitInput(const QString& inDevDesc) = 0;
-    virtual bool reinitOutput(const QString& outDevDesc) = 0;
-
-    virtual bool isOutputReady() const = 0;
-
-    virtual QStringList outDeviceNames() = 0;
-    virtual QStringList inDeviceNames() = 0;
-
-    virtual std::unique_ptr<IAudioSink> makeSink() = 0;
-    virtual std::unique_ptr<IAudioSource> makeSource() = 0;
-
-protected:
-    // Public default audio settings
-    // Samplerate for Tox calls and sounds
-    static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000;
-    static constexpr uint32_t AUDIO_FRAME_DURATION = 20;
-    static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL =
-        AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE / 1000;
-    uint32_t AUDIO_FRAME_SAMPLE_COUNT_TOTAL = 0;
+    static std::unique_ptr<IAudioControl> makeAudio(IAudioSettings& settings);
 };
 
 #endif // AUDIO_H
diff --git a/src/audio/backend/openal.h b/src/audio/backend/openal.h
index 6079ad4bb..96b9d96ab 100644
--- a/src/audio/backend/openal.h
+++ b/src/audio/backend/openal.h
@@ -21,7 +21,7 @@
 #ifndef OPENAL_H
 #define OPENAL_H
 
-#include "src/audio/audio.h"
+#include "src/audio/iaudiocontrol.h"
 #include "src/audio/backend/alsink.h"
 #include "src/audio/backend/alsource.h"
 
@@ -45,7 +45,7 @@
 #include <AL/alext.h>
 #endif
 
-class OpenAL : public Audio
+class OpenAL : public IAudioControl
 {
     Q_OBJECT
 
diff --git a/src/audio/backend/openal2.cpp b/src/audio/backend/openal2.cpp
index 1271bc485..f603a9b87 100644
--- a/src/audio/backend/openal2.cpp
+++ b/src/audio/backend/openal2.cpp
@@ -159,7 +159,7 @@ bool OpenAL2::initOutputEchoCancel()
                       ALC_FORMAT_TYPE_SOFT,
                       ALC_SHORT_SOFT,
                       ALC_FREQUENCY,
-                      Audio::AUDIO_SAMPLE_RATE,
+                      IAudioControl::AUDIO_SAMPLE_RATE,
                       0}; // End of List
 
     alProxyDev = alcLoopbackOpenDeviceSOFT(nullptr);
diff --git a/src/audio/backend/openal2.h b/src/audio/backend/openal2.h
index 85fcb0503..01ee649eb 100644
--- a/src/audio/backend/openal2.h
+++ b/src/audio/backend/openal2.h
@@ -21,7 +21,7 @@
 #ifndef OPENAL2_H
 #define OPENAL2_H
 
-#include "src/audio/audio.h"
+#include "src/audio/iaudiocontrol.h"
 #include "src/audio/backend/openal.h"
 
 #include <atomic>
diff --git a/src/audio/iaudiocontrol.h b/src/audio/iaudiocontrol.h
new file mode 100644
index 000000000..8d30e6e4a
--- /dev/null
+++ b/src/audio/iaudiocontrol.h
@@ -0,0 +1,131 @@
+#ifndef IAUDIOCONTROL_H
+#define IAUDIOCONTROL_H
+
+#include <QObject>
+#include <QStringList>
+#include <memory>
+
+/**
+ * @class IAudioControl
+ *
+ * @var IAudioControl::AUDIO_SAMPLE_RATE
+ * @brief The next best Opus would take is 24k
+ *
+ * @var IAudioControl::AUDIO_FRAME_DURATION
+ * @brief In milliseconds
+ *
+ * @var IAudioControl::AUDIO_FRAME_SAMPLE_COUNT
+ * @brief Frame sample count
+ *
+ * @fn qreal IAudioControl::outputVolume() const
+ * @brief Returns the current output volume (between 0 and 1)
+ *
+ * @fn void IAudioControl::setOutputVolume(qreal volume)
+ * @brief Set the master output volume.
+ *
+ * @param[in] volume   the master volume (between 0 and 1)
+ *
+ * @fn qreal IAudioControl::minInputGain() const
+ * @brief The minimum gain value for an input device.
+ *
+ * @return minimum gain value in dB
+ *
+ * @fn void IAudioControl::setMinInputGain(qreal dB)
+ * @brief Set the minimum allowed gain value in dB.
+ *
+ * @note Default is -30dB; usually you don't need to alter this value;
+ *
+ * @fn qreal IAudioControl::maxInputGain() const
+ * @brief The maximum gain value for an input device.
+ *
+ * @return maximum gain value in dB
+ *
+ * @fn void IAudioControl::setMaxInputGain(qreal dB)
+ * @brief Set the maximum allowed gain value in dB.
+ *
+ * @note Default is 30dB; usually you don't need to alter this value.
+ *
+ * @fn bool IAudioControl::isOutputReady() const
+ * @brief check if the output is ready to play audio
+ *
+ * @return true if the output device is open, false otherwise
+ *
+ * @fn QStringList IAudioControl::outDeviceNames()
+ * @brief Get the names of available output devices
+ *
+ * @return list of output devices
+ *
+ * @fn QStringList IAudioControl::inDeviceNames()
+ * @brief Get the names of available input devices
+ *
+ * @return list of input devices
+ *
+ * @fn qreal IAudioControl::inputGain() const
+ * @brief get the current input gain
+ *
+ * @return current input gain in dB
+ *
+ * @fn void IAudioControl::setInputGain(qreal dB)
+ * @brief set the input gain
+ *
+ * @fn void IAudioControl::getInputThreshold()
+ * @brief get the current input threshold
+ *
+ * @return current input threshold percentage
+ *
+ * @fn void IAudioControl::setInputThreshold(qreal percent)
+ * @brief set the input threshold
+ *
+ * @param[in] percent the new input threshold percentage
+ */
+
+class IAudioSink;
+class IAudioSource;
+class IAudioControl : public QObject
+{
+    Q_OBJECT
+
+public:
+    virtual qreal outputVolume() const = 0;
+    virtual void setOutputVolume(qreal volume) = 0;
+    virtual qreal maxOutputVolume() const = 0;
+    virtual qreal minOutputVolume() const = 0;
+
+    virtual qreal minInputGain() const = 0;
+    virtual void setMinInputGain(qreal dB) = 0;
+
+    virtual qreal maxInputGain() const = 0;
+    virtual void setMaxInputGain(qreal dB) = 0;
+
+    virtual qreal inputGain() const = 0;
+    virtual void setInputGain(qreal dB) = 0;
+
+    virtual qreal minInputThreshold() const = 0;
+    virtual qreal maxInputThreshold() const = 0;
+
+    virtual qreal getInputThreshold() const = 0;
+    virtual void setInputThreshold(qreal percent) = 0;
+
+    virtual void reinitInput(const QString& inDevDesc) = 0;
+    virtual bool reinitOutput(const QString& outDevDesc) = 0;
+
+    virtual bool isOutputReady() const = 0;
+
+    virtual QStringList outDeviceNames() = 0;
+    virtual QStringList inDeviceNames() = 0;
+
+    virtual std::unique_ptr<IAudioSink> makeSink() = 0;
+    virtual std::unique_ptr<IAudioSource> makeSource() = 0;
+
+protected:
+    // Public default audio settings
+    // Samplerate for Tox calls and sounds
+    static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000;
+    static constexpr uint32_t AUDIO_FRAME_DURATION = 20;
+    static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL =
+        AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE / 1000;
+    uint32_t AUDIO_FRAME_SAMPLE_COUNT_TOTAL = 0;
+};
+
+
+#endif // IAUDIOCONTROL_H
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 46cdec2b4..7a88539ee 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -281,6 +281,7 @@ ToxCorePtr Core::makeToxCore(const QByteArray& savedata, const ICoreSettings* co
     assert(core->tox != nullptr);
 
     // toxcore is successfully created, create toxav
+    // TODO(sudden6): don't create CoreAv here, Core should be usable without CoreAV
     core->av = CoreAV::makeCoreAV(core->tox.get());
     if (!core->av) {
         qCritical() << "Toxav failed to start";
diff --git a/src/core/core.h b/src/core/core.h
index 9982d9d8e..da8a8d1e9 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -40,6 +40,7 @@
 
 class CoreAV;
 class CoreFile;
+class IAudioControl;
 class ICoreSettings;
 class GroupInvite;
 class Profile;
diff --git a/src/core/coreav.cpp b/src/core/coreav.cpp
index 3417b1d72..23a757b80 100644
--- a/src/core/coreav.cpp
+++ b/src/core/coreav.cpp
@@ -81,7 +81,8 @@ std::map<uint32_t, CoreAV::ToxFriendCallPtr> CoreAV::calls;
 std::map<int, CoreAV::ToxGroupCallPtr> CoreAV::groupCalls;
 
 CoreAV::CoreAV(std::unique_ptr<ToxAV, ToxAVDeleter> toxav)
-    : toxav{std::move(toxav)}
+    : audio{nullptr}
+    , toxav{std::move(toxav)}
     , coreavThread{new QThread{this}}
     , iterateTimer{new QTimer{this}}
     , threadSwitchLock{false}
@@ -102,7 +103,8 @@ CoreAV::CoreAV(std::unique_ptr<ToxAV, ToxAVDeleter> toxav)
     coreavThread->start();
 }
 
-void CoreAV::connectCallbacks(ToxAV& toxav) {
+void CoreAV::connectCallbacks(ToxAV& toxav)
+{
     toxav_callback_call(&toxav, CoreAV::callCallback, this);
     toxav_callback_call_state(&toxav, CoreAV::stateCallback, this);
     toxav_callback_audio_bit_rate(&toxav, CoreAV::audioBitrateCallback, this);
@@ -139,6 +141,27 @@ CoreAV::CoreAVPtr CoreAV::makeCoreAV(Tox* core)
     return CoreAVPtr{new CoreAV{std::move(toxav)}};
 }
 
+/**
+ * @brief Set the audio backend
+ * @param audio The audio backend to use
+ * @note This must be called before starting CoreAV and audio must outlive CoreAV
+ */
+void CoreAV::setAudio(IAudioControl& newAudio)
+{
+    audio.exchange(&newAudio);
+}
+
+/**
+ * @brief Get the audio backend used
+ * @return Pointer to the audio backend
+ * @note This is needed only for the case CoreAV needs to restart and the restarting class doesn't
+ * have access to the audio backend and wants to keep it the same.
+ */
+IAudioControl* CoreAV::getAudio()
+{
+    return audio;
+}
+
 CoreAV::~CoreAV()
 {
     for (const auto& call : calls) {
@@ -253,7 +276,8 @@ bool CoreAV::answerCall(uint32_t friendNum, bool video)
     TOXAV_ERR_ANSWER err;
 
     const uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0;
-    if (toxav_answer(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(), videoBitrate, &err)) {
+    if (toxav_answer(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(),
+                     videoBitrate, &err)) {
         it->second->setActive(true);
         return true;
     } else {
@@ -289,10 +313,13 @@ bool CoreAV::startCall(uint32_t friendNum, bool video)
     }
 
     uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0;
-    if (!toxav_call(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(), videoBitrate, nullptr))
+    if (!toxav_call(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(), videoBitrate,
+                    nullptr))
         return false;
 
-    ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall(friendNum, video, *this));
+    // Audio backend must be set before making a call
+    assert(audio != nullptr);
+    ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall(friendNum, video, *this, *audio));
     assert(call != nullptr);
     auto ret = calls.emplace(friendNum, std::move(call));
     ret.first->second->startTimeout(friendNum);
@@ -420,8 +447,8 @@ void CoreAV::sendCallVideo(uint32_t callId, std::shared_ptr<VideoFrame> vframe)
     TOXAV_ERR_SEND_FRAME err;
     int retries = 0;
     do {
-        if (!toxav_video_send_frame(toxav.get(), callId, frame.width, frame.height, frame.y, frame.u,
-                                    frame.v, &err)) {
+        if (!toxav_video_send_frame(toxav.get(), callId, frame.width, frame.height, frame.y,
+                                    frame.u, frame.v, &err)) {
             if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
                 ++retries;
                 QThread::usleep(500);
@@ -544,7 +571,9 @@ void CoreAV::joinGroupCall(int groupId)
 {
     qDebug() << QString("Joining group call %1").arg(groupId);
 
-    ToxGroupCallPtr groupcall = ToxGroupCallPtr(new ToxGroupCall{groupId, *this});
+    // Audio backend must be set before starting a call
+    assert(audio != nullptr);
+    ToxGroupCallPtr groupcall = ToxGroupCallPtr(new ToxGroupCall{groupId, *this, *audio});
     assert(groupcall != nullptr);
     auto ret = groupCalls.emplace(groupId, std::move(groupcall));
     if (ret.second == false) {
@@ -708,7 +737,9 @@ void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool vid
         return;
     }
 
-    ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall{friendNum, video, *self});
+    // Audio backend must be set before receiving a call
+    assert(self->audio != nullptr);
+    ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall{friendNum, video, *self, *self->audio});
     assert(call != nullptr);
 
     auto it = self->calls.emplace(friendNum, std::move(call));
diff --git a/src/core/coreav.h b/src/core/coreav.h
index a9bd67225..30e1fa88f 100644
--- a/src/core/coreav.h
+++ b/src/core/coreav.h
@@ -29,6 +29,7 @@
 
 class Friend;
 class Group;
+class IAudioControl;
 class QThread;
 class QTimer;
 class CoreVideoSource;
@@ -43,10 +44,12 @@ class CoreAV : public QObject
     Q_OBJECT
 
 public:
-
     using CoreAVPtr = std::unique_ptr<CoreAV>;
     static CoreAVPtr makeCoreAV(Tox* core);
 
+    void setAudio(IAudioControl& newAudio);
+    IAudioControl* getAudio();
+
     ~CoreAV();
 
     bool anyActiveCalls() const;
@@ -124,7 +127,8 @@ private:
     static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 2500;
 
 private:
-
+    // atomic because potentially accessed by different threads
+    std::atomic<IAudioControl*> audio;
     std::unique_ptr<ToxAV, ToxAVDeleter> toxav;
     std::unique_ptr<QThread> coreavThread;
     QTimer* iterateTimer = nullptr;
@@ -133,8 +137,6 @@ private:
     using ToxGroupCallPtr = std::unique_ptr<ToxGroupCall>;
     static std::map<int, ToxGroupCallPtr> groupCalls;
     std::atomic_flag threadSwitchLock;
-
-    friend class Audio;
 };
 
 #endif // COREAV_H
diff --git a/src/core/toxcall.cpp b/src/core/toxcall.cpp
index 9944cbe2a..f217ee889 100644
--- a/src/core/toxcall.cpp
+++ b/src/core/toxcall.cpp
@@ -28,10 +28,11 @@
  * @brief Keeps sources for users in group calls.
  */
 
-ToxCall::ToxCall(bool VideoEnabled, CoreAV& av)
+ToxCall::ToxCall(bool VideoEnabled, CoreAV& av, IAudioControl& audio)
     : av{&av}
+    , audio(audio)
     , videoEnabled{VideoEnabled}
-    , audioSource{Audio::getInstance().makeSource()}
+    , audioSource(audio.makeSource())
 {}
 
 ToxCall::~ToxCall()
@@ -100,9 +101,9 @@ CoreVideoSource* ToxCall::getVideoSource() const
     return videoSource;
 }
 
-ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av)
-    : ToxCall(VideoEnabled, av)
-    , sink(Audio::getInstance().makeSink())
+ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av, IAudioControl& audio)
+    : ToxCall(VideoEnabled, av, audio)
+    , sink(audio.makeSink())
     , friendId{FriendNum}
 {
     // TODO(sudden6): move this to audio source
@@ -149,7 +150,7 @@ ToxFriendCall::~ToxFriendCall()
 
 void ToxFriendCall::onAudioSourceInvalidated()
 {
-    auto newSrc = Audio::getInstance().makeSource();
+    auto newSrc = audio.makeSource();
     audioInConn =
         QObject::connect(newSrc.get(), &IAudioSource::frameAvailable,
                          [this](const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) {
@@ -163,7 +164,7 @@ void ToxFriendCall::onAudioSourceInvalidated()
 
 void ToxFriendCall::onAudioSinkInvalidated()
 {
-    auto newSink = Audio::getInstance().makeSink();
+    auto newSink = audio.makeSink();
 
     audioSinkInvalid = QObject::connect(newSink.get(), &IAudioSink::invalidated,
                                         [this]() { this->onAudioSinkInvalidated(); });
@@ -211,8 +212,8 @@ void ToxFriendCall::playAudioBuffer(const int16_t* data, int samples, unsigned c
     }
 }
 
-ToxGroupCall::ToxGroupCall(int GroupNum, CoreAV& av)
-    : ToxCall(false, av)
+ToxGroupCall::ToxGroupCall(int GroupNum, CoreAV& av, IAudioControl& audio)
+    : ToxCall(false, av, audio)
     , groupId{GroupNum}
 {
     // register audio
@@ -238,7 +239,7 @@ ToxGroupCall::~ToxGroupCall()
 
 void ToxGroupCall::onAudioSourceInvalidated()
 {
-    auto newSrc = Audio::getInstance().makeSource();
+    auto newSrc = audio.makeSource();
     // TODO(sudden6): move this to audio source
     audioInConn =
         QObject::connect(audioSource.get(), &IAudioSource::frameAvailable,
@@ -274,7 +275,6 @@ void ToxGroupCall::removePeer(ToxPk peerId)
 
 void ToxGroupCall::addPeer(ToxPk peerId)
 {
-    auto& audio = Audio::getInstance();
     std::unique_ptr<IAudioSink> newSink = audio.makeSink();
     peers.emplace(peerId, std::move(newSink));
 
diff --git a/src/core/toxcall.h b/src/core/toxcall.h
index f404b7623..7bc850e7d 100644
--- a/src/core/toxcall.h
+++ b/src/core/toxcall.h
@@ -1,6 +1,7 @@
 #ifndef TOXCALL_H
 #define TOXCALL_H
 
+#include "src/audio/iaudiocontrol.h"
 #include "src/audio/iaudiosink.h"
 #include "src/audio/iaudiosource.h"
 #include <src/core/toxpk.h>
@@ -22,7 +23,7 @@ class ToxCall
 {
 protected:
     ToxCall() = delete;
-    ToxCall(bool VideoEnabled, CoreAV& av);
+    ToxCall(bool VideoEnabled, CoreAV& av, IAudioControl& audio);
     ~ToxCall();
 
 public:
@@ -53,6 +54,7 @@ protected:
     bool active{false};
     CoreAV* av{nullptr};
     // audio
+    IAudioControl& audio;
     QMetaObject::Connection audioInConn;
     bool muteMic{false};
     bool muteVol{false};
@@ -69,7 +71,7 @@ class ToxFriendCall : public ToxCall
 {
 public:
     ToxFriendCall() = delete;
-    ToxFriendCall(uint32_t friendId, bool VideoEnabled, CoreAV& av);
+    ToxFriendCall(uint32_t friendId, bool VideoEnabled, CoreAV& av, IAudioControl& audio);
     ToxFriendCall(ToxFriendCall&& other) = delete;
     ToxFriendCall& operator=(ToxFriendCall&& other) = delete;
     ~ToxFriendCall();
@@ -101,7 +103,7 @@ class ToxGroupCall : public ToxCall
 {
 public:
     ToxGroupCall() = delete;
-    ToxGroupCall(int GroupNum, CoreAV& av);
+    ToxGroupCall(int GroupNum, CoreAV& av, IAudioControl& audio);
     ToxGroupCall(ToxGroupCall&& other) = delete;
     ~ToxGroupCall();
 
diff --git a/src/main.cpp b/src/main.cpp
index 717ae6433..8b5a93d0f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,11 +17,13 @@
     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include "persistence/settings.h"
+#include "src/audio/audio.h"
+#include "src/core/coreav.h"
 #include "src/ipc.h"
 #include "src/net/toxuri.h"
 #include "src/nexus.h"
 #include "src/persistence/profile.h"
+#include "src/persistence/settings.h"
 #include "src/persistence/toxsave.h"
 #include "src/video/camerasource.h"
 #include "src/widget/loginscreen.h"
@@ -347,9 +349,17 @@ int main(int argc, char* argv[])
     }
 
     Nexus::getInstance().setProfile(profile);
-    Settings::getInstance().setCurrentProfile(profileName);
+    Settings& s = Settings::getInstance();
+    s.setCurrentProfile(profileName);
+
+    auto audio = Audio::makeAudio(s);
+    assert(audio != nullptr);
+    // TODO(sudden6): init CoreAV audio backend somewhere else so main doesn't depend on coreav.h
+    profile->getCore()->getAv()->setAudio(*audio);
 
     Nexus& nexus = Nexus::getInstance();
+    // TODO(sudden6): remove once we get rid of Nexus
+    nexus.audio = audio.get();
     nexus.start();
 
     // Start to accept Inter-process communication
diff --git a/src/nexus.cpp b/src/nexus.cpp
index 269ad7767..bfc0bd0cc 100644
--- a/src/nexus.cpp
+++ b/src/nexus.cpp
@@ -175,7 +175,7 @@ void Nexus::showMainGUI()
     assert(profile);
 
     // Create GUI
-    widget = Widget::getInstance();
+    widget = Widget::getInstance(audio);
 
     // Start GUI
     widget->init();
diff --git a/src/nexus.h b/src/nexus.h
index cee767a66..2ce624edf 100644
--- a/src/nexus.h
+++ b/src/nexus.h
@@ -23,6 +23,8 @@
 
 #include <QObject>
 
+#include "src/audio/iaudiocontrol.h"
+
 class Widget;
 class Profile;
 class Core;
@@ -79,6 +81,9 @@ private:
     QSignalMapper* windowMapper;
     QActionGroup* windowActions = nullptr;
 #endif
+public:
+    // TODO(sudden6): hack to pass the audio instance
+    IAudioControl* audio = nullptr;
 
 private:
     explicit Nexus(QObject* parent = nullptr);
diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp
index 10dadf7b6..bc5197cdb 100644
--- a/src/persistence/profile.cpp
+++ b/src/persistence/profile.cpp
@@ -32,6 +32,7 @@
 #include "profilelocker.h"
 #include "settings.h"
 #include "src/core/core.h"
+#include "src/core/coreav.h"
 #include "src/core/corefile.h"
 #include "src/net/avatarbroadcaster.h"
 #include "src/nexus.h"
@@ -96,7 +97,8 @@ void Profile::initCore(const QByteArray& toxsave, ICoreSettings& s, bool isNewPr
             Qt::ConnectionType::QueuedConnection);
 }
 
-Profile::Profile(QString name, const QString& password, bool isNewProfile, const QByteArray& toxsave, std::unique_ptr<ToxEncrypt> passkey)
+Profile::Profile(QString name, const QString& password, bool isNewProfile,
+                 const QByteArray& toxsave, std::unique_ptr<ToxEncrypt> passkey)
     : name{name}
     , passkey{std::move(passkey)}
     , isRemoved{false}
@@ -797,7 +799,10 @@ void Profile::restartCore()
         if (saveToxSave(savedata)) {
             qDebug() << "Restarting Core";
             const bool isNewProfile{false};
+            IAudioControl* audioBak = core->getAv()->getAudio();
+            assert(audioBak != nullptr);
             initCore(savedata, Settings::getInstance(), isNewProfile);
+            core->getAv()->setAudio(*audioBak);
             core->start();
         } else {
             qCritical() << "Failed to save, not restarting core";
diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp
index dc52cec8e..6026b9b5e 100644
--- a/src/widget/form/settings/avform.cpp
+++ b/src/widget/form/settings/avform.cpp
@@ -44,10 +44,10 @@
 #define ALC_ALL_DEVICES_SPECIFIER ALC_DEVICE_SPECIFIER
 #endif
 
-AVForm::AVForm(Audio* audio, CoreAV* coreAV, CameraSource& camera, IAudioSettings* audioSettings,
-               IVideoSettings* videoSettings)
+AVForm::AVForm(IAudioControl& audio, CoreAV* coreAV, CameraSource& camera,
+               IAudioSettings* audioSettings, IVideoSettings* videoSettings)
     : GenericForm(QPixmap(":/img/settings/av.png"))
-    , audio{audio}
+    , audio(audio)
     , coreAV{coreAV}
     , audioSettings{audioSettings}
     , videoSettings{videoSettings}
@@ -78,8 +78,8 @@ AVForm::AVForm(Audio* audio, CoreAV* coreAV, CameraSource& camera, IAudioSetting
 
     microphoneSlider->setToolTip(tr("Use slider to set the gain of your input device ranging"
                                     " from %1dB to %2dB.")
-                                     .arg(audio->minInputGain())
-                                     .arg(audio->maxInputGain()));
+                                     .arg(audio.minInputGain())
+                                     .arg(audio.maxInputGain()));
     microphoneSlider->setMaximum(totalSliderSteps);
     microphoneSlider->setTickPosition(QSlider::TicksBothSides);
     static const int numTicks = 4;
@@ -87,14 +87,14 @@ AVForm::AVForm(Audio* audio, CoreAV* coreAV, CameraSource& camera, IAudioSetting
     microphoneSlider->setTracking(false);
     microphoneSlider->installEventFilter(this);
     microphoneSlider->setValue(
-        getStepsFromValue(audio->inputGain(), audio->minInputGain(), audio->maxInputGain()));
+        getStepsFromValue(audio.inputGain(), audio.minInputGain(), audio.maxInputGain()));
 
     audioThresholdSlider->setToolTip(tr("Use slider to set the activation volume for your"
                                         " input device."));
     audioThresholdSlider->setMaximum(totalSliderSteps);
     audioThresholdSlider->setValue(getStepsFromValue(audioSettings->getAudioThreshold(),
-                                                     audio->minInputThreshold(),
-                                                     audio->maxInputThreshold()));
+                                                     audio.minInputThreshold(),
+                                                     audio.maxInputThreshold()));
     audioThresholdSlider->setTracking(false);
     audioThresholdSlider->installEventFilter(this);
 
@@ -139,12 +139,12 @@ void AVForm::showEvent(QShowEvent* event)
     getVideoDevices();
 
     if (audioSrc == nullptr) {
-        audioSrc = audio->makeSource();
+        audioSrc = audio.makeSource();
         connect(audioSrc.get(), &IAudioSource::volumeAvailable, this, &AVForm::setVolume);
     }
 
     if (audioSink == nullptr) {
-        audioSink = audio->makeSink();
+        audioSink = audio.makeSink();
     }
 
     GenericForm::showEvent(event);
@@ -167,8 +167,7 @@ void AVForm::rescanDevices()
 
 void AVForm::setVolume(float value)
 {
-    volumeDisplay->setValue(
-        getStepsFromValue(value, audio->minOutputVolume(), audio->maxOutputVolume()));
+    volumeDisplay->setValue(getStepsFromValue(value, audio.minOutputVolume(), audio.maxOutputVolume()));
 }
 
 void AVForm::on_cbEnableBackend2_stateChanged()
@@ -493,7 +492,7 @@ int AVForm::getModeSize(VideoMode mode)
 void AVForm::getAudioInDevices()
 {
     QStringList deviceNames;
-    deviceNames << tr("Disabled") << audio->inDeviceNames();
+    deviceNames << tr("Disabled") << audio.inDeviceNames();
 
     inDevCombobox->blockSignals(true);
     inDevCombobox->clear();
@@ -512,7 +511,7 @@ void AVForm::getAudioInDevices()
 void AVForm::getAudioOutDevices()
 {
     QStringList deviceNames;
-    deviceNames << tr("Disabled") << audio->outDeviceNames();
+    deviceNames << tr("Disabled") << audio.outDeviceNames();
 
     outDevCombobox->blockSignals(true);
     outDevCombobox->clear();
@@ -541,8 +540,8 @@ void AVForm::on_inDevCombobox_currentIndexChanged(int deviceIndex)
     const QString oldName = audioSettings->getInDev();
     if (oldName != deviceName) {
         audioSettings->setInDev(deviceName);
-        audio->reinitInput(deviceName);
-        audioSrc = audio->makeSource();
+        audio.reinitInput(deviceName);
+        audioSrc = audio.makeSource();
         connect(audioSrc.get(), &IAudioSource::volumeAvailable, this, &AVForm::setVolume);
     }
 
@@ -566,8 +565,8 @@ void AVForm::on_outDevCombobox_currentIndexChanged(int deviceIndex)
 
     if (oldName != deviceName) {
         audioSettings->setOutDev(deviceName);
-        audio->reinitOutput(deviceName);
-        audioSink = Audio::getInstance().makeSink();
+        audio.reinitOutput(deviceName);
+        audioSink = audio.makeSink();
     }
 
     playbackSlider->setEnabled(outputEnabled);
@@ -579,10 +578,10 @@ void AVForm::on_playbackSlider_valueChanged(int sliderSteps)
                                                  audioSettings->getOutVolumeMax());
     audioSettings->setOutVolume(settingsVolume);
 
-    if (audio->isOutputReady()) {
+    if (audio.isOutputReady()) {
         const qreal volume =
-            getValueFromSteps(sliderSteps, audio->minOutputVolume(), audio->maxOutputVolume());
-        audio->setOutputVolume(volume);
+            getValueFromSteps(sliderSteps, audio.minOutputVolume(), audio.maxOutputVolume());
+        audio.setOutputVolume(volume);
 
         if (cbEnableTestSound->isChecked() && audioSink) {
             audioSink->playMono16Sound(IAudioSink::Sound::Test);
@@ -594,24 +593,24 @@ void AVForm::on_cbEnableTestSound_stateChanged()
 {
     audioSettings->setEnableTestSound(cbEnableTestSound->isChecked());
 
-    if (cbEnableTestSound->isChecked() && audio->isOutputReady() && audioSink) {
+    if (cbEnableTestSound->isChecked() && audio.isOutputReady() && audioSink) {
         audioSink->playMono16Sound(IAudioSink::Sound::Test);
     }
 }
 
 void AVForm::on_microphoneSlider_valueChanged(int sliderSteps)
 {
-    const qreal dB = getValueFromSteps(sliderSteps, audio->minInputGain(), audio->maxInputGain());
+    const qreal dB = getValueFromSteps(sliderSteps, audio.minInputGain(), audio.maxInputGain());
     audioSettings->setAudioInGainDecibel(dB);
-    audio->setInputGain(dB);
+    audio.setInputGain(dB);
 }
 
 void AVForm::on_audioThresholdSlider_valueChanged(int sliderSteps)
 {
     const qreal normThreshold =
-        getValueFromSteps(sliderSteps, audio->minInputThreshold(), audio->maxInputThreshold());
+        getValueFromSteps(sliderSteps, audio.minInputThreshold(), audio.maxInputThreshold());
     audioSettings->setAudioThreshold(normThreshold);
-    Audio::getInstance().setInputThreshold(normThreshold);
+    audio.setInputThreshold(normThreshold);
 }
 void AVForm::createVideoSurface()
 {
diff --git a/src/widget/form/settings/avform.h b/src/widget/form/settings/avform.h
index 4b4675650..163137cd0 100644
--- a/src/widget/form/settings/avform.h
+++ b/src/widget/form/settings/avform.h
@@ -30,7 +30,7 @@
 
 #include <memory>
 
-class Audio;
+class IAudioControl;
 class IAudioSettings;
 class IAudioSink;
 class IAudioSource;
@@ -42,8 +42,8 @@ class AVForm : public GenericForm, private Ui::AVForm
 {
     Q_OBJECT
 public:
-    AVForm(Audio* audio, CoreAV* coreAV, CameraSource& camera, IAudioSettings* audioSettings,
-           IVideoSettings* videoSettings);
+    AVForm(IAudioControl& audio, CoreAV* coreAV, CameraSource& camera,
+           IAudioSettings* audioSettings, IVideoSettings* videoSettings);
     ~AVForm() override;
     QString getFormName() final override
     {
@@ -97,7 +97,7 @@ private:
     qreal getValueFromSteps(int steps, qreal valMin, qreal valMax);
 
 private:
-    Audio* audio;
+    IAudioControl& audio;
     CoreAV* coreAV;
     IAudioSettings* audioSettings;
     IVideoSettings* videoSettings;
diff --git a/src/widget/form/settingswidget.cpp b/src/widget/form/settingswidget.cpp
index 7a0b97a0f..d85faabfb 100644
--- a/src/widget/form/settingswidget.cpp
+++ b/src/widget/form/settingswidget.cpp
@@ -20,8 +20,8 @@
 #include "settingswidget.h"
 
 #include "src/audio/audio.h"
-#include "src/core/coreav.h"
 #include "src/core/core.h"
+#include "src/core/coreav.h"
 #include "src/net/updatecheck.h"
 #include "src/persistence/settings.h"
 #include "src/video/camerasource.h"
@@ -41,10 +41,9 @@
 
 #include <memory>
 
-SettingsWidget::SettingsWidget(UpdateCheck* updateCheck, QWidget* parent)
+SettingsWidget::SettingsWidget(UpdateCheck* updateCheck, IAudioControl& audio, QWidget* parent)
     : QWidget(parent, Qt::Window)
 {
-    Audio* audio = &Audio::getInstance();
     CoreAV* coreAV = Core::getInstance()->getAv();
     IAudioSettings* audioSettings = &Settings::getInstance();
     IVideoSettings* videoSettings = &Settings::getInstance();
@@ -74,7 +73,8 @@ SettingsWidget::SettingsWidget(UpdateCheck* updateCheck, QWidget* parent)
     }
 #endif
 
-    cfgForms = {{std::move(gfrm), std::move(uifrm), std::move(pfrm), std::move(avfrm), std::move(expfrm), std::move(abtfrm)}};
+    cfgForms = {{std::move(gfrm), std::move(uifrm), std::move(pfrm), std::move(avfrm),
+                 std::move(expfrm), std::move(abtfrm)}};
     for (auto& cfgForm : cfgForms)
         settingsWidgets->addTab(cfgForm.get(), cfgForm->getFormIcon(), cfgForm->getFormName());
 
diff --git a/src/widget/form/settingswidget.h b/src/widget/form/settingswidget.h
index 25ab84abc..bf9edc358 100644
--- a/src/widget/form/settingswidget.h
+++ b/src/widget/form/settingswidget.h
@@ -30,6 +30,7 @@
 class Camera;
 class GenericForm;
 class GeneralForm;
+class IAudioControl;
 class PrivacyForm;
 class AVForm;
 class QLabel;
@@ -41,7 +42,7 @@ class SettingsWidget : public QWidget
 {
     Q_OBJECT
 public:
-    SettingsWidget(UpdateCheck* updateCheck, QWidget* parent = nullptr);
+    SettingsWidget(UpdateCheck* updateCheck, IAudioControl& audio, QWidget* parent = nullptr);
     ~SettingsWidget();
 
     bool isShown() const;
diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp
index 9f4f94df0..7c1a3ef1f 100644
--- a/src/widget/widget.cpp
+++ b/src/widget/widget.cpp
@@ -52,16 +52,15 @@
 #include "src/core/core.h"
 #include "src/core/coreav.h"
 #include "src/core/corefile.h"
-#include "src/model/chatroom/friendchatroom.h"
-#include "src/model/chatroom/groupchatroom.h"
-#include "src/model/friend.h"
 #include "src/friendlist.h"
 #include "src/grouplist.h"
+#include "src/model/chatroom/friendchatroom.h"
+#include "src/model/chatroom/groupchatroom.h"
 #include "src/model/friend.h"
 #include "src/model/group.h"
 #include "src/model/groupinvite.h"
-#include "src/model/status.h"
 #include "src/model/profile/profileinfo.h"
+#include "src/model/status.h"
 #include "src/net/updatecheck.h"
 #include "src/nexus.h"
 #include "src/persistence/offlinemsgengine.h"
@@ -95,7 +94,7 @@ bool toxActivateEventHandler(const QByteArray&)
 
 Widget* Widget::instance{nullptr};
 
-Widget::Widget(QWidget* parent)
+Widget::Widget(IAudioControl& audio, QWidget* parent)
     : QMainWindow(parent)
     , icon{nullptr}
     , trayMenu{nullptr}
@@ -103,6 +102,7 @@ Widget::Widget(QWidget* parent)
     , activeChatroomWidget{nullptr}
     , eventFlag(false)
     , eventIcon(false)
+    , audio(audio)
     , settings(Settings::getInstance())
 {
     installEventFilter(this);
@@ -129,7 +129,8 @@ void Widget::init()
 
     // Preparing icons and set their size
     statusOnline = new QAction(this);
-    statusOnline->setIcon(prepareIcon(Status::getIconPath(Status::Status::Online), icon_size, icon_size));
+    statusOnline->setIcon(
+        prepareIcon(Status::getIconPath(Status::Status::Online), icon_size, icon_size));
     connect(statusOnline, &QAction::triggered, this, &Widget::setStatusOnline);
 
     statusAway = new QAction(this);
@@ -148,7 +149,8 @@ void Widget::init()
     actionQuit->setMenuRole(QAction::QuitRole);
 #endif
 
-    actionQuit->setIcon(prepareIcon(Style::getImagePath("rejectCall/rejectCall.svg"), icon_size, icon_size));
+    actionQuit->setIcon(
+        prepareIcon(Style::getImagePath("rejectCall/rejectCall.svg"), icon_size, icon_size));
     connect(actionQuit, &QAction::triggered, qApp, &QApplication::quit);
 
     layout()->setContentsMargins(0, 0, 0, 0);
@@ -233,7 +235,7 @@ void Widget::init()
     updateCheck = std::unique_ptr<UpdateCheck>(new UpdateCheck(settings));
     connect(updateCheck.get(), &UpdateCheck::updateAvailable, this, &Widget::onUpdateAvailable);
 #endif
-    settingsWidget = new SettingsWidget(updateCheck.get(), this);
+    settingsWidget = new SettingsWidget(updateCheck.get(), audio, this);
 #if UPDATE_CHECK_ENABLED
     updateCheck->checkForUpdate();
 #endif
@@ -444,7 +446,6 @@ void Widget::updateIcons()
         Status::getAssetSuffix(static_cast<Status::Status>(ui->statusButton->property("status").toInt()))
         + (eventIcon ? "_event" : "");
 
-
     // Some builds of Qt appear to have a bug in icon loading:
     // QIcon::hasThemeIcon is sometimes unaware that the icon returned
     // from QIcon::fromTheme was a fallback icon, causing hasThemeIcon to
@@ -538,12 +539,17 @@ Widget::~Widget()
 }
 
 /**
+ * @param audio Only used for initialization from Nexus, to pass IAudioControl
  * @brief Returns the singleton instance.
  */
-Widget* Widget::getInstance()
+Widget* Widget::getInstance(IAudioControl* audio)
 {
     if (!instance) {
-        instance = new Widget();
+        // Passing audio via pointer here is a hack
+        // to allow for default paramters.
+        // once Widget::getInstance is removed it won't be neccessary
+        assert(audio != nullptr);
+        instance = new Widget(*audio);
     }
 
     return instance;
@@ -587,8 +593,7 @@ void Widget::closeEvent(QCloseEvent* event)
 void Widget::changeEvent(QEvent* event)
 {
     if (event->type() == QEvent::WindowStateChange) {
-        if (isMinimized() && settings.getShowSystemTray()
-            && settings.getMinimizeToTray()) {
+        if (isMinimized() && settings.getShowSystemTray() && settings.getMinimizeToTray()) {
             this->hide();
         }
     }
@@ -916,8 +921,7 @@ void Widget::setStatusMessage(const QString& statusMessage)
     ui->statusLabel->setText(statusMessage);
     // escape HTML from tooltips and preserve newlines
     // TODO: move newspace preservance to a generic function
-    ui->statusLabel->setToolTip("<p style='white-space:pre'>" + statusMessage.toHtmlEscaped()
-                                + "</p>");
+    ui->statusLabel->setToolTip("<p style='white-space:pre'>" + statusMessage.toHtmlEscaped() + "</p>");
 }
 
 void Widget::reloadHistory()
@@ -932,19 +936,20 @@ void Widget::reloadHistory()
  * @param sound Sound to play
  * @param loop if true, loop the sound until onStopNotification() is called
  */
-void Widget::playNotificationSound(IAudioSink::Sound sound, bool loop) {
-    if(audioNotification == nullptr) {
-        audioNotification = std::unique_ptr<IAudioSink>(Audio::getInstance().makeSink());
-        if(audioNotification == nullptr) {
+void Widget::playNotificationSound(IAudioSink::Sound sound, bool loop)
+{
+    if (audioNotification == nullptr) {
+        audioNotification = std::unique_ptr<IAudioSink>(audio.makeSink());
+        if (audioNotification == nullptr) {
             qDebug() << "Failed to allocate AudioSink";
             return;
         }
     }
 
-    connect(audioNotification.get(), &IAudioSink::finishedPlaying,
-            this, &Widget::cleanupNotificationSound);
+    connect(audioNotification.get(), &IAudioSink::finishedPlaying, this,
+            &Widget::cleanupNotificationSound);
 
-    if(loop) {
+    if (loop) {
         audioNotification->startLoop();
     }
 
@@ -1011,7 +1016,8 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk)
         settings.setFriendActivity(friendPk, chatTime);
     }
 
-    contactListWidget->addFriendWidget(widget, Status::Status::Offline, settings.getFriendCircleID(friendPk));
+    contactListWidget->addFriendWidget(widget, Status::Status::Offline,
+                                       settings.getFriendCircleID(friendPk));
 
     connect(newfriend, &Friend::aliasChanged, this, &Widget::onFriendAliasChanged);
     connect(newfriend, &Friend::displayedNameChanged, this, &Widget::onFriendDisplayedNameChanged);
@@ -1249,7 +1255,8 @@ void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog)
 
     auto form = chatForms[friendPk];
     auto chatroom = friendChatrooms[friendPk];
-    FriendWidget* friendWidget = ContentDialogManager::getInstance()->addFriendToDialog(dialog, chatroom, form);
+    FriendWidget* friendWidget =
+        ContentDialogManager::getInstance()->addFriendToDialog(dialog, chatroom, form);
 
     friendWidget->setStatusMsg(widget->getStatusMsg());
 
@@ -1305,7 +1312,8 @@ void Widget::addGroupDialog(Group* group, ContentDialog* dialog)
 
     auto chatForm = groupChatForms[groupId].data();
     auto chatroom = groupChatrooms[groupId];
-    auto groupWidget = ContentDialogManager::getInstance()->addGroupToDialog(dialog, chatroom, chatForm);
+    auto groupWidget =
+        ContentDialogManager::getInstance()->addGroupToDialog(dialog, chatroom, chatForm);
 
 #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0))
     auto removeGroup = QOverload<const GroupId&>::of(&Widget::removeGroup);
@@ -1610,7 +1618,8 @@ ContentDialog* Widget::createContentDialog() const
     connect(contentDialog, &ContentDialog::friendDialogShown, this, &Widget::onFriendDialogShown);
     connect(contentDialog, &ContentDialog::groupDialogShown, this, &Widget::onGroupDialogShown);
     connect(core, &Core::usernameSet, contentDialog, &ContentDialog::setUsername);
-    connect(&settings, &Settings::groupchatPositionChanged, contentDialog, &ContentDialog::reorderLayouts);
+    connect(&settings, &Settings::groupchatPositionChanged, contentDialog,
+            &ContentDialog::reorderLayouts);
 
 #ifdef Q_OS_MAC
     Nexus& n = Nexus::getInstance();
@@ -1652,8 +1661,7 @@ ContentLayout* Widget::createContentDialog(DialogType type) const
 
         void retranslateUi()
         {
-            setWindowTitle(core->getUsername() + QStringLiteral(" - ")
-                           + Widget::fromDialogType(type));
+            setWindowTitle(core->getUsername() + QStringLiteral(" - ") + Widget::fromDialogType(type));
         }
 
     protected:
@@ -1761,7 +1769,8 @@ void Widget::onGroupMessageReceived(int groupnumber, int peernumber, const QStri
         return;
     }
 
-    const auto mention = !core->getUsername().isEmpty() && (message.contains(nameMention) || message.contains(sanitizedNameMention));
+    const auto mention = !core->getUsername().isEmpty()
+                         && (message.contains(nameMention) || message.contains(sanitizedNameMention));
     const auto targeted = !isSelf && mention;
     const auto date = QDateTime::currentDateTime();
     auto form = groupChatForms[groupId].data();
@@ -1883,7 +1892,8 @@ Group* Widget::createGroup(uint32_t groupnumber, const GroupId& groupId)
 
     const auto groupName = tr("Groupchat #%1").arg(groupnumber);
     bool enabled = core->getGroupAvEnabled(groupnumber);
-    Group* newgroup = GroupList::addGroup(groupnumber, groupId, groupName, enabled, core->getUsername());
+    Group* newgroup =
+        GroupList::addGroup(groupnumber, groupId, groupName, enabled, core->getUsername());
     std::shared_ptr<GroupChatroom> chatroom(new GroupChatroom(newgroup));
     const auto compact = settings.getCompactLayout();
     auto widget = new GroupWidget(chatroom, compact);
@@ -1990,7 +2000,8 @@ void Widget::onUserAwayCheck()
 {
 #ifdef QTOX_PLATFORM_EXT
     uint32_t autoAwayTime = settings.getAutoAwayTime() * 60 * 1000;
-    bool online = static_cast<Status::Status>(ui->statusButton->property("status").toInt()) == Status::Status::Online;
+    bool online = static_cast<Status::Status>(ui->statusButton->property("status").toInt())
+                  == Status::Status::Online;
     bool away = autoAwayTime && Platform::getIdleTime() >= autoAwayTime;
 
     if (online && away) {
@@ -2453,7 +2464,7 @@ void Widget::focusChatInput()
     }
 }
 
-void Widget::refreshPeerListsLocal(const QString &username)
+void Widget::refreshPeerListsLocal(const QString& username)
 {
     for (Group* g : GroupList::getAllGroups()) {
         g->updateUsername(core->getSelfPublicKey(), username);
diff --git a/src/widget/widget.h b/src/widget/widget.h
index fef8e91c3..1a6a4ef8f 100644
--- a/src/widget/widget.h
+++ b/src/widget/widget.h
@@ -29,12 +29,13 @@
 
 #include "genericchatitemwidget.h"
 
+#include "src/audio/iaudiocontrol.h"
 #include "src/audio/iaudiosink.h"
 #include "src/core/core.h"
-#include "src/core/toxfile.h"
 #include "src/core/groupid.h"
-#include "src/core/toxpk.h"
+#include "src/core/toxfile.h"
 #include "src/core/toxid.h"
+#include "src/core/toxpk.h"
 #if DESKTOP_NOTIFICATIONS
 #include "src/platform/desktop_notifications/desktopnotify.h"
 #endif
@@ -112,13 +113,13 @@ private:
     };
 
 public:
-    explicit Widget(QWidget* parent = nullptr);
+    explicit Widget(IAudioControl& audio, QWidget* parent = nullptr);
     ~Widget() override;
     void init();
     void setCentralWidget(QWidget* widget, const QString& widgetName);
     QString getUsername();
     Camera* getCamera();
-    static Widget* getInstance();
+    static Widget* getInstance(IAudioControl* audio = nullptr);
     void showUpdateDownloadProgress();
     void addFriendDialog(const Friend* frnd, ContentDialog* dialog);
     void addGroupDialog(Group* group, ContentDialog* dialog);
@@ -185,7 +186,7 @@ public slots:
     void onFriendDialogShown(const Friend* f);
     void onGroupDialogShown(Group* g);
     void toggleFullscreen();
-    void refreshPeerListsLocal(const QString &username);
+    void refreshPeerListsLocal(const QString& username);
     void onUpdateAvailable(QString latestVersion, QUrl link);
 
 signals:
@@ -312,6 +313,8 @@ private:
     QPushButton* groupInvitesButton;
     unsigned int unreadGroupInvites;
     int icon_size;
+
+    IAudioControl& audio;
     std::unique_ptr<IAudioSink> audioNotification = nullptr;
     Settings& settings;