From 8b00954ec79fad71691ad2d4c82d5c1c60e21b0c Mon Sep 17 00:00:00 2001
From: MerryMage <MerryMage@users.noreply.github.com>
Date: Sun, 21 Feb 2016 13:13:52 +0000
Subject: [PATCH] AudioCore: Skeleton Implementation

This commit:
* Adds a new subproject, audio_core.
* Defines structures that exist in DSP shared memory.
* Hooks up various other parts of the emulator into audio core.

This sets the foundation for a later HLE DSP implementation.
---
 src/CMakeLists.txt               |   1 +
 src/audio_core/CMakeLists.txt    |  16 +
 src/audio_core/audio_core.cpp    |  53 ++++
 src/audio_core/audio_core.h      |  26 ++
 src/audio_core/hle/dsp.cpp       |  42 +++
 src/audio_core/hle/dsp.h         | 502 +++++++++++++++++++++++++++++++
 src/audio_core/hle/pipe.cpp      |  55 ++++
 src/audio_core/hle/pipe.h        |  38 +++
 src/audio_core/sink.h            |  34 +++
 src/citra/CMakeLists.txt         |   2 +-
 src/citra_qt/CMakeLists.txt      |   2 +-
 src/common/bit_field.h           |   2 +-
 src/common/logging/backend.cpp   |   2 +
 src/common/logging/log.h         |   2 +
 src/core/hle/kernel/memory.cpp   |   5 +-
 src/core/hle/service/dsp_dsp.cpp | 139 +++++----
 src/core/hle/service/dsp_dsp.h   |  12 +-
 src/core/hw/gpu.cpp              |   6 -
 src/core/system.cpp              |   7 +-
 19 files changed, 875 insertions(+), 71 deletions(-)
 create mode 100644 src/audio_core/CMakeLists.txt
 create mode 100644 src/audio_core/audio_core.cpp
 create mode 100644 src/audio_core/audio_core.h
 create mode 100644 src/audio_core/hle/dsp.cpp
 create mode 100644 src/audio_core/hle/dsp.h
 create mode 100644 src/audio_core/hle/pipe.cpp
 create mode 100644 src/audio_core/hle/pipe.h
 create mode 100644 src/audio_core/sink.h

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cb09f3cd1..2bb411492 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,6 +4,7 @@ include_directories(.)
 add_subdirectory(common)
 add_subdirectory(core)
 add_subdirectory(video_core)
