mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	audio_core: Remove global state
This commit is contained in:
		
							parent
							
								
									dca5fd291f
								
							
						
					
					
						commit
						ab3d53131a
					
				
					 34 changed files with 711 additions and 650 deletions
				
			
		|  | @ -1,17 +1,17 @@ | |||
| add_library(audio_core STATIC | ||||
|     audio_core.cpp | ||||
|     audio_core.h | ||||
|     audio_types.h | ||||
|     codec.cpp | ||||
|     codec.h | ||||
|     dsp_interface.cpp | ||||
|     dsp_interface.h | ||||
|     hle/common.h | ||||
|     hle/dsp.cpp | ||||
|     hle/dsp.h | ||||
|     hle/filter.cpp | ||||
|     hle/filter.h | ||||
|     hle/hle.cpp | ||||
|     hle/hle.h | ||||
|     hle/mixers.cpp | ||||
|     hle/mixers.h | ||||
|     hle/pipe.cpp | ||||
|     hle/pipe.h | ||||
|     hle/shared_memory.h | ||||
|     hle/source.cpp | ||||
|     hle/source.h | ||||
|     interpolate.cpp | ||||
|  |  | |||
|  | @ -1,61 +0,0 @@ | |||
| // Copyright 2016 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/hle/dsp.h" | ||||
| #include "audio_core/hle/pipe.h" | ||||
| #include "audio_core/null_sink.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "audio_core/sink_details.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/service/dsp_dsp.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| // Audio Ticks occur about every 5 miliseconds.
 | ||||
| static CoreTiming::EventType* 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()) { | ||||
|         // TODO(merry): Signal all the other interrupts as appropriate.
 | ||||
|         Service::DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Audio); | ||||
|         // HACK(merry): Added to prevent regressions. Will remove soon.
 | ||||
|         Service::DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Binary); | ||||
|     } | ||||
| 
 | ||||
|     // Reschedule recurrent event
 | ||||
|     CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); | ||||
| } | ||||
| 
 | ||||
| void Init() { | ||||
|     DSP::HLE::Init(); | ||||
| 
 | ||||
|     tick_event = CoreTiming::RegisterEvent("AudioCore::tick_event", AudioTickCallback); | ||||
|     CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event); | ||||
| } | ||||
| 
 | ||||
| std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory() { | ||||
|     return DSP::HLE::g_dsp_memory.raw_memory; | ||||
| } | ||||
| 
 | ||||
| void SelectSink(std::string sink_id) { | ||||
|     const SinkDetails& sink_details = GetSinkDetails(sink_id); | ||||
|     DSP::HLE::SetSink(sink_details.factory()); | ||||
| } | ||||
| 
 | ||||
| void EnableStretching(bool enable) { | ||||
|     DSP::HLE::EnableStretching(enable); | ||||
| } | ||||
| 
 | ||||
| void Shutdown() { | ||||
|     CoreTiming::UnscheduleEvent(tick_event, 0); | ||||
|     DSP::HLE::Shutdown(); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -1,31 +0,0 @@ | |||
| // Copyright 2016 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <string> | ||||
| #include "common/common_types.h" | ||||
| #include "core/memory.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| constexpr int native_sample_rate = 32728; ///< 32kHz
 | ||||
| 
 | ||||
| /// Initialise Audio Core
 | ||||
| void Init(); | ||||
| 
 | ||||
| /// Returns a reference to the array backing DSP memory
 | ||||
| std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory(); | ||||
| 
 | ||||
| /// Select the sink to use based on sink id.
 | ||||
| void SelectSink(std::string sink_id); | ||||
| 
 | ||||
| /// Enable/Disable stretching.
 | ||||
| void EnableStretching(bool enable); | ||||
| 
 | ||||
| /// Shutdown Audio Core
 | ||||
| void Shutdown(); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										43
									
								
								src/audio_core/audio_types.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/audio_core/audio_types.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <cstddef> | ||||
| #include <deque> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| /// Samples per second which the 3DS's audio hardware natively outputs at
 | ||||
| constexpr int native_sample_rate = 32728; // Hz
 | ||||
| 
 | ||||
| /// Samples per audio frame at native sample rate
 | ||||
| constexpr int samples_per_frame = 160; | ||||
| 
 | ||||
| /// The final output to the speakers is stereo. Preprocessing output in Source is also stereo.
 | ||||
| using StereoFrame16 = std::array<std::array<s16, 2>, samples_per_frame>; | ||||
| 
 | ||||
| /// The DSP is quadraphonic internally.
 | ||||
| using QuadFrame32 = std::array<std::array<s32, 4>, samples_per_frame>; | ||||
| 
 | ||||
| /// A variable length buffer of signed PCM16 stereo samples.
 | ||||
| using StereoBuffer16 = std::deque<std::array<s16, 2>>; | ||||
| 
 | ||||
| constexpr size_t num_dsp_pipe = 8; | ||||
| enum class DspPipe { | ||||
|     Debug = 0, | ||||
|     Dma = 1, | ||||
|     Audio = 2, | ||||
|     Binary = 3, | ||||
| }; | ||||
| 
 | ||||
| enum class DspState { | ||||
|     Off, | ||||
|     On, | ||||
|     Sleeping, | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -5,12 +5,13 @@ | |||
| #include <array> | ||||
| #include <cstddef> | ||||
| #include <cstring> | ||||
| #include <vector> | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/codec.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/math_util.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| namespace Codec { | ||||
| 
 | ||||
| StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, | ||||
|  | @ -124,4 +125,5 @@ StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, | |||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| }; | ||||
| } // namespace Codec
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -5,14 +5,12 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <deque> | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| namespace Codec { | ||||
| 
 | ||||
| /// A variable length buffer of signed PCM16 stereo samples.
 | ||||
| using StereoBuffer16 = std::deque<std::array<s16, 2>>; | ||||
| 
 | ||||
| /// See: Codec::DecodeADPCM
 | ||||
| struct ADPCMState { | ||||
|     // Two historical samples from previous processed buffer,
 | ||||
|  | @ -48,4 +46,5 @@ StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, | |||
|  */ | ||||
| StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, | ||||
|                            const size_t sample_count); | ||||
| }; | ||||
| } // namespace Codec
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
							
								
								
									
										75
									
								
								src/audio_core/dsp_interface.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/audio_core/dsp_interface.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include "audio_core/dsp_interface.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "audio_core/sink_details.h" | ||||
| #include "common/assert.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| DspInterface::DspInterface() = default; | ||||
| 
 | ||||
| DspInterface::~DspInterface() { | ||||
|     if (perform_time_stretching) { | ||||
|         FlushResidualStretcherAudio(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DspInterface::SetSink(const std::string& sink_id) { | ||||
|     const SinkDetails& sink_details = GetSinkDetails(sink_id); | ||||
|     sink = sink_details.factory(); | ||||
|     time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); | ||||
| } | ||||
| 
 | ||||
| Sink& DspInterface::GetSink() { | ||||
|     ASSERT(sink); | ||||
|     return *sink.get(); | ||||
| } | ||||
| 
 | ||||
| void DspInterface::EnableStretching(bool enable) { | ||||
|     if (perform_time_stretching == enable) | ||||
|         return; | ||||
| 
 | ||||
|     if (!enable) { | ||||
|         FlushResidualStretcherAudio(); | ||||
|     } | ||||
|     perform_time_stretching = enable; | ||||
| } | ||||
| 
 | ||||
| void DspInterface::OutputFrame(const StereoFrame16& frame) { | ||||
|     if (!sink) | ||||
|         return; | ||||
| 
 | ||||
|     if (perform_time_stretching) { | ||||
|         time_stretcher.AddSamples(&frame[0][0], frame.size()); | ||||
|         std::vector<s16> stretched_samples = time_stretcher.Process(sink->SamplesInQueue()); | ||||
|         sink->EnqueueSamples(stretched_samples.data(), stretched_samples.size() / 2); | ||||
|     } else { | ||||
|         constexpr size_t maximum_sample_latency = 2048; // about 64 miliseconds
 | ||||
|         if (sink->SamplesInQueue() > maximum_sample_latency) { | ||||
|             // This can occur if we're running too fast and samples are starting to back up.
 | ||||
|             // Just drop the samples.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         sink->EnqueueSamples(&frame[0][0], frame.size()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DspInterface::FlushResidualStretcherAudio() { | ||||
|     if (!sink) | ||||
|         return; | ||||
| 
 | ||||
|     time_stretcher.Flush(); | ||||
|     while (true) { | ||||
|         std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue()); | ||||
|         if (residual_audio.empty()) | ||||
|             break; | ||||
|         sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										81
									
								
								src/audio_core/dsp_interface.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/audio_core/dsp_interface.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/time_stretch.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/memory.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class Sink; | ||||
| 
 | ||||
| class DspInterface { | ||||
| public: | ||||
|     DspInterface(); | ||||
|     virtual ~DspInterface(); | ||||
| 
 | ||||
|     DspInterface(const DspInterface&) = delete; | ||||
|     DspInterface(DspInterface&&) = delete; | ||||
|     DspInterface& operator=(const DspInterface&) = delete; | ||||
|     DspInterface& operator=(DspInterface&&) = delete; | ||||
| 
 | ||||
|     /// Get the state of the DSP
 | ||||
|     virtual DspState GetDspState() const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Reads `length` bytes from the DSP pipe identified with `pipe_number`. | ||||
|      * @note Can read up to the maximum value of a u16 in bytes (65,535). | ||||
|      * @note IF an error is encoutered with either an invalid `pipe_number` or `length` value, an | ||||
|      * empty vector will be returned. | ||||
|      * @note IF `length` is set to 0, an empty vector will be returned. | ||||
|      * @note IF `length` is greater than the amount of data available, this function will only read | ||||
|      * the available amount. | ||||
|      * @param pipe_number a `DspPipe` | ||||
|      * @param length the number of bytes to read. The max is 65,535 (max of u16). | ||||
|      * @returns a vector of bytes from the specified pipe. On error, will be empty. | ||||
|      */ | ||||
|     virtual std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * How much data is left in pipe | ||||
|      * @param pipe_number The Pipe ID | ||||
|      * @return The amount of data remaning in the pipe. This is the maximum length PipeRead will | ||||
|      * return. | ||||
|      */ | ||||
|     virtual size_t GetPipeReadableSize(DspPipe pipe_number) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Write to a DSP pipe. | ||||
|      * @param pipe_number The Pipe ID | ||||
|      * @param buffer The data to write to the pipe. | ||||
|      */ | ||||
|     virtual void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) = 0; | ||||
| 
 | ||||
|     /// Returns a reference to the array backing DSP memory
 | ||||
|     virtual std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory() = 0; | ||||
| 
 | ||||
|     /// Select the sink to use based on sink id.
 | ||||
|     void SetSink(const std::string& sink_id); | ||||
|     /// Get the current sink
 | ||||
|     Sink& GetSink(); | ||||
|     /// Enable/Disable audio stretching.
 | ||||
|     void EnableStretching(bool enable); | ||||
| 
 | ||||
| protected: | ||||
|     void OutputFrame(const StereoFrame16& frame); | ||||
| 
 | ||||
| private: | ||||
|     void FlushResidualStretcherAudio(); | ||||
| 
 | ||||
|     std::unique_ptr<Sink> sink; | ||||
|     bool perform_time_stretching = false; | ||||
|     TimeStretcher time_stretcher; | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -5,20 +5,12 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include "common/common_types.h" | ||||
| #include <cstddef> | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace AudioCore { | ||||
| namespace HLE { | ||||
| 
 | ||||
| constexpr int num_sources = 24; | ||||
| constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate
 | ||||
| 
 | ||||
| /// The final output to the speakers is stereo. Preprocessing output in Source is also stereo.
 | ||||
| using StereoFrame16 = std::array<std::array<s16, 2>, samples_per_frame>; | ||||
| 
 | ||||
| /// The DSP is quadraphonic internally.
 | ||||
| using QuadFrame32 = std::array<std::array<s32, 4>, samples_per_frame>; | ||||
| constexpr size_t num_sources = 24; | ||||
| 
 | ||||
| /**
 | ||||
|  * This performs the filter operation defined by FilterT::ProcessSample on the frame in-place. | ||||
|  | @ -31,4 +23,4 @@ void FilterFrame(FrameT& frame, FilterT& filter) { | |||
| } | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -1,172 +0,0 @@ | |||
| // Copyright 2016 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include "audio_core/hle/dsp.h" | ||||
| #include "audio_core/hle/mixers.h" | ||||
| #include "audio_core/hle/pipe.h" | ||||
| #include "audio_core/hle/source.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "audio_core/time_stretch.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace HLE { | ||||
| 
 | ||||
| // Region management
 | ||||
| 
 | ||||
| DspMemory g_dsp_memory; | ||||
| 
 | ||||
| static size_t CurrentRegionIndex() { | ||||
|     // The region with the higher frame counter is chosen unless there is wraparound.
 | ||||
|     // This function only returns a 0 or 1.
 | ||||
|     u16 frame_counter_0 = g_dsp_memory.region_0.frame_counter; | ||||
|     u16 frame_counter_1 = g_dsp_memory.region_1.frame_counter; | ||||
| 
 | ||||
|     if (frame_counter_0 == 0xFFFFu && frame_counter_1 != 0xFFFEu) { | ||||
|         // Wraparound has occurred.
 | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     if (frame_counter_1 == 0xFFFFu && frame_counter_0 != 0xFFFEu) { | ||||
|         // Wraparound has occurred.
 | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return (frame_counter_0 > frame_counter_1) ? 0 : 1; | ||||
| } | ||||
| 
 | ||||
| static SharedMemory& ReadRegion() { | ||||
|     return CurrentRegionIndex() == 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1; | ||||
| } | ||||
| 
 | ||||
| static SharedMemory& WriteRegion() { | ||||
|     return CurrentRegionIndex() != 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1; | ||||
| } | ||||
| 
 | ||||
| // Audio processing and mixing
 | ||||
| 
 | ||||
| static std::array<Source, num_sources> sources = { | ||||
|     Source(0),  Source(1),  Source(2),  Source(3),  Source(4),  Source(5),  Source(6),  Source(7), | ||||
|     Source(8),  Source(9),  Source(10), Source(11), Source(12), Source(13), Source(14), Source(15), | ||||
|     Source(16), Source(17), Source(18), Source(19), Source(20), Source(21), Source(22), Source(23), | ||||
| }; | ||||
| static Mixers mixers; | ||||
| 
 | ||||
| static StereoFrame16 GenerateCurrentFrame() { | ||||
|     SharedMemory& read = ReadRegion(); | ||||
|     SharedMemory& write = WriteRegion(); | ||||
| 
 | ||||
|     std::array<QuadFrame32, 3> intermediate_mixes = {}; | ||||
| 
 | ||||
|     // Generate intermediate mixes
 | ||||
|     for (size_t i = 0; i < num_sources; i++) { | ||||
|         write.source_statuses.status[i] = | ||||
|             sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]); | ||||
|         for (size_t mix = 0; mix < 3; mix++) { | ||||
|             sources[i].MixInto(intermediate_mixes[mix], mix); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Generate final mix
 | ||||
|     write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, | ||||
|                                    write.intermediate_mix_samples, intermediate_mixes); | ||||
| 
 | ||||
|     StereoFrame16 output_frame = mixers.GetOutput(); | ||||
| 
 | ||||
|     // Write current output frame to the shared memory region
 | ||||
|     for (size_t samplei = 0; samplei < output_frame.size(); samplei++) { | ||||
|         for (size_t channeli = 0; channeli < output_frame[0].size(); channeli++) { | ||||
|             write.final_samples.pcm16[samplei][channeli] = s16_le(output_frame[samplei][channeli]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return output_frame; | ||||
| } | ||||
| 
 | ||||
| // Audio output
 | ||||
| 
 | ||||
| static bool perform_time_stretching = true; | ||||
| static std::unique_ptr<AudioCore::Sink> sink; | ||||
| static AudioCore::TimeStretcher time_stretcher; | ||||
| 
 | ||||
| static void FlushResidualStretcherAudio() { | ||||
|     time_stretcher.Flush(); | ||||
|     while (true) { | ||||
|         std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue()); | ||||
|         if (residual_audio.empty()) | ||||
|             break; | ||||
|         sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void OutputCurrentFrame(const StereoFrame16& frame) { | ||||
|     if (perform_time_stretching) { | ||||
|         time_stretcher.AddSamples(&frame[0][0], frame.size()); | ||||
|         std::vector<s16> stretched_samples = time_stretcher.Process(sink->SamplesInQueue()); | ||||
|         sink->EnqueueSamples(stretched_samples.data(), stretched_samples.size() / 2); | ||||
|     } else { | ||||
|         constexpr size_t maximum_sample_latency = 2048; // about 64 miliseconds
 | ||||
|         if (sink->SamplesInQueue() > maximum_sample_latency) { | ||||
|             // This can occur if we're running too fast and samples are starting to back up.
 | ||||
|             // Just drop the samples.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         sink->EnqueueSamples(&frame[0][0], frame.size()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void EnableStretching(bool enable) { | ||||
|     if (perform_time_stretching == enable) | ||||
|         return; | ||||
| 
 | ||||
|     if (!enable) { | ||||
|         FlushResidualStretcherAudio(); | ||||
|     } | ||||
|     perform_time_stretching = enable; | ||||
| } | ||||
| 
 | ||||
| // Public Interface
 | ||||
| 
 | ||||
| void Init() { | ||||
|     DSP::HLE::ResetPipes(); | ||||
| 
 | ||||
|     for (auto& source : sources) { | ||||
|         source.Reset(); | ||||
|     } | ||||
| 
 | ||||
|     mixers.Reset(); | ||||
| 
 | ||||
|     time_stretcher.Reset(); | ||||
|     if (sink) { | ||||
|         time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Shutdown() { | ||||
|     if (perform_time_stretching) { | ||||
|         FlushResidualStretcherAudio(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Tick() { | ||||
|     StereoFrame16 current_frame = {}; | ||||
| 
 | ||||
|     // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to
 | ||||
|     // shared memory region)
 | ||||
|     current_frame = GenerateCurrentFrame(); | ||||
| 
 | ||||
|     OutputCurrentFrame(current_frame); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void SetSink(std::unique_ptr<AudioCore::Sink> sink_) { | ||||
|     sink = std::move(sink_); | ||||
|     time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); | ||||
| } | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
|  | @ -5,12 +5,12 @@ | |||
| #include <array> | ||||
| #include <cstddef> | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "audio_core/hle/dsp.h" | ||||
| #include "audio_core/hle/filter.h" | ||||
| #include "audio_core/hle/shared_memory.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/math_util.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace AudioCore { | ||||
| namespace HLE { | ||||
| 
 | ||||
| void SourceFilters::Reset() { | ||||
|  | @ -114,4 +114,4 @@ std::array<s16, 2> SourceFilters::BiquadFilter::ProcessSample(const std::array<s | |||
| } | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -5,11 +5,11 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "audio_core/hle/dsp.h" | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/hle/shared_memory.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace AudioCore { | ||||
| namespace HLE { | ||||
| 
 | ||||
| /// Preprocessing filters. There is an independent set of filters for each Source.
 | ||||
|  | @ -114,4 +114,4 @@ private: | |||
| }; | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
							
								
								
									
										341
									
								
								src/audio_core/hle/hle.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/audio_core/hle/hle.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,341 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "audio_core/hle/hle.h" | ||||
| #include "audio_core/hle/mixers.h" | ||||
| #include "audio_core/hle/shared_memory.h" | ||||
| #include "audio_core/hle/source.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/service/dsp_dsp.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles
 | ||||
| 
 | ||||
| struct DspHle::Impl final { | ||||
| public: | ||||
|     explicit Impl(DspHle& parent); | ||||
|     ~Impl(); | ||||
| 
 | ||||
|     DspState GetDspState() const; | ||||
| 
 | ||||
|     std::vector<u8> PipeRead(DspPipe pipe_number, u32 length); | ||||
|     size_t GetPipeReadableSize(DspPipe pipe_number) const; | ||||
|     void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer); | ||||
| 
 | ||||
|     std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory(); | ||||
| 
 | ||||
| private: | ||||
|     void ResetPipes(); | ||||
|     void WriteU16(DspPipe pipe_number, u16 value); | ||||
|     void AudioPipeWriteStructAddresses(); | ||||
| 
 | ||||
|     size_t CurrentRegionIndex() const; | ||||
|     HLE::SharedMemory& ReadRegion(); | ||||
|     HLE::SharedMemory& WriteRegion(); | ||||
| 
 | ||||
|     StereoFrame16 GenerateCurrentFrame(); | ||||
|     bool Tick(); | ||||
|     void AudioTickCallback(int cycles_late); | ||||
| 
 | ||||
|     DspState dsp_state = DspState::Off; | ||||
|     std::array<std::vector<u8>, num_dsp_pipe> pipe_data; | ||||
| 
 | ||||
|     HLE::DspMemory dsp_memory; | ||||
|     std::array<HLE::Source, HLE::num_sources> sources{{ | ||||
|         HLE::Source(0),  HLE::Source(1),  HLE::Source(2),  HLE::Source(3),  HLE::Source(4), | ||||
|         HLE::Source(5),  HLE::Source(6),  HLE::Source(7),  HLE::Source(8),  HLE::Source(9), | ||||
|         HLE::Source(10), HLE::Source(11), HLE::Source(12), HLE::Source(13), HLE::Source(14), | ||||
|         HLE::Source(15), HLE::Source(16), HLE::Source(17), HLE::Source(18), HLE::Source(19), | ||||
|         HLE::Source(20), HLE::Source(21), HLE::Source(22), HLE::Source(23), | ||||
|     }}; | ||||
|     HLE::Mixers mixers; | ||||
| 
 | ||||
|     DspHle& parent; | ||||
|     CoreTiming::EventType* tick_event; | ||||
| }; | ||||
| 
 | ||||
| DspHle::Impl::Impl(DspHle& parent_) : parent(parent_) { | ||||
|     tick_event = | ||||
|         CoreTiming::RegisterEvent("AudioCore::DspHle::tick_event", [this](u64, int cycles_late) { | ||||
|             this->AudioTickCallback(cycles_late); | ||||
|         }); | ||||
|     CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event); | ||||
| } | ||||
| 
 | ||||
| DspHle::Impl::~Impl() { | ||||
|     CoreTiming::UnscheduleEvent(tick_event, 0); | ||||
| } | ||||
| 
 | ||||
| DspState DspHle::Impl::GetDspState() const { | ||||
|     return dsp_state; | ||||
| } | ||||
| 
 | ||||
| std::vector<u8> DspHle::Impl::PipeRead(DspPipe pipe_number, u32 length) { | ||||
|     const size_t pipe_index = static_cast<size_t>(pipe_number); | ||||
| 
 | ||||
|     if (pipe_index >= num_dsp_pipe) { | ||||
|         LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     if (length > UINT16_MAX) { // Can only read at most UINT16_MAX from the pipe
 | ||||
|         LOG_ERROR(Audio_DSP, "length of %u greater than max of %u", length, UINT16_MAX); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<u8>& data = pipe_data[pipe_index]; | ||||
| 
 | ||||
|     if (length > data.size()) { | ||||
|         LOG_WARNING( | ||||
|             Audio_DSP, | ||||
|             "pipe_number = %zu is out of data, application requested read of %u but %zu remain", | ||||
|             pipe_index, length, data.size()); | ||||
|         length = static_cast<u32>(data.size()); | ||||
|     } | ||||
| 
 | ||||
|     if (length == 0) | ||||
|         return {}; | ||||
| 
 | ||||
|     std::vector<u8> ret(data.begin(), data.begin() + length); | ||||
|     data.erase(data.begin(), data.begin() + length); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t DspHle::Impl::GetPipeReadableSize(DspPipe pipe_number) const { | ||||
|     const size_t pipe_index = static_cast<size_t>(pipe_number); | ||||
| 
 | ||||
|     if (pipe_index >= num_dsp_pipe) { | ||||
|         LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return pipe_data[pipe_index].size(); | ||||
| } | ||||
| 
 | ||||
| void DspHle::Impl::PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) { | ||||
|     switch (pipe_number) { | ||||
|     case DspPipe::Audio: { | ||||
|         if (buffer.size() != 4) { | ||||
|             LOG_ERROR(Audio_DSP, "DspPipe::Audio: Unexpected buffer length %zu was written", | ||||
|                       buffer.size()); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         enum class StateChange { | ||||
|             Initialize = 0, | ||||
|             Shutdown = 1, | ||||
|             Wakeup = 2, | ||||
|             Sleep = 3, | ||||
|         }; | ||||
| 
 | ||||
|         // The difference between Initialize and Wakeup is that Input state is maintained
 | ||||
|         // when sleeping but isn't when turning it off and on again. (TODO: Implement this.)
 | ||||
|         // Waking up from sleep garbles some of the structs in the memory region. (TODO:
 | ||||
|         // Implement this.) Applications store away the state of these structs before
 | ||||
|         // sleeping and reset it back after wakeup on behalf of the DSP.
 | ||||
| 
 | ||||
|         switch (static_cast<StateChange>(buffer[0])) { | ||||
|         case StateChange::Initialize: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested initialization of DSP hardware"); | ||||
|             ResetPipes(); | ||||
|             AudioPipeWriteStructAddresses(); | ||||
|             dsp_state = DspState::On; | ||||
|             break; | ||||
|         case StateChange::Shutdown: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested shutdown of DSP hardware"); | ||||
|             dsp_state = DspState::Off; | ||||
|             break; | ||||
|         case StateChange::Wakeup: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested wakeup of DSP hardware"); | ||||
|             ResetPipes(); | ||||
|             AudioPipeWriteStructAddresses(); | ||||
|             dsp_state = DspState::On; | ||||
|             break; | ||||
|         case StateChange::Sleep: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested sleep of DSP hardware"); | ||||
|             UNIMPLEMENTED(); | ||||
|             dsp_state = DspState::Sleeping; | ||||
|             break; | ||||
|         default: | ||||
|             LOG_ERROR(Audio_DSP, | ||||
|                       "Application has requested unknown state transition of DSP hardware %hhu", | ||||
|                       buffer[0]); | ||||
|             dsp_state = DspState::Off; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         return; | ||||
|     } | ||||
|     default: | ||||
|         LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", | ||||
|                      static_cast<size_t>(pipe_number)); | ||||
|         UNIMPLEMENTED(); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::array<u8, Memory::DSP_RAM_SIZE>& DspHle::Impl::GetDspMemory() { | ||||
|     return dsp_memory.raw_memory; | ||||
| } | ||||
| 
 | ||||
| void DspHle::Impl::ResetPipes() { | ||||
|     for (auto& data : pipe_data) { | ||||
|         data.clear(); | ||||
|     } | ||||
|     dsp_state = DspState::Off; | ||||
| } | ||||
| 
 | ||||
| void DspHle::Impl::WriteU16(DspPipe pipe_number, u16 value) { | ||||
|     const size_t pipe_index = static_cast<size_t>(pipe_number); | ||||
| 
 | ||||
|     std::vector<u8>& data = pipe_data.at(pipe_index); | ||||
|     // Little endian
 | ||||
|     data.emplace_back(value & 0xFF); | ||||
|     data.emplace_back(value >> 8); | ||||
| } | ||||
| 
 | ||||
| void DspHle::Impl::AudioPipeWriteStructAddresses() { | ||||
|     // These struct addresses are DSP dram addresses.
 | ||||
|     // See also: DSP_DSP::ConvertProcessAddressFromDspDram
 | ||||
|     static const std::array<u16, 15> struct_addresses = { | ||||
|         0x8000 + offsetof(HLE::SharedMemory, frame_counter) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, source_configurations) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, source_statuses) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, adpcm_coefficients) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, dsp_configuration) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, dsp_status) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, final_samples) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, intermediate_mix_samples) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, compressor) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, dsp_debug) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, unknown10) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, unknown11) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, unknown12) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, unknown13) / 2, | ||||
|         0x8000 + offsetof(HLE::SharedMemory, unknown14) / 2, | ||||
|     }; | ||||
| 
 | ||||
|     // Begin with a u16 denoting the number of structs.
 | ||||
|     WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size())); | ||||
|     // Then write the struct addresses.
 | ||||
|     for (u16 addr : struct_addresses) { | ||||
|         WriteU16(DspPipe::Audio, addr); | ||||
|     } | ||||
|     // Signal that we have data on this pipe.
 | ||||
|     Service::DSP_DSP::SignalPipeInterrupt(DspPipe::Audio); | ||||
| } | ||||
| 
 | ||||
| size_t DspHle::Impl::CurrentRegionIndex() const { | ||||
|     // The region with the higher frame counter is chosen unless there is wraparound.
 | ||||
|     // This function only returns a 0 or 1.
 | ||||
|     const u16 frame_counter_0 = dsp_memory.region_0.frame_counter; | ||||
|     const u16 frame_counter_1 = dsp_memory.region_1.frame_counter; | ||||
| 
 | ||||
|     if (frame_counter_0 == 0xFFFFu && frame_counter_1 != 0xFFFEu) { | ||||
|         // Wraparound has occurred.
 | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     if (frame_counter_1 == 0xFFFFu && frame_counter_0 != 0xFFFEu) { | ||||
|         // Wraparound has occurred.
 | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return (frame_counter_0 > frame_counter_1) ? 0 : 1; | ||||
| } | ||||
| 
 | ||||
| HLE::SharedMemory& DspHle::Impl::ReadRegion() { | ||||
|     return CurrentRegionIndex() == 0 ? dsp_memory.region_0 : dsp_memory.region_1; | ||||
| } | ||||
| 
 | ||||
| HLE::SharedMemory& DspHle::Impl::WriteRegion() { | ||||
|     return CurrentRegionIndex() != 0 ? dsp_memory.region_0 : dsp_memory.region_1; | ||||
| } | ||||
| 
 | ||||
| StereoFrame16 DspHle::Impl::GenerateCurrentFrame() { | ||||
|     HLE::SharedMemory& read = ReadRegion(); | ||||
|     HLE::SharedMemory& write = WriteRegion(); | ||||
| 
 | ||||
|     std::array<QuadFrame32, 3> intermediate_mixes = {}; | ||||
| 
 | ||||
|     // Generate intermediate mixes
 | ||||
|     for (size_t i = 0; i < HLE::num_sources; i++) { | ||||
|         write.source_statuses.status[i] = | ||||
|             sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]); | ||||
|         for (size_t mix = 0; mix < 3; mix++) { | ||||
|             sources[i].MixInto(intermediate_mixes[mix], mix); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Generate final mix
 | ||||
|     write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, | ||||
|                                    write.intermediate_mix_samples, intermediate_mixes); | ||||
| 
 | ||||
|     StereoFrame16 output_frame = mixers.GetOutput(); | ||||
| 
 | ||||
|     // Write current output frame to the shared memory region
 | ||||
|     for (size_t samplei = 0; samplei < output_frame.size(); samplei++) { | ||||
|         for (size_t channeli = 0; channeli < output_frame[0].size(); channeli++) { | ||||
|             write.final_samples.pcm16[samplei][channeli] = s16_le(output_frame[samplei][channeli]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return output_frame; | ||||
| } | ||||
| 
 | ||||
| bool DspHle::Impl::Tick() { | ||||
|     StereoFrame16 current_frame = {}; | ||||
| 
 | ||||
|     // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to
 | ||||
|     // shared memory region)
 | ||||
|     current_frame = GenerateCurrentFrame(); | ||||
| 
 | ||||
|     parent.OutputFrame(current_frame); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void DspHle::Impl::AudioTickCallback(int cycles_late) { | ||||
|     if (Tick()) { | ||||
|         // TODO(merry): Signal all the other interrupts as appropriate.
 | ||||
|         Service::DSP_DSP::SignalPipeInterrupt(DspPipe::Audio); | ||||
|         // HACK(merry): Added to prevent regressions. Will remove soon.
 | ||||
|         Service::DSP_DSP::SignalPipeInterrupt(DspPipe::Binary); | ||||
|     } | ||||
| 
 | ||||
|     // Reschedule recurrent event
 | ||||
|     CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); | ||||
| } | ||||
| 
 | ||||
| DspHle::DspHle() : impl(std::make_unique<Impl>(*this)) {} | ||||
| DspHle::~DspHle() = default; | ||||
| 
 | ||||
| DspState DspHle::GetDspState() const { | ||||
|     return impl->GetDspState(); | ||||
| } | ||||
| 
 | ||||
| std::vector<u8> DspHle::PipeRead(DspPipe pipe_number, u32 length) { | ||||
|     return impl->PipeRead(pipe_number, length); | ||||
| } | ||||
| 
 | ||||
| size_t DspHle::GetPipeReadableSize(DspPipe pipe_number) const { | ||||
|     return impl->GetPipeReadableSize(pipe_number); | ||||
| } | ||||
| 
 | ||||
| void DspHle::PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) { | ||||
|     impl->PipeWrite(pipe_number, buffer); | ||||
| } | ||||
| 
 | ||||
| std::array<u8, Memory::DSP_RAM_SIZE>& DspHle::GetDspMemory() { | ||||
|     return impl->GetDspMemory(); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										36
									
								
								src/audio_core/hle/hle.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/audio_core/hle/hle.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/dsp_interface.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/memory.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class DspHle final : public DspInterface { | ||||
| public: | ||||
|     DspHle(); | ||||
|     ~DspHle(); | ||||
| 
 | ||||
|     DspState GetDspState() const override; | ||||
| 
 | ||||
|     std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) override; | ||||
|     size_t GetPipeReadableSize(DspPipe pipe_number) const override; | ||||
|     void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) override; | ||||
| 
 | ||||
|     std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory() override; | ||||
| 
 | ||||
| private: | ||||
|     struct Impl; | ||||
|     friend struct Impl; | ||||
|     std::unique_ptr<Impl> impl; | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -4,14 +4,12 @@ | |||
| 
 | ||||
| #include <cstddef> | ||||
| 
 | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "audio_core/hle/dsp.h" | ||||
| #include "audio_core/hle/mixers.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/math_util.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace AudioCore { | ||||
| namespace HLE { | ||||
| 
 | ||||
| void Mixers::Reset() { | ||||
|  | @ -207,4 +205,4 @@ DspStatus Mixers::GetCurrentStatus() const { | |||
| } | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -5,10 +5,10 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "audio_core/hle/dsp.h" | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/hle/shared_memory.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace AudioCore { | ||||
| namespace HLE { | ||||
| 
 | ||||
| class Mixers final { | ||||
|  | @ -58,4 +58,4 @@ private: | |||
| }; | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -1,177 +0,0 @@ | |||
| // 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/dsp.h" | ||||
| #include "audio_core/hle/pipe.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/hle/service/dsp_dsp.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace HLE { | ||||
| 
 | ||||
| static DspState dsp_state = DspState::Off; | ||||
| 
 | ||||
| static std::array<std::vector<u8>, NUM_DSP_PIPE> pipe_data; | ||||
| 
 | ||||
| void ResetPipes() { | ||||
|     for (auto& data : pipe_data) { | ||||
|         data.clear(); | ||||
|     } | ||||
|     dsp_state = DspState::Off; | ||||
| } | ||||
| 
 | ||||
| std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) { | ||||
|     const size_t pipe_index = static_cast<size_t>(pipe_number); | ||||
| 
 | ||||
|     if (pipe_index >= NUM_DSP_PIPE) { | ||||
|         LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     if (length > UINT16_MAX) { // Can only read at most UINT16_MAX from the pipe
 | ||||
|         LOG_ERROR(Audio_DSP, "length of %u greater than max of %u", length, UINT16_MAX); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<u8>& data = pipe_data[pipe_index]; | ||||
| 
 | ||||
|     if (length > data.size()) { | ||||
|         LOG_WARNING( | ||||
|             Audio_DSP, | ||||
|             "pipe_number = %zu is out of data, application requested read of %u but %zu remain", | ||||
|             pipe_index, length, data.size()); | ||||
|         length = static_cast<u32>(data.size()); | ||||
|     } | ||||
| 
 | ||||
|     if (length == 0) | ||||
|         return {}; | ||||
| 
 | ||||
|     std::vector<u8> ret(data.begin(), data.begin() + length); | ||||
|     data.erase(data.begin(), data.begin() + length); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t GetPipeReadableSize(DspPipe pipe_number) { | ||||
|     const size_t pipe_index = static_cast<size_t>(pipe_number); | ||||
| 
 | ||||
|     if (pipe_index >= NUM_DSP_PIPE) { | ||||
|         LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return pipe_data[pipe_index].size(); | ||||
| } | ||||
| 
 | ||||
| static void WriteU16(DspPipe pipe_number, u16 value) { | ||||
|     const size_t pipe_index = static_cast<size_t>(pipe_number); | ||||
| 
 | ||||
|     std::vector<u8>& data = pipe_data.at(pipe_index); | ||||
|     // Little endian
 | ||||
|     data.emplace_back(value & 0xFF); | ||||
|     data.emplace_back(value >> 8); | ||||
| } | ||||
| 
 | ||||
| static void AudioPipeWriteStructAddresses() { | ||||
|     // These struct addresses are DSP dram addresses.
 | ||||
|     // See also: DSP_DSP::ConvertProcessAddressFromDspDram
 | ||||
|     static const std::array<u16, 15> struct_addresses = { | ||||
|         0x8000 + offsetof(SharedMemory, frame_counter) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, source_configurations) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, source_statuses) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, adpcm_coefficients) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, dsp_configuration) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, dsp_status) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, final_samples) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, intermediate_mix_samples) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, compressor) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, dsp_debug) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, unknown10) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, unknown11) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, unknown12) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, unknown13) / 2, | ||||
|         0x8000 + offsetof(SharedMemory, unknown14) / 2, | ||||
|     }; | ||||
| 
 | ||||
|     // Begin with a u16 denoting the number of structs.
 | ||||
|     WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size())); | ||||
|     // Then write the struct addresses.
 | ||||
|     for (u16 addr : struct_addresses) { | ||||
|         WriteU16(DspPipe::Audio, addr); | ||||
|     } | ||||
|     // Signal that we have data on this pipe.
 | ||||
|     Service::DSP_DSP::SignalPipeInterrupt(DspPipe::Audio); | ||||
| } | ||||
| 
 | ||||
| void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) { | ||||
|     switch (pipe_number) { | ||||
|     case DspPipe::Audio: { | ||||
|         if (buffer.size() != 4) { | ||||
|             LOG_ERROR(Audio_DSP, "DspPipe::Audio: Unexpected buffer length %zu was written", | ||||
|                       buffer.size()); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         enum class StateChange { | ||||
|             Initialize = 0, | ||||
|             Shutdown = 1, | ||||
|             Wakeup = 2, | ||||
|             Sleep = 3, | ||||
|         }; | ||||
| 
 | ||||
|         // The difference between Initialize and Wakeup is that Input state is maintained
 | ||||
|         // when sleeping but isn't when turning it off and on again. (TODO: Implement this.)
 | ||||
|         // Waking up from sleep garbles some of the structs in the memory region. (TODO:
 | ||||
|         // Implement this.) Applications store away the state of these structs before
 | ||||
|         // sleeping and reset it back after wakeup on behalf of the DSP.
 | ||||
| 
 | ||||
|         switch (static_cast<StateChange>(buffer[0])) { | ||||
|         case StateChange::Initialize: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested initialization of DSP hardware"); | ||||
|             ResetPipes(); | ||||
|             AudioPipeWriteStructAddresses(); | ||||
|             dsp_state = DspState::On; | ||||
|             break; | ||||
|         case StateChange::Shutdown: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested shutdown of DSP hardware"); | ||||
|             dsp_state = DspState::Off; | ||||
|             break; | ||||
|         case StateChange::Wakeup: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested wakeup of DSP hardware"); | ||||
|             ResetPipes(); | ||||
|             AudioPipeWriteStructAddresses(); | ||||
|             dsp_state = DspState::On; | ||||
|             break; | ||||
|         case StateChange::Sleep: | ||||
|             LOG_INFO(Audio_DSP, "Application has requested sleep of DSP hardware"); | ||||
|             UNIMPLEMENTED(); | ||||
|             dsp_state = DspState::Sleeping; | ||||
|             break; | ||||
|         default: | ||||
|             LOG_ERROR(Audio_DSP, | ||||
|                       "Application has requested unknown state transition of DSP hardware %hhu", | ||||
|                       buffer[0]); | ||||
|             dsp_state = DspState::Off; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         return; | ||||
|     } | ||||
|     default: | ||||
|         LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", | ||||
|                      static_cast<size_t>(pipe_number)); | ||||
|         UNIMPLEMENTED(); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DspState GetDspState() { | ||||
|     return dsp_state; | ||||
| } | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
|  | @ -1,63 +0,0 @@ | |||
| // Copyright 2016 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace HLE { | ||||
| 
 | ||||
| /// Reset the pipes by setting pipe positions back to the beginning.
 | ||||
| void ResetPipes(); | ||||
| 
 | ||||
| enum class DspPipe { | ||||
|     Debug = 0, | ||||
|     Dma = 1, | ||||
|     Audio = 2, | ||||
|     Binary = 3, | ||||
| }; | ||||
| constexpr size_t NUM_DSP_PIPE = 8; | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads `length` bytes from the DSP pipe identified with `pipe_number`. | ||||
|  * @note Can read up to the maximum value of a u16 in bytes (65,535). | ||||
|  * @note IF an error is encoutered with either an invalid `pipe_number` or `length` value, an empty | ||||
|  * vector will be returned. | ||||
|  * @note IF `length` is set to 0, an empty vector will be returned. | ||||
|  * @note IF `length` is greater than the amount of data available, this function will only read the | ||||
|  * available amount. | ||||
|  * @param pipe_number a `DspPipe` | ||||
|  * @param length the number of bytes to read. The max is 65,535 (max of u16). | ||||
|  * @returns a vector of bytes from the specified pipe. On error, will be empty. | ||||
|  */ | ||||
| std::vector<u8> PipeRead(DspPipe pipe_number, u32 length); | ||||
| 
 | ||||
| /**
 | ||||
|  * How much data is left in pipe | ||||
|  * @param pipe_number The Pipe ID | ||||
|  * @return The amount of data remaning in the pipe. This is the maximum length PipeRead will return. | ||||
|  */ | ||||
| size_t GetPipeReadableSize(DspPipe pipe_number); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write to a DSP pipe. | ||||
|  * @param pipe_number The Pipe ID | ||||
|  * @param buffer The data to write to the pipe. | ||||
|  */ | ||||
| void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer); | ||||
| 
 | ||||
| enum class DspState { | ||||
|     Off, | ||||
|     On, | ||||
|     Sleeping, | ||||
| }; | ||||
| 
 | ||||
| /// Get the state of the DSP
 | ||||
| DspState GetDspState(); | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
|  | @ -8,6 +8,7 @@ | |||
| #include <cstddef> | ||||
| #include <memory> | ||||
| #include <type_traits> | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "common/bit_field.h" | ||||
| #include "common/common_funcs.h" | ||||
|  | @ -15,10 +16,6 @@ | |||
| #include "common/swap.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| class Sink; | ||||
| } | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace HLE { | ||||
| 
 | ||||
| // The application-accessible region of DSP memory consists of two parts. Both are marked as IO and
 | ||||
|  | @ -86,7 +83,7 @@ static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivial | |||
| //       0           0xBFFF                     Frame Counter                         Application
 | ||||
| //
 | ||||
| // #: This refers to the order in which they appear in the DspPipe::Audio DSP pipe.
 | ||||
| //    See also: DSP::HLE::PipeRead.
 | ||||
| //    See also: HLE::PipeRead.
 | ||||
| //
 | ||||
| // 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
 | ||||
|  | @ -527,69 +524,40 @@ static_assert(offsetof(DspMemory, region_0) == region0_offset, | |||
| static_assert(offsetof(DspMemory, region_1) == region1_offset, | ||||
|               "DSP region 1 is at the wrong offset"); | ||||
| 
 | ||||
| extern DspMemory g_dsp_memory; | ||||
| 
 | ||||
| // Structures must have an offset that is a multiple of two.
 | ||||
| static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, source_configurations) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, source_statuses) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, adpcm_coefficients) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, dsp_configuration) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, dsp_status) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, final_samples) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, intermediate_mix_samples) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, compressor) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, dsp_debug) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, unknown10) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, unknown11) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, unknown12) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, unknown13) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| static_assert(offsetof(SharedMemory, unknown14) % 2 == 0, | ||||
|               "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); | ||||
|               "Structures in HLE::SharedMemory must be 2-byte aligned"); | ||||
| 
 | ||||
