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