+add_subdirectory(audio_core)
 if (ENABLE_GLFW)
     add_subdirectory(citra)
 endif()
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
new file mode 100644
index 000000000..b0d1c7eb6
--- /dev/null
+++ b/src/audio_core/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(SRCS
+            audio_core.cpp
+            hle/dsp.cpp
+            hle/pipe.cpp
+            )
+
+set(HEADERS
+            audio_core.h
+            hle/dsp.h
+            hle/pipe.h
+            sink.h
+            )
+
+create_directory_groups(${SRCS} ${HEADERS})
+
+add_library(audio_core STATIC ${SRCS} ${HEADERS})
\ No newline at end of file
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
new file mode 100644
index 000000000..894f46990
--- /dev/null
+++ b/src/audio_core/audio_core.cpp
@@ -0,0 +1,53 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/audio_core.h"
+#include "audio_core/hle/dsp.h"
+
+#include "core/core_timing.h"
+#include "core/hle/kernel/vm_manager.h"
+#include "core/hle/service/dsp_dsp.h"
+
+namespace AudioCore {
+
+// Audio Ticks occur about every 5 miliseconds.
+static int tick_event;                               ///< CoreTiming event
+static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles
+
+static void AudioTickCallback(u64 /*userdata*/, int cycles_late) {
+    if (DSP::HLE::Tick()) {
+        // HACK: We're not signaling the interrups when they should be, but just firing them all off together.
+        // It should be only (interrupt_id = 2, channel_id = 2) that's signalled here.
+        // TODO(merry): Understand when the other interrupts are fired.
+        DSP_DSP::SignalAllInterrupts();
+    }
+
+    // Reschedule recurrent event
+    CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event);
+}
+
+/// Initialise Audio
+void Init() {
+    DSP::HLE::Init();
+
+    tick_event = CoreTiming::RegisterEvent("AudioCore::tick_event", AudioTickCallback);
+    CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event);
+}
+
+/// Add DSP address spaces to Process's address space.
+void AddAddressSpace(Kernel::VMManager& address_space) {
+    auto r0_vma = address_space.MapBackingMemory(DSP::HLE::region0_base, reinterpret_cast<u8*>(&DSP::HLE::g_region0), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
+    address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite);
+
+    auto r1_vma = address_space.MapBackingMemory(DSP::HLE::region1_base, reinterpret_cast<u8*>(&DSP::HLE::g_region1), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
+    address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite);
+}
+
+/// Shutdown Audio
+void Shutdown() {
+    CoreTiming::UnscheduleEvent(tick_event, 0);
+    DSP::HLE::Shutdown();
+}
+
+} //namespace
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
new file mode 100644
index 000000000..64c330914
--- /dev/null
+++ b/src/audio_core/audio_core.h
@@ -0,0 +1,26 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Kernel {
+class VMManager;
+}
+
+namespace AudioCore {
+
+constexpr int num_sources = 24;
+constexpr int samples_per_frame = 160;     ///< Samples per audio frame at native sample rate
+constexpr int native_sample_rate = 32728;  ///< 32kHz
+
+/// Initialise Audio Core
+void Init();
+
+/// Add DSP address spaces to a Process.
+void AddAddressSpace(Kernel::VMManager& vm_manager);
+
+/// Shutdown Audio Core
+void Shutdown();
+
+} // namespace
diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp
new file mode 100644
index 000000000..c89356edc
--- /dev/null
+++ b/src/audio_core/hle/dsp.cpp
@@ -0,0 +1,42 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/pipe.h"
+
+namespace DSP {
+namespace HLE {
+
+SharedMemory g_region0;
+SharedMemory g_region1;
+
+void Init() {
+    DSP::HLE::ResetPipes();
+}
+
+void Shutdown() {
+}
+
+bool Tick() {
+    return true;
+}
+
+SharedMemory& CurrentRegion() {
+    // The region with the higher frame counter is chosen unless there is wraparound.
+
+    if (g_region0.frame_counter == 0xFFFFu && g_region1.frame_counter != 0xFFFEu) {
+        // Wraparound has occured.
+        return g_region1;
+    }
+
+    if (g_region1.frame_counter == 0xFFFFu && g_region0.frame_counter != 0xFFFEu) {
+        // Wraparound has occured.
+        return g_region0;
+    }
+
+    return (g_region0.frame_counter > g_region1.frame_counter) ? g_region0 : g_region1;
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h
new file mode 100644
index 000000000..14c4000c6
--- /dev/null
+++ b/src/audio_core/hle/dsp.h
@@ -0,0 +1,502 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+
+#include "audio_core/audio_core.h"
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace DSP {
+namespace HLE {
+
+// The application-accessible region of DSP memory consists of two parts.
+// Both are marked as IO and have Read/Write permissions.
+//
+// First Region:  0x1FF50000 (Size: 0x8000)
+// Second Region: 0x1FF70000 (Size: 0x8000)
+//
+// The DSP reads from each region alternately based on the frame counter for each region much like a
+// double-buffer. The frame counter is located as the very last u16 of each region and is incremented
+// each audio tick.
+
+struct SharedMemory;
+
+constexpr VAddr region0_base = 0x1FF50000;
+extern SharedMemory g_region0;
+
+constexpr VAddr region1_base = 0x1FF70000;
+extern SharedMemory g_region1;
+
+/**
+ * The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from
+ * its memory regions, the higher and lower 16-bit halves are swapped compared to the little-endian
+ * layout of the ARM11. Hence from the ARM11's point of view the memory space appears to be
+ * middle-endian.
+ *
+ * Unusually this does not appear to be an issue for floating point numbers. The DSP makes the more
+ * sensible choice of keeping that little-endian. There are also some exceptions such as the
+ * IntermediateMixSamples structure, which is little-endian.
+ *
+ * This struct implements the conversion to and from this middle-endianness.
+ */
+struct u32_dsp {
+    u32_dsp() = default;
+    operator u32() const {
+        return Convert(storage);
+    }
+    void operator=(u32 new_value) {
+        storage = Convert(new_value);
+    }
+private:
+    static constexpr u32 Convert(u32 value) {
+        return (value << 16) | (value >> 16);
+    }
+    u32_le storage;
+};
+#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
+static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivially copyable");
+#endif
+
+// There are 15 structures in each memory region. A table of them in the order they appear in memory
+// is presented below
+//
+//       Pipe 2 #    First Region DSP Address   Purpose                               Control
+//       5           0x8400                     DSP Status                            DSP
+//       9           0x8410                     DSP Debug Info                        DSP
+//       6           0x8540                     Final Mix Samples                     DSP
+//       2           0x8680                     Source Status [24]                    DSP
+//       8           0x8710                     Compressor Table                      Application
+//       4           0x9430                     DSP Configuration                     Application
+//       7           0x9492                     Intermediate Mix Samples              DSP + App
+//       1           0x9E92                     Source Configuration [24]             Application
+//       3           0xA792                     Source ADPCM Coefficients [24]        Application
+//       10          0xA912                     Surround Sound Related
+//       11          0xAA12                     Surround Sound Related
+//       12          0xAAD2                     Surround Sound Related
+//       13          0xAC52                     Surround Sound Related
+//       14          0xAC5C                     Surround Sound Related
+//       0           0xBFFF                     Frame Counter                         Application
+//
+// Note that the above addresses do vary slightly between audio firmwares observed; the addresses are
+// not fixed in stone. The addresses above are only an examplar; they're what this implementation
+// does and provides to applications.
+//
+// Application requests the DSP service to convert DSP addresses into ARM11 virtual addresses using the
+// ConvertProcessAddressFromDspDram service call. Applications seem to derive the addresses for the
+// second region via:
+//     second_region_dsp_addr = first_region_dsp_addr | 0x10000
+//
+// Applications maintain most of its own audio state, the memory region is used mainly for
+// communication and not storage of state.
+//
+// In the documentation below, filter and effect transfer functions are specified in the z domain.
+// (If you are more familiar with the Laplace transform, z = exp(sT). The z domain is the digital
+//  frequency domain, just like how the s domain is the analog frequency domain.)
+
+#define INSERT_PADDING_DSPWORDS(num_words) INSERT_PADDING_BYTES(2 * (num_words))
+
+// GCC versions < 5.0 do not implement std::is_trivially_copyable.
+// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable.
+#if (__GNUC__ >= 5) || defined(__clang__)
+    #define ASSERT_DSP_STRUCT(name, size) \
+        static_assert(std::is_standard_layout<name>::value, "DSP structure " #name " doesn't use standard layout"); \
+        static_assert(std::is_trivially_copyable<name>::value, "DSP structure " #name " isn't trivially copyable"); \
+        static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name)
+#else
+    #define ASSERT_DSP_STRUCT(name, size) \
+        static_assert(std::is_standard_layout<name>::value, "DSP structure " #name " doesn't use standard layout"); \
+        static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name)
+#endif
+
+struct SourceConfiguration {
+    struct Configuration {
+        /// These dirty flags are set by the application when it updates the fields in this struct.
+        /// The DSP clears these each audio frame.
+        union {
+            u32_le dirty_raw;
+
+            BitField<2, 1, u32_le> adpcm_coefficients_dirty;
+            BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
+
+            BitField<16, 1, u32_le> enable_dirty;
+            BitField<17, 1, u32_le> interpolation_dirty;
+            BitField<18, 1, u32_le> rate_multiplier_dirty;
+            BitField<19, 1, u32_le> buffer_queue_dirty;
+            BitField<20, 1, u32_le> loop_related_dirty;
+            BitField<21, 1, u32_le> play_position_dirty; ///< Tends to also be set when embedded buffer is updated.
+            BitField<22, 1, u32_le> filters_enabled_dirty;
+            BitField<23, 1, u32_le> simple_filter_dirty;
+            BitField<24, 1, u32_le> biquad_filter_dirty;
+            BitField<25, 1, u32_le> gain_0_dirty;
+            BitField<26, 1, u32_le> gain_1_dirty;
+            BitField<27, 1, u32_le> gain_2_dirty;
+            BitField<28, 1, u32_le> sync_dirty;
+            BitField<29, 1, u32_le> reset_flag;
+
+            BitField<31, 1, u32_le> embedded_buffer_dirty;
+        };
+
+        // Gain control
+
+        /**
+         * Gain is between 0.0-1.0. This determines how much will this source appear on
+         * each of the 12 channels that feed into the intermediate mixers.
+         * Each of the three intermediate mixers is fed two left and two right channels.
+         */
+        float_le gain[3][4];
+
+        // Interpolation
+
+        /// Multiplier for sample rate. Resampling occurs with the selected interpolation method.
+        float_le rate_multiplier;
+
+        enum class InterpolationMode : u8 {
+            None = 0,
+            Linear = 1,
+            Polyphase = 2
+        };
+
+        InterpolationMode interpolation_mode;
+        INSERT_PADDING_BYTES(1); ///< Interpolation related
+
+        // Filters
+
+        /**
+         * This is the simplest normalized first-order digital recursive filter.
+         * The transfer function of this filter is:
+         *     H(z) = b0 / (1 + a1 z^-1)
+         * Values are signed fixed point with 15 fractional bits.
+         */
+        struct SimpleFilter {
+            s16_le b0;
+            s16_le a1;
+        };
+
+        /**
+         * This is a normalised biquad filter (second-order).
+         * The transfer function of this filter is:
+         *     H(z) = (b0 + b1 z^-1 + b2 z^-2) / (1 - a1 z^-1 - a2 z^-2)
+         * Nintendo chose to negate the feedbackward coefficients. This differs from standard notation
+         * as in: https://ccrma.stanford.edu/~jos/filters/Direct_Form_I.html
+         * Values are signed fixed point with 14 fractional bits.
+         */
+        struct BiquadFilter {
+            s16_le b0;
+            s16_le b1;
+            s16_le b2;
+            s16_le a1;
+            s16_le a2;
+        };
+
+        union {
+            u16_le filters_enabled;
+            BitField<0, 1, u16_le> simple_filter_enabled;
+            BitField<1, 1, u16_le> biquad_filter_enabled;
+        };
+
+        SimpleFilter simple_filter;
+        BiquadFilter biquad_filter;
+
+        // Buffer Queue
+
+        /// A buffer of audio data from the application, along with metadata about it.
+        struct Buffer {
+            /// Physical memory address of the start of the buffer
+            u32_dsp physical_address;
+
+            /// This is length in terms of samples.
+            /// Note that in different buffer formats a sample takes up different number of bytes.
+            u32_dsp length;
+
+            /// ADPCM Predictor (4 bits) and Scale (4 bits)
+            union {
+                u16_le adpcm_ps;
+                BitField<0, 4, u16_le> adpcm_scale;
+                BitField<4, 4, u16_le> adpcm_predictor;
+            };
+
+            /// ADPCM Historical Samples (y[n-1] and y[n-2])
+            u16_le adpcm_yn[2];
+
+            /// This is non-zero when the ADPCM values above are to be updated.
+            u8 adpcm_dirty;
+
+            /// Is a looping buffer.
+            u8 is_looping;
+
+            /// This value is shown in SourceStatus::previous_buffer_id when this buffer has finished.
+            /// This allows the emulated application to tell what buffer is currently playing
+            u16_le buffer_id;
+
+            INSERT_PADDING_DSPWORDS(1);
+        };
+
+        u16_le buffers_dirty;             ///< Bitmap indicating which buffers are dirty (bit i -> buffers[i])
+        Buffer buffers[4];                ///< Queued Buffers
+
+        // Playback controls
+
+        u32_dsp loop_related;
+        u8 enable;
+        INSERT_PADDING_BYTES(1);
+        u16_le sync;                      ///< Application-side sync (See also: SourceStatus::sync)
+        u32_dsp play_position;            ///< Position. (Units: number of samples)
+        INSERT_PADDING_DSPWORDS(2);
+
+        // Embedded Buffer
+        // This buffer is often the first buffer to be used when initiating audio playback,
+        // after which the buffer queue is used.
+
+        u32_dsp physical_address;
+
+        /// This is length in terms of samples.
+        /// Note a sample takes up different number of bytes in different buffer formats.
+        u32_dsp length;
+
+        enum class MonoOrStereo : u16_le {
+            Mono = 1,
+            Stereo = 2
+        };
+
+        enum class Format : u16_le {
+            PCM8 = 0,
+            PCM16 = 1,
+            ADPCM = 2
+        };
+
+        union {
+            u16_le flags1_raw;
+            BitField<0, 2, MonoOrStereo> mono_or_stereo;
+            BitField<2, 2, Format> format;
+            BitField<5, 1, u16_le> fade_in;
+        };
+
+        /// ADPCM Predictor (4 bit) and Scale (4 bit)
+        union {
+            u16_le adpcm_ps;
+            BitField<0, 4, u16_le> adpcm_scale;
+            BitField<4, 4, u16_le> adpcm_predictor;
+        };
+
+        /// ADPCM Historical Samples (y[n-1] and y[n-2])
+        u16_le adpcm_yn[2];
+
+        union {
+            u16_le flags2_raw;
+            BitField<0, 1, u16_le> adpcm_dirty; ///< Has the ADPCM info above been changed?
+            BitField<1, 1, u16_le> is_looping; ///< Is this a looping buffer?
+        };
+
+        /// Buffer id of embedded buffer (used as a buffer id in SourceStatus to reference this buffer).
+        u16_le buffer_id;
+    };
+
+    Configuration config[AudioCore::num_sources];
+};
+ASSERT_DSP_STRUCT(SourceConfiguration::Configuration, 192);
+ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20);
+
+struct SourceStatus {
+    struct Status {
+        u8 is_enabled;               ///< Is this channel enabled? (Doesn't have to be playing anything.)
+        u8 previous_buffer_id_dirty; ///< Non-zero when previous_buffer_id changes
+        u16_le sync;                 ///< Is set by the DSP to the value of SourceConfiguration::sync
+        u32_dsp buffer_position;     ///< Number of samples into the current buffer
+        u16_le previous_buffer_id;   ///< Updated when a buffer finishes playing
+        INSERT_PADDING_DSPWORDS(1);
+    };
+
+    Status status[AudioCore::num_sources];
+};
+ASSERT_DSP_STRUCT(SourceStatus::Status, 12);
+
+struct DspConfiguration {
+    /// These dirty flags are set by the application when it updates the fields in this struct.
+    /// The DSP clears these each audio frame.
+    union {
+        u32_le dirty_raw;
+
+        BitField<8, 1, u32_le> mixer1_enabled_dirty;
+        BitField<9, 1, u32_le> mixer2_enabled_dirty;
+        BitField<10, 1, u32_le> delay_effect_0_dirty;
+        BitField<11, 1, u32_le> delay_effect_1_dirty;
+        BitField<12, 1, u32_le> reverb_effect_0_dirty;
+        BitField<13, 1, u32_le> reverb_effect_1_dirty;
+
+        BitField<16, 1, u32_le> volume_0_dirty;
+
+        BitField<24, 1, u32_le> volume_1_dirty;
+        BitField<25, 1, u32_le> volume_2_dirty;
+        BitField<26, 1, u32_le> output_format_dirty;
+        BitField<27, 1, u32_le> limiter_enabled_dirty;
+        BitField<28, 1, u32_le> headphones_connected_dirty;
+    };
+
+    /// The DSP has three intermediate audio mixers. This controls the volume level (0.0-1.0) for each at the final mixer
+    float_le volume[3];
+
+    INSERT_PADDING_DSPWORDS(3);
+
+    enum class OutputFormat : u16_le {
+        Mono = 0,
+        Stereo = 1,
+        Surround = 2
+    };
+
+    OutputFormat output_format;
+
+    u16_le limiter_enabled;      ///< Not sure of the exact gain equation for the limiter.
+    u16_le headphones_connected; ///< Application updates the DSP on headphone status.
+    INSERT_PADDING_DSPWORDS(4);  ///< TODO: Surround sound related
+    INSERT_PADDING_DSPWORDS(2);  ///< TODO: Intermediate mixer 1/2 related
+    u16_le mixer1_enabled;
+    u16_le mixer2_enabled;
+
+    /**
+     * This is delay with feedback.
+     * Transfer function:
+     *     H(z) = a z^-N / (1 - b z^-1 + a g z^-N)
+     *   where
+     *     N = frame_count * samples_per_frame
+     * g, a and b are fixed point with 7 fractional bits
+     */
+    struct DelayEffect {
+        /// These dirty flags are set by the application when it updates the fields in this struct.
+        /// The DSP clears these each audio frame.
+        union {
+            u16_le dirty_raw;
+            BitField<0, 1, u16_le> enable_dirty;
+            BitField<1, 1, u16_le> work_buffer_address_dirty;
+            BitField<2, 1, u16_le> other_dirty; ///< Set when anything else has been changed
+        };
+
+        u16_le enable;
+        INSERT_PADDING_DSPWORDS(1);
+        u16_le outputs;
+        u32_dsp work_buffer_address; ///< The application allocates a block of memory for the DSP to use as a work buffer.
+        u16_le frame_count;  ///< Frames to delay by
+
+        // Coefficients
+        s16_le g; ///< Fixed point with 7 fractional bits
+        s16_le a; ///< Fixed point with 7 fractional bits
+        s16_le b; ///< Fixed point with 7 fractional bits
+    };
+
+    DelayEffect delay_effect[2];
+
+    struct ReverbEffect {
+        INSERT_PADDING_DSPWORDS(26); ///< TODO
+    };
+
+    ReverbEffect reverb_effect[2];
+
+    INSERT_PADDING_DSPWORDS(4);
+};
+ASSERT_DSP_STRUCT(DspConfiguration, 196);
+ASSERT_DSP_STRUCT(DspConfiguration::DelayEffect, 20);
+ASSERT_DSP_STRUCT(DspConfiguration::ReverbEffect, 52);
+
+struct AdpcmCoefficients {
+    /// Coefficients are signed fixed point with 11 fractional bits.
+    /// Each source has 16 coefficients associated with it.
+    s16_le coeff[AudioCore::num_sources][16];
+};
+ASSERT_DSP_STRUCT(AdpcmCoefficients, 768);
+
+struct DspStatus {
+    u16_le unknown;
+    u16_le dropped_frames;
+    INSERT_PADDING_DSPWORDS(0xE);
+};
+ASSERT_DSP_STRUCT(DspStatus, 32);
+
+/// Final mixed output in PCM16 stereo format, what you hear out of the speakers.
+/// When the application writes to this region it has no effect.
+struct FinalMixSamples {
+    s16_le pcm16[2 * AudioCore::samples_per_frame];
+};
+ASSERT_DSP_STRUCT(FinalMixSamples, 640);
+
+/// DSP writes output of intermediate mixers 1 and 2 here.
+/// Writes to this region by the application edits the output of the intermediate mixers.
+/// This seems to be intended to allow the application to do custom effects on the ARM11.
+/// Values that exceed s16 range will be clipped by the DSP after further processing.
+struct IntermediateMixSamples {
+    struct Samples {
+        s32_le pcm32[4][AudioCore::samples_per_frame]; ///< Little-endian as opposed to DSP middle-endian.
+    };
+
+    Samples mix1;
+    Samples mix2;
+};
+ASSERT_DSP_STRUCT(IntermediateMixSamples, 5120);
+
+/// Compressor table
+struct Compressor {
+    INSERT_PADDING_DSPWORDS(0xD20); ///< TODO
+};
+
+/// There is no easy way to implement this in a HLE implementation.
+struct DspDebug {
+    INSERT_PADDING_DSPWORDS(0x130);
+};
+ASSERT_DSP_STRUCT(DspDebug, 0x260);
+
+struct SharedMemory {
+    /// Padding
+    INSERT_PADDING_DSPWORDS(0x400);
+
+    DspStatus dsp_status;
+
+    DspDebug dsp_debug;
+
+    FinalMixSamples final_samples;
+
+    SourceStatus source_statuses;
+
+    Compressor compressor;
+
+    DspConfiguration dsp_configuration;
+
+    IntermediateMixSamples intermediate_mix_samples;
+
+    SourceConfiguration source_configurations;
+
+    AdpcmCoefficients adpcm_coefficients;
+
+    /// Unknown 10-14 (Surround sound related)
+    INSERT_PADDING_DSPWORDS(0x16ED);
+
+    u16_le frame_counter;
+};
+ASSERT_DSP_STRUCT(SharedMemory, 0x8000);
+
+#undef INSERT_PADDING_DSPWORDS
+#undef ASSERT_DSP_STRUCT
+
+/// Initialize DSP hardware
+void Init();
+
+/// Shutdown DSP hardware
+void Shutdown();
+
+/**
+ * Perform processing and updates state of current shared memory buffer.
+ * This function is called every audio tick before triggering the audio interrupt.
+ * @return Whether an audio interrupt should be triggered this frame.
+ */
+bool Tick();
+
+/// Returns a mutable reference to the current region. Current region is selected based on the frame counter.
+SharedMemory& CurrentRegion();
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/pipe.cpp b/src/audio_core/hle/pipe.cpp
new file mode 100644
index 000000000..6542c760c
--- /dev/null
+++ b/src/audio_core/hle/pipe.cpp
@@ -0,0 +1,55 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <vector>
+
+#include "audio_core/hle/pipe.h"
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
+
+namespace DSP {
+namespace HLE {
+
+static size_t pipe2position = 0;
+
+void ResetPipes() {
+    pipe2position = 0;
+}
+
+std::vector<u8> PipeRead(u32 pipe_number, u32 length) {
+    if (pipe_number != 2) {
+        LOG_WARNING(Audio_DSP, "pipe_number = %u (!= 2), unimplemented", pipe_number);
+        return {}; // We currently don't handle anything other than the audio pipe.
+    }
+
+    // Canned DSP responses that games expect. These were taken from HW by 3dmoo team.
+    // TODO: Our implementation will actually use a slightly different response than this one.
+    // TODO: Use offsetof on DSP structures instead for a proper response.
+    static const std::array<u8, 32> canned_response {{
+        0x0F, 0x00, 0xFF, 0xBF, 0x8E, 0x9E, 0x80, 0x86, 0x8E, 0xA7, 0x30, 0x94, 0x00, 0x84, 0x40, 0x85,
+        0x8E, 0x94, 0x10, 0x87, 0x10, 0x84, 0x0E, 0xA9, 0x0E, 0xAA, 0xCE, 0xAA, 0x4E, 0xAC, 0x58, 0xAC
+    }};
+
+    // TODO: Move this into dsp::DSP service since it happens on the service side.
+    // Hardware observation: No data is returned if requested length reads beyond the end of the data in-pipe.
+    if (pipe2position + length > canned_response.size()) {
+        return {};
+    }
+
+    std::vector<u8> ret;
+    for (size_t i = 0; i < length; i++, pipe2position++) {
+        ret.emplace_back(canned_response[pipe2position]);
+    }
+
+    return ret;
+}
+
+void PipeWrite(u32 pipe_number, const std::vector<u8>& buffer) {
+    // TODO: proper pipe behaviour
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/pipe.h b/src/audio_core/hle/pipe.h
new file mode 100644
index 000000000..ff6536950
--- /dev/null
+++ b/src/audio_core/hle/pipe.h
@@ -0,0 +1,38 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace DSP {
+namespace HLE {
+
+/// Reset the pipes by setting pipe positions back to the beginning.
+void ResetPipes();
+
+/**
+ * Read a DSP pipe.
+ * Pipe IDs:
+ *   pipe_number = 0: Debug
+ *   pipe_number = 1: P-DMA
+ *   pipe_number = 2: Audio
+ *   pipe_number = 3: Binary
+ * @param pipe_number The Pipe ID
+ * @param length How much data to request.
+ * @return The data read from the pipe. The size of this vector can be less than the length requested.
+ */
+std::vector<u8> PipeRead(u32 pipe_number, u32 length);
+
+/**
+ * Write to a DSP pipe.
+ * @param pipe_number The Pipe ID
+ * @param buffer The data to write to the pipe.
+ */
+void PipeWrite(u32 pipe_number, const std::vector<u8>& buffer);
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
new file mode 100644
index 000000000..cad21a85e
--- /dev/null
+++ b/src/audio_core/sink.h
@@ -0,0 +1,34 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+/**
+ * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed PCM16 format to be output.
+ * Sinks *do not* handle resampling and expect the correct sample rate. They are dumb outputs.
+ */
+class Sink {
+public:
+    virtual ~Sink() = default;
+
+    /// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec)
+    virtual unsigned GetNativeSampleRate() const = 0;
+
+    /**
+     * Feed stereo samples to sink.
+     * @param samples Samples in interleaved stereo PCM16 format. Size of vector must be multiple of two.
+     */
+    virtual void EnqueueSamples(const std::vector<s16>& samples) = 0;
+
+    /// Samples enqueued that have not been played yet.
+    virtual std::size_t SamplesInQueue() const = 0;
+};
+
+} // namespace
diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt
index e7f8a17f9..b9abb818e 100644
--- a/src/citra/CMakeLists.txt
+++ b/src/citra/CMakeLists.txt
@@ -17,7 +17,7 @@ include_directories(${GLFW_INCLUDE_DIRS})
 link_directories(${GLFW_LIBRARY_DIRS})
 
 add_executable(citra ${SRCS} ${HEADERS})
-target_link_libraries(citra core video_core common)
+target_link_libraries(citra core video_core audio_core common)
 target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih glad)
 if (MSVC)
     target_link_libraries(citra getopt)
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index bbf6ae001..b3d1205a4 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -79,7 +79,7 @@ if (APPLE)
 else()
     add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
 endif()
-target_link_libraries(citra-qt core video_core common qhexedit)
+target_link_libraries(citra-qt core video_core audio_core common qhexedit)
 target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS})
 target_link_libraries(citra-qt ${PLATFORM_LIBRARIES})
 
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index 600e0c70c..371eb17a1 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -185,6 +185,6 @@ private:
 };
 #pragma pack()
 
-#if (__GNUC__ >= 5) || defined __clang__ || defined _MSC_VER
+#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
 static_assert(std::is_trivially_copyable<BitField<0, 1, u32>>::value, "BitField must be trivially copyable");
 #endif
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index d186ba8f8..58819012d 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -58,6 +58,8 @@ namespace Log {
         CLS(Render) \
         SUB(Render, Software) \
         SUB(Render, OpenGL) \
+        CLS(Audio) \
+        SUB(Audio, DSP) \
         CLS(Loader)
 
 // GetClassName is a macro defined by Windows.h, grrr...
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 2d9323a7b..ec7bb00b8 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -73,6 +73,8 @@ enum class Class : ClassType {
     Render,                     ///< Emulator video output and hardware acceleration
     Render_Software,            ///< Software renderer backend
     Render_OpenGL,              ///< OpenGL backend
+    Audio,                      ///< Emulator audio output
+    Audio_DSP,                  ///< The HLE implementation of the DSP
     Loader,                     ///< ROM loader
 
     Count ///< Total number of logging classes
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 0cfb43fc7..862643448 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -7,6 +7,8 @@
 #include <utility>
 #include <vector>
 
+#include "audio_core/audio_core.h"
+
 #include "common/common_types.h"
 #include "common/logging/log.h"
 
@@ -107,7 +109,6 @@ struct MemoryArea {
 static MemoryArea memory_areas[] = {
     {SHARED_MEMORY_VADDR, SHARED_MEMORY_SIZE,     "Shared Memory"}, // Shared memory
     {VRAM_VADDR,          VRAM_SIZE,              "VRAM"},          // Video memory (VRAM)
-    {DSP_RAM_VADDR,       DSP_RAM_SIZE,           "DSP RAM"},       // DSP memory
     {TLS_AREA_VADDR,      TLS_AREA_SIZE,          "TLS Area"},      // TLS memory
 };
 
@@ -133,6 +134,8 @@ void InitLegacyAddressSpace(Kernel::VMManager& address_space) {
     auto shared_page_vma = address_space.MapBackingMemory(SHARED_PAGE_VADDR,
             (u8*)&SharedPage::shared_page, SHARED_PAGE_SIZE, MemoryState::Shared).MoveFrom();
     address_space.Reprotect(shared_page_vma, VMAPermission::Read);
+
+    AudioCore::AddAddressSpace(address_space);
 }
 
 } // namespace
diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp
index f9f931f6d..15d3274ec 100644
--- a/src/core/hle/service/dsp_dsp.cpp
+++ b/src/core/hle/service/dsp_dsp.cpp
@@ -2,6 +2,8 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "audio_core/hle/pipe.h"
+
 #include "common/logging/log.h"
 
 #include "core/hle/kernel/event.h"
@@ -14,17 +16,30 @@ namespace DSP_DSP {
 
 static u32 read_pipe_count;
 static Kernel::SharedPtr<Kernel::Event> semaphore_event;
-static Kernel::SharedPtr<Kernel::Event> interrupt_event;
 
-void SignalInterrupt() {
-    // TODO(bunnei): This is just a stub, it does not do anything other than signal to the emulated
-    // application that a DSP interrupt occurred, without specifying which one. Since we do not
-    // emulate the DSP yet (and how it works is largely unknown), this is a work around to get games
-    // that check the DSP interrupt signal event to run. We should figure out the different types of
-    // DSP interrupts, and trigger them at the appropriate times.
+struct PairHash {
+    template <typename T, typename U>
+    std::size_t operator()(const std::pair<T, U> &x) const {
+        // TODO(yuriks): Replace with better hash combining function.
+        return std::hash<T>()(x.first) ^ std::hash<U>()(x.second);
+    }
+};
 
-    if (interrupt_event != 0)
-        interrupt_event->Signal();
+/// Map of (audio interrupt number, channel number) to Kernel::Events. See: RegisterInterruptEvents
+static std::unordered_map<std::pair<u32, u32>, Kernel::SharedPtr<Kernel::Event>, PairHash> interrupt_events;
+
+// DSP Interrupts:
+// Interrupt #2 occurs every frame tick. Userland programs normally have a thread that's waiting
+// for an interrupt event. Immediately after this interrupt event, userland normally updates the
+// state in the next region and increments the relevant frame counter by two.
+void SignalAllInterrupts() {
+    // HACK: The other interrupts have currently unknown purpose, we trigger them each tick in any case.
+    for (auto& interrupt_event : interrupt_events)
+        interrupt_event.second->Signal();
+}
+
+void SignalInterrupt(u32 interrupt, u32 channel) {
+    interrupt_events[std::make_pair(interrupt, channel)]->Signal();
 }
 
 /**
@@ -43,7 +58,7 @@ static void ConvertProcessAddressFromDspDram(Service::Interface* self) {
     cmd_buff[1] = 0; // No error
     cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000);
 
-    LOG_WARNING(Service_DSP, "(STUBBED) called with address 0x%08X", addr);
+    LOG_TRACE(Service_DSP, "addr=0x%08X", addr);
 }
 
 /**
@@ -121,8 +136,8 @@ static void FlushDataCache(Service::Interface* self) {
 /**
  * DSP_DSP::RegisterInterruptEvents service function
  *  Inputs:
- *      1 : Parameter 0 (purpose unknown)
- *      2 : Parameter 1 (purpose unknown)
+ *      1 : Interrupt Number
+ *      2 : Channel Number
  *      4 : Interrupt event handle
  *  Outputs:
  *      1 : Result of function, 0 on success, otherwise error code
@@ -130,22 +145,24 @@ static void FlushDataCache(Service::Interface* self) {
 static void RegisterInterruptEvents(Service::Interface* self) {
     u32* cmd_buff = Kernel::GetCommandBuffer();
 
-    u32 param0 = cmd_buff[1];
-    u32 param1 = cmd_buff[2];
+    u32 interrupt = cmd_buff[1];
+    u32 channel = cmd_buff[2];
     u32 event_handle = cmd_buff[4];
 
-    auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
-    if (evt != nullptr) {
-        interrupt_event = evt;
-        cmd_buff[1] = 0; // No error
+    if (event_handle) {
+        auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
+        if (evt) {
+            interrupt_events[std::make_pair(interrupt, channel)] = evt;
+            cmd_buff[1] = RESULT_SUCCESS.raw;
+            LOG_WARNING(Service_DSP, "Registered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle);
+        } else {
+            cmd_buff[1] = -1;
+            LOG_ERROR(Service_DSP, "Invalid event handle! interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle);
+        }
     } else {
-        LOG_ERROR(Service_DSP, "called with invalid handle=%08X", cmd_buff[4]);
-
-        // TODO(yuriks): An error should be returned from SendSyncRequest, not in the cmdbuf
-        cmd_buff[1] = -1;
+        interrupt_events.erase(std::make_pair(interrupt, channel));
+        LOG_WARNING(Service_DSP, "Unregistered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle);
     }
-
-    LOG_WARNING(Service_DSP, "(STUBBED) called param0=%u, param1=%u, event_handle=0x%08X", param0, param1, event_handle);
 }
 
 /**
@@ -158,8 +175,6 @@ static void RegisterInterruptEvents(Service::Interface* self) {
 static void SetSemaphore(Service::Interface* self) {
     u32* cmd_buff = Kernel::GetCommandBuffer();
 
-    SignalInterrupt();
-
     cmd_buff[1] = 0; // No error
 
     LOG_WARNING(Service_DSP, "(STUBBED) called");
@@ -168,9 +183,9 @@ static void SetSemaphore(Service::Interface* self) {
 /**
  * DSP_DSP::WriteProcessPipe service function
  *  Inputs:
- *      1 : Number
+ *      1 : Channel
  *      2 : Size
- *      3 : (size <<14) | 0x402
+ *      3 : (size << 14) | 0x402
  *      4 : Buffer
  *  Outputs:
  *      0 : Return header
@@ -179,21 +194,42 @@ static void SetSemaphore(Service::Interface* self) {
 static void WriteProcessPipe(Service::Interface* self) {
     u32* cmd_buff = Kernel::GetCommandBuffer();
 
-    u32 number   = cmd_buff[1];
+    u32 channel  = cmd_buff[1];
     u32 size     = cmd_buff[2];
-    u32 new_size = cmd_buff[3];
     u32 buffer   = cmd_buff[4];
 
+    if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) {
+        LOG_ERROR(Service_DSP, "IPC static buffer descriptor failed validation (0x%X). channel=%u, size=0x%X, buffer=0x%08X", cmd_buff[3], channel, size, buffer);
+        cmd_buff[1] = -1; // TODO
+        return;
+    }
+
+    if (!Memory::GetPointer(buffer)) {
+        LOG_ERROR(Service_DSP, "Invalid Buffer: channel=%u, size=0x%X, buffer=0x%08X", channel, size, buffer);
+        cmd_buff[1] = -1; // TODO
+        return;
+    }
+
+    std::vector<u8> message(size);
+
+    for (size_t i = 0; i < size; i++) {
+        message[i] = Memory::Read8(buffer + i);
+    }
+
+    DSP::HLE::PipeWrite(channel, message);
+
     cmd_buff[1] = RESULT_SUCCESS.raw; // No error
 
-    LOG_WARNING(Service_DSP, "(STUBBED) called number=%u, size=0x%X, new_size=0x%X, buffer=0x%08X",
-                number, size, new_size, buffer);
+    LOG_TRACE(Service_DSP, "channel=%u, size=0x%X, buffer=0x%08X", channel, size, buffer);
 }
 
 /**
  * DSP_DSP::ReadPipeIfPossible service function
+ *      A pipe is a means of communication between the ARM11 and DSP that occurs on
+ *      hardware by writing to/reading from the DSP registers at 0x10203000.
+ *      Pipes are used for initialisation. See also DSP::HLE::PipeRead.
  *  Inputs:
- *      1 : Unknown
+ *      1 : Pipe Number
  *      2 : Unknown
  *      3 : Size in bytes of read (observed only lower half word used)
  *      0x41 : Virtual address to read from DSP pipe to in memory
@@ -204,35 +240,25 @@ static void WriteProcessPipe(Service::Interface* self) {
 static void ReadPipeIfPossible(Service::Interface* self) {
     u32* cmd_buff = Kernel::GetCommandBuffer();
 
-    u32 unk1 = cmd_buff[1];
+    u32 pipe = cmd_buff[1];
     u32 unk2 = cmd_buff[2];
     u32 size = cmd_buff[3] & 0xFFFF;// Lower 16 bits are size
     VAddr addr = cmd_buff[0x41];
 
-    // Canned DSP responses that games expect. These were taken from HW by 3dmoo team.
-    // TODO: Remove this hack :)
-    static const std::array<u16, 16> canned_read_pipe = {{
-        0x000F, 0xBFFF, 0x9E8E, 0x8680, 0xA78E, 0x9430, 0x8400, 0x8540,
-        0x948E, 0x8710, 0x8410, 0xA90E, 0xAA0E, 0xAACE, 0xAC4E, 0xAC58
-    }};
-
-    u32 initial_size = read_pipe_count;
-
-    for (unsigned offset = 0; offset < size; offset += sizeof(u16)) {
-        if (read_pipe_count < canned_read_pipe.size()) {
-            Memory::Write16(addr + offset, canned_read_pipe[read_pipe_count]);
-            read_pipe_count++;
-        } else {
-            LOG_ERROR(Service_DSP, "canned read pipe log exceeded!");
-            break;
-        }
+    if (!Memory::GetPointer(addr)) {
+        LOG_ERROR(Service_DSP, "Invalid addr: pipe=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", pipe, unk2, size, addr);
+        cmd_buff[1] = -1; // TODO
+        return;
     }
 
-    cmd_buff[1] = 0; // No error
-    cmd_buff[2] = (read_pipe_count - initial_size) * sizeof(u16);
+    std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
 
-    LOG_WARNING(Service_DSP, "(STUBBED) called unk1=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X",
-                unk1, unk2, size, addr);
+    Memory::WriteBlock(addr, response.data(), response.size());
+
+    cmd_buff[1] = 0; // No error
+    cmd_buff[2] = (u32)response.size();
+
+    LOG_TRACE(Service_DSP, "pipe=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", pipe, unk2, size, addr);
 }
 
 /**
@@ -311,7 +337,6 @@ const Interface::FunctionInfo FunctionTable[] = {
 
 Interface::Interface() {
     semaphore_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "DSP_DSP::semaphore_event");
-    interrupt_event = nullptr;
     read_pipe_count = 0;
 
     Register(FunctionTable);
@@ -319,7 +344,7 @@ Interface::Interface() {
 
 Interface::~Interface() {
     semaphore_event = nullptr;
-    interrupt_event = nullptr;
+    interrupt_events.clear();
 }
 
 } // namespace
diff --git a/src/core/hle/service/dsp_dsp.h b/src/core/hle/service/dsp_dsp.h
index b6f611db5..32b89e9bb 100644
--- a/src/core/hle/service/dsp_dsp.h
+++ b/src/core/hle/service/dsp_dsp.h
@@ -23,7 +23,15 @@ public:
     }
 };
 
-/// Signals that a DSP interrupt has occurred to userland code
-void SignalInterrupt();
+/// Signal all audio related interrupts.
+void SignalAllInterrupts();
+
+/**
+ * Signal a specific audio related interrupt based on interrupt id and channel id.
+ * @param interrupt_id The interrupt id
+ * @param channel_id The channel id
+ * The significance of various values of interrupt_id and channel_id is not yet known.
+ */
+void SignalInterrupt(u32 interrupt_id, u32 channel_id);
 
 } // namespace
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index c60310586..5312baa83 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -17,7 +17,6 @@
 #include "core/core_timing.h"
 
 #include "core/hle/service/gsp_gpu.h"
-#include "core/hle/service/dsp_dsp.h"
 #include "core/hle/service/hid/hid.h"
 
 #include "core/hw/hw.h"
@@ -414,11 +413,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) {
     GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0);
     GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1);
 
-    // TODO(bunnei): Fake a DSP interrupt on each frame. This does not belong here, but
-    // until we can emulate DSP interrupts, this is probably the only reasonable place to do
-    // this. Certain games expect this to be periodically signaled.
-    DSP_DSP::SignalInterrupt();
-
     // Check for user input updates
     Service::HID::Update();
 
diff --git a/src/core/system.cpp b/src/core/system.cpp
index 7e9c56538..b62ebf69e 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "audio_core/audio_core.h"
+
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/system.h"
+#include "core/gdbstub/gdbstub.h"
 #include "core/hw/hw.h"
 #include "core/hle/hle.h"
 #include "core/hle/kernel/kernel.h"
@@ -12,8 +15,6 @@
 
 #include "video_core/video_core.h"
 
-#include "core/gdbstub/gdbstub.h"
-
 namespace System {
 
 void Init(EmuWindow* emu_window) {
@@ -24,11 +25,13 @@ void Init(EmuWindow* emu_window) {
     Kernel::Init();
     HLE::Init();
     VideoCore::Init(emu_window);
+    AudioCore::Init();
     GDBStub::Init();
 }
 
 void Shutdown() {
     GDBStub::Shutdown();
+    AudioCore::Shutdown();
     VideoCore::Shutdown();
     HLE::Shutdown();
     Kernel::Shutdown();