Audio Core (#2)

* DSP: Implement Pipe 2

Pipe 2 is a DSP pipe that is used to initialize both the DSP hardware (the
application signals to the DSP to initialize) and the application (the DSP
provides the memory location of structures in the shared memory region).

* AudioCore: Implement codecs (DecodeADPCM, DecodePCM8, DecodePCM16)

* DSP Pipes: Implement as FIFO

* AudioCore: File structure

* AudioCore: More structure

* AudioCore: Buffer management

* DSP/Source: Reorganise Source's AdvanceFrame.

* Audio Output

* lolidk

* huh?

* interp

* More interp stuff

* oops

* Zero State

* Don't mix Source frame if it's not enabled

* DSP: Forgot to zero a buffer, adjusted thread synchronisation, adjusted format spec for buffers

* asdf

* Get it to compile and tweak stretching a bit.

* revert stretch test

* deleted accidental partial catch submodule commit

* new audio stretching algorithm

* update .gitmodule

* fix OS X build

* remove getopt from rubberband

* #include <stddef> to audio_core.h

* typo

* -framework Accelerate

* OptionTransientsSmooth -> OptionTransientsCrisp

* tweak stretch tempo smoothing coefficient. also switch back to smooth.

* tweak mroe

* remove printf

* sola

* #include <cmath>

* VERY QUICK MERGE TO GET IT WORKING DOESN'T ACTIVATE AUDIO FILTERS

* Reminder to self

* fix comparison

* common/thread: Correct code style

* Thread: Make Barrier reusable

* fix threading synchonisation code

* add profiling code

* print error to console when audio clips

* fix metallic sound

* reduce logspam
This commit is contained in:
Dragios
2016-04-16 01:10:29 +08:00
parent 7805f66784
commit a8d0c51c69
65 changed files with 8679 additions and 51 deletions

View File

@@ -2,8 +2,14 @@ set(SRCS
audio_core.cpp
codec.cpp
hle/dsp.cpp
hle/effects.cpp
hle/filter.cpp
hle/final.cpp
hle/pipe.cpp
hle/source.cpp
interpolate.cpp
null_sink.cpp
time_stretch.cpp
)
set(HEADERS
@@ -11,11 +17,31 @@ set(HEADERS
codec.h
hle/common.h
hle/dsp.h
hle/effects.h
hle/filter.h
hle/final.h
hle/pipe.h
hle/source.h
interpolate.h
null_sink.h
sink.h
time_stretch.h
)
if(SDL2_FOUND)
set(SRCS ${SRCS} sdl2_sink.cpp)
set(HEADERS ${HEADERS} sdl2_sink.h)
include_directories(${SDL2_INCLUDE_DIR})
endif()
include_directories(../../externals/soundtouch)
include_directories(../../externals/rubberband/rubberband/rubberband)
create_directory_groups(${SRCS} ${HEADERS})
add_library(audio_core STATIC ${SRCS} ${HEADERS})
add_library(audio_core STATIC ${SRCS} ${HEADERS})
target_link_libraries(audio_core SoundTouch rubberband)
if(SDL2_FOUND)
target_link_libraries(audio_core ${SDL2_LIBRARY})
endif()

View File

@@ -4,6 +4,7 @@
#include "audio_core/audio_core.h"
#include "audio_core/hle/dsp.h"
#include "audio_core/sdl2_sink.h"
#include "core/core_timing.h"
#include "core/hle/kernel/vm_manager.h"

View File

@@ -4,15 +4,17 @@
#pragma once
#include <cstddef>
namespace Kernel {
class VMManager;
}
namespace AudioCore {
constexpr int num_sources = 24;
constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate
constexpr int native_sample_rate = 32728; ///< 32kHz
constexpr size_t num_sources = 24;
constexpr size_t samples_per_frame = 160; ///< Samples per audio frame at native sample rate
constexpr unsigned native_sample_rate = 32728; ///< 32kHz
/// Initialise Audio Core
void Init();

View File

@@ -77,9 +77,7 @@ StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, cons
}
static s16 SignExtendS8(u8 x) {
// The data is actually signed PCM8.
// We sign extend this to signed PCM16.
return static_cast<s16>(static_cast<s8>(x));
return s16(u16(x) << 8);
}
StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count) {

View File

View File

@@ -2,8 +2,23 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#include <thread>
#include "audio_core/audio_core.h"
#include "audio_core/hle/effects.h"
#include "audio_core/hle/dsp.h"
#include "audio_core/hle/final.h"
#include "audio_core/hle/pipe.h"
#include "audio_core/hle/source.h"
#include "audio_core/sink.h"
#include "audio_core/time_stretch.h"
#include "common/microprofile.h"
#include "common/profiler.h"
#include "common/thread.h"
#include "core/hle/service/dsp_dsp.h"
namespace DSP {
namespace HLE {
@@ -11,14 +26,104 @@ namespace HLE {
SharedMemory g_region0;
SharedMemory g_region1;
static void ThreadFunc();
static Common::Barrier ThreadFunc_barrier(2);
static std::atomic<bool> ThreadFunc_quit = true;
void Init() {
DSP::HLE::ResetPipes();
ResetPipes();
SourceInit();
EffectsInit();
FinalInit();
TimeStretch::Init();
ThreadFunc_quit = false;
std::thread thread(ThreadFunc);
thread.detach();
// Lioncash won't like this. Don't do it.
std::memset(&g_region0, 0, sizeof(SharedMemory));
std::memset(&g_region1, 0, sizeof(SharedMemory));
}
void Shutdown() {
TimeStretch::Shutdown();
if (!ThreadFunc_quit) {
ThreadFunc_quit = true;
ThreadFunc_barrier.Sync();
}
}
static bool next_region_is_ready = true;
unsigned num_frames = 500;
double time_for_a_frame = 0.005;
static Common::Profiling::TimingCategory profile_tick("DSP::Tick");
static Common::Profiling::TimingCategory profile_work("DSP::Work");
MICROPROFILE_DEFINE(DSP_Tick, "DSP", "Tick", MP_RGB(204, 204, 0));
MICROPROFILE_DEFINE(DSP_Work, "DSP", "Work", MP_RGB(153, 153, 0));
static void DoWork() {
Common::Profiling::ScopeTimer timer_work(profile_work);
MICROPROFILE_SCOPE(DSP_Work);
auto& region = CurrentRegion();
for (int i = 0; i < AudioCore::num_sources; i++) {
auto& config = region.source_configurations.config[i];
auto& coeffs = region.adpcm_coefficients.coeff[i];
auto& status = region.source_statuses.status[i];
SourceUpdate(i, config, coeffs, status);
}
EffectsUpdate(region.dsp_configuration, region.intermediate_mix_samples);
FinalUpdate(region.dsp_configuration, region.dsp_status, region.final_samples);
StereoFrame16 samples = FinalFrame();
#if 0
std::vector<s16> output;
output.reserve(AudioCore::samples_per_frame * 2);
for (int i = 0; i < AudioCore::samples_per_frame; i++) {
output.push_back(samples[0][i]);
output.push_back(samples[1][i]);
}
AudioCore::sink->EnqueueSamples(output);
#else
TimeStretch::Tick(AudioCore::sink->SamplesInQueue());
TimeStretch::AddSamples(samples);
TimeStretch::OutputSamples([&](const std::vector<s16>& output) {
if (AudioCore::sink->SamplesInQueue() < 16000) {
AudioCore::sink->EnqueueSamples(output);
}
});
#endif
}
static void ThreadFunc() {
while (true) {
ThreadFunc_barrier.Sync();
if (ThreadFunc_quit) {
break;
}
DoWork();
}
}
bool Tick() {
Common::Profiling::ScopeTimer timer_tick(profile_tick);
MICROPROFILE_SCOPE(DSP_Tick);
if (GetDspState() != DspState::On || !DSP_DSP::SemaphoreSignalled())
return false;
ThreadFunc_barrier.Sync();
return true;
}

View File

@@ -313,10 +313,10 @@ ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20);
struct SourceStatus {
struct Status {
u8 is_enabled; ///< Is this channel enabled? (Doesn't have to be playing anything.)
u8 previous_buffer_id_dirty; ///< Non-zero when previous_buffer_id changes
u8 current_buffer_id_dirty; ///< Non-zero when current_buffer_id changes
u16_le sync; ///< Is set by the DSP to the value of SourceConfiguration::sync
u32_dsp buffer_position; ///< Number of samples into the current buffer
u16_le previous_buffer_id; ///< Updated when a buffer finishes playing
u16_le current_buffer_id; ///< Updated when a buffer finishes playing
INSERT_PADDING_DSPWORDS(1);
};
@@ -330,6 +330,13 @@ struct DspConfiguration {
union {
u32_le dirty_raw;
BitField<0, 1, u32_le> unknown10_dirty;
BitField<1, 1, u32_le> unknown11_dirty;
BitField<2, 1, u32_le> unknown12_dirty;
BitField<3, 1, u32_le> unknown13_dirty;
BitField<4, 1, u32_le> unknown14_dirty;
BitField<5, 1, u32_le> unknown15_dirty;
BitField<8, 1, u32_le> mixer1_enabled_dirty;
BitField<9, 1, u32_le> mixer2_enabled_dirty;
BitField<10, 1, u32_le> delay_effect_0_dirty;
@@ -337,6 +344,7 @@ struct DspConfiguration {
BitField<12, 1, u32_le> reverb_effect_0_dirty;
BitField<13, 1, u32_le> reverb_effect_1_dirty;
BitField<15, 1, u32_le> unknown17_dirty;
BitField<16, 1, u32_le> volume_0_dirty;
BitField<24, 1, u32_le> volume_1_dirty;
@@ -344,12 +352,16 @@ struct DspConfiguration {
BitField<26, 1, u32_le> output_format_dirty;
BitField<27, 1, u32_le> limiter_enabled_dirty;
BitField<28, 1, u32_le> headphones_connected_dirty;
BitField<30, 1, u32_le> unknown16_dirty;
BitField<31, 1, u32_le> unknown18_dirty;
};
/// The DSP has three intermediate audio mixers. This controls the volume level (0.0-1.0) for each at the final mixer
float_le volume[3];
INSERT_PADDING_DSPWORDS(3);
u16 unknown17;
INSERT_PADDING_DSPWORDS(2);
enum class OutputFormat : u16_le {
Mono = 0,
@@ -361,7 +373,10 @@ struct DspConfiguration {
u16_le limiter_enabled; ///< Not sure of the exact gain equation for the limiter.
u16_le headphones_connected; ///< Application updates the DSP on headphone status.
INSERT_PADDING_DSPWORDS(4); ///< TODO: Surround sound related
INSERT_PADDING_DSPWORDS(1); ///< TODO: Surround sound related
u16 unknown16;
u16 unknown15;
u16 unknown18;
INSERT_PADDING_DSPWORDS(2); ///< TODO: Intermediate mixer 1/2 related
u16_le mixer1_enabled;
u16_le mixer2_enabled;
@@ -479,25 +494,29 @@ struct SharedMemory {
AdpcmCoefficients adpcm_coefficients;
struct {
INSERT_PADDING_DSPWORDS(0x100);
u16 unknown[256];
} unknown10;
struct {
INSERT_PADDING_DSPWORDS(0xC0);
u16 unknown[192];
} unknown11;
struct {
INSERT_PADDING_DSPWORDS(0x180);
u16 unknown[384];
} unknown12;
struct {
INSERT_PADDING_DSPWORDS(0xA);
// biq
u32_dsp unknown[5];
} unknown13;
struct {
INSERT_PADDING_DSPWORDS(0x13A3);
// biq
u32_dsp unknown[5];
} unknown14;
INSERT_PADDING_DSPWORDS(0x1399);
u16_le frame_counter;
};
ASSERT_DSP_STRUCT(SharedMemory, 0x8000);

View File

@@ -0,0 +1,44 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/audio_core.h"
#include "audio_core/hle/common.h"
#include "audio_core/hle/effects.h"
#include "audio_core/hle/source.h"
namespace DSP {
namespace HLE {
struct State {
QuadFrame32 current_frame;
};
static std::array<State, 3> state;
void EffectsInit() {
state = {};
}
void EffectsUpdate(const DspConfiguration& config, IntermediateMixSamples& samples) {
state[0].current_frame.fill({});
state[1].current_frame.fill({});
state[2].current_frame.fill({});
for (size_t source_id = 0; source_id < AudioCore::num_sources; source_id++) {
SourceFrameMixInto(state[0].current_frame, source_id, 0);
SourceFrameMixInto(state[1].current_frame, source_id, 1);
SourceFrameMixInto(state[2].current_frame, source_id, 2);
}
// TODO: Delay
// TODO: Reverb
}
const QuadFrame32& IntermediateMixFrame(int mix_id) {
return state[mix_id].current_frame;
}
}
}

View File

@@ -0,0 +1,20 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "audio_core/hle/common.h"
#include "audio_core/hle/dsp.h"
namespace DSP {
namespace HLE {
void EffectsInit();
void EffectsUpdate(const DspConfiguration& config, IntermediateMixSamples& samples);
const QuadFrame32& IntermediateMixFrame(int mix_id);
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/hle/common.h"
#include "audio_core/hle/effects.h"
#include "audio_core/hle/final.h"
#include "common/logging/log.h"
#include "common/math_util.h"
namespace DSP {
namespace HLE {
struct State {
StereoFrame16 current_frame;
std::array<float, 3> volumes = {1.0, 0.0, 0.0};
};
static State state;
void FinalInit() {
state = {};
}
void FinalUpdate(const DspConfiguration& config, DspStatus& status, FinalMixSamples& samples) {
// TODO: Final processing
bool clipping = false;
std::array<QuadFrame32, 3> mix;
for (int k = 0; k < 3; k++) {
mix[k] = IntermediateMixFrame(k);
}
for (int i = 0; i < AudioCore::samples_per_frame; i++) {
for (int j = 0; j < 2; j++) {
s32 value = 0;
for (int k = 0; k < 3; k++) {
value += 0.2 * state.volumes[0] * mix[k][i][j + 0];
value += 0.2 * state.volumes[0] * mix[k][i][j + 2];
}
if (value > 0x8000 || value < -0x7FFF) {
clipping = true;
}
state.current_frame[i][j] = static_cast<s16>(MathUtil::Clamp(value, -0x7FFF, 0x8000));
}
}
if (clipping) {
LOG_ERROR(Audio_DSP, "AUDIO IS CLIPPING");
}
}
const StereoFrame16& FinalFrame() {
return state.current_frame;
}
}
}

View File

@@ -0,0 +1,19 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "audio_core/hle/dsp.h"
namespace DSP {
namespace HLE {
void FinalInit();
void FinalUpdate(const DspConfiguration& config, DspStatus& status, FinalMixSamples& samples);
const StereoFrame16& FinalFrame();
}
}

View File

@@ -17,7 +17,7 @@ namespace HLE {
static DspState dsp_state = DspState::Off;
static std::array<std::vector<u8>, static_cast<size_t>(DspPipe::DspPipe_MAX)> pipe_data;
static std::array<std::vector<u8>, DspPipe_MAX> pipe_data;
void ResetPipes() {
for (auto& data : pipe_data) {

View File

@@ -23,6 +23,8 @@ enum class DspPipe {
DspPipe_MAX
};
constexpr size_t DspPipe_MAX = static_cast<size_t>(DspPipe::DspPipe_MAX);
/**
* Read a DSP pipe.
* @param pipe_number The Pipe ID

View File

@@ -0,0 +1,334 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <cmath>
#include <queue>
#include <vector>
#include "audio_core/codec.h"
#include "audio_core/hle/common.h"
#include "audio_core/hle/source.h"
#include "audio_core/interpolate.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/memory.h"
namespace DSP {
namespace HLE {
using MonoOrStereo = SourceConfiguration::Configuration::MonoOrStereo;
using Format = SourceConfiguration::Configuration::Format;
using DspBuffer = SourceConfiguration::Configuration::Buffer;
struct Buffer {
PAddr physical_address;
u32 length;
u8 adpcm_ps;
u16 adpcm_yn[2];
bool adpcm_dirty;
bool is_looping;
u16 buffer_id;
MonoOrStereo mono_or_stereo;
Format format;
bool from_queue;
bool operator < (const Buffer& other) const {
// We want things with lower id to appear first, unless we have wraparound.
// priority_queue puts a before b when b < a.
if (this->buffer_id < 10 && other.buffer_id > 65520) return true;
if (other.buffer_id < 10 && this->buffer_id > 65520) return false;
return this->buffer_id > other.buffer_id;
}
};
struct State {
size_t source_id;
bool enabled = false;
float rate_multiplier = 1.0;
u16 sync = 0;
std::array<std::array<float, 4>, 3> gains = {};
MonoOrStereo mono_or_stereo = MonoOrStereo::Mono;
Format format = Format::PCM16;
std::array<s16, 16> adpcm_coeffs = {};
Codec::ADPCMState adpcm_state = {0, 0};
AudioInterp::State interp_state = {};
bool do_not_trigger_update = true;
bool buffer_update = false;
u32 current_buffer_id = 0;
u32 previous_buffer_id = 0;
std::priority_queue<Buffer> queue = {};
u32 current_sample_number = 0;
u32 next_sample_number = 0;
Codec::StereoBuffer16 current_buffer = {};
QuadFrame32 current_frame = {};
};
static void ParseConfig(State& s, SourceConfiguration::Configuration& config, const s16_le adpcm_coeffs[16]) {
if (!config.dirty_raw) {
return;
}
if (config.reset_flag) {
config.reset_flag.Assign(0);
size_t id = s.source_id;
s = {};
s.source_id = id;
LOG_DEBUG(Audio_DSP, "source_id=%zu reset", s.source_id);
}
if (config.enable_dirty) {
config.enable_dirty.Assign(0);
s.enabled = config.enable != 0;
LOG_TRACE(Audio_DSP, "source_id=%zu enable=%d", s.source_id, s.enabled);
}
if (config.sync_dirty) {
config.sync_dirty.Assign(0);
s.sync = config.sync;
LOG_DEBUG(Audio_DSP, "source_id=%zu sync=%u", s.source_id, s.sync);
}
if (config.rate_multiplier_dirty) {
config.rate_multiplier_dirty.Assign(0);
s.rate_multiplier = config.rate_multiplier;
LOG_TRACE(Audio_DSP, "source_id=%zu rate=%f", s.source_id, s.rate_multiplier);
}
if (config.adpcm_coefficients_dirty) {
config.adpcm_coefficients_dirty.Assign(0);
std::copy(adpcm_coeffs, adpcm_coeffs + s.adpcm_coeffs.size(), s.adpcm_coeffs.begin());
LOG_TRACE(Audio_DSP, "source_id=%zu adpcm update", s.source_id);
}
if (config.gain_0_dirty) {
config.gain_0_dirty.Assign(0);
for (int i = 0; i < 4; i++) {
s.gains[0][i] = config.gain[0][i];
LOG_TRACE(Audio_DSP, "source_id=%zu gains[0][%i] = %f", s.source_id, i, s.gains[0][i]);
}
}
if (config.gain_1_dirty) {
config.gain_1_dirty.Assign(0);
for (int i = 0; i < 4; i++) {
s.gains[1][i] = config.gain[1][i];
LOG_TRACE(Audio_DSP, "source_id=%zu gains[1][%i] = %f", s.source_id, i, s.gains[1][i]);
}
}
if (config.gain_2_dirty) {
config.gain_2_dirty.Assign(0);
for (int i = 0; i < 4; i++) {
s.gains[2][i] = config.gain[2][i];
LOG_TRACE(Audio_DSP, "source_id=%zu gains[2][%i] = %f", s.source_id, i, s.gains[2][i]);
}
}
// if (config.unknown_flag) {
//config.unknown_flag = 0;
// LOG_WARNING(Audio_DSP, "(STUB) unknown_flag is set!!!");
// }
if (config.format_dirty || config.embedded_buffer_dirty) {
config.format_dirty.Assign(0);
s.format = config.format;
LOG_DEBUG(Audio_DSP, "source_id=%zu format=%u", s.source_id, s.format);
}
if (config.mono_or_stereo_dirty || config.embedded_buffer_dirty) {
config.mono_or_stereo_dirty.Assign(0);
s.mono_or_stereo = config.mono_or_stereo;
LOG_DEBUG(Audio_DSP, "source_id=%zu mono_or_stereo=%u", s.source_id, s.mono_or_stereo);
}
if (config.buffer_queue_dirty) {
config.buffer_queue_dirty.Assign(0);
for (int i = 0; i < 4; i++) {
if (config.buffers_dirty & (1 << i)) {
const auto& b = config.buffers[i];
s.queue.emplace(Buffer{
b.physical_address,
b.length,
(u8)b.adpcm_ps,
{ b.adpcm_yn[0], b.adpcm_yn[1] },
b.adpcm_dirty != 0,
b.is_looping != 0,
b.buffer_id,
s.mono_or_stereo,
s.format,
true
});
LOG_TRACE(Audio_DSP, "enqueueing queued %i addr=0x%08x len=%u id=%u", i, b.physical_address, b.length, b.buffer_id);
}
}
config.buffers_dirty = 0;
}
if (config.embedded_buffer_dirty) {
config.embedded_buffer_dirty.Assign(0);
s.queue.emplace(Buffer {
config.physical_address,
config.length,
(u8)config.adpcm_ps,
{ config.adpcm_yn[0], config.adpcm_yn[1] },
config.adpcm_dirty.ToBool(),
config.is_looping.ToBool(),
config.buffer_id,
s.mono_or_stereo,
s.format,
false
});
LOG_TRACE(Audio_DSP, "enqueueing embedded addr=0x%08x len=%u id=%u", config.physical_address, config.length, config.buffer_id);
}
if (config.interpolation_dirty) {
config.interpolation_dirty.Assign(0);
//config.interpolation_mode
LOG_DEBUG(Audio_DSP, "source_id=%zu interpolation_mode=%u ", s.source_id, config.interpolation_mode);
}
if (config.dirty_raw) {
LOG_WARNING(Audio_DSP, "source_id=%zu remaining_dirty=%x", s.source_id, config.dirty_raw);
}
config.dirty_raw = 0;
}
static bool DequeueBuffer(State& s) {
if (!s.current_buffer.empty())
return true;
if (s.queue.empty())
return false;
const Buffer buf = s.queue.top();
s.queue.pop();
const u8* const memory = Memory::GetPhysicalPointer(buf.physical_address);
ASSERT(memory);
if (buf.adpcm_dirty) {
s.adpcm_state.yn1 = buf.adpcm_yn[0];
s.adpcm_state.yn2 = buf.adpcm_yn[1];
}
if (buf.is_looping) {
LOG_ERROR(Audio_DSP, "Looped buffers are unimplemented at the moment");
}
const unsigned num_channels = buf.mono_or_stereo == MonoOrStereo::Stereo ? 2 : 1;
switch (buf.format) {
case Format::PCM8:
s.current_buffer = Codec::DecodePCM8(num_channels, memory, buf.length);
break;
case Format::PCM16:
s.current_buffer = Codec::DecodePCM16(num_channels, memory, buf.length);
break;
case Format::ADPCM:
ASSERT(num_channels == 1);
s.current_buffer = Codec::DecodeADPCM(memory, buf.length, s.adpcm_coeffs, s.adpcm_state);
break;
default:
UNIMPLEMENTED();
break;
}
s.current_sample_number = s.next_sample_number = 0;
s.current_buffer_id = buf.buffer_id;
s.buffer_update = buf.from_queue;
LOG_TRACE(Audio_DSP, "source_id=%u buffer_id=%u from_queue=%d", s.source_id, buf.buffer_id, buf.from_queue);
return true;
}
static void ResampleBuffer(State& s) {
s.current_frame.fill({});
s.current_sample_number = s.next_sample_number;
while (true) {
if (!DequeueBuffer(s))
break;
auto result = AudioInterp::None(s.interp_state, s.current_frame, s.current_buffer, s.rate_multiplier);
s.next_sample_number += std::get<0>(result);
if (!std::get<1>(result))
break;
}
}
static void AdvanceFrame(State& s) {
ResampleBuffer(s);
if (s.current_sample_number == s.next_sample_number) {
s.enabled = false;
}
// TODO: Filters
}
static void UpdateStatus(State& s, SourceStatus::Status& status) {
// Applications depend on the correct emulation of
// previous_buffer_id_dirty and previous_buffer_id to synchronise
// audio with video.
status.is_enabled = s.enabled;
status.current_buffer_id_dirty = s.buffer_update ? 1 : 0;
s.buffer_update = false;
status.current_buffer_id = s.current_buffer_id;
status.buffer_position = s.current_sample_number;
status.sync = s.sync;
}
std::array<State, AudioCore::num_sources> state = {};
void SourceInit() {
state = {};
for (size_t i = 0; i < state.size(); i++) {
state[i] = {};
state[i].source_id = i;
}
}
void SourceUpdate(int source_id, SourceConfiguration::Configuration& config, const s16_le adpcm_coeffs[16], SourceStatus::Status& status) {
ASSERT(source_id >= 0 && source_id < AudioCore::num_sources);
ParseConfig(state[source_id], config, adpcm_coeffs);
AdvanceFrame(state[source_id]);
UpdateStatus(state[source_id], status);
}
const QuadFrame32& SourceFrame(int source_id) {
ASSERT(source_id >= 0 && source_id < AudioCore::num_sources);
ASSERT(state[source_id].enabled);
return state[source_id].current_frame;
}
void SourceFrameMixInto(QuadFrame32& dest, int source_id, int intermediate_mix_id) {
ASSERT(source_id >= 0 && source_id < AudioCore::num_sources);
ASSERT(intermediate_mix_id >= 0 && intermediate_mix_id < 3);
const State& s = state[source_id];
if (!s.enabled)
return;
for (int i = 0; i < dest.size(); i++) {
for (int channel = 0; channel < 4; channel++) {
dest[i][channel] += s.gains[intermediate_mix_id][channel] * s.current_frame[i][channel];
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "audio_core/audio_core.h"
#include "audio_core/hle/common.h"
#include "audio_core/hle/dsp.h"
namespace DSP {
namespace HLE {
/// Initialise this DSP module
void SourceInit();
/**
* Perform processing for this DSP module.
* This module performs:
* - Buffer management
* - Decoding of buffers
* - Buffer resampling and interpolation
* - Per-source filtering (SimpleFilter, BiquadFilter)
* - Per-source gain
*/
void SourceUpdate(int source_id, SourceConfiguration::Configuration& config, const s16_le adpcm_coeffs[16], SourceStatus::Status& status);
/// Output of this DSP module.
const QuadFrame32& SourceFrame(int source_id);
/// Mix current frame from source_id into buffer based on gain coefficients for intermediate_mix_id.
void SourceFrameMixInto(QuadFrame32& dest, int source_id, int intermediate_mix_id);
}
}

View File

@@ -0,0 +1,237 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#define _USE_MATH_DEFINES
#include <cmath>
#include "audio_core/interpolate.h"
#include "common/assert.h"
#include "common/math_util.h"
namespace AudioInterp {
/// Kaiser window with alpha=2.4, N=11
static const std::array<double, required_history/2> kaiser_window {{
0.327949,
0.521302,
0.707379,
0.861996,
0.964250
}};
struct BiquadLpf {
/// Calculate coefficients required for a biquad filter to behave as a low-pass filter.
void Init(double freq) {
const double w0 = 2 * M_PI * freq;
const double Q = 0.707;
const double a = sin(w0) / (2.0*Q);
double a0;
b0 = 0.5 * (1.0 - cos(w0));
b1 = (1.0 - cos(w0));
b2 = 0.5 * (1.0 - cos(w0));
a0 = 1.0 + a;
a1 = -2.0 * cos(w0);
a2 = 1.0 - a;
// Normalize;
b0 /= a0;
b1 /= a0;
b2 /= a0;
a1 /= a0;
a2 /= a0;
}
inline s32 Process(s32 x) {
double xn0 = x;
double yn0 = b0 * xn0 + b1 * xn1 + b2 * xn2 - a1 * yn1 - a2 * yn2;
// Advance state
xn2 = xn1;
xn1 = xn0;
yn2 = yn1;
yn1 = yn0;
return (s32)yn0;
}
private:
double a1, a2;
double b0, b1, b2;
double xn1 = 0.0, xn2 = 0.0, yn1 = 0.0, yn2 = 0.0;
};
double sinc(double x) {
DEBUG_ASSERT(x != 0);
return sin(x) / x;
}
std::tuple<size_t, bool> KaiserSinc(State& state, DSP::HLE::QuadFrame32& output, std::array<std::vector<s16>, 2>& input, const float rate_change) {
ASSERT(input[0].size() == input[1].size());
ASSERT(input[0].size() > required_history);
size_t position = 0;
double& position_fractional = state.position_fractional;
for (int j = 0; j < 2; j++) {
input[j].insert(input[j].begin(), state.history[j].begin(), state.history[j].end());
}
std::array<BiquadLpf, 2> lpf;
const double lpf_cutoff = std::min(0.5 * rate_change, 0.5 / rate_change);
lpf[0].Init(lpf_cutoff);
lpf[1].Init(lpf_cutoff);
auto step = [&](size_t i) -> s32 {
auto& in = input[i];
s32 sample = 0;
sample += kaiser_window[0] * sinc(-5.0 - position_fractional) * in[position + 0];
sample += kaiser_window[1] * sinc(-4.0 - position_fractional) * in[position + 1];
sample += kaiser_window[2] * sinc(-3.0 - position_fractional) * in[position + 2];
sample += kaiser_window[3] * sinc(-2.0 - position_fractional) * in[position + 3];
sample += kaiser_window[4] * sinc(-1.0 - position_fractional) * in[position + 4];
sample += in[position + 5];
sample += kaiser_window[4] * sinc(+1.0 - position_fractional) * in[position + 6];
sample += kaiser_window[3] * sinc(+2.0 - position_fractional) * in[position + 7];
sample += kaiser_window[2] * sinc(+3.0 - position_fractional) * in[position + 8];
sample += kaiser_window[1] * sinc(+4.0 - position_fractional) * in[position + 9];
sample += kaiser_window[0] * sinc(+5.0 - position_fractional) * in[position + 10];
//sample = lpf[i].Process(sample);
return sample;
};
const size_t position_stop = input[0].size() - required_history;
while (state.output_position < output[0].size() && position < position_stop) {
s32 sample0 = step(0);
s32 sample1 = step(1);
output[0][state.output_position] = sample0;
output[1][state.output_position] = sample0;
output[2][state.output_position] = sample1;
output[3][state.output_position] = sample1;
position_fractional += rate_change;
position += (size_t)position_fractional;
position_fractional -= (size_t)position_fractional;
state.output_position++;
}
bool continue_feeding_me = true;
if (state.output_position >= output[0].size()) {
state.output_position = 0;
continue_feeding_me = false;
}
for (int j = 0; j < 2; j++) {
std::copy(input[j].begin() + position,
input[j].begin() + position + required_history,
state.history[j].begin());
if (position + required_history >= input[j].size()) {
input[j].clear();
} else {
input[j].erase(input[j].begin(),
input[j].begin() + position + required_history);
}
}
ASSERT(input[0].size() == input[1].size());
return std::make_tuple(position, continue_feeding_me);
}
std::tuple<size_t, bool> Linear(State& state, DSP::HLE::QuadFrame32& output, std::array<std::vector<s16>, 2>& input, const float rate_change) {
ASSERT(input[0].size() == input[1].size());
while (input[0].size() < 2) {
input[0].emplace_back(0);
input[1].emplace_back(0);
}
size_t position = 0;
double& position_fractional = state.position_fractional;
auto step = [&](size_t i) -> s32 {
auto& in = input[i];
s32 sample = 0;
sample = position_fractional * in[position + 0] + (1.0 - position_fractional) * in[position + 1];
return sample;
};
const size_t position_stop = input.size() - 1;
while (state.output_position < output.size() && position < position_stop) {
s32 sample0 = step(0);
s32 sample1 = step(1);
output[state.output_position][0] = sample0;
output[state.output_position][1] = sample0;
output[state.output_position][2] = sample1;
output[state.output_position][3] = sample1;
position_fractional += rate_change;
position += (size_t)position_fractional;
position_fractional -= (size_t)position_fractional;
state.output_position++;
}
bool continue_feeding_me = true;
if (state.output_position >= output[0].size()) {
state.output_position = 0;
continue_feeding_me = false;
}
for (int j = 0; j < 2; j++) {
if (position >= input[j].size()) {
input[j].clear();
} else {
input[j].erase(input[j].begin(),
input[j].begin() + position);
}
}
ASSERT(input[0].size() == input[1].size());
return std::make_tuple(position, continue_feeding_me);
}
std::tuple<size_t, bool> None(State& state, DSP::HLE::QuadFrame32& output, std::vector<std::array<s16, 2>>& input, const float rate_change) {
size_t position = 0;
double& position_fractional = state.position_fractional;
auto step = [&](size_t i) -> s32 {
return input[position][i];
};
const size_t position_stop = input.size();
while (state.output_position < output.size() && position < position_stop) {
s32 sample0 = step(0);
s32 sample1 = step(1);
output[state.output_position][0] = sample0;
output[state.output_position][1] = sample0;
output[state.output_position][2] = sample1;
output[state.output_position][3] = sample1;
position_fractional += rate_change;
position += (size_t)position_fractional;
position_fractional -= (size_t)position_fractional;
state.output_position++;
}
bool continue_feeding_me = true;
if (state.output_position >= output.size()) {
state.output_position = 0;
continue_feeding_me = false;
}
if (position >= input.size()) {
input.clear();
} else {
input.erase(input.begin(), input.begin() + position);
}
return std::make_tuple(position, continue_feeding_me);
}
}

View File

@@ -0,0 +1,28 @@
// 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/common.h"
#include "common/common_types.h"
namespace AudioInterp {
constexpr size_t required_history = 11;
struct State {
double position_fractional = 0;
size_t output_position = 0;
std::array<std::array<s16, required_history>, 2> history = {};
};
std::tuple<size_t, bool> KaiserSinc(State& state, DSP::HLE::QuadFrame32& output, std::array<std::vector<s16>, 2>& input, const float rate_change);
std::tuple<size_t, bool> Linear(State& state, DSP::HLE::QuadFrame32& output, std::array<std::vector<s16>, 2>& input, const float rate_change);
std::tuple<size_t, bool> None(State& state, DSP::HLE::QuadFrame32& output, std::vector<std::array<s16, 2>>& input, const float rate_change);
}

View File

@@ -0,0 +1,3 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

View File

@@ -0,0 +1,9 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace AudioCore {
}

View File

@@ -0,0 +1,118 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <SDL.h>
#include "audio_core/audio_core.h"
#include "audio_core/sdl2_sink.h"
#include "common/assert.h"
#include "common/logging/log.h"
namespace AudioCore {
std::unique_ptr<Sink> sink(new SDL2Sink());
SDL2Sink::SDL2Sink() {
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
LOG_CRITICAL(Audio_SDL2, "SDL_Init(SDL_INIT_AUDIO) failed");
exit(-2);
}
SDL_AudioSpec desired_audiospec;
SDL_zero(desired_audiospec);
desired_audiospec.format = AUDIO_S16;
desired_audiospec.channels = 2;
desired_audiospec.freq = AudioCore::native_sample_rate;
desired_audiospec.samples = 4096;
desired_audiospec.userdata = this;
desired_audiospec.callback = &SDL2Sink::Callback; // We're going to use SDL_QueueAudio
SDL_AudioSpec obtained_audiospec;
SDL_zero(obtained_audiospec);
audio_device_id = SDL_OpenAudioDevice(nullptr, /*iscapture=*/false, &desired_audiospec, &obtained_audiospec, 0);
if (audio_device_id < 0) {
LOG_CRITICAL(Audio_SDL2, "SDL_OpenAudioDevice failed");
exit(-2);
}
sample_rate = obtained_audiospec.freq;
SDL_PauseAudioDevice(audio_device_id, 0);
}
SDL2Sink::~SDL2Sink() {
}
/// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec)
unsigned SDL2Sink::GetNativeSampleRate() const {
return sample_rate;
}
/**
* Feed stereo samples to sink.
* @param samples Samples in interleaved stereo PCM16 format. Size of vector must be multiple of two.
*/
void SDL2Sink::EnqueueSamples(const std::vector<s16>& samples) {
ASSERT(samples.size() % 2 == 0);
SDL_LockAudioDevice(audio_device_id);
queue.emplace_back(samples);
SDL_UnlockAudioDevice(audio_device_id);
}
/// Samples enqueued that have not been played yet.
size_t SDL2Sink::SamplesInQueue() const {
const size_t queue_size = RealQueueSize() + dequeue_consumed;
if (dequeue_consumed == 0)
return queue_size;
const std::chrono::duration<double> duration = std::chrono::steady_clock::now() - dequeue_time;
const size_t estimated_samples_consumed = sample_rate * duration.count();
if (estimated_samples_consumed > queue_size)
return 0;
return queue_size - estimated_samples_consumed;
}
size_t SDL2Sink::RealQueueSize() const {
size_t total_size = 0;
SDL_LockAudioDevice(audio_device_id);
for (const auto& buf : queue) {
total_size += buf.size() / 2;
}
SDL_UnlockAudioDevice(audio_device_id);
return total_size;
}
void SDL2Sink::Callback(void* sink_, u8* buffer, int buffer_size) {
SDL2Sink* sink = reinterpret_cast<SDL2Sink*>(sink_);
buffer_size /= sizeof(s16); // Convert to number of half-samples.
sink->dequeue_time = std::chrono::steady_clock::now();
sink->dequeue_consumed = buffer_size / 2;
while (buffer_size > 0 && !sink->queue.empty()) {
if (sink->queue.front().size() <= buffer_size) {
memcpy(buffer, sink->queue.front().data(), sink->queue.front().size() * sizeof(s16));
buffer += sink->queue.front().size() * sizeof(s16);
buffer_size -= sink->queue.front().size();
sink->queue.pop_front();
} else {
memcpy(buffer, sink->queue.front().data(), buffer_size * sizeof(s16));
buffer += buffer_size * sizeof(s16);
sink->queue.front().erase(sink->queue.front().begin(), sink->queue.front().begin() + buffer_size);
buffer_size = 0;
}
}
if (buffer_size > 0) {
sink->dequeue_consumed -= buffer_size / 2;
memset(buffer, 0, buffer_size * sizeof(s16));
}
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <chrono>
#include <cstddef>
#include <list>
#include <vector>
#include "audio_core/sink.h"
namespace AudioCore {
class SDL2Sink final : public Sink {
public:
SDL2Sink();
~SDL2Sink() override;
/// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec)
unsigned GetNativeSampleRate() const override;
/**
* Feed stereo samples to sink.
* @param samples Samples in interleaved stereo PCM16 format. Size of vector must be multiple of two.
*/
void EnqueueSamples(const std::vector<s16>& samples) override;
/// Samples enqueued that have not been played yet.
size_t SamplesInQueue() const override;
private:
using SDL_AudioDeviceID = u32;
unsigned sample_rate;
SDL_AudioDeviceID audio_device_id;
std::list<std::vector<s16>> queue;
size_t RealQueueSize() const;
static void Callback(void* sink, u8* buffer, int buffer_size);
std::chrono::steady_clock::time_point dequeue_time;
size_t dequeue_consumed = 0;
};
}

View File

@@ -4,6 +4,7 @@
#pragma once
#include <memory>
#include <vector>
#include "common/common_types.h"
@@ -31,4 +32,6 @@ public:
virtual std::size_t SamplesInQueue() const = 0;
};
extern std::unique_ptr<Sink> sink;
} // namespace

View File

@@ -0,0 +1,83 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <chrono>
#include <cmath>
#include <functional>
#include <list>
#include <numeric>
#include <vector>
#include <SoundTouch.h>
#include "audio_core/audio_core.h"
#include "audio_core/sink.h"
#include "audio_core/time_stretch.h"
#include "common/common_types.h"
#include "common/math_util.h"
#include "common/logging/log.h"
namespace TimeStretch {
static soundtouch::SoundTouch soundtouch;
using steady_clock = std::chrono::steady_clock;
steady_clock::time_point frame_timer = steady_clock::now();
double smooth_ratio = 1.0;
void Tick(unsigned samples_in_queue) {
const steady_clock::time_point now = steady_clock::now();
const std::chrono::duration<double> duration = now - frame_timer;
frame_timer = now;
constexpr double native_frame_time = (double)AudioCore::samples_per_frame / (double)AudioCore::native_sample_rate;
const double actual_frame_time = duration.count();
double ratio = actual_frame_time / native_frame_time;
ratio = MathUtil::Clamp<double>(ratio, 0.01, 100.0);
// TODO: Uhh was just reading this and this seems super wonky double-check your logic wtf are you thinking.
if (samples_in_queue < 4096) {
ratio = ratio > 1.0 ? ratio * ratio : 1.0;
ratio = MathUtil::Clamp<double>(ratio, 0.01, 100.0);
} else if (AudioCore::sink->SamplesInQueue() > 16000) {
ratio = ratio > 1.0 ? sqrt(ratio) : 0.01;
ratio = MathUtil::Clamp<double>(ratio, 0.01, 100.0);
}
smooth_ratio = 0.993 * smooth_ratio + 0.007 * ratio;
smooth_ratio = MathUtil::Clamp<double>(smooth_ratio, 0.01, 100.0);
//printf("%f, %f\n", ratio, smooth_ratio);
soundtouch.setTempo(1.0 / smooth_ratio);
}
void Init() {
soundtouch.setTempo(1.0);
soundtouch.setChannels(2);
}
void Shutdown() {
soundtouch.setTempo(1.0);
}
void AddSamples(const std::array<std::array<s16, 2>, AudioCore::samples_per_frame>& samples) {
// FIXME: lol don't do this c-style cast
soundtouch.putSamples((s16*)samples.data(), AudioCore::samples_per_frame);
}
void OutputSamples(std::function<void(const std::vector<s16>&)> fn) {
size_t available = soundtouch.numSamples();
std::vector<s16> output(available * 2);
soundtouch.receiveSamples(output.data(), available);
fn(output);
}
}

View File

@@ -0,0 +1,24 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <functional>
#include <vector>
#include "common/common_types.h"
namespace TimeStretch {
void Init();
void Shutdown();
void Tick(unsigned samples_in_queue);
void AddSamples(const std::array<std::array<s16, 2>, AudioCore::samples_per_frame>& samples);
void OutputSamples(std::function<void(const std::vector<s16>&)> fn);
}

View File

@@ -117,6 +117,8 @@ if (Qt5_FOUND AND MSVC)
)
windows_copy_files(citra-qt ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
windows_copy_files(citra-qt ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll)
unset(Qt5_DLL_DIR)
unset(Qt5_PLATFORMS_DIR)
unset(DLL_DEST)

View File

@@ -65,6 +65,7 @@ namespace Log {
SUB(Render, OpenGL) \
CLS(Audio) \
SUB(Audio, DSP) \
SUB(Audio, SDL2) \
CLS(Loader)
// GetClassName is a macro defined by Windows.h, grrr...

View File

@@ -80,6 +80,7 @@ enum class Class : ClassType {
Render_OpenGL, ///< OpenGL backend
Audio, ///< Emulator audio output
Audio_DSP, ///< The HLE implementation of the DSP
Audio_SDL2, ///< SDL2 frontend for audio output
Loader, ///< ROM loader
Count ///< Total number of logging classes

View File

@@ -20,29 +20,55 @@ namespace DSP_DSP {
static u32 read_pipe_count;
static Kernel::SharedPtr<Kernel::Event> semaphore_event;
struct PairHash {
template <typename T, typename U>
std::size_t operator()(const std::pair<T, U> &x) const {
// TODO(yuriks): Replace with better hash combining function.
return std::hash<T>()(x.first) ^ std::hash<U>()(x.second);
}
enum class InterruptType {
Zero = 0, // Unknown purpose. Channel is always zero.
One = 1, // Unknown purpose. Channel is always zero.
Pipe = 2, // Related to a pipe
MAX
};
constexpr size_t InterruptType_MAX = static_cast<size_t>(InterruptType::MAX);
/// Map of (audio interrupt number, channel number) to Kernel::Events. See: RegisterInterruptEvents
static std::unordered_map<std::pair<u32, u32>, Kernel::SharedPtr<Kernel::Event>, PairHash> interrupt_events;
/// Map of (interrupt number, channel number) to Kernel::Events. See: RegisterInterruptEvents
static std::array<std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>>, InterruptType_MAX> interrupt_events;
constexpr size_t max_number_of_interrupt_events = 6;
size_t GetNumberOfRegisteredEvents() {
size_t number = 0;
for (const auto& events : interrupt_events) {
number += events.size();
}
return number;
}
// DSP Interrupts:
// Interrupt #2 occurs every frame tick. Userland programs normally have a thread that's waiting
// Interrupt (2, 2) occurs every frame tick. Userland programs normally have a thread that's waiting
// for an interrupt event. Immediately after this interrupt event, userland normally updates the
// state in the next region and increments the relevant frame counter by two.
void SignalAllInterrupts() {
// HACK: The other interrupts have currently unknown purpose, we trigger them each tick in any case.
for (auto& interrupt_event : interrupt_events)
interrupt_event.second->Signal();
for (auto& events : interrupt_events)
for (auto& event : events)
event.second->Signal();
}
void SignalInterrupt(u32 interrupt, u32 channel) {
interrupt_events[std::make_pair(interrupt, channel)]->Signal();
ASSERT(interrupt < interrupt_events.size());
if (interrupt == 0 || interrupt == 1)
ASSERT(channel == 0);
auto& events = interrupt_events[interrupt];
if (events.find(channel) != events.end()) {
events[channel]->Signal();
}
}
bool SemaphoreSignalled() {
if (semaphore_event->signaled) {
semaphore_event->Clear();
return true;
} else {
return false;
}
}
/**
@@ -58,6 +84,7 @@ static void ConvertProcessAddressFromDspDram(Service::Interface* self) {
u32 addr = cmd_buff[1];
cmd_buff[0] = 0xC0080;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000);
@@ -113,9 +140,11 @@ static void LoadComponent(Service::Interface* self) {
static void GetSemaphoreEventHandle(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[0] = 0x160042;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).MoveFrom(); // Event handle
LOG_WARNING(Service_DSP, "(STUBBED) called");
}
@@ -157,23 +186,47 @@ static void FlushDataCache(Service::Interface* self) {
static void RegisterInterruptEvents(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 interrupt = cmd_buff[1];
u32 type_num = cmd_buff[1];
u32 channel = cmd_buff[2];
u32 event_handle = cmd_buff[4];
if (cmd_buff[3] != 0) {
cmd_buff[0] = 0x40;
cmd_buff[1] = 0xD9001830;
return;
}
InterruptType type = static_cast<InterruptType>(type_num);
if (type == InterruptType::Zero || type == InterruptType::One) {
channel = 0;
} else if (type == InterruptType::Pipe) {
if (channel >= DSP::HLE::DspPipe_MAX) {
LOG_ERROR(Service_DSP, "Invalid (type, channel) combination (%u, %u)", type, channel);
}
} else {
// I suspect that interrupt values greater than two are invalid.
LOG_ERROR(Service_DSP, "Unimplemented (type, channel) combination (%u, %u)", type, channel);
UNIMPLEMENTED();
}
cmd_buff[0] = 0x150040;
if (event_handle) {
auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
if (evt) {
interrupt_events[std::make_pair(interrupt, channel)] = evt;
cmd_buff[1] = RESULT_SUCCESS.raw;
LOG_INFO(Service_DSP, "Registered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle);
if (GetNumberOfRegisteredEvents() < max_number_of_interrupt_events) {
interrupt_events[type_num][channel] = evt;
LOG_INFO(Service_DSP, "Registered type=%u, channel=%u, event_handle=0x%08X", type, channel, event_handle);
} else {
cmd_buff[1] = 0xC860A7FF;
LOG_ERROR(Service_DSP, "Ran out of space to register interrupts");
}
} else {
LOG_CRITICAL(Service_DSP, "Invalid event handle! interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle);
LOG_CRITICAL(Service_DSP, "Invalid event handle! type=%u, channel=%u, event_handle=0x%08X", type, channel, event_handle);
ASSERT(false); // This should really be handled at a IPC translation layer.
}
} else {
interrupt_events.erase(std::make_pair(interrupt, channel));
LOG_INFO(Service_DSP, "Unregistered interrupt=%u, channel=%u, event_handle=0x%08X", interrupt, channel, event_handle);
interrupt_events[type_num].erase(channel);
LOG_INFO(Service_DSP, "Unregistered type=%u, channel=%u, event_handle=0x%08X", type, channel, event_handle);
}
}
@@ -187,8 +240,13 @@ static void RegisterInterruptEvents(Service::Interface* self) {
static void SetSemaphore(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[0] = 0x70040;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
// Observed Behaviour: Waits for DSP_PSEM to be clear then sets DSP_PSEM.
SignalAllInterrupts(); // This is a HACK
LOG_WARNING(Service_DSP, "(STUBBED) called");
}
@@ -210,7 +268,13 @@ static void WriteProcessPipe(Service::Interface* self) {
u32 size = cmd_buff[2];
u32 buffer = cmd_buff[4];
ASSERT_MSG(IPC::StaticBufferDesc(size, 1) == cmd_buff[3], "IPC static buffer descriptor failed validation (0x%X). pipe=%u, size=0x%X, buffer=0x%08X", cmd_buff[3], pipe, size, buffer);
if (IPC::StaticBufferDesc(size, 1) != cmd_buff[3]) {
LOG_ERROR(Service_DSP, "IPC static buffer descriptor failed validation (0x%X). pipe=%u, size=0x%X, buffer=0x%08X", cmd_buff[3], pipe, size, buffer);
cmd_buff[0] = 0x40;
cmd_buff[1] = 0xD9001830;
return;
}
ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe, size, buffer);
std::vector<u8> message(size);
@@ -221,6 +285,7 @@ static void WriteProcessPipe(Service::Interface* self) {
DSP::HLE::PipeWrite(pipe, message);
cmd_buff[0] = 0xD0040;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_DEBUG(Service_DSP, "pipe=%u, size=0x%X, buffer=0x%08X", pipe, size, buffer);
@@ -250,16 +315,14 @@ static void ReadPipeIfPossible(Service::Interface* self) {
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr);
cmd_buff[0] = 0x100082;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
Memory::WriteBlock(addr, response.data(), response.size());
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);
cmd_buff[2] = static_cast<u32>(response.size());
} else {
cmd_buff[2] = 0; // Return no data
}
Memory::WriteBlock(addr, response.data(), response.size());
cmd_buff[2] = static_cast<u32>(response.size());
LOG_DEBUG(Service_DSP, "pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X, return cmd_buff[2]=0x%08X", pipe, unknown, size, addr, cmd_buff[2]);
}
@@ -333,6 +396,7 @@ static void SetSemaphoreMask(Service::Interface* self) {
u32 mask = cmd_buff[1];
cmd_buff[0] = 0x170040;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x%08X", mask);
@@ -350,10 +414,11 @@ static void SetSemaphoreMask(Service::Interface* self) {
static void GetHeadphoneStatus(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[0] = 0x1F0080;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[2] = 0; // Not using headphones?
LOG_WARNING(Service_DSP, "(STUBBED) called");
LOG_TRACE(Service_DSP, "called");
}
/**
@@ -376,6 +441,7 @@ static void RecvData(Service::Interface* self) {
// Application reads this after requesting DSP shutdown, to verify the DSP has indeed shutdown or slept.
cmd_buff[0] = 0x10080;
cmd_buff[1] = RESULT_SUCCESS.raw;
switch (DSP::HLE::GetDspState()) {
case DSP::HLE::DspState::On:
@@ -411,6 +477,7 @@ static void RecvDataIsReady(Service::Interface* self) {
ASSERT_MSG(register_number == 0, "Unknown register_number %u", register_number);
cmd_buff[0] = 0x20080;
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = 1; // Ready to read
@@ -465,7 +532,8 @@ Interface::Interface() {
Interface::~Interface() {
semaphore_event = nullptr;
interrupt_events.clear();
for (auto& events : interrupt_events)
events.clear();
}
} // namespace

View File

@@ -34,4 +34,7 @@ void SignalAllInterrupts();
*/
void SignalInterrupt(u32 interrupt_id, u32 channel_id);
/// Returns true it the application signalled the semaphore, then clears the semaphore.
bool SemaphoreSignalled();
} // namespace

View File

@@ -440,8 +440,7 @@ static void DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
level = Log::Level::Debug;
break;
}
LOG_GENERIC(Log::Class::Render_OpenGL, level, "%s %s %d: %s",
GetSource(source), GetType(type), id, message);
//LOG_GENERIC(Log::Class::Render_OpenGL, level, "%s %s %d: %s", GetSource(source), GetType(type), id, message);
}
/// Initialize the renderer