diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index ec89b7a4c..00b31f972 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -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 diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp deleted file mode 100644 index ae2b68f9c..000000000 --- a/src/audio_core/audio_core.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#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& 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 diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h deleted file mode 100644 index ab323ce1f..000000000 --- a/src/audio_core/audio_core.h +++ /dev/null @@ -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 -#include -#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& 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 diff --git a/src/audio_core/audio_types.h b/src/audio_core/audio_types.h new file mode 100644 index 000000000..de3dbacc8 --- /dev/null +++ b/src/audio_core/audio_types.h @@ -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 +#include +#include +#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, samples_per_frame>; + +/// The DSP is quadraphonic internally. +using QuadFrame32 = std::array, samples_per_frame>; + +/// A variable length buffer of signed PCM16 stereo samples. +using StereoBuffer16 = std::deque>; + +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 diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp index 6fba9fdae..81fd44b20 100644 --- a/src/audio_core/codec.cpp +++ b/src/audio_core/codec.cpp @@ -5,12 +5,13 @@ #include #include #include -#include +#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 diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h index 877b2202d..7574bc562 100644 --- a/src/audio_core/codec.h +++ b/src/audio_core/codec.h @@ -5,14 +5,12 @@ #pragma once #include -#include +#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>; - /// 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 diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp new file mode 100644 index 000000000..c93d4571a --- /dev/null +++ b/src/audio_core/dsp_interface.cpp @@ -0,0 +1,75 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#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 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 residual_audio = time_stretcher.Process(sink->SamplesInQueue()); + if (residual_audio.empty()) + break; + sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2); + } +} + +} // namespace AudioCore diff --git a/src/audio_core/dsp_interface.h b/src/audio_core/dsp_interface.h new file mode 100644 index 000000000..1f3024dc8 --- /dev/null +++ b/src/audio_core/dsp_interface.h @@ -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 +#include +#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 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& buffer) = 0; + + /// Returns a reference to the array backing DSP memory + virtual std::array& 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; + bool perform_time_stretching = false; + TimeStretcher time_stretcher; +}; + +} // namespace AudioCore diff --git a/src/audio_core/hle/common.h b/src/audio_core/hle/common.h index 7fbc3ad9a..43b6b6a4c 100644 --- a/src/audio_core/hle/common.h +++ b/src/audio_core/hle/common.h @@ -5,20 +5,12 @@ #pragma once #include -#include -#include "common/common_types.h" +#include -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, samples_per_frame>; - -/// The DSP is quadraphonic internally. -using QuadFrame32 = std::array, 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 diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp deleted file mode 100644 index 260b182ed..000000000 --- a/src/audio_core/hle/dsp.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#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 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 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 sink; -static AudioCore::TimeStretcher time_stretcher; - -static void FlushResidualStretcherAudio() { - time_stretcher.Flush(); - while (true) { - std::vector 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 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 sink_) { - sink = std::move(sink_); - time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); -} - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/filter.cpp b/src/audio_core/hle/filter.cpp index b24a79b89..9e6ce9196 100644 --- a/src/audio_core/hle/filter.cpp +++ b/src/audio_core/hle/filter.cpp @@ -5,12 +5,12 @@ #include #include #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 SourceFilters::BiquadFilter::ProcessSample(const std::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 diff --git a/src/audio_core/hle/hle.cpp b/src/audio_core/hle/hle.cpp new file mode 100644 index 000000000..c6fbb3ce0 --- /dev/null +++ b/src/audio_core/hle/hle.cpp @@ -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 PipeRead(DspPipe pipe_number, u32 length); + size_t GetPipeReadableSize(DspPipe pipe_number) const; + void PipeWrite(DspPipe pipe_number, const std::vector& buffer); + + std::array& 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, num_dsp_pipe> pipe_data; + + HLE::DspMemory dsp_memory; + std::array 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 DspHle::Impl::PipeRead(DspPipe pipe_number, u32 length) { + const size_t pipe_index = static_cast(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& 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(data.size()); + } + + if (length == 0) + return {}; + + std::vector 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(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& 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(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(pipe_number)); + UNIMPLEMENTED(); + return; + } +} + +std::array& 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(pipe_number); + + std::vector& 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 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(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 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(*this)) {} +DspHle::~DspHle() = default; + +DspState DspHle::GetDspState() const { + return impl->GetDspState(); +} + +std::vector 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& buffer) { + impl->PipeWrite(pipe_number, buffer); +} + +std::array& DspHle::GetDspMemory() { + return impl->GetDspMemory(); +} + +} // namespace AudioCore diff --git a/src/audio_core/hle/hle.h b/src/audio_core/hle/hle.h new file mode 100644 index 000000000..00ccb6dcf --- /dev/null +++ b/src/audio_core/hle/hle.h @@ -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 +#include +#include +#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 PipeRead(DspPipe pipe_number, u32 length) override; + size_t GetPipeReadableSize(DspPipe pipe_number) const override; + void PipeWrite(DspPipe pipe_number, const std::vector& buffer) override; + + std::array& GetDspMemory() override; + +private: + struct Impl; + friend struct Impl; + std::unique_ptr impl; +}; + +} // namespace AudioCore diff --git a/src/audio_core/hle/mixers.cpp b/src/audio_core/hle/mixers.cpp index 6cc81dfca..d40044eec 100644 --- a/src/audio_core/hle/mixers.cpp +++ b/src/audio_core/hle/mixers.cpp @@ -4,14 +4,12 @@ #include -#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 diff --git a/src/audio_core/hle/mixers.h b/src/audio_core/hle/mixers.h index bf4e865ae..80628793a 100644 --- a/src/audio_core/hle/mixers.h +++ b/src/audio_core/hle/mixers.h @@ -5,10 +5,10 @@ #pragma once #include -#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 diff --git a/src/audio_core/hle/pipe.cpp b/src/audio_core/hle/pipe.cpp deleted file mode 100644 index 24074a514..000000000 --- a/src/audio_core/hle/pipe.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#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, NUM_DSP_PIPE> pipe_data; - -void ResetPipes() { - for (auto& data : pipe_data) { - data.clear(); - } - dsp_state = DspState::Off; -} - -std::vector PipeRead(DspPipe pipe_number, u32 length) { - const size_t pipe_index = static_cast(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& 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(data.size()); - } - - if (length == 0) - return {}; - - std::vector 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(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(pipe_number); - - std::vector& 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 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(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& 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(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(pipe_number)); - UNIMPLEMENTED(); - return; - } -} - -DspState GetDspState() { - return dsp_state; -} - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/pipe.h b/src/audio_core/hle/pipe.h index ac053c029..e69de29bb 100644 --- a/src/audio_core/hle/pipe.h +++ b/src/audio_core/hle/pipe.h @@ -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 -#include -#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 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& buffer); - -enum class DspState { - Off, - On, - Sleeping, -}; - -/// Get the state of the DSP -DspState GetDspState(); - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/shared_memory.h similarity index 90% rename from src/audio_core/hle/dsp.h rename to src/audio_core/hle/shared_memory.h index 94ce48863..c1c64b2d3 100644 --- a/src/audio_core/hle/dsp.h +++ b/src/audio_core/hle/shared_memory.h @@ -8,6 +8,7 @@ #include #include #include +#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::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 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 diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp index 0a9ac4007..41fd876b5 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp @@ -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 diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h index c4d2debc2..2480c4ac1 100644 --- a/src/audio_core/hle/source.h +++ b/src/audio_core/hle/source.h @@ -7,14 +7,14 @@ #include #include #include +#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 diff --git a/src/audio_core/interpolate.cpp b/src/audio_core/interpolate.cpp index 83573d772..19ce714e7 100644 --- a/src/audio_core/interpolate.cpp +++ b/src/audio_core/interpolate.cpp @@ -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 -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 diff --git a/src/audio_core/interpolate.h b/src/audio_core/interpolate.h index 8dff6111a..bf172a5ad 100644 --- a/src/audio_core/interpolate.h +++ b/src/audio_core/interpolate.h @@ -6,9 +6,10 @@ #include #include -#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 diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index c732926a2..3b2129f8f 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -5,7 +5,7 @@ #pragma once #include -#include "audio_core/audio_core.h" +#include "audio_core/audio_types.h" #include "audio_core/sink.h" namespace AudioCore { diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp index 7852ca8d2..219152ab1 100644 --- a/src/audio_core/sdl2_sink.cpp +++ b/src/audio_core/sdl2_sink.cpp @@ -5,7 +5,7 @@ #include #include #include -#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" diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 6972395af..2e30d94a9 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "audio_core/null_sink.h" #include "audio_core/sink_details.h" diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp index 53cb64655..e21bd932a 100644 --- a/src/audio_core/time_stretch.cpp +++ b/src/audio_core/time_stretch.cpp @@ -6,7 +6,7 @@ #include #include #include -#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" diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index af58df308..46424dbb9 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include -#include "audio_core/audio_core.h" #include "audio_core/sink.h" #include "audio_core/sink_details.h" #include "citra_qt/configuration/configure_audio.h" diff --git a/src/core/core.cpp b/src/core/core.cpp index 56bb78240..49181d181 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -4,7 +4,8 @@ #include #include -#include "audio_core/audio_core.h" +#include "audio_core/dsp_interface.h" +#include "audio_core/hle/hle.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" #ifdef ARCHITECTURE_x86_64 @@ -149,6 +150,8 @@ void System::Reschedule() { System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { LOG_DEBUG(HW_Memory, "initialized OK"); + CoreTiming::Init(); + if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 cpu_core = std::make_unique(USER32MODE); @@ -160,13 +163,15 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { cpu_core = std::make_unique(USER32MODE); } + dsp_core = std::make_unique(); + dsp_core->SetSink(Settings::values.sink_id); + dsp_core->EnableStretching(Settings::values.enable_audio_stretching); + telemetry_session = std::make_unique(); - CoreTiming::Init(); HW::Init(); Kernel::Init(system_mode); Service::Init(); - AudioCore::Init(); GDBStub::Init(); Movie::GetInstance().Init(); @@ -196,15 +201,16 @@ void System::Shutdown() { // Shutdown emulation session Movie::GetInstance().Shutdown(); GDBStub::Shutdown(); - AudioCore::Shutdown(); VideoCore::Shutdown(); Service::Shutdown(); Kernel::Shutdown(); HW::Shutdown(); - CoreTiming::Shutdown(); - cpu_core = nullptr; - app_loader = nullptr; telemetry_session = nullptr; + dsp_core = nullptr; + cpu_core = nullptr; + CoreTiming::Shutdown(); + app_loader = nullptr; + if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info{}; room_member->SendGameInfo(game_info); diff --git a/src/core/core.h b/src/core/core.h index 3bf0b2402..899d94105 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -15,6 +15,10 @@ class EmuWindow; class ARM_Interface; +namespace AudioCore { +class DspInterface; +} + namespace Core { class System { @@ -102,6 +106,14 @@ public: return *cpu_core; } + /** + * Gets a reference to the emulated DSP. + * @returns A reference to the emulated DSP. + */ + AudioCore::DspInterface& DSP() { + return *dsp_core; + } + PerfStats perf_stats; FrameLimiter frame_limiter; @@ -138,6 +150,9 @@ private: ///< ARM11 CPU core std::unique_ptr cpu_core; + ///< DSP core + std::unique_ptr dsp_core; + /// When true, signals that a reschedule should happen bool reschedule_pending{}; @@ -154,6 +169,10 @@ inline ARM_Interface& CPU() { return System::GetInstance().CPU(); } +inline AudioCore::DspInterface& DSP() { + return System::GetInstance().DSP(); +} + inline TelemetrySession& Telemetry() { return System::GetInstance().TelemetrySession(); } diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index 5ff2262a0..68d9387f8 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -5,10 +5,12 @@ #include #include #include -#include "audio_core/hle/pipe.h" +#include "audio_core/audio_types.h" +#include "audio_core/dsp_interface.h" #include "common/assert.h" #include "common/hash.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" @@ -16,7 +18,7 @@ #include "core/hle/service/dsp_dsp.h" #include "core/memory.h" -using DspPipe = DSP::HLE::DspPipe; +using DspPipe = AudioCore::DspPipe; namespace Service { namespace DSP_DSP { @@ -44,7 +46,7 @@ public: return one; case InterruptType::Pipe: { const size_t pipe_index = static_cast(dsp_pipe); - ASSERT(pipe_index < DSP::HLE::NUM_DSP_PIPE); + ASSERT(pipe_index < AudioCore::num_dsp_pipe); return pipe[pipe_index]; } } @@ -73,7 +75,7 @@ private: /// Currently unknown purpose Kernel::SharedPtr one = nullptr; /// Each DSP pipe has an associated interrupt - std::array, DSP::HLE::NUM_DSP_PIPE> pipe = {{}}; + std::array, AudioCore::num_dsp_pipe> pipe = {{}}; }; static InterruptEvents interrupt_events; @@ -216,7 +218,7 @@ static void RegisterInterruptEvents(Service::Interface* self) { u32 pipe_index = cmd_buff[2]; u32 event_handle = cmd_buff[4]; - ASSERT_MSG(type_index < NUM_INTERRUPT_TYPE && pipe_index < DSP::HLE::NUM_DSP_PIPE, + ASSERT_MSG(type_index < NUM_INTERRUPT_TYPE && pipe_index < AudioCore::num_dsp_pipe, "Invalid type or pipe: type = %u, pipe = %u", type_index, pipe_index); InterruptType type = static_cast(cmd_buff[1]); @@ -289,7 +291,7 @@ static void WriteProcessPipe(Service::Interface* self) { u32 size = cmd_buff[2]; u32 buffer = cmd_buff[4]; - DSP::HLE::DspPipe pipe = static_cast(pipe_index); + AudioCore::DspPipe pipe = static_cast(pipe_index); if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) { LOG_ERROR(Service_DSP, "IPC static buffer descriptor failed validation (0x%X). pipe=%u, " @@ -312,12 +314,12 @@ static void WriteProcessPipe(Service::Interface* self) { // The likely reason for this is that games tend to pass in garbage at these bytes // because they read random bytes off the stack. switch (pipe) { - case DSP::HLE::DspPipe::Audio: + case AudioCore::DspPipe::Audio: ASSERT(message.size() >= 4); message[2] = 0; message[3] = 0; break; - case DSP::HLE::DspPipe::Binary: + case AudioCore::DspPipe::Binary: ASSERT(message.size() >= 8); message[4] = 1; message[5] = 0; @@ -326,7 +328,7 @@ static void WriteProcessPipe(Service::Interface* self) { break; } - DSP::HLE::PipeWrite(pipe, message); + Core::DSP().PipeWrite(pipe, message); cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error @@ -338,7 +340,7 @@ static void WriteProcessPipe(Service::Interface* self) { * 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. + * Pipes are used for initialisation. See also DspInterface::PipeRead. * Inputs: * 1 : Pipe Number * 2 : Unknown @@ -356,7 +358,7 @@ static void ReadPipeIfPossible(Service::Interface* self) { u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size VAddr addr = cmd_buff[0x41]; - DSP::HLE::DspPipe pipe = static_cast(pipe_index); + AudioCore::DspPipe pipe = static_cast(pipe_index); ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, @@ -364,8 +366,8 @@ static void ReadPipeIfPossible(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x10, 1, 2); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - if (DSP::HLE::GetPipeReadableSize(pipe) >= size) { - std::vector response = DSP::HLE::PipeRead(pipe, size); + if (Core::DSP().GetPipeReadableSize(pipe) >= size) { + std::vector response = Core::DSP().PipeRead(pipe, size); Memory::WriteBlock(addr, response.data(), response.size()); @@ -400,14 +402,14 @@ static void ReadPipe(Service::Interface* self) { u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size VAddr addr = cmd_buff[0x41]; - DSP::HLE::DspPipe pipe = static_cast(pipe_index); + AudioCore::DspPipe pipe = static_cast(pipe_index); ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr); - if (DSP::HLE::GetPipeReadableSize(pipe) >= size) { - std::vector response = DSP::HLE::PipeRead(pipe, size); + if (Core::DSP().GetPipeReadableSize(pipe) >= size) { + std::vector response = Core::DSP().PipeRead(pipe, size); Memory::WriteBlock(addr, response.data(), response.size()); @@ -441,11 +443,11 @@ static void GetPipeReadableSize(Service::Interface* self) { u32 pipe_index = cmd_buff[1]; u32 unknown = cmd_buff[2]; - DSP::HLE::DspPipe pipe = static_cast(pipe_index); + AudioCore::DspPipe pipe = static_cast(pipe_index); cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = static_cast(DSP::HLE::GetPipeReadableSize(pipe)); + cmd_buff[2] = static_cast(Core::DSP().GetPipeReadableSize(pipe)); LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe_index, unknown, cmd_buff[2]); @@ -511,12 +513,12 @@ static void RecvData(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x1, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - switch (DSP::HLE::GetDspState()) { - case DSP::HLE::DspState::On: + switch (Core::DSP().GetDspState()) { + case AudioCore::DspState::On: cmd_buff[2] = 0; break; - case DSP::HLE::DspState::Off: - case DSP::HLE::DspState::Sleeping: + case AudioCore::DspState::Off: + case AudioCore::DspState::Sleeping: cmd_buff[2] = 1; break; default: diff --git a/src/core/hle/service/dsp_dsp.h b/src/core/hle/service/dsp_dsp.h index 691d6f716..c9733f926 100644 --- a/src/core/hle/service/dsp_dsp.h +++ b/src/core/hle/service/dsp_dsp.h @@ -7,11 +7,9 @@ #include #include "core/hle/service/service.h" -namespace DSP { -namespace HLE { +namespace AudioCore { enum class DspPipe; } -} namespace Service { namespace DSP_DSP { @@ -30,7 +28,7 @@ public: * Signal a specific DSP related interrupt of type == InterruptType::Pipe, pipe == pipe. * @param pipe The DSP pipe for which to signal an interrupt for. */ -void SignalPipeInterrupt(DSP::HLE::DspPipe pipe); +void SignalPipeInterrupt(AudioCore::DspPipe pipe); } // namespace DSP_DSP } // namespace Service diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 81e60a66f..d420fe35c 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -4,7 +4,7 @@ #include #include -#include "audio_core/audio_core.h" +#include "audio_core/dsp_interface.h" #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" @@ -311,7 +311,7 @@ u8* GetPhysicalPointer(PAddr address) { target_pointer = vram.data() + offset_into_region; break; case DSP_RAM_PADDR: - target_pointer = AudioCore::GetDspMemory().data() + offset_into_region; + target_pointer = Core::DSP().GetDspMemory().data() + offset_into_region; break; case FCRAM_PADDR: for (const auto& region : Kernel::memory_regions) { diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 79778f578..f457c3d9c 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -2,7 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "audio_core/audio_core.h" +#include "audio_core/dsp_interface.h" +#include "core/core.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir.h" @@ -28,8 +29,10 @@ void Apply() { VideoCore::g_emu_window->UpdateCurrentFramebufferLayout(layout.width, layout.height); } - AudioCore::SelectSink(values.sink_id); - AudioCore::EnableStretching(values.enable_audio_stretching); + if (Core::System::GetInstance().IsPoweredOn()) { + Core::DSP().SetSink(values.sink_id); + Core::DSP().EnableStretching(values.enable_audio_stretching); + } Service::HID::ReloadInputDevices(); Service::IR::ReloadInputDevices();