| #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(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Set the output sink. This must be called before calling Tick(). | ||||
|  * @param sink The sink to which audio will be output to. | ||||
|  */ | ||||
| void SetSink(std::unique_ptr<AudioCore::Sink> sink); | ||||
| 
 | ||||
| /**
 | ||||
|  * Enables/Disables audio-stretching. | ||||
|  * Audio stretching is an enhancement that stretches audio to match emulation | ||||
|  * speed to prevent stuttering at the cost of some audio latency. | ||||
|  * @param enable true to enable, false to disable. | ||||
|  */ | ||||
| void EnableStretching(bool enable); | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -12,7 +12,7 @@ | |||
| #include "common/logging/log.h" | ||||
| #include "core/memory.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace AudioCore { | ||||
| namespace HLE { | ||||
| 
 | ||||
| SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config, | ||||
|  | @ -345,4 +345,4 @@ SourceStatus::Status Source::GetCurrentStatus() { | |||
| } | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -7,14 +7,14 @@ | |||
| #include <array> | ||||
| #include <queue> | ||||
| #include <vector> | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/codec.h" | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "audio_core/hle/dsp.h" | ||||
| #include "audio_core/hle/filter.h" | ||||
| #include "audio_core/interpolate.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace DSP { | ||||
| namespace AudioCore { | ||||
| namespace HLE { | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -146,4 +146,4 @@ private: | |||
| }; | ||||
| 
 | ||||
| } // namespace HLE
 | ||||
| } // namespace DSP
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include "common/assert.h" | ||||
| #include "common/math_util.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| namespace AudioInterp { | ||||
| 
 | ||||
