diff options
Diffstat (limited to 'src/audio_core')
| -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 |
8 files changed, 766 insertions, 0 deletions
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 | ||