Merge pull request #3322 from MerryMage/DSP
audio_core: Remove global state
This commit is contained in:
commit
33fe6c30e0
35 changed files with 714 additions and 657 deletions
|
@ -1,17 +1,17 @@
|
||||||
add_library(audio_core STATIC
|
add_library(audio_core STATIC
|
||||||
audio_core.cpp
|
audio_types.h
|
||||||
audio_core.h
|
|
||||||
codec.cpp
|
codec.cpp
|
||||||
codec.h
|
codec.h
|
||||||
|
dsp_interface.cpp
|
||||||
|
dsp_interface.h
|
||||||
hle/common.h
|
hle/common.h
|
||||||
hle/dsp.cpp
|
|
||||||
hle/dsp.h
|
|
||||||
hle/filter.cpp
|
hle/filter.cpp
|
||||||
hle/filter.h
|
hle/filter.h
|
||||||
|
hle/hle.cpp
|
||||||
|
hle/hle.h
|
||||||
hle/mixers.cpp
|
hle/mixers.cpp
|
||||||
hle/mixers.h
|
hle/mixers.h
|
||||||
hle/pipe.cpp
|
hle/shared_memory.h
|
||||||
hle/pipe.h
|
|
||||||
hle/source.cpp
|
hle/source.cpp
|
||||||
hle/source.h
|
hle/source.h
|
||||||
interpolate.cpp
|
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 <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/codec.h"
|
#include "audio_core/codec.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
|
|
||||||
|
namespace AudioCore {
|
||||||
namespace Codec {
|
namespace Codec {
|
||||||
|
|
||||||
StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count,
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
};
|
} // namespace Codec
|
||||||
|
} // namespace AudioCore
|
||||||
|
|
|
@ -5,14 +5,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <deque>
|
#include "audio_core/audio_types.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace AudioCore {
|
||||||
namespace Codec {
|
namespace Codec {
|
||||||
|
|
||||||
/// A variable length buffer of signed PCM16 stereo samples.
|
|
||||||
using StereoBuffer16 = std::deque<std::array<s16, 2>>;
|
|
||||||
|
|
||||||
/// See: Codec::DecodeADPCM
|
/// See: Codec::DecodeADPCM
|
||||||
struct ADPCMState {
|
struct ADPCMState {
|
||||||
// Two historical samples from previous processed buffer,
|
// 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,
|
StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data,
|
||||||
const size_t sample_count);
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <cstddef>
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
constexpr int num_sources = 24;
|
constexpr size_t 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>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This performs the filter operation defined by FilterT::ProcessSample on the frame in-place.
|
* 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 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 <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include "audio_core/hle/common.h"
|
#include "audio_core/hle/common.h"
|
||||||
#include "audio_core/hle/dsp.h"
|
|
||||||
#include "audio_core/hle/filter.h"
|
#include "audio_core/hle/filter.h"
|
||||||
|
#include "audio_core/hle/shared_memory.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
void SourceFilters::Reset() {
|
void SourceFilters::Reset() {
|
||||||
|
@ -114,4 +114,4 @@ std::array<s16, 2> SourceFilters::BiquadFilter::ProcessSample(const std::array<s
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HLE
|
} // namespace HLE
|
||||||
} // namespace DSP
|
} // namespace AudioCore
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include "audio_core/hle/common.h"
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/hle/dsp.h"
|
#include "audio_core/hle/shared_memory.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
/// Preprocessing filters. There is an independent set of filters for each Source.
|
/// Preprocessing filters. There is an independent set of filters for each Source.
|
||||||
|
@ -114,4 +114,4 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace HLE
|
} // namespace HLE
|
||||||
} // namespace DSP
|
} // namespace AudioCore
|
||||||
|
|
343
src/audio_core/hle/hle.cpp
Normal file
343
src/audio_core/hle/hle.cpp
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
// 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_) {
|
||||||
|
dsp_memory.raw_memory.fill(0);
|
||||||
|
|
||||||
|
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 <cstddef>
|
||||||
|
|
||||||
#include "audio_core/hle/common.h"
|
|
||||||
#include "audio_core/hle/dsp.h"
|
|
||||||
#include "audio_core/hle/mixers.h"
|
#include "audio_core/hle/mixers.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
void Mixers::Reset() {
|
void Mixers::Reset() {
|
||||||
|
@ -207,4 +205,4 @@ DspStatus Mixers::GetCurrentStatus() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HLE
|
} // namespace HLE
|
||||||
} // namespace DSP
|
} // namespace AudioCore
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include "audio_core/hle/common.h"
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/hle/dsp.h"
|
#include "audio_core/hle/shared_memory.h"
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
class Mixers final {
|
class Mixers final {
|
||||||
|
@ -58,4 +58,4 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace HLE
|
} // 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 <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/hle/common.h"
|
#include "audio_core/hle/common.h"
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
|
@ -15,10 +16,6 @@
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
|
|
||||||
namespace AudioCore {
|
namespace AudioCore {
|
||||||
class Sink;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace DSP {
|
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
// The application-accessible region of DSP memory consists of two parts. Both are marked as IO and
|
// 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
|
// 0 0xBFFF Frame Counter Application
|
||||||
//
|
//
|
||||||
// #: This refers to the order in which they appear in the DspPipe::Audio DSP pipe.
|
// #: 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
|
// 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
|
// are not fixed in stone. The addresses above are only an examplar; they're what this
|
||||||
|
@ -513,7 +510,7 @@ struct SharedMemory {
|
||||||
ASSERT_DSP_STRUCT(SharedMemory, 0x8000);
|
ASSERT_DSP_STRUCT(SharedMemory, 0x8000);
|
||||||
|
|
||||||
union DspMemory {
|
union DspMemory {
|
||||||
std::array<u8, 0x80000> raw_memory;
|
std::array<u8, 0x80000> raw_memory{};
|
||||||
struct {
|
struct {
|
||||||
u8 unused_0[0x50000];
|
u8 unused_0[0x50000];
|
||||||
SharedMemory region_0;
|
SharedMemory region_0;
|
||||||
|
@ -527,69 +524,40 @@ static_assert(offsetof(DspMemory, region_0) == region0_offset,
|
||||||
static_assert(offsetof(DspMemory, region_1) == region1_offset,
|
static_assert(offsetof(DspMemory, region_1) == region1_offset,
|
||||||
"DSP region 1 is at the wrong 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.
|
// Structures must have an offset that is a multiple of two.
|
||||||
static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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 INSERT_PADDING_DSPWORDS
|
||||||
#undef ASSERT_DSP_STRUCT
|
#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 HLE
|
||||||
} // namespace DSP
|
} // namespace AudioCore
|
|
@ -12,7 +12,7 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config,
|
SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config,
|
||||||
|
@ -345,4 +345,4 @@ SourceStatus::Status Source::GetCurrentStatus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HLE
|
} // namespace HLE
|
||||||
} // namespace DSP
|
} // namespace AudioCore
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/codec.h"
|
#include "audio_core/codec.h"
|
||||||
#include "audio_core/hle/common.h"
|
#include "audio_core/hle/common.h"
|
||||||
#include "audio_core/hle/dsp.h"
|
|
||||||
#include "audio_core/hle/filter.h"
|
#include "audio_core/hle/filter.h"
|
||||||
#include "audio_core/interpolate.h"
|
#include "audio_core/interpolate.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
namespace HLE {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,4 +146,4 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace HLE
|
} // namespace HLE
|
||||||
} // namespace DSP
|
} // namespace AudioCore
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
|
|
||||||
|
namespace AudioCore {
|
||||||
namespace AudioInterp {
|
namespace AudioInterp {
|
||||||
|
|
||||||
// Calculations are done in fixed point with 24 fractional bits.
|
// 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.
|
/// 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.
|
/// Three adjacent samples are passed to fn each step.
|
||||||
template <typename Function>
|
template <typename Function>
|
||||||
static void StepOverSamples(State& state, StereoBuffer16& input, float rate,
|
static void StepOverSamples(State& state, StereoBuffer16& input, float rate, StereoFrame16& output,
|
||||||
DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) {
|
size_t& outputi, Function fn) {
|
||||||
ASSERT(rate > 0);
|
ASSERT(rate > 0);
|
||||||
|
|
||||||
if (input.empty())
|
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));
|
input.erase(input.begin(), std::next(input.begin(), inputi + 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
|
void None(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, size_t& outputi) {
|
||||||
size_t& outputi) {
|
|
||||||
StepOverSamples(
|
StepOverSamples(
|
||||||
state, input, rate, output, outputi,
|
state, input, rate, output, outputi,
|
||||||
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
|
[](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) {
|
size_t& outputi) {
|
||||||
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
|
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
|
||||||
StepOverSamples(state, input, rate, output, outputi,
|
StepOverSamples(state, input, rate, output, outputi,
|
||||||
|
@ -74,3 +74,4 @@ void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFra
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AudioInterp
|
} // namespace AudioInterp
|
||||||
|
} // namespace AudioCore
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include "audio_core/hle/common.h"
|
#include "audio_core/audio_types.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace AudioCore {
|
||||||
namespace AudioInterp {
|
namespace AudioInterp {
|
||||||
|
|
||||||
/// A variable length buffer of signed PCM16 stereo samples.
|
/// A variable length buffer of signed PCM16 stereo samples.
|
||||||
|
@ -31,8 +32,7 @@ struct State {
|
||||||
* @param output The resampled audio buffer.
|
* @param output The resampled audio buffer.
|
||||||
* @param outputi The index of output to start writing to.
|
* @param outputi The index of output to start writing to.
|
||||||
*/
|
*/
|
||||||
void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
|
void None(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, size_t& outputi);
|
||||||
size_t& outputi);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
|
* 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 output The resampled audio buffer.
|
||||||
* @param outputi The index of output to start writing to.
|
* @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);
|
size_t& outputi);
|
||||||
|
|
||||||
} // namespace AudioInterp
|
} // namespace AudioInterp
|
||||||
|
} // namespace AudioCore
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/sink.h"
|
#include "audio_core/sink.h"
|
||||||
|
|
||||||
namespace AudioCore {
|
namespace AudioCore {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/sdl2_sink.h"
|
#include "audio_core/sdl2_sink.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "audio_core/null_sink.h"
|
#include "audio_core/null_sink.h"
|
||||||
#include "audio_core/sink_details.h"
|
#include "audio_core/sink_details.h"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <SoundTouch.h>
|
#include <SoundTouch.h>
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_types.h"
|
||||||
#include "audio_core/time_stretch.h"
|
#include "audio_core/time_stretch.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "audio_core/audio_core.h"
|
|
||||||
#include "audio_core/sink.h"
|
#include "audio_core/sink.h"
|
||||||
#include "audio_core/sink_details.h"
|
#include "audio_core/sink_details.h"
|
||||||
#include "citra_qt/configuration/configure_audio.h"
|
#include "citra_qt/configuration/configure_audio.h"
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/dsp_interface.h"
|
||||||
|
#include "audio_core/hle/hle.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
|
@ -149,6 +150,8 @@ void System::Reschedule() {
|
||||||
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||||
|
|
||||||
|
CoreTiming::Init();
|
||||||
|
|
||||||
if (Settings::values.use_cpu_jit) {
|
if (Settings::values.use_cpu_jit) {
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE);
|
cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE);
|
||||||
|
@ -160,13 +163,15 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||||
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
|
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dsp_core = std::make_unique<AudioCore::DspHle>();
|
||||||
|
dsp_core->SetSink(Settings::values.sink_id);
|
||||||
|
dsp_core->EnableStretching(Settings::values.enable_audio_stretching);
|
||||||
|
|
||||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||||
|
|
||||||
CoreTiming::Init();
|
|
||||||
HW::Init();
|
HW::Init();
|
||||||
Kernel::Init(system_mode);
|
Kernel::Init(system_mode);
|
||||||
Service::Init();
|
Service::Init();
|
||||||
AudioCore::Init();
|
|
||||||
GDBStub::Init();
|
GDBStub::Init();
|
||||||
Movie::GetInstance().Init();
|
Movie::GetInstance().Init();
|
||||||
|
|
||||||
|
@ -196,15 +201,16 @@ void System::Shutdown() {
|
||||||
// Shutdown emulation session
|
// Shutdown emulation session
|
||||||
Movie::GetInstance().Shutdown();
|
Movie::GetInstance().Shutdown();
|
||||||
GDBStub::Shutdown();
|
GDBStub::Shutdown();
|
||||||
AudioCore::Shutdown();
|
|
||||||
VideoCore::Shutdown();
|
VideoCore::Shutdown();
|
||||||
Service::Shutdown();
|
Service::Shutdown();
|
||||||
Kernel::Shutdown();
|
Kernel::Shutdown();
|
||||||
HW::Shutdown();
|
HW::Shutdown();
|
||||||
CoreTiming::Shutdown();
|
|
||||||
cpu_core = nullptr;
|
|
||||||
app_loader = nullptr;
|
|
||||||
telemetry_session = nullptr;
|
telemetry_session = nullptr;
|
||||||
|
dsp_core = nullptr;
|
||||||
|
cpu_core = nullptr;
|
||||||
|
CoreTiming::Shutdown();
|
||||||
|
app_loader = nullptr;
|
||||||
|
|
||||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
Network::GameInfo game_info{};
|
Network::GameInfo game_info{};
|
||||||
room_member->SendGameInfo(game_info);
|
room_member->SendGameInfo(game_info);
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
class EmuWindow;
|
class EmuWindow;
|
||||||
class ARM_Interface;
|
class ARM_Interface;
|
||||||
|
|
||||||
|
namespace AudioCore {
|
||||||
|
class DspInterface;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
class System {
|
class System {
|
||||||
|
@ -102,6 +106,14 @@ public:
|
||||||
return *cpu_core;
|
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;
|
PerfStats perf_stats;
|
||||||
FrameLimiter frame_limiter;
|
FrameLimiter frame_limiter;
|
||||||
|
|
||||||
|
@ -138,6 +150,9 @@ private:
|
||||||
///< ARM11 CPU core
|
///< ARM11 CPU core
|
||||||
std::unique_ptr<ARM_Interface> cpu_core;
|
std::unique_ptr<ARM_Interface> cpu_core;
|
||||||
|
|
||||||
|
///< DSP core
|
||||||
|
std::unique_ptr<AudioCore::DspInterface> dsp_core;
|
||||||
|
|
||||||
/// When true, signals that a reschedule should happen
|
/// When true, signals that a reschedule should happen
|
||||||
bool reschedule_pending{};
|
bool reschedule_pending{};
|
||||||
|
|
||||||
|
@ -154,6 +169,10 @@ inline ARM_Interface& CPU() {
|
||||||
return System::GetInstance().CPU();
|
return System::GetInstance().CPU();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline AudioCore::DspInterface& DSP() {
|
||||||
|
return System::GetInstance().DSP();
|
||||||
|
}
|
||||||
|
|
||||||
inline TelemetrySession& Telemetry() {
|
inline TelemetrySession& Telemetry() {
|
||||||
return System::GetInstance().TelemetrySession();
|
return System::GetInstance().TelemetrySession();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include "audio_core/hle/pipe.h"
|
#include "audio_core/audio_types.h"
|
||||||
|
#include "audio_core/dsp_interface.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/hash.h"
|
#include "common/hash.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/hle/ipc.h"
|
#include "core/hle/ipc.h"
|
||||||
#include "core/hle/kernel/event.h"
|
#include "core/hle/kernel/event.h"
|
||||||
#include "core/hle/kernel/handle_table.h"
|
#include "core/hle/kernel/handle_table.h"
|
||||||
|
@ -16,7 +18,7 @@
|
||||||
#include "core/hle/service/dsp_dsp.h"
|
#include "core/hle/service/dsp_dsp.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
using DspPipe = DSP::HLE::DspPipe;
|
using DspPipe = AudioCore::DspPipe;
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace DSP_DSP {
|
namespace DSP_DSP {
|
||||||
|
@ -44,7 +46,7 @@ public:
|
||||||
return one;
|
return one;
|
||||||
case InterruptType::Pipe: {
|
case InterruptType::Pipe: {
|
||||||
const size_t pipe_index = static_cast<size_t>(dsp_pipe);
|
const size_t pipe_index = static_cast<size_t>(dsp_pipe);
|
||||||
ASSERT(pipe_index < DSP::HLE::NUM_DSP_PIPE);
|
ASSERT(pipe_index < AudioCore::num_dsp_pipe);
|
||||||
return pipe[pipe_index];
|
return pipe[pipe_index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,7 @@ private:
|
||||||
/// Currently unknown purpose
|
/// Currently unknown purpose
|
||||||
Kernel::SharedPtr<Kernel::Event> one = nullptr;
|
Kernel::SharedPtr<Kernel::Event> one = nullptr;
|
||||||
/// Each DSP pipe has an associated interrupt
|
/// Each DSP pipe has an associated interrupt
|
||||||
std::array<Kernel::SharedPtr<Kernel::Event>, DSP::HLE::NUM_DSP_PIPE> pipe = {{}};
|
std::array<Kernel::SharedPtr<Kernel::Event>, AudioCore::num_dsp_pipe> pipe = {{}};
|
||||||
};
|
};
|
||||||
|
|
||||||
static InterruptEvents interrupt_events;
|
static InterruptEvents interrupt_events;
|
||||||
|
@ -216,7 +218,7 @@ static void RegisterInterruptEvents(Service::Interface* self) {
|
||||||
u32 pipe_index = cmd_buff[2];
|
u32 pipe_index = cmd_buff[2];
|
||||||
u32 event_handle = cmd_buff[4];
|
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);
|
"Invalid type or pipe: type = %u, pipe = %u", type_index, pipe_index);
|
||||||
|
|
||||||
InterruptType type = static_cast<InterruptType>(cmd_buff[1]);
|
InterruptType type = static_cast<InterruptType>(cmd_buff[1]);
|
||||||
|
@ -289,7 +291,7 @@ static void WriteProcessPipe(Service::Interface* self) {
|
||||||
u32 size = cmd_buff[2];
|
u32 size = cmd_buff[2];
|
||||||
u32 buffer = cmd_buff[4];
|
u32 buffer = cmd_buff[4];
|
||||||
|
|
||||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
AudioCore::DspPipe pipe = static_cast<AudioCore::DspPipe>(pipe_index);
|
||||||
|
|
||||||
if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) {
|
if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) {
|
||||||
LOG_ERROR(Service_DSP, "IPC static buffer descriptor failed validation (0x%X). pipe=%u, "
|
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
|
// The likely reason for this is that games tend to pass in garbage at these bytes
|
||||||
// because they read random bytes off the stack.
|
// because they read random bytes off the stack.
|
||||||
switch (pipe) {
|
switch (pipe) {
|
||||||
case DSP::HLE::DspPipe::Audio:
|
case AudioCore::DspPipe::Audio:
|
||||||
ASSERT(message.size() >= 4);
|
ASSERT(message.size() >= 4);
|
||||||
message[2] = 0;
|
message[2] = 0;
|
||||||
message[3] = 0;
|
message[3] = 0;
|
||||||
break;
|
break;
|
||||||
case DSP::HLE::DspPipe::Binary:
|
case AudioCore::DspPipe::Binary:
|
||||||
ASSERT(message.size() >= 8);
|
ASSERT(message.size() >= 8);
|
||||||
message[4] = 1;
|
message[4] = 1;
|
||||||
message[5] = 0;
|
message[5] = 0;
|
||||||
|
@ -326,7 +328,7 @@ static void WriteProcessPipe(Service::Interface* self) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
DSP::HLE::PipeWrite(pipe, message);
|
Core::DSP().PipeWrite(pipe, message);
|
||||||
|
|
||||||
cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0);
|
cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0);
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||||
|
@ -338,7 +340,7 @@ static void WriteProcessPipe(Service::Interface* self) {
|
||||||
* DSP_DSP::ReadPipeIfPossible service function
|
* DSP_DSP::ReadPipeIfPossible service function
|
||||||
* A pipe is a means of communication between the ARM11 and DSP that occurs on
|
* 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.
|
* 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:
|
* Inputs:
|
||||||
* 1 : Pipe Number
|
* 1 : Pipe Number
|
||||||
* 2 : Unknown
|
* 2 : Unknown
|
||||||
|
@ -356,7 +358,7 @@ static void ReadPipeIfPossible(Service::Interface* self) {
|
||||||
u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size
|
u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size
|
||||||
VAddr addr = cmd_buff[0x41];
|
VAddr addr = cmd_buff[0x41];
|
||||||
|
|
||||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
AudioCore::DspPipe pipe = static_cast<AudioCore::DspPipe>(pipe_index);
|
||||||
|
|
||||||
ASSERT_MSG(Memory::IsValidVirtualAddress(addr),
|
ASSERT_MSG(Memory::IsValidVirtualAddress(addr),
|
||||||
"Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index,
|
"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[0] = IPC::MakeHeader(0x10, 1, 2);
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||||
if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
|
if (Core::DSP().GetPipeReadableSize(pipe) >= size) {
|
||||||
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
|
std::vector<u8> response = Core::DSP().PipeRead(pipe, size);
|
||||||
|
|
||||||
Memory::WriteBlock(addr, response.data(), response.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
|
u32 size = cmd_buff[3] & 0xFFFF; // Lower 16 bits are size
|
||||||
VAddr addr = cmd_buff[0x41];
|
VAddr addr = cmd_buff[0x41];
|
||||||
|
|
||||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
AudioCore::DspPipe pipe = static_cast<AudioCore::DspPipe>(pipe_index);
|
||||||
|
|
||||||
ASSERT_MSG(Memory::IsValidVirtualAddress(addr),
|
ASSERT_MSG(Memory::IsValidVirtualAddress(addr),
|
||||||
"Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index,
|
"Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index,
|
||||||
unknown, size, addr);
|
unknown, size, addr);
|
||||||
|
|
||||||
if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
|
if (Core::DSP().GetPipeReadableSize(pipe) >= size) {
|
||||||
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
|
std::vector<u8> response = Core::DSP().PipeRead(pipe, size);
|
||||||
|
|
||||||
Memory::WriteBlock(addr, response.data(), response.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 pipe_index = cmd_buff[1];
|
||||||
u32 unknown = cmd_buff[2];
|
u32 unknown = cmd_buff[2];
|
||||||
|
|
||||||
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
|
AudioCore::DspPipe pipe = static_cast<AudioCore::DspPipe>(pipe_index);
|
||||||
|
|
||||||
cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
|
cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
||||||
cmd_buff[2] = static_cast<u32>(DSP::HLE::GetPipeReadableSize(pipe));
|
cmd_buff[2] = static_cast<u32>(Core::DSP().GetPipeReadableSize(pipe));
|
||||||
|
|
||||||
LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe_index,
|
LOG_DEBUG(Service_DSP, "pipe=%u, unknown=0x%08X, return cmd_buff[2]=0x%08X", pipe_index,
|
||||||
unknown, cmd_buff[2]);
|
unknown, cmd_buff[2]);
|
||||||
|
@ -511,12 +513,12 @@ static void RecvData(Service::Interface* self) {
|
||||||
|
|
||||||
cmd_buff[0] = IPC::MakeHeader(0x1, 2, 0);
|
cmd_buff[0] = IPC::MakeHeader(0x1, 2, 0);
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||||
switch (DSP::HLE::GetDspState()) {
|
switch (Core::DSP().GetDspState()) {
|
||||||
case DSP::HLE::DspState::On:
|
case AudioCore::DspState::On:
|
||||||
cmd_buff[2] = 0;
|
cmd_buff[2] = 0;
|
||||||
break;
|
break;
|
||||||
case DSP::HLE::DspState::Off:
|
case AudioCore::DspState::Off:
|
||||||
case DSP::HLE::DspState::Sleeping:
|
case AudioCore::DspState::Sleeping:
|
||||||
cmd_buff[2] = 1;
|
cmd_buff[2] = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -7,11 +7,9 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace DSP {
|
namespace AudioCore {
|
||||||
namespace HLE {
|
|
||||||
enum class DspPipe;
|
enum class DspPipe;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace DSP_DSP {
|
namespace DSP_DSP {
|
||||||
|
@ -30,7 +28,7 @@ public:
|
||||||
* Signal a specific DSP related interrupt of type == InterruptType::Pipe, pipe == pipe.
|
* Signal a specific DSP related interrupt of type == InterruptType::Pipe, pipe == pipe.
|
||||||
* @param pipe The DSP pipe for which to signal an interrupt for.
|
* @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 DSP_DSP
|
||||||
} // namespace Service
|
} // namespace Service
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/dsp_interface.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
@ -311,7 +311,7 @@ u8* GetPhysicalPointer(PAddr address) {
|
||||||
target_pointer = vram.data() + offset_into_region;
|
target_pointer = vram.data() + offset_into_region;
|
||||||
break;
|
break;
|
||||||
case DSP_RAM_PADDR:
|
case DSP_RAM_PADDR:
|
||||||
target_pointer = AudioCore::GetDspMemory().data() + offset_into_region;
|
target_pointer = Core::DSP().GetDspMemory().data() + offset_into_region;
|
||||||
break;
|
break;
|
||||||
case FCRAM_PADDR:
|
case FCRAM_PADDR:
|
||||||
for (const auto& region : Kernel::memory_regions) {
|
for (const auto& region : Kernel::memory_regions) {
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/dsp_interface.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
#include "core/hle/service/hid/hid.h"
|
#include "core/hle/service/hid/hid.h"
|
||||||
#include "core/hle/service/ir/ir.h"
|
#include "core/hle/service/ir/ir.h"
|
||||||
|
@ -28,8 +29,10 @@ void Apply() {
|
||||||
VideoCore::g_emu_window->UpdateCurrentFramebufferLayout(layout.width, layout.height);
|
VideoCore::g_emu_window->UpdateCurrentFramebufferLayout(layout.width, layout.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioCore::SelectSink(values.sink_id);
|
if (Core::System::GetInstance().IsPoweredOn()) {
|
||||||
AudioCore::EnableStretching(values.enable_audio_stretching);
|
Core::DSP().SetSink(values.sink_id);
|
||||||
|
Core::DSP().EnableStretching(values.enable_audio_stretching);
|
||||||
|
}
|
||||||
|
|
||||||
Service::HID::ReloadInputDevices();
|
Service::HID::ReloadInputDevices();
|
||||||
Service::IR::ReloadInputDevices();
|
Service::IR::ReloadInputDevices();
|
||||||
|
|
|
@ -39,12 +39,6 @@ TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") {
|
||||||
process->vm_manager, {Memory::IO_AREA_VADDR, Memory::IO_AREA_SIZE, false, false});
|
process->vm_manager, {Memory::IO_AREA_VADDR, Memory::IO_AREA_SIZE, false, false});
|
||||||
CHECK_FALSE(Memory::IsValidVirtualAddress(*process, Memory::IO_AREA_VADDR) == true);
|
CHECK_FALSE(Memory::IsValidVirtualAddress(*process, Memory::IO_AREA_VADDR) == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("DSP") {
|
|
||||||
Kernel::HandleSpecialMapping(
|
|
||||||
process->vm_manager, {Memory::DSP_RAM_VADDR, Memory::DSP_RAM_SIZE, false, false});
|
|
||||||
CHECK(Memory::IsValidVirtualAddress(*process, Memory::DSP_RAM_VADDR) == true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Unmapping a VAddr should make it invalid") {
|
SECTION("Unmapping a VAddr should make it invalid") {
|
||||||
|
|
Loading…
Reference in a new issue