commit
33fe6c30e0
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue