diff options
| author | 2016-02-21 13:13:52 +0000 | |
|---|---|---|
| committer | 2016-02-21 13:13:52 +0000 | |
| commit | 8b00954ec79fad71691ad2d4c82d5c1c60e21b0c (patch) | |
| tree | 443d275fd39c58928e68ef22ce3fe0fa56c73642 /src | |
| parent | Merge pull request #1406 from MerryMage/bitfield2 (diff) | |
| download | yuzu-8b00954ec79fad71691ad2d4c82d5c1c60e21b0c.tar.gz yuzu-8b00954ec79fad71691ad2d4c82d5c1c60e21b0c.tar.xz yuzu-8b00954ec79fad71691ad2d4c82d5c1c60e21b0c.zip | |
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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/audio_core/CMakeLists.txt | 16 | ||||
| -rw-r--r-- | src/audio_core/audio_core.cpp | 53 | ||||
| -rw-r--r-- | src/audio_core/audio_core.h | 26 | ||||
| -rw-r--r-- | src/audio_core/hle/dsp.cpp | 42 | ||||
| -rw-r--r-- | src/audio_core/hle/dsp.h | 502 | ||||
| -rw-r--r-- | src/audio_core/hle/pipe.cpp | 55 | ||||
| -rw-r--r-- | src/audio_core/hle/pipe.h | 38 | ||||
| -rw-r--r-- | src/audio_core/sink.h | 34 | ||||
| -rw-r--r-- | src/citra/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/citra_qt/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/common/bit_field.h | 2 | ||||
| -rw-r--r-- | src/common/logging/backend.cpp | 2 | ||||
| -rw-r--r-- | src/common/logging/log.h | 2 | ||||
| -rw-r--r-- | src/core/hle/kernel/memory.cpp | 5 | ||||
| -rw-r--r-- | src/core/hle/service/dsp_dsp.cpp | 135 | ||||
| -rw-r--r-- | src/core/hle/service/dsp_dsp.h | 12 | ||||
| -rw-r--r-- | src/core/hw/gpu.cpp | 6 | ||||
| -rw-r--r-- | src/core/system.cpp | 7 |
19 files changed, 873 insertions, 69 deletions
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(.) | |||
| 4 | add_subdirectory(common) | 4 | add_subdirectory(common) |
| 5 | add_subdirectory(core) | 5 | add_subdirectory(core) |
| 6 | add_subdirectory(video_core) | 6 | add_subdirectory(video_core) |
| 7 | add_subdirectory(audio_core) | ||
| 7 | if (ENABLE_GLFW) | 8 | if (ENABLE_GLFW) |
| 8 | add_subdirectory(citra) | 9 | add_subdirectory(citra) |
| 9 | endif() | 10 | 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 @@ | |||
| 1 | set(SRCS | ||
| 2 | audio_core.cpp | ||
| 3 | hle/dsp.cpp | ||
| 4 | hle/pipe.cpp | ||
| 5 | ) | ||
| 6 | |||
| 7 | set(HEADERS | ||
| 8 | audio_core.h | ||
| 9 | hle/dsp.h | ||
| 10 | hle/pipe.h | ||
| 11 | sink.h | ||
| 12 | ) | ||
| 13 | |||
| 14 | create_directory_groups(${SRCS} ${HEADERS}) | ||
| 15 | |||
| 16 | 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 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "audio_core/audio_core.h" | ||
| 6 | #include "audio_core/hle/dsp.h" | ||
| 7 | |||
| 8 | #include "core/core_timing.h" | ||
| 9 | #include "core/hle/kernel/vm_manager.h" | ||
| 10 | #include "core/hle/service/dsp_dsp.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | |||
| 14 | // Audio Ticks occur about every 5 miliseconds. | ||
| 15 | static int tick_event; ///< CoreTiming event | ||
| 16 | static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles | ||
| 17 | |||
| 18 | static void AudioTickCallback(u64 /*userdata*/, int cycles_late) { | ||
| 19 | if (DSP::HLE::Tick()) { | ||
| 20 | // HACK: We're not signaling the interrups when they should be, but just firing them all off together. | ||
| 21 | // It should be only (interrupt_id = 2, channel_id = 2) that's signalled here. | ||
| 22 | // TODO(merry): Understand when the other interrupts are fired. | ||
| 23 | DSP_DSP::SignalAllInterrupts(); | ||
| 24 | } | ||
| 25 | |||
| 26 | // Reschedule recurrent event | ||
| 27 | CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); | ||
| 28 | } | ||
| 29 | |||
| 30 | /// Initialise Audio | ||
| 31 | void Init() { | ||
| 32 | DSP::HLE::Init(); | ||
| 33 | |||
| 34 | tick_event = CoreTiming::RegisterEvent("AudioCore::tick_event", AudioTickCallback); | ||
| 35 | CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event); | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Add DSP address spaces to Process's address space. | ||
| 39 | void AddAddressSpace(Kernel::VMManager& address_space) { | ||
| 40 | 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(); | ||
| 41 | address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite); | ||
| 42 | |||
| 43 | 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(); | ||
| 44 | address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite); | ||
| 45 | } | ||
| 46 | |||
| 47 | /// Shutdown Audio | ||
| 48 | void Shutdown() { | ||
| 49 | CoreTiming::UnscheduleEvent(tick_event, 0); | ||
| 50 | DSP::HLE::Shutdown(); | ||
| 51 | } | ||
| 52 | |||
| 53 | } //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 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | namespace Kernel { | ||
| 8 | class VMManager; | ||
| 9 | } | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | constexpr int num_sources = 24; | ||
| 14 | constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate | ||
| 15 | constexpr int native_sample_rate = 32728; ///< 32kHz | ||
| 16 | |||
| 17 | /// Initialise Audio Core | ||
| 18 | void Init(); | ||
| 19 | |||
| 20 | /// Add DSP address spaces to a Process. | ||
| 21 | void AddAddressSpace(Kernel::VMManager& vm_manager); | ||
| 22 | |||
| 23 | /// Shutdown Audio Core | ||
| 24 | void Shutdown(); | ||
| 25 | |||
| 26 | } // 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 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "audio_core/hle/dsp.h" | ||
| 6 | #include "audio_core/hle/pipe.h" | ||
| 7 | |||
| 8 | namespace DSP { | ||
| 9 | namespace HLE { | ||
| 10 | |||
| 11 | SharedMemory g_region0; | ||
| 12 | SharedMemory g_region1; | ||
| 13 | |||
| 14 | void Init() { | ||
| 15 | DSP::HLE::ResetPipes(); | ||
| 16 | } | ||
| 17 | |||
| 18 | void Shutdown() { | ||
| 19 | } | ||
| 20 | |||
| 21 | bool Tick() { | ||
| 22 | return true; | ||
| 23 | } | ||
| 24 | |||
| 25 | SharedMemory& CurrentRegion() { | ||
| 26 | // The region with the higher frame counter is chosen unless there is wraparound. | ||
| 27 | |||
| 28 | if (g_region0.frame_counter == 0xFFFFu && g_region1.frame_counter != 0xFFFEu) { | ||
| 29 | // Wraparound has occured. | ||
| 30 | return g_region1; | ||
| 31 | } | ||
| 32 | |||
| 33 | if (g_region1.frame_counter == 0xFFFFu && g_region0.frame_counter != 0xFFFEu) { | ||
| 34 | // Wraparound has occured. | ||
| 35 | return g_region0; | ||
| 36 | } | ||
| 37 | |||
| 38 | return (g_region0.frame_counter > g_region1.frame_counter) ? g_region0 : g_region1; | ||
| 39 | } | ||
| 40 | |||
| 41 | } // namespace HLE | ||
| 42 | } // 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 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cstddef> | ||
| 8 | #include <type_traits> | ||
| 9 | |||
| 10 | #include "audio_core/audio_core.h" | ||
| 11 | |||
| 12 | #include "common/bit_field.h" | ||
| 13 | #include "common/common_funcs.h" | ||
| 14 | #include "common/common_types.h" | ||
| 15 | #include "common/swap.h" | ||
| 16 | |||
| 17 | namespace DSP { | ||
| 18 | namespace HLE { | ||
| 19 | |||
| 20 | // The application-accessible region of DSP memory consists of two parts. | ||
| 21 | // Both are marked as IO and have Read/Write permissions. | ||
| 22 | // | ||
| 23 | // First Region: 0x1FF50000 (Size: 0x8000) | ||
| 24 | // Second Region: 0x1FF70000 (Size: 0x8000) | ||
| 25 | // | ||
| 26 | // The DSP reads from each region alternately based on the frame counter for each region much like a | ||
| 27 | // double-buffer. The frame counter is located as the very last u16 of each region and is incremented | ||
| 28 | // each audio tick. | ||
| 29 | |||
| 30 | struct SharedMemory; | ||
| 31 | |||
| 32 | constexpr VAddr region0_base = 0x1FF50000; | ||
| 33 | extern SharedMemory g_region0; | ||
| 34 | |||
| 35 | constexpr VAddr region1_base = 0x1FF70000; | ||
| 36 | extern SharedMemory g_region1; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from | ||
| 40 | * its memory regions, the higher and lower 16-bit halves are swapped compared to the little-endian | ||
| 41 | * layout of the ARM11. Hence from the ARM11's point of view the memory space appears to be | ||
| 42 | * middle-endian. | ||
| 43 | * | ||
| 44 | * Unusually this does not appear to be an issue for floating point numbers. The DSP makes the more | ||
| 45 | * sensible choice of keeping that little-endian. There are also some exceptions such as the | ||
| 46 | * IntermediateMixSamples structure, which is little-endian. | ||
| 47 | * | ||
| 48 | * This struct implements the conversion to and from this middle-endianness. | ||
| 49 | */ | ||
| 50 | struct u32_dsp { | ||
| 51 | u32_dsp() = default; | ||
| 52 | operator u32() const { | ||
| 53 | return Convert(storage); | ||
| 54 | } | ||
| 55 | void operator=(u32 new_value) { | ||
| 56 | storage = Convert(new_value); | ||
| 57 | } | ||
| 58 | private: | ||
| 59 | static constexpr u32 Convert(u32 value) { | ||
| 60 | return (value << 16) | (value >> 16); | ||
| 61 | } | ||
| 62 | u32_le storage; | ||
| 63 | }; | ||
| 64 | #if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) | ||
| 65 | static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivially copyable"); | ||
| 66 | #endif | ||
| 67 | |||
| 68 | // There are 15 structures in each memory region. A table of them in the order they appear in memory | ||
| 69 | // is presented below | ||
| 70 | // | ||
| 71 | // Pipe 2 # First Region DSP Address Purpose Control | ||
| 72 | // 5 0x8400 DSP Status DSP | ||
| 73 | // 9 0x8410 DSP Debug Info DSP | ||
| 74 | // 6 0x8540 Final Mix Samples DSP | ||
| 75 | // 2 0x8680 Source Status [24] DSP | ||
| 76 | // 8 0x8710 Compressor Table Application | ||
| 77 | // 4 0x9430 DSP Configuration Application | ||
| 78 | // 7 0x9492 Intermediate Mix Samples DSP + App | ||
| 79 | // 1 0x9E92 Source Configuration [24] Application | ||
| 80 | // 3 0xA792 Source ADPCM Coefficients [24] Application | ||
| 81 | // 10 0xA912 Surround Sound Related | ||
| 82 | // 11 0xAA12 Surround Sound Related | ||
| 83 | // 12 0xAAD2 Surround Sound Related | ||
| 84 | // 13 0xAC52 Surround Sound Related | ||
| 85 | // 14 0xAC5C Surround Sound Related | ||
| 86 | // 0 0xBFFF Frame Counter Application | ||
| 87 | // | ||
| 88 | // Note that the above addresses do vary slightly between audio firmwares observed; the addresses are | ||
| 89 | // not fixed in stone. The addresses above are only an examplar; they're what this implementation | ||
| 90 | // does and provides to applications. | ||
| 91 | // | ||
| 92 | // Application requests the DSP service to convert DSP addresses into ARM11 virtual addresses using the | ||
| 93 | // ConvertProcessAddressFromDspDram service call. Applications seem to derive the addresses for the | ||
| 94 | // second region via: | ||
| 95 | // second_region_dsp_addr = first_region_dsp_addr | 0x10000 | ||
| 96 | // | ||
| 97 | // Applications maintain most of its own audio state, the memory region is used mainly for | ||
| 98 | // communication and not storage of state. | ||
| 99 | // | ||
| 100 | // In the documentation below, filter and effect transfer functions are specified in the z domain. | ||
| 101 | // (If you are more familiar with the Laplace transform, z = exp(sT). The z domain is the digital | ||
| 102 | // frequency domain, just like how the s domain is the analog frequency domain.) | ||
| 103 | |||
| 104 | #define INSERT_PADDING_DSPWORDS(num_words) INSERT_PADDING_BYTES(2 * (num_words)) | ||
| 105 | |||
| 106 | // GCC versions < 5.0 do not implement std::is_trivially_copyable. | ||
| 107 | // Excluding MSVC because it has weird behaviour for std::is_trivially_copyable. | ||
| 108 | #if (__GNUC__ >= 5) || defined(__clang__) | ||
| 109 | #define ASSERT_DSP_STRUCT(name, size) \ | ||
| 110 | static_assert(std::is_standard_layout<name>::value, "DSP structure " #name " doesn't use standard layout"); \ | ||
| 111 | static_assert(std::is_trivially_copyable<name>::value, "DSP structure " #name " isn't trivially copyable"); \ | ||
| 112 | static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name) | ||
| 113 | #else | ||
| 114 | #define ASSERT_DSP_STRUCT(name, size) \ | ||
| 115 | static_assert(std::is_standard_layout<name>::value, "DSP structure " #name " doesn't use standard layout"); \ | ||
| 116 | static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name) | ||
| 117 | #endif | ||
| 118 | |||
| 119 | struct SourceConfiguration { | ||
| 120 | struct Configuration { | ||
| 121 | /// These dirty flags are set by the application when it updates the fields in this struct. | ||
| 122 | /// The DSP clears these each audio frame. | ||
| 123 | union { | ||
| 124 | u32_le dirty_raw; | ||
| 125 | |||
| 126 | BitField<2, 1, u32_le> adpcm_coefficients_dirty; | ||
| 127 | BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued. | ||
| 128 | |||
| 129 | BitField<16, 1, u32_le> enable_dirty; | ||
| 130 | BitField<17, 1, u32_le> interpolation_dirty; | ||
| 131 | BitField<18, 1, u32_le> rate_multiplier_dirty; | ||
| 132 | BitField<19, 1, u32_le> buffer_queue_dirty; | ||
| 133 | BitField<20, 1, u32_le> loop_related_dirty; | ||
| 134 | BitField<21, 1, u32_le> play_position_dirty; ///< Tends to also be set when embedded buffer is updated. | ||
| 135 | BitField<22, 1, u32_le> filters_enabled_dirty; | ||
| 136 | BitField<23, 1, u32_le> simple_filter_dirty; | ||
| 137 | BitField<24, 1, u32_le> biquad_filter_dirty; | ||
| 138 | BitField<25, 1, u32_le> gain_0_dirty; | ||
| 139 | BitField<26, 1, u32_le> gain_1_dirty; | ||
| 140 | BitField<27, 1, u32_le> gain_2_dirty; | ||
| 141 | BitField<28, 1, u32_le> sync_dirty; | ||
| 142 | BitField<29, 1, u32_le> reset_flag; | ||
| 143 | |||
| 144 | BitField<31, 1, u32_le> embedded_buffer_dirty; | ||
| 145 | }; | ||
| 146 | |||
| 147 | // Gain control | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Gain is between 0.0-1.0. This determines how much will this source appear on | ||
| 151 | * each of the 12 channels that feed into the intermediate mixers. | ||
| 152 | * Each of the three intermediate mixers is fed two left and two right channels. | ||
| 153 | */ | ||
| 154 | float_le gain[3][4]; | ||
| 155 | |||
| 156 | // Interpolation | ||
| 157 | |||
| 158 | /// Multiplier for sample rate. Resampling occurs with the selected interpolation method. | ||
| 159 | float_le rate_multiplier; | ||
| 160 | |||
| 161 | enum class InterpolationMode : u8 { | ||
| 162 | None = 0, | ||
| 163 | Linear = 1, | ||
| 164 | Polyphase = 2 | ||
| 165 | }; | ||
| 166 | |||
| 167 | InterpolationMode interpolation_mode; | ||
| 168 | INSERT_PADDING_BYTES(1); ///< Interpolation related | ||
| 169 | |||
| 170 | // Filters | ||
| 171 | |||
| 172 | /** | ||
| 173 | * This is the simplest normalized first-order digital recursive filter. | ||
| 174 | * The transfer function of this filter is: | ||
| 175 | * H(z) = b0 / (1 + a1 z^-1) | ||
| 176 | * Values are signed fixed point with 15 fractional bits. | ||
| 177 | */ | ||
| 178 | struct SimpleFilter { | ||
| 179 | s16_le b0; | ||
| 180 | s16_le a1; | ||
| 181 | }; | ||
| 182 | |||
| 183 | /** | ||
| 184 | * This is a normalised biquad filter (second-order). | ||
| 185 | * The transfer function of this filter is: | ||
| 186 | * H(z) = (b0 + b1 z^-1 + b2 z^-2) / (1 - a1 z^-1 - a2 z^-2) | ||
| 187 | * Nintendo chose to negate the feedbackward coefficients. This differs from standard notation | ||
| 188 | * as in: https://ccrma.stanford.edu/~jos/filters/Direct_Form_I.html | ||
| 189 | * Values are signed fixed point with 14 fractional bits. | ||
| 190 | */ | ||
| 191 | struct BiquadFilter { | ||
| 192 | s16_le b0; | ||
| 193 | s16_le b1; | ||
| 194 | s16_le b2; | ||
| 195 | s16_le a1; | ||
| 196 | s16_le a2; | ||
| 197 | }; | ||
| 198 | |||
| 199 | union { | ||
| 200 | u16_le filters_enabled; | ||
| 201 | BitField<0, 1, u16_le> simple_filter_enabled; | ||
| 202 | BitField<1, 1, u16_le> biquad_filter_enabled; | ||
| 203 | }; | ||
| 204 | |||
| 205 | SimpleFilter simple_filter; | ||
| 206 | BiquadFilter biquad_filter; | ||
| 207 | |||
| 208 | // Buffer Queue | ||
| 209 | |||
| 210 | /// A buffer of audio data from the application, along with metadata about it. | ||
| 211 | struct Buffer { | ||
| 212 | /// Physical memory address of the start of the buffer | ||
| 213 | u32_dsp physical_address; | ||
| 214 | |||
| 215 | /// This is length in terms of samples. | ||
| 216 | /// Note that in different buffer formats a sample takes up different number of bytes. | ||
| 217 | u32_dsp length; | ||
| 218 | |||
| 219 | /// ADPCM Predictor (4 bits) and Scale (4 bits) | ||
| 220 | union { | ||
| 221 | u16_le adpcm_ps; | ||
| 222 | BitField<0, 4, u16_le> adpcm_scale; | ||
| 223 | BitField<4, 4, u16_le> adpcm_predictor; | ||
| 224 | }; | ||
| 225 | |||
| 226 | /// ADPCM Historical Samples (y[n-1] and y[n-2]) | ||
| 227 | u16_le adpcm_yn[2]; | ||
| 228 | |||
| 229 | /// This is non-zero when the ADPCM values above are to be updated. | ||
| 230 | u8 adpcm_dirty; | ||
| 231 | |||
| 232 | /// Is a looping buffer. | ||
| 233 | u8 is_looping; | ||
| 234 | |||
| 235 | /// This value is shown in SourceStatus::previous_buffer_id when this buffer has finished. | ||
| 236 | /// This allows the emulated application to tell what buffer is currently playing | ||
| 237 | u16_le buffer_id; | ||
| 238 | |||
| 239 | INSERT_PADDING_DSPWORDS(1); | ||
| 240 | }; | ||
| 241 | |||
| 242 | u16_le buffers_dirty; ///< Bitmap indicating which buffers are dirty (bit i -> buffers[i]) | ||
| 243 | Buffer buffers[4]; ///< Queued Buffers | ||
| 244 | |||
| 245 | // Playback controls | ||
| 246 | |||
| 247 | u32_dsp loop_related; | ||
| 248 | u8 enable; | ||
| 249 | INSERT_PADDING_BYTES(1); | ||
| 250 | u16_le sync; ///< Application-side sync (See also: SourceStatus::sync) | ||
| 251 | u32_dsp play_position; ///< Position. (Units: number of samples) | ||
| 252 | INSERT_PADDING_DSPWORDS(2); | ||
| 253 | |||
| 254 | // Embedded Buffer | ||
| 255 | // This buffer is often the first buffer to be used when initiating audio playback, | ||
| 256 | // after which the buffer queue is used. | ||
| 257 | |||
| 258 | u32_dsp physical_address; | ||
| 259 | |||
| 260 | /// This is length in terms of samples. | ||
| 261 | /// Note a sample takes up different number of bytes in different buffer formats. | ||
| 262 | u32_dsp length; | ||
| 263 | |||
| 264 | enum class MonoOrStereo : u16_le { | ||
| 265 | Mono = 1, | ||
| 266 | Stereo = 2 | ||
| 267 | }; | ||
| 268 | |||
| 269 | enum class Format : u16_le { | ||
| 270 | PCM8 = 0, | ||
| 271 | PCM16 = 1, | ||
| 272 | ADPCM = 2 | ||
| 273 | }; | ||
| 274 | |||
| 275 | union { | ||
| 276 | u16_le flags1_raw; | ||
| 277 | BitField<0, 2, MonoOrStereo> mono_or_stereo; | ||
| 278 | BitField<2, 2, Format> format; | ||
| 279 | BitField<5, 1, u16_le> fade_in; | ||
| 280 | }; | ||
| 281 | |||
| 282 | /// ADPCM Predictor (4 bit) and Scale (4 bit) | ||
| 283 | union { | ||
| 284 | u16_le adpcm_ps; | ||
| 285 | BitField<0, 4, u16_le> adpcm_scale; | ||
| 286 | BitField<4, 4, u16_le> adpcm_predictor; | ||
| 287 | }; | ||
| 288 | |||
| 289 | /// ADPCM Historical Samples (y[n-1] and y[n-2]) | ||
| 290 | u16_le adpcm_yn[2]; | ||
| 291 | |||
| 292 | union { | ||
| 293 | u16_le flags2_raw; | ||
| 294 | BitField<0, 1, u16_le> adpcm_dirty; ///< Has the ADPCM info above been changed? | ||
| 295 | BitField<1, 1, u16_le> is_looping; ///< Is this a looping buffer? | ||
| 296 | }; | ||
| 297 | |||
| 298 | /// Buffer id of embedded buffer (used as a buffer id in SourceStatus to reference this buffer). | ||
| 299 | u16_le buffer_id; | ||
| 300 | }; | ||
| 301 | |||
| 302 | Configuration config[AudioCore::num_sources]; | ||
| 303 | }; | ||
| 304 | ASSERT_DSP_STRUCT(SourceConfiguration::Configuration, 192); | ||
| 305 | ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20); | ||
| 306 | |||
| 307 | struct SourceStatus { | ||
| 308 | struct Status { | ||
| 309 | u8 is_enabled; ///< Is this channel enabled? (Doesn't have to be playing anything.) | ||
| 310 | u8 previous_buffer_id_dirty; ///< Non-zero when previous_buffer_id changes | ||
| 311 | u16_le sync; ///< Is set by the DSP to the value of SourceConfiguration::sync | ||
| 312 | u32_dsp buffer_position; ///< Number of samples into the current buffer | ||
| 313 | u16_le previous_buffer_id; ///< Updated when a buffer finishes playing | ||
| 314 | INSERT_PADDING_DSPWORDS(1); | ||
| 315 | }; | ||
| 316 | |||
| 317 | Status status[AudioCore::num_sources]; | ||
| 318 | }; | ||
| 319 | ASSERT_DSP_STRUCT(SourceStatus::Status, 12); | ||
| 320 | |||
| 321 | struct DspConfiguration { | ||
| 322 | /// These dirty flags are set by the application when it updates the fields in this struct. | ||
| 323 | /// The DSP clears these each audio frame. | ||
| 324 | union { | ||
| 325 | u32_le dirty_raw; | ||
| 326 | |||
| 327 | BitField<8, 1, u32_le> mixer1_enabled_dirty; | ||
| 328 | BitField<9, 1, u32_le> mixer2_enabled_dirty; | ||
| 329 | BitField<10, 1, u32_le> delay_effect_0_dirty; | ||
| 330 | BitField<11, 1, u32_le> delay_effect_1_dirty; | ||
| 331 | BitField<12, 1, u32_le> reverb_effect_0_dirty; | ||
| 332 | BitField<13, 1, u32_le> reverb_effect_1_dirty; | ||
| 333 | |||
| 334 | BitField<16, 1, u32_le> volume_0_dirty; | ||
| 335 | |||
| 336 | BitField<24, 1, u32_le> volume_1_dirty; | ||
| 337 | BitField<25, 1, u32_le> volume_2_dirty; | ||
| 338 | BitField<26, 1, u32_le> output_format_dirty; | ||
| 339 | BitField<27, 1, u32_le> limiter_enabled_dirty; | ||
| 340 | BitField<28, 1, u32_le> headphones_connected_dirty; | ||
| 341 | }; | ||
| 342 | |||
| 343 | /// The DSP has three intermediate audio mixers. This controls the volume level (0.0-1.0) for each at the final mixer | ||
| 344 | float_le volume[3]; | ||
| 345 | |||
| 346 | INSERT_PADDING_DSPWORDS(3); | ||
| 347 | |||
| 348 | enum class OutputFormat : u16_le { | ||
| 349 | Mono = 0, | ||
| 350 | Stereo = 1, | ||
| 351 | Surround = 2 | ||
| 352 | }; | ||
| 353 | |||
| 354 | OutputFormat output_format; | ||
| 355 | |||
| 356 | u16_le limiter_enabled; ///< Not sure of the exact gain equation for the limiter. | ||
| 357 | u16_le headphones_connected; ///< Application updates the DSP on headphone status. | ||
| 358 | INSERT_PADDING_DSPWORDS(4); ///< TODO: Surround sound related | ||
| 359 | INSERT_PADDING_DSPWORDS(2); ///< TODO: Intermediate mixer 1/2 related | ||
| 360 | u16_le mixer1_enabled; | ||
| 361 | u16_le mixer2_enabled; | ||
| 362 | |||
| 363 | /** | ||
| 364 | * This is delay with feedback. | ||
| 365 | * Transfer function: | ||
| 366 | * H(z) = a z^-N / (1 - b z^-1 + a g z^-N) | ||
| 367 | * where | ||
| 368 | * N = frame_count * samples_per_frame | ||
| 369 | * g, a and b are fixed point with 7 fractional bits | ||
| 370 | */ | ||
| 371 | struct DelayEffect { | ||
| 372 | /// These dirty flags are set by the application when it updates the fields in this struct. | ||
| 373 | /// The DSP clears these each audio frame. | ||
| 374 | union { | ||
| 375 | u16_le dirty_raw; | ||
| 376 | BitField<0, 1, u16_le> enable_dirty; | ||
| 377 | BitField<1, 1, u16_le> work_buffer_address_dirty; | ||
| 378 | BitField<2, 1, u16_le> other_dirty; ///< Set when anything else has been changed | ||
| 379 | }; | ||
| 380 | |||
| 381 | u16_le enable; | ||
| 382 | INSERT_PADDING_DSPWORDS(1); | ||
| 383 | u16_le outputs; | ||
| 384 | u32_dsp work_buffer_address; ///< The application allocates a block of memory for the DSP to use as a work buffer. | ||
| 385 | u16_le frame_count; ///< Frames to delay by | ||
| 386 | |||
| 387 | // Coefficients | ||
| 388 | s16_le g; ///< Fixed point with 7 fractional bits | ||
| 389 | s16_le a; ///< Fixed point with 7 fractional bits | ||
| 390 | s16_le b; ///< Fixed point with 7 fractional bits | ||
| 391 | }; | ||
| 392 | |||
| 393 | DelayEffect delay_effect[2]; | ||
| 394 | |||
| 395 | struct ReverbEffect { | ||
| 396 | INSERT_PADDING_DSPWORDS(26); ///< TODO | ||
| 397 | }; | ||
| 398 | |||
| 399 | ReverbEffect reverb_effect[2]; | ||
| 400 | |||
| 401 | INSERT_PADDING_DSPWORDS(4); | ||
| 402 | }; | ||
| 403 | ASSERT_DSP_STRUCT(DspConfiguration, 196); | ||
| 404 | ASSERT_DSP_STRUCT(DspConfiguration::DelayEffect, 20); | ||
| 405 | ASSERT_DSP_STRUCT(DspConfiguration::ReverbEffect, 52); | ||
| 406 | |||
| 407 | struct AdpcmCoefficients { | ||
| 408 | /// Coefficients are signed fixed point with 11 fractional bits. | ||
| 409 | /// Each source has 16 coefficients associated with it. | ||
| 410 | s16_le coeff[AudioCore::num_sources][16]; | ||
| 411 | }; | ||
| 412 | ASSERT_DSP_STRUCT(AdpcmCoefficients, 768); | ||
| 413 | |||
| 414 | struct DspStatus { | ||
| 415 | u16_le unknown; | ||
| 416 | u16_le dropped_frames; | ||
| 417 | INSERT_PADDING_DSPWORDS(0xE); | ||
| 418 | }; | ||
| 419 | ASSERT_DSP_STRUCT(DspStatus, 32); | ||
| 420 | |||
| 421 | /// Final mixed output in PCM16 stereo format, what you hear out of the speakers. | ||
| 422 | /// When the application writes to this region it has no effect. | ||
| 423 | struct FinalMixSamples { | ||
| 424 | s16_le pcm16[2 * AudioCore::samples_per_frame]; | ||
| 425 | }; | ||
| 426 | ASSERT_DSP_STRUCT(FinalMixSamples, 640); | ||
| 427 | |||
| 428 | /// DSP writes output of intermediate mixers 1 and 2 here. | ||
| 429 | /// Writes to this region by the application edits the output of the intermediate mixers. | ||
| 430 | /// This seems to be intended to allow the application to do custom effects on the ARM11. | ||
| 431 | /// Values that exceed s16 range will be clipped by the DSP after further processing. | ||
| 432 | struct IntermediateMixSamples { | ||
| 433 | struct Samples { | ||
| 434 | s32_le pcm32[4][AudioCore::samples_per_frame]; ///< Little-endian as opposed to DSP middle-endian. | ||
| 435 | }; | ||
| 436 | |||
| 437 | Samples mix1; | ||
| 438 | Samples mix2; | ||
| 439 | }; | ||
| 440 | ASSERT_DSP_STRUCT(IntermediateMixSamples, 5120); | ||
| 441 | |||
| 442 | /// Compressor table | ||
| 443 | struct Compressor { | ||
| 444 | INSERT_PADDING_DSPWORDS(0xD20); ///< TODO | ||
| 445 | }; | ||
| 446 | |||
| 447 | /// There is no easy way to implement this in a HLE implementation. | ||
| 448 | struct DspDebug { | ||
| 449 | INSERT_PADDING_DSPWORDS(0x130); | ||
| 450 | }; | ||
| 451 | ASSERT_DSP_STRUCT(DspDebug, 0x260); | ||
| 452 | |||
| 453 | struct SharedMemory { | ||
| 454 | /// Padding | ||
| 455 | INSERT_PADDING_DSPWORDS(0x400); | ||
| 456 | |||
| 457 | DspStatus dsp_status; | ||
| 458 | |||
| 459 | DspDebug dsp_debug; | ||
| 460 | |||
| 461 | FinalMixSamples final_samples; | ||
| 462 | |||
| 463 | SourceStatus source_statuses; | ||
| 464 | |||
| 465 | Compressor compressor; | ||
| 466 | |||
| 467 | DspConfiguration dsp_configuration; | ||
| 468 | |||
| 469 | IntermediateMixSamples intermediate_mix_samples; | ||
| 470 | |||
| 471 | SourceConfiguration source_configurations; | ||
| 472 | |||
| 473 | AdpcmCoefficients adpcm_coefficients; | ||
| 474 | |||
| 475 | /// Unknown 10-14 (Surround sound related) | ||
| 476 | INSERT_PADDING_DSPWORDS(0x16ED); | ||
| 477 | |||
| 478 | u16_le frame_counter; | ||
| 479 | }; | ||
| 480 | ASSERT_DSP_STRUCT(SharedMemory, 0x8000); | ||
| 481 | |||
| 482 | #undef INSERT_PADDING_DSPWORDS | ||
| 483 | #undef ASSERT_DSP_STRUCT | ||
| 484 | |||
| 485 | /// Initialize DSP hardware | ||
| 486 | void Init(); | ||
| 487 | |||
| 488 | /// Shutdown DSP hardware | ||
| 489 | void Shutdown(); | ||
| 490 | |||
| 491 | /** | ||
| 492 | * Perform processing and updates state of current shared memory buffer. | ||
| 493 | * This function is called every audio tick before triggering the audio interrupt. | ||
| 494 | * @return Whether an audio interrupt should be triggered this frame. | ||
| 495 | */ | ||
| 496 | bool Tick(); | ||
| 497 | |||
| 498 | /// Returns a mutable reference to the current region. Current region is selected based on the frame counter. | ||
| 499 | SharedMemory& CurrentRegion(); | ||
| 500 | |||
| 501 | } // namespace HLE | ||
| 502 | } // 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 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <vector> | ||
| 7 | |||
| 8 | #include "audio_core/hle/pipe.h" | ||
| 9 | |||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/logging/log.h" | ||
| 12 | |||
| 13 | namespace DSP { | ||
| 14 | namespace HLE { | ||
| 15 | |||
| 16 | static size_t pipe2position = 0; | ||
| 17 | |||
| 18 | void ResetPipes() { | ||
| 19 | pipe2position = 0; | ||
| 20 | } | ||
| 21 | |||
| 22 | std::vector<u8> PipeRead(u32 pipe_number, u32 length) { | ||
| 23 | if (pipe_number != 2) { | ||
| 24 | LOG_WARNING(Audio_DSP, "pipe_number = %u (!= 2), unimplemented", pipe_number); | ||
| 25 | return {}; // We currently don't handle anything other than the audio pipe. | ||
| 26 | } | ||
| 27 | |||
| 28 | // Canned DSP responses that games expect. These were taken from HW by 3dmoo team. | ||
| 29 | // TODO: Our implementation will actually use a slightly different response than this one. | ||
| 30 | // TODO: Use offsetof on DSP structures instead for a proper response. | ||
| 31 | static const std::array<u8, 32> canned_response {{ | ||
| 32 | 0x0F, 0x00, 0xFF, 0xBF, 0x8E, 0x9E, 0x80, 0x86, 0x8E, 0xA7, 0x30, 0x94, 0x00, 0x84, 0x40, 0x85, | ||
| 33 | 0x8E, 0x94, 0x10, 0x87, 0x10, 0x84, 0x0E, 0xA9, 0x0E, 0xAA, 0xCE, 0xAA, 0x4E, 0xAC, 0x58, 0xAC | ||
| 34 | }}; | ||
| 35 | |||
| 36 | // TODO: Move this into dsp::DSP service since it happens on the service side. | ||
| 37 | // Hardware observation: No data is returned if requested length reads beyond the end of the data in-pipe. | ||
| 38 | if (pipe2position + length > canned_response.size()) { | ||
| 39 | return {}; | ||
| 40 | } | ||
| 41 | |||
| 42 | std::vector<u8> ret; | ||
| 43 | for (size_t i = 0; i < length; i++, pipe2position++) { | ||
| 44 | ret.emplace_back(canned_response[pipe2position]); | ||
| 45 | } | ||
| 46 | |||
| 47 | return ret; | ||
| 48 | } | ||
| 49 | |||
| 50 | void PipeWrite(u32 pipe_number, const std::vector<u8>& buffer) { | ||
| 51 | // TODO: proper pipe behaviour | ||
| 52 | } | ||
| 53 | |||
| 54 | } // namespace HLE | ||
| 55 | } // 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 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace DSP { | ||
| 12 | namespace HLE { | ||
| 13 | |||
| 14 | /// Reset the pipes by setting pipe positions back to the beginning. | ||
| 15 | void ResetPipes(); | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Read a DSP pipe. | ||
| 19 | * Pipe IDs: | ||
| 20 | * pipe_number = 0: Debug | ||
| 21 | * pipe_number = 1: P-DMA | ||
| 22 | * pipe_number = 2: Audio | ||
| 23 | * pipe_number = 3: Binary | ||
| 24 | * @param pipe_number The Pipe ID | ||
| 25 | * @param length How much data to request. | ||
| 26 | * @return The data read from the pipe. The size of this vector can be less than the length requested. | ||
| 27 | */ | ||
| 28 | std::vector<u8> PipeRead(u32 pipe_number, u32 length); | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Write to a DSP pipe. | ||
| 32 | * @param pipe_number The Pipe ID | ||
| 33 | * @param buffer The data to write to the pipe. | ||
| 34 | */ | ||
| 35 | void PipeWrite(u32 pipe_number, const std::vector<u8>& buffer); | ||
| 36 | |||
| 37 | } // namespace HLE | ||
| 38 | } // 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 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed PCM16 format to be output. | ||
| 15 | * Sinks *do not* handle resampling and expect the correct sample rate. They are dumb outputs. | ||
| 16 | */ | ||
| 17 | class Sink { | ||
| 18 | public: | ||
| 19 | virtual ~Sink() = default; | ||
| 20 | |||
| 21 | /// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec) | ||
| 22 | virtual unsigned GetNativeSampleRate() const = 0; | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Feed stereo samples to sink. | ||
| 26 | * @param samples Samples in interleaved stereo PCM16 format. Size of vector must be multiple of two. | ||
| 27 | */ | ||
| 28 | virtual void EnqueueSamples(const std::vector<s16>& samples) = 0; | ||
| 29 | |||
| 30 | /// Samples enqueued that have not been played yet. | ||
| 31 | virtual std::size_t SamplesInQueue() const = 0; | ||
| 32 | }; | ||
| 33 | |||
| 34 | } // 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}) | |||
| 17 | link_directories(${GLFW_LIBRARY_DIRS}) | 17 | link_directories(${GLFW_LIBRARY_DIRS}) |
| 18 | 18 | ||
| 19 | add_executable(citra ${SRCS} ${HEADERS}) | 19 | add_executable(citra ${SRCS} ${HEADERS}) |
| 20 | target_link_libraries(citra core video_core common) | 20 | target_link_libraries(citra core video_core audio_core common) |
| 21 | target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih glad) | 21 | target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih glad) |
| 22 | if (MSVC) | 22 | if (MSVC) |
| 23 | target_link_libraries(citra getopt) | 23 | 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) | |||
| 79 | else() | 79 | else() |
| 80 | add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) | 80 | add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) |
| 81 | endif() | 81 | endif() |
| 82 | target_link_libraries(citra-qt core video_core common qhexedit) | 82 | target_link_libraries(citra-qt core video_core audio_core common qhexedit) |
| 83 | target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) | 83 | target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) |
| 84 | target_link_libraries(citra-qt ${PLATFORM_LIBRARIES}) | 84 | target_link_libraries(citra-qt ${PLATFORM_LIBRARIES}) |
| 85 | 85 | ||
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: | |||
| 185 | }; | 185 | }; |
| 186 | #pragma pack() | 186 | #pragma pack() |
| 187 | 187 | ||
| 188 | #if (__GNUC__ >= 5) || defined __clang__ || defined _MSC_VER | 188 | #if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) |
| 189 | static_assert(std::is_trivially_copyable<BitField<0, 1, u32>>::value, "BitField must be trivially copyable"); | 189 | static_assert(std::is_trivially_copyable<BitField<0, 1, u32>>::value, "BitField must be trivially copyable"); |
| 190 | #endif | 190 | #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 { | |||
| 58 | CLS(Render) \ | 58 | CLS(Render) \ |
| 59 | SUB(Render, Software) \ | 59 | SUB(Render, Software) \ |
| 60 | SUB(Render, OpenGL) \ | 60 | SUB(Render, OpenGL) \ |
| 61 | CLS(Audio) \ | ||
| 62 | SUB(Audio, DSP) \ | ||
| 61 | CLS(Loader) | 63 | CLS(Loader) |
| 62 | 64 | ||
| 63 | // GetClassName is a macro defined by Windows.h, grrr... | 65 | // 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 { | |||
| 73 | Render, ///< Emulator video output and hardware acceleration | 73 | Render, ///< Emulator video output and hardware acceleration |
| 74 | Render_Software, ///< Software renderer backend | 74 | Render_Software, ///< Software renderer backend |
| 75 | Render_OpenGL, ///< OpenGL backend | 75 | Render_OpenGL, ///< OpenGL backend |
| 76 | Audio, ///< Emulator audio output | ||
| 77 | Audio_DSP, ///< The HLE implementation of the DSP | ||
| 76 | Loader, ///< ROM loader | 78 | Loader, ///< ROM loader |
| 77 | 79 | ||
| 78 | Count ///< Total number of logging classes | 80 | 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 @@ | |||
| 7 | #include <utility> | 7 | #include <utility> |
| 8 | #include <vector> | 8 | #include <vector> |
| 9 | 9 | ||
| 10 | #include "audio_core/audio_core.h" | ||
| 11 | |||
| 10 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 11 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 12 | 14 | ||
| @@ -107,7 +109,6 @@ struct MemoryArea { | |||
| 107 | static MemoryArea memory_areas[] = { | 109 | static MemoryArea memory_areas[] = { |
| 108 | {SHARED_MEMORY_VADDR, SHARED_MEMORY_SIZE, "Shared Memory"}, // Shared memory | 110 | {SHARED_MEMORY_VADDR, SHARED_MEMORY_SIZE, "Shared Memory"}, // Shared memory |
| 109 | {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) | 111 | {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) |
| 110 | {DSP_RAM_VADDR, DSP_RAM_SIZE, "DSP RAM"}, // DSP memory | ||
| 111 | {TLS_AREA_VADDR, TLS_AREA_SIZE, "TLS Area"}, // TLS memory | 112 | {TLS_AREA_VADDR, TLS_AREA_SIZE, "TLS Area"}, // TLS memory |
| 112 | }; | 113 | }; |
| 113 | 114 | ||
| @@ -133,6 +134,8 @@ void InitLegacyAddressSpace(Kernel::VMManager& address_space) { | |||
| 133 | auto shared_page_vma = address_space.MapBackingMemory(SHARED_PAGE_VADDR, | 134 | auto shared_page_vma = address_space.MapBackingMemory(SHARED_PAGE_VADDR, |
| 134 | (u8*)&SharedPage::shared_page, SHARED_PAGE_SIZE, MemoryState::Shared).MoveFrom(); | 135 | (u8*)&SharedPage::shared_page, SHARED_PAGE_SIZE, MemoryState::Shared).MoveFrom(); |
| 135 | address_space.Reprotect(shared_page_vma, VMAPermission::Read); | 136 | address_space.Reprotect(shared_page_vma, VMAPermission::Read); |
| 137 | |||
| 138 | AudioCore::AddAddressSpace(address_space); | ||
| 136 | } | 139 | } |
| 137 | 140 | ||
| 138 | } // namespace | 141 | } // 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 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "audio_core/hle/pipe.h" | ||
| 6 | |||
| 5 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 6 | 8 | ||
| 7 | #include "core/hle/kernel/event.h" | 9 | #include "core/hle/kernel/event.h" |
| @@ -14,17 +16,30 @@ namespace DSP_DSP { | |||
| 14 | 16 | ||
| 15 | static u32 read_pipe_count; | 17 | static u32 read_pipe_count; |
| 16 | static Kernel::SharedPtr<Kernel::Event> semaphore_event; | 18 | static Kernel::SharedPtr<Kernel::Event> semaphore_event; |
| 17 | static Kernel::SharedPtr<Kernel::Event> interrupt_event; | ||
| 18 | 19 | ||
| 19 | void SignalInterrupt() { | 20 | struct PairHash { |
| 20 | // TODO(bunnei): This is just a stub, it does not do anything other than signal to the emulated | 21 | template <typename T, typename U> |
| 21 | // application that a DSP interrupt occurred, without specifying which one. Since we do not | 22 | std::size_t operator()(const std::pair<T, U> &x) const { |
| 22 | // emulate the DSP yet (and how it works is largely unknown), this is a work around to get games | 23 | // TODO(yuriks): Replace with better hash combining function. |
| 23 | // that check the DSP interrupt signal event to run. We should figure out the different types of | 24 | return std::hash<T>()(x.first) ^ std::hash<U>()(x.second); |
| 24 | // DSP interrupts, and trigger them at the appropriate times. | 25 | } |
| 26 | }; | ||
| 27 | |||
| 28 | /// Map of (audio interrupt number, channel number) to Kernel::Events. See: RegisterInterruptEvents | ||
| 29 | static std::unordered_map<std::pair<u32, u32>, Kernel::SharedPtr<Kernel::Event>, PairHash> interrupt_events; | ||
| 30 | |||
| 31 | // DSP Interrupts: | ||
| 32 | // Interrupt #2 occurs every frame tick. Userland programs normally have a thread that's waiting | ||
| 33 | // for an interrupt event. Immediately after this interrupt event, userland normally updates the | ||
| 34 | // state in the next region and increments the relevant frame counter by two. | ||
| 35 | void SignalAllInterrupts() { | ||
| 36 | // HACK: The other interrupts have currently unknown purpose, we trigger them each tick in any case. | ||
| 37 | for (auto& interrupt_event : interrupt_events) | ||
| 38 | interrupt_event.second->Signal(); | ||
| 39 | } | ||
| 25 | 40 | ||
| 26 | if (interrupt_event != 0) | 41 | void SignalInterrupt(u32 interrupt, u32 channel) { |
| 27 | interrupt_event->Signal(); | 42 | interrupt_events[std::make_pair(interrupt, channel)]->Signal(); |
| 28 | } | 43 | } |
| 29 | 44 | ||
| 30 | /** | 45 | /** |
| @@ -43,7 +58,7 @@ static void ConvertProcessAddressFromDspDram(Service::Interface* self) { | |||
| 43 | cmd_buff[1] = 0; // No error | 58 | cmd_buff[1] = 0; // No error |
| 44 | cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000); | 59 | cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000); |
| 45 | 60 | ||
| 46 | LOG_WARNING(Service_DSP, "(STUBBED) called with address 0x%08X", addr); | 61 | LOG_TRACE(Service_DSP, "addr=0x%08X", addr); |
| 47 | } | 62 | } |
| 48 | 63 | ||
| 49 | /** | 64 | /** |
| @@ -121,8 +136,8 @@ static void FlushDataCache(Service::Interface* self) { | |||
| 121 | /** | 136 | /** |
| 122 | * DSP_DSP::RegisterInterruptEvents service function | 137 | * DSP_DSP::RegisterInterruptEvents service function |
| 123 | * Inputs: | 138 | * Inputs: |
| 124 | * 1 : Parameter 0 (purpose unknown) | 139 | * 1 : Interrupt Number |
| 125 | * 2 : Parameter 1 (purpose unknown) | 140 | * 2 : Channel Number |
| 126 | * 4 : Interrupt event handle | 141 | * 4 : Interrupt event handle |
| 127 | * Outputs: | 142 | * Outputs: |
| 128 | * 1 : Result of function, 0 on success, otherwise error code | 143 | * 1 : Result of function, 0 on success, otherwise error code |
| @@ -130,22 +145,24 @@ static void FlushDataCache(Service::Interface* self) { | |||
| 130 | static void RegisterInterruptEvents(Service::Interface* self) { | 145 | static void RegisterInterruptEvents(Service::Interface* self) { |
| 131 | u32* cmd_buff = Kernel::GetCommandBuffer(); | 146 | u32* cmd_buff = Kernel::GetCommandBuffer(); |
| 132 | 147 | ||
| 133 | u32 param0 = cmd_buff[1]; | 148 | u32 interrupt = cmd_buff[1]; |
| 134 | u32 param1 = cmd_buff[2]; | 149 | u32 channel = cmd_buff[2]; |
| 135 | u32 event_handle = cmd_buff[4]; | 150 | u32 event_handle = cmd_buff[4]; |
| 136 | 151 | ||
| 137 | auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); | 152 | if (event_handle) { |
| 138 | if (evt != nullptr) { | 153 | auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); |
| 139 | interrupt_event = evt; | 154 | if (evt) { |
| 140 | cmd_buff[1] = 0; // No error | 155 | interrupt_events[std::make_pair(interrupt, channel)] = evt; |
| 156 | cmd_buff[1] = RESULT_SUCCESS.raw; | ||
| 157 | LOG_WARNING(Service_DSP, "Registered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle); | ||
| 158 | } else { | ||
| 159 | cmd_buff[1] = -1; | ||
| 160 | LOG_ERROR(Service_DSP, "Invalid event handle! interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle); | ||
| 161 | } | ||
| 141 | } else { | 162 | } else { |
| 142 | LOG_ERROR(Service_DSP, "called with invalid handle=%08X", cmd_buff[4]); | 163 | interrupt_events.erase(std::make_pair(interrupt, channel)); |
| 143 | 164 | LOG_WARNING(Service_DSP, "Unregistered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle); | |
| 144 | // TODO(yuriks): An error should be returned from SendSyncRequest, not in the cmdbuf | ||
| 145 | cmd_buff[1] = -1; | ||
| 146 | } | 165 | } |
| 147 | |||
| 148 | LOG_WARNING(Service_DSP, "(STUBBED) called param0=%u, param1=%u, event_handle=0x%08X", param0, param1, event_handle); | ||
| 149 | } | 166 | } |
| 150 | 167 | ||
| 151 | /** | 168 | /** |
| @@ -158,8 +175,6 @@ static void RegisterInterruptEvents(Service::Interface* self) { | |||
| 158 | static void SetSemaphore(Service::Interface* self) { | 175 | static void SetSemaphore(Service::Interface* self) { |
| 159 | u32* cmd_buff = Kernel::GetCommandBuffer(); | 176 | u32* cmd_buff = Kernel::GetCommandBuffer(); |
| 160 | 177 | ||
| 161 | SignalInterrupt(); | ||
| 162 | |||
| 163 | cmd_buff[1] = 0; // No error | 178 | cmd_buff[1] = 0; // No error |
| 164 | 179 | ||
| 165 | LOG_WARNING(Service_DSP, "(STUBBED) called"); | 180 | LOG_WARNING(Service_DSP, "(STUBBED) called"); |
| @@ -168,9 +183,9 @@ static void SetSemaphore(Service::Interface* self) { | |||
| 168 | /** | 183 | /** |
| 169 | * DSP_DSP::WriteProcessPipe service function | 184 | * DSP_DSP::WriteProcessPipe service function |
| 170 | * Inputs: | 185 | * Inputs: |
| 171 | * 1 : Number | 186 | * 1 : Channel |
| 172 | * 2 : Size | 187 | * 2 : Size |
| 173 | * 3 : (size <<14) | 0x402 | 188 | * 3 : (size << 14) | 0x402 |
| 174 | * 4 : Buffer | 189 | * 4 : Buffer |
| 175 | * Outputs: | 190 | * Outputs: |
| 176 | * 0 : Return header | 191 | * 0 : Return header |
| @@ -179,21 +194,42 @@ static void SetSemaphore(Service::Interface* self) { | |||
| 179 | static void WriteProcessPipe(Service::Interface* self) { | 194 | static void WriteProcessPipe(Service::Interface* self) { |
| 180 | u32* cmd_buff = Kernel::GetCommandBuffer(); | 195 | u32* cmd_buff = Kernel::GetCommandBuffer(); |
| 181 | 196 | ||
| 182 | u32 number = cmd_buff[1]; | 197 | u32 channel = cmd_buff[1]; |
| 183 | u32 size = cmd_buff[2]; | 198 | u32 size = cmd_buff[2]; |
| 184 | u32 new_size = cmd_buff[3]; | ||
| 185 | u32 buffer = cmd_buff[4]; | 199 | u32 buffer = cmd_buff[4]; |
| 186 | 200 | ||
| 201 | if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) { | ||
| 202 | 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); | ||
| 203 | cmd_buff[1] = -1; // TODO | ||
| 204 | return; | ||
| 205 | } | ||
| 206 | |||
| 207 | if (!Memory::GetPointer(buffer)) { | ||
| 208 | LOG_ERROR(Service_DSP, "Invalid Buffer: channel=%u, size=0x%X, buffer=0x%08X", channel, size, buffer); | ||
| 209 | cmd_buff[1] = -1; // TODO | ||
| 210 | return; | ||
| 211 | } | ||
| 212 | |||
| 213 | std::vector<u8> message(size); | ||
| 214 | |||
| 215 | for (size_t i = 0; i < size; i++) { | ||
| 216 | message[i] = Memory::Read8(buffer + i); | ||
| 217 | } | ||
| 218 | |||
| 219 | DSP::HLE::PipeWrite(channel, message); | ||
| 220 | |||
| 187 | cmd_buff[1] = RESULT_SUCCESS.raw; // No error | 221 | cmd_buff[1] = RESULT_SUCCESS.raw; // No error |
| 188 | 222 | ||
| 189 | LOG_WARNING(Service_DSP, "(STUBBED) called number=%u, size=0x%X, new_size=0x%X, buffer=0x%08X", | 223 | LOG_TRACE(Service_DSP, "channel=%u, size=0x%X, buffer=0x%08X", channel, size, buffer); |
| 190 | number, size, new_size, buffer); | ||
| 191 | } | 224 | } |
| 192 | 225 | ||
| 193 | /** | 226 | /** |
| 194 | * DSP_DSP::ReadPipeIfPossible service function | 227 | * DSP_DSP::ReadPipeIfPossible service function |
| 228 | * A pipe is a means of communication between the ARM11 and DSP that occurs on | ||
| 229 | * hardware by writing to/reading from the DSP registers at 0x10203000. | ||
| 230 | * Pipes are used for initialisation. See also DSP::HLE::PipeRead. | ||
| 195 | * Inputs: | 231 | * Inputs: |
| 196 | * 1 : Unknown | 232 | * 1 : Pipe Number |
| 197 | * 2 : Unknown | 233 | * 2 : Unknown |
| 198 | * 3 : Size in bytes of read (observed only lower half word used) | 234 | * 3 : Size in bytes of read (observed only lower half word used) |
| 199 | * 0x41 : Virtual address to read from DSP pipe to in memory | 235 | * 0x41 : Virtual address to read from DSP pipe to in memory |
| @@ -204,35 +240,25 @@ static void WriteProcessPipe(Service::Interface* self) { | |||
| 204 | static void ReadPipeIfPossible(Service::Interface* self) { | 240 | static void ReadPipeIfPossible(Service::Interface* self) { |
| 205 | u32* cmd_buff = Kernel::GetCommandBuffer(); | 241 | u32* cmd_buff = Kernel::GetCommandBuffer(); |
| 206 | 242 | ||
| 207 | u32 unk1 = cmd_buff[1]; | 243 | u32 pipe = cmd_buff[1]; |
| 208 | u32 unk2 = cmd_buff[2]; | 244 | u32 unk2 = cmd_buff[2]; |
| 209 | u32 size = cmd_buff[3] & 0xFFFF;// Lower 16 bits are size | 245 | u32 size = cmd_buff[3] & 0xFFFF;// Lower 16 bits are size |
| 210 | VAddr addr = cmd_buff[0x41]; | 246 | VAddr addr = cmd_buff[0x41]; |
| 211 | 247 | ||
| 212 | // Canned DSP responses that games expect. These were taken from HW by 3dmoo team. | 248 | if (!Memory::GetPointer(addr)) { |
| 213 | // TODO: Remove this hack :) | 249 | LOG_ERROR(Service_DSP, "Invalid addr: pipe=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", pipe, unk2, size, addr); |
| 214 | static const std::array<u16, 16> canned_read_pipe = {{ | 250 | cmd_buff[1] = -1; // TODO |
| 215 | 0x000F, 0xBFFF, 0x9E8E, 0x8680, 0xA78E, 0x9430, 0x8400, 0x8540, | 251 | return; |
| 216 | 0x948E, 0x8710, 0x8410, 0xA90E, 0xAA0E, 0xAACE, 0xAC4E, 0xAC58 | 252 | } |
| 217 | }}; | ||
| 218 | 253 | ||
| 219 | u32 initial_size = read_pipe_count; | 254 | std::vector<u8> response = DSP::HLE::PipeRead(pipe, size); |
| 220 | 255 | ||
| 221 | for (unsigned offset = 0; offset < size; offset += sizeof(u16)) { | 256 | Memory::WriteBlock(addr, response.data(), response.size()); |
| 222 | if (read_pipe_count < canned_read_pipe.size()) { | ||
| 223 | Memory::Write16(addr + offset, canned_read_pipe[read_pipe_count]); | ||
| 224 | read_pipe_count++; | ||
| 225 | } else { | ||
| 226 | LOG_ERROR(Service_DSP, "canned read pipe log exceeded!"); | ||
| 227 | break; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | 257 | ||
| 231 | cmd_buff[1] = 0; // No error | 258 | cmd_buff[1] = 0; // No error |
| 232 | cmd_buff[2] = (read_pipe_count - initial_size) * sizeof(u16); | 259 | cmd_buff[2] = (u32)response.size(); |
| 233 | 260 | ||
| 234 | LOG_WARNING(Service_DSP, "(STUBBED) called unk1=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", | 261 | LOG_TRACE(Service_DSP, "pipe=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", pipe, unk2, size, addr); |
| 235 | unk1, unk2, size, addr); | ||
| 236 | } | 262 | } |
| 237 | 263 | ||
| 238 | /** | 264 | /** |
| @@ -311,7 +337,6 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
| 311 | 337 | ||
| 312 | Interface::Interface() { | 338 | Interface::Interface() { |
| 313 | semaphore_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "DSP_DSP::semaphore_event"); | 339 | semaphore_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "DSP_DSP::semaphore_event"); |
| 314 | interrupt_event = nullptr; | ||
| 315 | read_pipe_count = 0; | 340 | read_pipe_count = 0; |
| 316 | 341 | ||
| 317 | Register(FunctionTable); | 342 | Register(FunctionTable); |
| @@ -319,7 +344,7 @@ Interface::Interface() { | |||
| 319 | 344 | ||
| 320 | Interface::~Interface() { | 345 | Interface::~Interface() { |
| 321 | semaphore_event = nullptr; | 346 | semaphore_event = nullptr; |
| 322 | interrupt_event = nullptr; | 347 | interrupt_events.clear(); |
| 323 | } | 348 | } |
| 324 | 349 | ||
| 325 | } // namespace | 350 | } // 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: | |||
| 23 | } | 23 | } |
| 24 | }; | 24 | }; |
| 25 | 25 | ||
| 26 | /// Signals that a DSP interrupt has occurred to userland code | 26 | /// Signal all audio related interrupts. |
| 27 | void SignalInterrupt(); | 27 | void SignalAllInterrupts(); |
| 28 | |||
| 29 | /** | ||
| 30 | * Signal a specific audio related interrupt based on interrupt id and channel id. | ||
| 31 | * @param interrupt_id The interrupt id | ||
| 32 | * @param channel_id The channel id | ||
| 33 | * The significance of various values of interrupt_id and channel_id is not yet known. | ||
| 34 | */ | ||
| 35 | void SignalInterrupt(u32 interrupt_id, u32 channel_id); | ||
| 28 | 36 | ||
| 29 | } // namespace | 37 | } // 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 @@ | |||
| 17 | #include "core/core_timing.h" | 17 | #include "core/core_timing.h" |
| 18 | 18 | ||
| 19 | #include "core/hle/service/gsp_gpu.h" | 19 | #include "core/hle/service/gsp_gpu.h" |
| 20 | #include "core/hle/service/dsp_dsp.h" | ||
| 21 | #include "core/hle/service/hid/hid.h" | 20 | #include "core/hle/service/hid/hid.h" |
| 22 | 21 | ||
| 23 | #include "core/hw/hw.h" | 22 | #include "core/hw/hw.h" |
| @@ -414,11 +413,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { | |||
| 414 | GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); | 413 | GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); |
| 415 | GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); | 414 | GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); |
| 416 | 415 | ||
| 417 | // TODO(bunnei): Fake a DSP interrupt on each frame. This does not belong here, but | ||
| 418 | // until we can emulate DSP interrupts, this is probably the only reasonable place to do | ||
| 419 | // this. Certain games expect this to be periodically signaled. | ||
| 420 | DSP_DSP::SignalInterrupt(); | ||
| 421 | |||
| 422 | // Check for user input updates | 416 | // Check for user input updates |
| 423 | Service::HID::Update(); | 417 | Service::HID::Update(); |
| 424 | 418 | ||
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 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "audio_core/audio_core.h" | ||
| 6 | |||
| 5 | #include "core/core.h" | 7 | #include "core/core.h" |
| 6 | #include "core/core_timing.h" | 8 | #include "core/core_timing.h" |
| 7 | #include "core/system.h" | 9 | #include "core/system.h" |
| 10 | #include "core/gdbstub/gdbstub.h" | ||
| 8 | #include "core/hw/hw.h" | 11 | #include "core/hw/hw.h" |
| 9 | #include "core/hle/hle.h" | 12 | #include "core/hle/hle.h" |
| 10 | #include "core/hle/kernel/kernel.h" | 13 | #include "core/hle/kernel/kernel.h" |
| @@ -12,8 +15,6 @@ | |||
| 12 | 15 | ||
| 13 | #include "video_core/video_core.h" | 16 | #include "video_core/video_core.h" |
| 14 | 17 | ||
| 15 | #include "core/gdbstub/gdbstub.h" | ||
| 16 | |||
| 17 | namespace System { | 18 | namespace System { |
| 18 | 19 | ||
| 19 | void Init(EmuWindow* emu_window) { | 20 | void Init(EmuWindow* emu_window) { |
| @@ -24,11 +25,13 @@ void Init(EmuWindow* emu_window) { | |||
| 24 | Kernel::Init(); | 25 | Kernel::Init(); |
| 25 | HLE::Init(); | 26 | HLE::Init(); |
| 26 | VideoCore::Init(emu_window); | 27 | VideoCore::Init(emu_window); |
| 28 | AudioCore::Init(); | ||
| 27 | GDBStub::Init(); | 29 | GDBStub::Init(); |
| 28 | } | 30 | } |
| 29 | 31 | ||
| 30 | void Shutdown() { | 32 | void Shutdown() { |
| 31 | GDBStub::Shutdown(); | 33 | GDBStub::Shutdown(); |
| 34 | AudioCore::Shutdown(); | ||
| 32 | VideoCore::Shutdown(); | 35 | VideoCore::Shutdown(); |
| 33 | HLE::Shutdown(); | 36 | HLE::Shutdown(); |
| 34 | Kernel::Shutdown(); | 37 | Kernel::Shutdown(); |