| // Calculations are done in fixed point with 24 fractional bits.
 | ||||
|  | @ -16,8 +17,8 @@ constexpr u64 scale_mask = scale_factor - 1; | |||
| /// Here we step over the input in steps of rate, until we consume all of the input.
 | ||||
| /// Three adjacent samples are passed to fn each step.
 | ||||
| template <typename Function> | ||||
| static void StepOverSamples(State& state, StereoBuffer16& input, float rate, | ||||
|                             DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) { | ||||
| static void StepOverSamples(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, | ||||
|                             size_t& outputi, Function fn) { | ||||
|     ASSERT(rate > 0); | ||||
| 
 | ||||
|     if (input.empty()) | ||||
|  | @ -50,14 +51,13 @@ static void StepOverSamples(State& state, StereoBuffer16& input, float rate, | |||
|     input.erase(input.begin(), std::next(input.begin(), inputi + 2)); | ||||
| } | ||||
| 
 | ||||
| void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, | ||||
|           size_t& outputi) { | ||||
| void None(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, size_t& outputi) { | ||||
|     StepOverSamples( | ||||
|         state, input, rate, output, outputi, | ||||
|         [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; }); | ||||
| } | ||||
| 
 | ||||
| void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, | ||||
| void Linear(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, | ||||
|             size_t& outputi) { | ||||
|     // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
 | ||||
|     StepOverSamples(state, input, rate, output, outputi, | ||||
|  | @ -74,3 +74,4 @@ void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFra | |||
| } | ||||
| 
 | ||||
| } // namespace AudioInterp
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -6,9 +6,10 @@ | |||
| 
 | ||||
| #include <array> | ||||
| #include <deque> | ||||
| #include "audio_core/hle/common.h" | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| namespace AudioInterp { | ||||
| 
 | ||||
| /// A variable length buffer of signed PCM16 stereo samples.
 | ||||
|  | @ -31,8 +32,7 @@ struct State { | |||
|  * @param output The resampled audio buffer. | ||||
|  * @param outputi The index of output to start writing to. | ||||
|  */ | ||||
| void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, | ||||
|           size_t& outputi); | ||||
| void None(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, size_t& outputi); | ||||
| 
 | ||||
| /**
 | ||||
|  * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. | ||||
|  | @ -43,7 +43,8 @@ void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame | |||
|  * @param output The resampled audio buffer. | ||||
|  * @param outputi The index of output to start writing to. | ||||
|  */ | ||||
| void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, | ||||
| void Linear(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, | ||||
|             size_t& outputi); | ||||
| 
 | ||||
| } // namespace AudioInterp
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/sink.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| #include <list> | ||||
| #include <numeric> | ||||
| #include <SDL.h> | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/sdl2_sink.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| 
 | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include "audio_core/null_sink.h" | ||||
| #include "audio_core/sink_details.h" | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
| #include <cmath> | ||||
| #include <vector> | ||||
| #include <SoundTouch.h> | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/time_stretch.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue